diff --git a/src/auto-evo/AutoEvoGlobalCache.cs b/src/auto-evo/AutoEvoGlobalCache.cs index 363486151d7..f6b26c1259a 100644 --- a/src/auto-evo/AutoEvoGlobalCache.cs +++ b/src/auto-evo/AutoEvoGlobalCache.cs @@ -29,6 +29,9 @@ public class AutoEvoGlobalCache public readonly CompoundConversionEfficiencyPressure TemperatureConversionEfficiencyPressure; public readonly EnvironmentalCompoundPressure TemperatureCompoundPressure; + public readonly CompoundConversionEfficiencyPressure RadiationConversionEfficiencyPressure; + public readonly ChunkCompoundPressure RadioactiveChunkPressure; + public readonly PredatorRoot PredatorRoot; public readonly bool HasTemperature; @@ -65,6 +68,11 @@ public AutoEvoGlobalCache(WorldGenerationSettings worldSettings) new CompoundConversionEfficiencyPressure(Compound.Sunlight, Compound.Glucose, 1.0f); SunlightCompoundPressure = new EnvironmentalCompoundPressure(Compound.Sunlight, Compound.Glucose, 20000, 1.0f); + RadiationConversionEfficiencyPressure = + new CompoundConversionEfficiencyPressure(Compound.Radiation, Compound.ATP, 1.0f); + RadioactiveChunkPressure = new ChunkCompoundPressure("radioactiveChunk", + new LocalizedString("RADIOACTIVE_CHUNK"), Compound.Radiation, Compound.ATP, 1.0f); + TemperatureConversionEfficiencyPressure = new CompoundConversionEfficiencyPressure(Compound.Temperature, Compound.ATP, 1.0f); TemperatureCompoundPressure = new EnvironmentalCompoundPressure(Compound.Temperature, Compound.ATP, 100, 1.0f); diff --git a/src/auto-evo/steps/GenerateMiche.cs b/src/auto-evo/steps/GenerateMiche.cs index 8ca85772e99..3524cc5c072 100644 --- a/src/auto-evo/steps/GenerateMiche.cs +++ b/src/auto-evo/steps/GenerateMiche.cs @@ -99,6 +99,19 @@ public Miche GenerateMicheTree(AutoEvoGlobalCache globalCache) generatedMiche.AddChild(hydrogenSulfideMiche); } + var hasRadioactiveChunk = + patch.Biome.Chunks.TryGetValue("radioactiveChunk", out var radioactiveChunk) && + radioactiveChunk.Density > 0; + + // Radioactive Chunk + if (hasRadioactiveChunk) + { + var radiationMiche = new Miche(globalCache.RadiationConversionEfficiencyPressure); + radiationMiche.AddChild(new Miche(globalCache.RadioactiveChunkPressure)); + + generatedMiche.AddChild(radiationMiche); + } + // Sunlight // TODO: should there be a dynamic energy level requirement rather than an absolute value? if (patch.Biome.TryGetCompound(Compound.Sunlight, CompoundAmountType.Biome, out var sunlightAmount) && diff --git a/src/microbe_stage/systems/MicrobeAISystem.cs b/src/microbe_stage/systems/MicrobeAISystem.cs index efbefc4c0e8..d2b2cb92b00 100644 --- a/src/microbe_stage/systems/MicrobeAISystem.cs +++ b/src/microbe_stage/systems/MicrobeAISystem.cs @@ -71,6 +71,9 @@ public sealed class MicrobeAISystem : AEntitySetSystem, ISpeciesMemberLoc private readonly List<(Entity Entity, Vector3 Position, float EngulfSize, CompoundBag Compounds)> chunkDataCache = new(); + private readonly List<(Entity Entity, Vector3 Position, CompoundBag Compounds)> + terrainChunkDataCache = new(); + private readonly Dictionary speciesUsingVaryingCompounds = new(); private readonly HashSet varyingCompoundsTemporary = new(); @@ -103,9 +106,8 @@ public MicrobeAISystem(IReadonlyCompoundClouds cloudSystem, IDaylightInfo lightI microbesSet = world.GetEntities().With().With() .With().With().With().Without().AsSet(); - // Engulfables, which are basically all chunks when they aren't cells, and aren't attached so that they - // also aren't eaten already - chunksSet = world.GetEntities().With().With().With() + // Chunks that aren't cells or attached so that they also aren't eaten already + chunksSet = world.GetEntities().With().With() .Without().Without().AsSet(); } @@ -330,6 +332,17 @@ private void ChooseActions(in Entity entity, ref MicrobeAI ai, ref CompoundAbsor control.SetMucocystState(ref organelles, ref compoundStorage, entity, false); } + var radiationAmount = compounds.GetCompoundAmount(Compound.Radiation); + var radiationFraction = radiationAmount / compounds.GetCapacityForCompound(Compound.Radiation); + + if (radiationFraction > Constants.RADIATION_DAMAGE_THRESHOLD * 0.7f) + { + if (RunFromNearestRadioactiveChunk(ref position, ref ai, ref control)) + { + return; + } + } + float atpLevel = compounds.GetCompoundAmount(Compound.ATP); // If this microbe is out of ATP, pick an amount of time to rest @@ -488,6 +501,16 @@ private void ChooseActions(in Entity entity, ref MicrobeAI ai, ref CompoundAbsor // There is no reason to be engulfing at this stage control.SetStateColonyAware(entity, MicrobeState.Normal); + // If the microbe has radiation protection it means it has melanosomes and can stay near the radioactive chunks + // to produce ATP + if (organelles.RadiationProtection > 0) + { + if (GoNearRadioactiveChunk(ref position, ref ai, ref control, speciesFocus, random)) + { + return; + } + } + // Otherwise just wander around and look for compounds if (!isSessile) { @@ -545,6 +568,86 @@ private bool CheckForHuntingConditions(ref MicrobeAI ai, ref WorldPosition posit return false; } + private (Entity Entity, Vector3 Position, CompoundBag Compounds)? GetNearestRadioactiveChunk( + ref WorldPosition position, float maxDistance) + { + (Entity Entity, Vector3 Position, CompoundBag Compounds)? chosenChunk = null; + float bestFoundChunkDistance = float.MaxValue; + + BuildChunksCache(); + + foreach (var chunk in terrainChunkDataCache) + { + if (!chunk.Compounds.Compounds.Keys.Contains(Compound.Radiation)) + { + continue; + } + + var distance = (chunk.Position - position.Position).LengthSquared(); + + if (distance > bestFoundChunkDistance) + continue; + + if (distance > maxDistance) + continue; + + chosenChunk = chunk; + } + + return chosenChunk; + } + + private bool RunFromNearestRadioactiveChunk(ref WorldPosition position, ref MicrobeAI ai, + ref MicrobeControl control) + { + var chosenChunk = GetNearestRadioactiveChunk(ref position, 500.0f); + + if (chosenChunk == null) + { + return false; + } + + var oppositeDirection = position.Position + (position.Position - chosenChunk.Value.Position); + oppositeDirection = oppositeDirection.Normalized() * 500.0f; + + ai.TargetPosition = oppositeDirection; + control.LookAtPoint = ai.TargetPosition; + + control.SetMoveSpeedTowardsPoint(ref position, ai.TargetPosition, Constants.AI_BASE_MOVEMENT); + control.Sprinting = true; + + return true; + } + + private bool GoNearRadioactiveChunk(ref WorldPosition position, ref MicrobeAI ai, + ref MicrobeControl control, float speciesFocus, Random random) + { + var maxDistance = 30000.0f * speciesFocus / Constants.MAX_SPECIES_FOCUS + 3000.0f; + var chosenChunk = GetNearestRadioactiveChunk(ref position, maxDistance); + + if (chosenChunk == null) + { + return false; + } + + // Range from 0.8 to 1.2 + var randomMultiplier = (float)random.NextDouble() * 0.4f + 0.8f; + + // If the microbe is close to the chunk it doesn't need to go any closer + if (position.Position.DistanceSquaredTo(chosenChunk.Value.Position) < 800.0f * randomMultiplier) + { + control.SetMoveSpeed(0.0f); + return true; + } + + ai.TargetPosition = chosenChunk.Value.Position; + control.LookAtPoint = ai.TargetPosition; + + control.SetMoveSpeedTowardsPoint(ref position, ai.TargetPosition, Constants.AI_BASE_MOVEMENT); + + return true; + } + private (Entity Entity, Vector3 Position, float EngulfSize, CompoundBag Compounds)? GetNearestChunkItem( in Entity entity, ref Engulfer engulfer, ref MicrobeControl control, ref WorldPosition position, CompoundBag ourCompounds, float speciesFocus, float speciesOpportunism, Random random, bool ironEater, @@ -1364,6 +1467,8 @@ private void CleanSpeciesUsingVaryingCompound() private void BuildChunksCache() { // To allow multithreaded AI access safely + // As the chunk lock is always held when building all of the chunk caches, + // the other individual cache objects don't need separate locks to protect them lock (chunkDataCache) { if (chunkCacheBuilt) @@ -1388,9 +1493,17 @@ private void BuildChunksCache() // TODO: determine if it is a good idea to resolve this data here immediately ref var position = ref chunk.Get(); - ref var engulfable = ref chunk.Get(); - chunkDataCache.Add((chunk, position.Position, engulfable.AdjustedEngulfSize, compounds.Compounds)); + if (chunk.Has()) + { + ref var engulfable = ref chunk.Get(); + chunkDataCache.Add((chunk, position.Position, engulfable.AdjustedEngulfSize, + compounds.Compounds)); + } + else + { + terrainChunkDataCache.Add((chunk, position.Position, compounds.Compounds)); + } } chunkCacheBuilt = true;