private void ProcessOpenRooms(bool testOptional, float targetGrowth) { var collisionMask = testOptional ? RoomCollisionFlag.Optional : RoomCollisionFlag.NoOptional; m_errorByType.Clear(); m_weightedChoice.Clear(); var entrySeedError = m_construction.ComputeErrorAgainstSeed(); m_manager.Debug("Target growth {0}", targetGrowth); var boundingBox = BoundingBox.CreateInvalid(); foreach (var k in m_construction.Rooms) { boundingBox = boundingBox.Include(k.BoundingBoxBoth); } foreach (var room in m_openRooms.Values) { if (room.CollisionMask.HasFlag(collisionMask)) { continue; } if (CollidesPredictive(room.Room, true, testOptional)) { room.CollisionMask |= collisionMask; continue; } using (m_construction.RegisterRoomUsing(room.Room)) { var randomScore = 1e1 * m_construction.Seed.DeterministicNoise(room.Room.Part.Name.GetHashCode() ^ room.Room.Transform.GetHashCode()); double growthScore = 0; { // Based on the target growth and destruct rates var count = 0; var freeMountPointCount = m_possibleRooms.Backing.Keys.Count; foreach (var point in room.Room.MountPoints) { if (point.AttachedTo != null) { count--; } else { count++; } } // Are we trying to shrink but this room makes us grow? if (targetGrowth < 0 && count > 0) { growthScore -= 1e20; } // Is the future mount count going to drop below zero while we are trying to grow? if (freeMountPointCount + count <= 0 && targetGrowth >= 0) { growthScore -= 1e20; } var error = count - targetGrowth; // We are reducing the number of mounts. Divide by total mounts since this isn't an issue when we have lots of choices. if (count <= 0) { growthScore -= error * error * 1e3 / Math.Sqrt(1 + freeMountPointCount); } else // increasing the number of mounts. The more mounts we have the larger of an issue this is. { growthScore -= error * error * Math.Sqrt(1 + freeMountPointCount); } } var sizeError = Vector3.DistanceSquared(boundingBox.Center, room.Room.BoundingBox.Center); var boundingBoxNew = BoundingBox.CreateMerged(boundingBox, room.Room.BoundingBox); sizeError += boundingBoxNew.Extents.Dot(boundingBoxNew.Extents); sizeError *= 1e3f; double roomError; if (!m_errorByType.TryGetValue(room.Room.Part, out roomError)) { var mySeedError = m_construction.ComputeErrorAgainstSeed(); roomError = m_errorByType[room.Room.Part] = mySeedError - entrySeedError; m_manager.Debug(" Type {0} has error {1:e}", room.Room.Part.Name, roomError); } double totalScore = 0; totalScore += randomScore; totalScore += growthScore; totalScore -= sizeError; totalScore -= roomError; m_weightedChoice.Add(room.Room, (float)totalScore); } } }
public ProceduralConstructionSeed(ProceduralFactionSeed faction, Vector4D locationDepth, Quaternion?orientation, long seed, int?populationOverride = null) { Seed = seed; Location = locationDepth.XYZ(); Faction = faction; var tmpRandom = new Random((int)seed); Population = populationOverride ?? (int)MyMath.Clamp((float)(Math.Round(5 * tmpRandom.NextExponential() * locationDepth.W)), 1, 100); var sqrtPopulation = Math.Sqrt(Population); var choice = new WeightedChoice <ProceduralStationSpeciality>(); foreach (var kv in Faction.Specialities) { foreach (var kt in kv.Key.StationSpecialities) { choice.Add(kt.Key, kv.Value * kt.Value); } } Speciality = choice.Choose(tmpRandom.NextDouble(), WeightedChoice <ProceduralStationSpeciality> .WeightedNormalization.ClampToZero); m_exports = new Dictionary <MyDefinitionId, TradeRequirements>(MyDefinitionId.Comparer); m_imports = new Dictionary <MyDefinitionId, TradeRequirements>(MyDefinitionId.Comparer); List <MyDefinitionBase> rchoice = null; var specialExport = tmpRandom.NextDouble() <= Speciality.SpecializationChance; if (specialExport) { rchoice = new List <MyDefinitionBase>(); } foreach (var kv in MyDefinitionManager.Static.GetAllDefinitions()) { if (Speciality.CanExport(kv)) { m_exports.Add(kv.Id, default(TradeRequirements)); rchoice?.Add(kv); } if (Speciality.CanImport(kv)) { m_imports.Add(kv.Id, default(TradeRequirements)); } } SpecialityExport = null; if (specialExport) { // ReSharper disable once PossibleNullReferenceException SpecialityExport = rchoice[tmpRandom.Next(0, rchoice.Count)].Id; m_exports.Clear(); m_exports.Add(SpecialityExport.Value, default(TradeRequirements)); } var nameBuilder = new StringBuilder(); nameBuilder.Append(Faction.FounderName).Append("'s "); if (SpecialityExport.HasValue) { nameBuilder.Append(MyDefinitionManager.Static.GetDefinition(SpecialityExport.Value).DisplayNameText).Append(" "); } else if (Speciality.GeneralizedPrefixes != null && Speciality.GeneralizedPrefixes.Length > 0) { nameBuilder.Append(tmpRandom.NextUniformChoice(Speciality.GeneralizedPrefixes)).Append(" "); } nameBuilder.Append(tmpRandom.NextUniformChoice(Speciality.Suffixes)); Name = nameBuilder.ToString(); // Compute hotlisted import amounts { var keys = m_imports.Keys.ToList(); foreach (var kv in keys) { var baseMult = Population * tmpRandom.NextExponential() / keys.Count; var k = MyDefinitionManager.Static.GetDefinition(kv); var pi = k as MyPhysicalItemDefinition; if (k is MyComponentDefinition) { baseMult *= 100 / pi.Mass; } else if (k.Id.TypeId == typeof(MyObjectBuilder_Ore)) { baseMult *= 10000 / (1 + Math.Sqrt(OreUtilities.GetRarity(k.Id))); } else if (k.Id.TypeId == typeof(MyObjectBuilder_Ingot)) { baseMult *= 5000 * OreUtilities.GetOutputRatio(k.Id); } else if (k.Id.TypeId == typeof(MyObjectBuilder_GasProperties)) { baseMult *= 250000; // 1/10th a large tank } else if (pi != null) { baseMult *= 10 / pi.Mass; } m_imports[kv] = new TradeRequirements(baseMult, Math.Sqrt(baseMult)); } } // Compute exported amounts { var keys = m_exports.Keys.ToList(); foreach (var kv in keys) { var baseMult = Population * Population * tmpRandom.NextExponential() / keys.Count; var k = MyDefinitionManager.Static.GetDefinition(kv); var pi = k as MyPhysicalItemDefinition; if (k is MyComponentDefinition) { baseMult *= 100 / pi.Mass; } else if (k.Id.TypeId == typeof(MyObjectBuilder_Ore)) { baseMult *= 10000 / (1 + Math.Sqrt(OreUtilities.GetRarity(k.Id))); } else if (k.Id.TypeId == typeof(MyObjectBuilder_Ingot)) { baseMult *= 5000 * OreUtilities.GetOutputRatio(k.Id); } else if (k.Id.TypeId == typeof(MyObjectBuilder_GasProperties)) { baseMult *= 250000; // 1/10th a large tank } m_exports[kv] = new TradeRequirements(baseMult, Math.Sqrt(baseMult)); } } // Using exports, compute imports { foreach (var kv in m_exports) { var producer = BlueprintIndex.Instance.GetAllProducing(kv.Key, true).ToList(); foreach (var blueprint in producer) { foreach (var ingredient in blueprint.Ingredients) { var aStorage = kv.Value.Storage * (double)ingredient.Value / producer.Count; var aThroughput = kv.Value.Throughput * (double)ingredient.Value / producer.Count; TradeRequirements current; m_imports[ingredient.Key] = m_imports.TryGetValue(ingredient.Key, out current) ? new TradeRequirements(current.Storage + aStorage, current.Throughput + aThroughput) : new TradeRequirements(aStorage, aThroughput); } } } } m_localStorage = new Dictionary <MyDefinitionId, TradeRequirements>(MyDefinitionId.Comparer) { // ~0.5MWH / person stored, ~1MW / person produced [MyResourceDistributorComponent.ElectricityId] = new TradeRequirements(Population * 0.5 * tmpRandom.NextNormal(1, 0.1), Population * tmpRandom.NextNormal(1, 0.1), 10, 10), // one large O2 tank / 10 person stored (~2 days), 1 O2/sec/person produced [MyResourceDistributorComponent.OxygenId] = new TradeRequirements(Population * 1e4 * tmpRandom.NextNormal(1, 0.1), Population * 1 * tmpRandom.NextNormal(1, 0.1), 10, 10), // I don't even know how I'd guess this [MyResourceDistributorComponent.HydrogenId] = new TradeRequirements(Population * 1e4 * tmpRandom.NextExponential(), tmpRandom.NextExponential() * sqrtPopulation) }; // Compute mass & volume of storage ComputeStorageVolumeMass(out StorageVolume, out StorageMass); // ReSharper disable once UseObjectOrCollectionInitializer m_blockCountRequirements = new Dictionary <SupportedBlockTypes, BlockRequirement>(SupportedBlockTypesEquality.Instance); // living quarters. var addlLiving = Faction.AttributeWeight(ProceduralFactionSpeciality.Housing); m_blockCountRequirements[SupportedBlockTypes.CryoChamber] = Population + Math.Max(0, (int)Math.Round(sqrtPopulation * (tmpRandom.NextNormal(1, 2) + addlLiving))); m_blockCountRequirements[SupportedBlockTypes.MedicalRoom] = new BlockRequirement(Math.Max(1, (int)Math.Round(sqrtPopulation * (tmpRandom.NextNormal(0.5, 0.5) + Math.Max(addlLiving, Faction.AttributeWeight(ProceduralFactionSpeciality.Military))))), 1e3); m_blockCountRequirements[SupportedBlockTypes.ShipController] = Math.Max(1, (int)Math.Round(Population * tmpRandom.NextNormal(0.5, 0.5))); // how "defensive" this group is m_blockCountRequirements[SupportedBlockTypes.Weapon] = Math.Max(0, (int)Math.Round(Population * tmpRandom.NextNormal(2, 2) * Faction.AttributeWeight(ProceduralFactionSpeciality.Military))); // ship repair? m_blockCountRequirements[SupportedBlockTypes.ShipConstruction] = Math.Max(0, (int)Math.Round(Population * tmpRandom.NextNormal(4, 3) * Faction.AttributeWeight(ProceduralFactionSpeciality.Repair))); // docking? m_blockCountRequirements[SupportedBlockTypes.Docking] = Math.Max(1, (int)Math.Round(sqrtPopulation * tmpRandom.NextNormal(1, 1) * Faction.AttributeWeight(ProceduralFactionSpeciality.Housing))); // comms? m_blockCountRequirements[SupportedBlockTypes.Communications] = new BlockRequirement(Math.Max(1, (int)Math.Round(sqrtPopulation * MyMath.Clamp((float)tmpRandom.NextNormal(), 0, 1))), 1e6); Orientation = tmpRandom.NextQuaternion(); // Branched, since we want to consume a quaternion from the random. if (orientation != null) { Orientation = orientation.Value; } }