3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
11 static class OutpostGenerator
18 public readonly SubmarineInfo Info;
22 public readonly PlacedModule PreviousModule;
26 public readonly OutpostModuleInfo.GapPosition ThisGapPosition = 0;
28 public OutpostModuleInfo.GapPosition UsedGapPositions = 0;
30 public readonly HashSet<Identifier> FulfilledModuleTypes =
new HashSet<Identifier>();
32 public Vector2 Offset;
34 public Vector2 MoveOffset;
36 public Gap ThisGap, PreviousGap;
41 public PlacedModule(SubmarineInfo thisModule, PlacedModule previousModule, OutpostModuleInfo.GapPosition thisGapPosition)
44 PreviousModule = previousModule;
45 ThisGapPosition = thisGapPosition;
46 UsedGapPositions = thisGapPosition;
47 if (PreviousModule !=
null)
49 previousModule.UsedGapPositions |= GetOpposingGapPosition(thisGapPosition);
53 public override string ToString()
55 return $
"OutpostGenerator.PlacedModule ({Info.Name})";
59 public static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType,
bool onlyEntrance =
false,
bool allowInvalidOutpost =
false)
61 return Generate(generationParams, locationType, location:
null, onlyEntrance, allowInvalidOutpost);
64 public static Submarine Generate(OutpostGenerationParams generationParams, Location location,
bool onlyEntrance =
false,
bool allowInvalidOutpost =
false)
66 return Generate(generationParams, location.Type, location, onlyEntrance, allowInvalidOutpost);
69 private static SubmarineInfo usedForceOutpostModule;
71 private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location,
bool onlyEntrance =
false,
bool allowInvalidOutpost =
false)
73 var outpostModuleFiles = ContentPackageManager.EnabledPackages.All
74 .SelectMany(p => p.GetFiles<OutpostModuleFile>())
75 .OrderBy(f => f.UintIdentifier).ToArray();
76 var uintIdDupes = outpostModuleFiles.Where(f1 =>
77 outpostModuleFiles.Any(f2 => f1 != f2 && f1.UintIdentifier == f2.UintIdentifier)).ToArray();
78 if (uintIdDupes.Any())
80 throw new Exception($
"OutpostModuleFile UintIdentifier duplicates found: {uintIdDupes.Select(f => f.Path)}");
84 if (location.IsCriticallyRadiated() && OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.Identifier == generationParams.ReplaceInRadiation) is { } newParams)
86 generationParams = newParams;
89 locationType = location.GetLocationType();
93 if (generationParams.OutpostTag.IsEmpty)
95 var forceOutpostModule = GameMain.GameSession?.ForceOutpostModule;
96 sub = GenerateFromModules(generationParams, outpostModuleFiles, sub, locationType, location, onlyEntrance, allowInvalidOutpost);
101 else if (forceOutpostModule !=
null)
108 var outpostFiles = ContentPackageManager.EnabledPackages.All
109 .SelectMany(p => p.GetFiles<OutpostFile>())
110 .Where(f => !TutorialPrefab.Prefabs.Any(tp => tp.OutpostPath == f.Path))
111 .OrderBy(f => f.UintIdentifier).ToList();
113 List<SubmarineInfo> outpostInfos =
new List<SubmarineInfo>();
114 foreach (var outpostFile
in outpostFiles)
116 outpostInfos.Add(
new SubmarineInfo(outpostFile.Path.Value));
118 if (!generationParams.OutpostTag.IsEmpty)
120 if (outpostInfos.Any(o => o.OutpostTags.Contains(generationParams.OutpostTag)))
122 outpostInfos = outpostInfos.FindAll(o => o.OutpostTags.Contains(generationParams.OutpostTag));
126 DebugConsole.ThrowError($
"Could not find any outposts with the tag {generationParams.OutpostTag}. Choosing a random one instead...");
129 if (!outpostInfos.Any())
131 throw new Exception(
"Failed to generate an outpost. Could not generate an outpost from the available outpost modules and there are no pre-built outposts available.");
133 var prebuiltOutpostInfo = outpostInfos.GetRandom(Rand.RandSync.ServerAndClient);
135 if (GameMain.NetworkMember?.ServerSettings is { } serverSettings &&
136 serverSettings.SelectedOutpostName !=
"Random")
138 var matchingOutpost = outpostInfos.FirstOrDefault(o => o.Name == serverSettings.SelectedOutpostName);
139 if (matchingOutpost !=
null)
141 prebuiltOutpostInfo = matchingOutpost;
146 sub =
new Submarine(prebuiltOutpostInfo);
147 sub.Info.OutpostGenerationParams = generationParams;
148 location?.RemoveTakenItems();
149 EnableFactionSpecificEntities(sub, location);
153 private static Submarine GenerateFromModules(OutpostGenerationParams generationParams, OutpostModuleFile[] outpostModuleFiles, Submarine sub, LocationType locationType, Location location,
bool onlyEntrance =
false,
bool allowInvalidOutpost =
false)
156 List<SubmarineInfo> outpostModules =
new List<SubmarineInfo>();
157 foreach (var outpostModuleFile
in outpostModuleFiles)
159 var subInfo =
new SubmarineInfo(outpostModuleFile.Path.Value);
160 if (subInfo.OutpostModuleInfo !=
null)
162 if (generationParams is RuinGeneration.RuinGenerationParams)
165 if (!subInfo.OutpostModuleInfo.ModuleFlags.Contains(
"ruin".ToIdentifier()) &&
166 !generationParams.ModuleCounts.Any(m => subInfo.OutpostModuleInfo.ModuleFlags.Contains(m.Identifier)))
171 else if (subInfo.OutpostModuleInfo.ModuleFlags.Contains(
"ruin".ToIdentifier()))
175 outpostModules.Add(subInfo);
179 List<PlacedModule> selectedModules =
new List<PlacedModule>();
180 bool generationFailed =
false;
181 int remainingTries = 5;
182 while (remainingTries > -1 && outpostModules.Any())
187 int eventCount = GameMain.Server.EntityEventManager.Events.Count();
188 int uniqueEventCount = GameMain.Server.EntityEventManager.UniqueEvents.Count();
190 HashSet<Submarine> connectedSubs =
new HashSet<Submarine>() { sub };
191 foreach (Submarine otherSub
in Submarine.Loaded)
194 if (otherSub.Submarine == sub) { connectedSubs.Add(otherSub); }
196 List<MapEntity> entities = MapEntity.MapEntityList.FindAll(e => connectedSubs.Contains(e.Submarine));
197 entities.ForEach(e => e.Remove());
198 foreach (Submarine otherSub
in connectedSubs)
204 GameMain.Server.EntityEventManager.Events.RemoveRange(eventCount, GameMain.Server.EntityEventManager.Events.Count - eventCount);
205 GameMain.Server.EntityEventManager.UniqueEvents.RemoveRange(uniqueEventCount, GameMain.Server.EntityEventManager.UniqueEvents.Count - uniqueEventCount);
207 if (remainingTries <= 0)
209 generationFailed =
true;
214 selectedModules.Clear();
216 List<Identifier> pendingModuleFlags =
new List<Identifier>();
217 if (generationParams.ModuleCounts.Any())
219 pendingModuleFlags = onlyEntrance ?
220 generationParams.ModuleCounts[0].Identifier.ToEnumerable().ToList() :
221 SelectModules(outpostModules, location, generationParams);
224 foreach (Identifier flag
in pendingModuleFlags)
226 if (flag ==
"none") {
continue; }
227 int pendingCount = pendingModuleFlags.Count(f => f == flag);
228 int availableModuleCount =
230 .Where(m => m.OutpostModuleInfo.ModuleFlags.Any(f => f == flag))
231 .Select(m => m.OutpostModuleInfo.MaxCount)
235 if (availableModuleCount < pendingCount)
237 DebugConsole.ThrowError($
"Error in outpost generation parameters. Trying to place {pendingCount} modules of the type \"{flag}\", but there aren't enough suitable modules available. You may need to increase the \"max count\" value of some of the modules in the sub editor or decrease the number of modules in the outpost.");
238 for (
int i = 0; i < (pendingCount - availableModuleCount); i++)
240 pendingModuleFlags.Remove(flag);
246 Identifier initialModuleFlag = pendingModuleFlags.FirstOrDefault().IfEmpty(
"airlock".ToIdentifier());
247 pendingModuleFlags.Remove(initialModuleFlag);
249 bool hasForceOutpostWithInitialFlag = GameMain.GameSession?.ForceOutpostModule !=
null && GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.ModuleFlags.Contains(initialModuleFlag);
250 var initialModule = hasForceOutpostWithInitialFlag ? GameMain.GameSession.ForceOutpostModule : GetRandomModule(outpostModules, initialModuleFlag, locationType);
252 if (hasForceOutpostWithInitialFlag)
254 DebugConsole.NewMessage($
"Using Force outpost module as initial in Outpost generation: {GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.Name}", Color.Yellow);
255 usedForceOutpostModule = GameMain.GameSession.ForceOutpostModule;
256 GameMain.GameSession.ForceOutpostModule =
null;
259 if (initialModule ==
null)
261 throw new Exception(
"Failed to generate an outpost (no airlock modules found).");
263 foreach (Identifier initialFlag
in initialModule.OutpostModuleInfo.ModuleFlags)
265 if (pendingModuleFlags.Contains(
"initialFlag".ToIdentifier())) { pendingModuleFlags.Remove(initialFlag); }
268 if (remainingTries == 1)
271 pendingModuleFlags = pendingModuleFlags.Distinct().ToList();
274 selectedModules.Add(
new PlacedModule(initialModule,
null, OutpostModuleInfo.GapPosition.None));
275 selectedModules.Last().FulfilledModuleTypes.Add(initialModuleFlag);
278 selectedModules.Last(), outpostModules.ToList(), pendingModuleFlags,
281 allowExtendBelowInitialModule: generationParams is RuinGeneration.RuinGenerationParams,
282 allowDifferentLocationType: remainingTries == 1);
284 if (GameMain.GameSession?.ForceOutpostModule !=
null)
286 if (remainingTries > 0)
291 DebugConsole.ThrowError($
"Could not place force outpost module: {GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.Name}");
292 GameMain.GameSession.ForceOutpostModule =
null;
296 if (pendingModuleFlags.Any(flag => flag !=
"none"))
298 if (!allowInvalidOutpost)
301 if (remainingTries <= 0)
303 DebugConsole.ThrowError(
"Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " +
string.Join(
", ", pendingModuleFlags));
309 DebugConsole.ThrowError(
"Could not generate an outpost with all of the required modules. Some modules may not have enough connections at the edges to generate a valid layout. Pending modules: " +
string.Join(
", ", pendingModuleFlags) +
". Won't retry because invalid outposts are allowed.");
313 var outpostInfo =
new SubmarineInfo()
317 generationFailed =
false;
318 outpostInfo.OutpostGenerationParams = generationParams;
319 sub =
new Submarine(outpostInfo, loadEntities: loadEntities);
320 sub.Info.OutpostGenerationParams = generationParams;
321 if (!generationFailed)
323 foreach (Hull hull
in Hull.HullList)
325 if (hull.Submarine != sub) {
continue; }
326 if (
string.IsNullOrEmpty(hull.RoomName))
328 hull.RoomName = hull.CreateRoomName();
331 if (Level.IsLoadedOutpost)
333 location?.RemoveTakenItems();
335 foreach (WayPoint wp
in WayPoint.WayPointList)
337 if (wp.CurrentHull ==
null && wp.Submarine == sub)
342 EnableFactionSpecificEntities(sub, location);
348 DebugConsole.AddSafeError(
"Failed to generate an outpost without overlapping modules. Trying to use a pre-built outpost instead...");
351 List<MapEntity> loadEntities(Submarine sub)
353 Dictionary<PlacedModule, List<MapEntity>> entities =
new Dictionary<PlacedModule, List<MapEntity>>();
354 int idOffset = sub.IdOffset;
355 for (
int i = 0; i < selectedModules.Count; i++)
357 var selectedModule = selectedModules[i];
358 sub.Info.GameVersion = selectedModule.Info.GameVersion;
359 var moduleEntities = MapEntity.LoadAll(sub, selectedModule.Info.SubmarineElement, selectedModule.Info.FilePath, idOffset);
361 if (usedForceOutpostModule !=
null && usedForceOutpostModule == selectedModule.Info)
363 sub.ForcedOutpostModuleWayPoints = moduleEntities.OfType<WayPoint>().ToList();
366 MapEntity.InitializeLoadedLinks(moduleEntities);
368 foreach (MapEntity entity
in moduleEntities.ToList())
370 entity.OriginalModuleIndex = i;
371 if (entity is not Item item) {
continue; }
372 var door = item.GetComponent<
Door>();
376 if (!moduleEntities.Contains(door.LinkedGap)) { moduleEntities.Add(door.LinkedGap); }
381 idOffset = moduleEntities.Max(e => e.ID) + 1;
383 var wallEntities = moduleEntities.Where(e => e is Structure s && s.HasBody).Cast<
Structure>();
384 var hullEntities = moduleEntities.Where(e => e is Hull).Cast<Hull>();
387 foreach (Hull hull
in hullEntities)
389 hull.SetModuleTags(selectedModule.Info.OutpostModuleInfo.ModuleFlags);
392 if (Screen.Selected is { IsEditor:
false })
394 foreach (Identifier layer
in selectedModule.Info.LayersHiddenByDefault)
396 Submarine.SetLayerEnabled(layer, enabled:
false, entities: moduleEntities);
400 if (!hullEntities.Any())
406 Point min =
new Point(hullEntities.Min(e => e.WorldRect.X), hullEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height));
407 Point max =
new Point(hullEntities.Max(e => e.WorldRect.Right), hullEntities.Max(e => e.WorldRect.Y));
408 selectedModule.HullBounds =
new Rectangle(min, max - min);
411 if (!wallEntities.Any())
417 Point min =
new Point(wallEntities.Min(e => e.WorldRect.X), wallEntities.Min(e => e.WorldRect.Y - e.WorldRect.Height));
418 Point max =
new Point(wallEntities.Max(e => e.WorldRect.Right), wallEntities.Max(e => e.WorldRect.Y));
419 selectedModule.Bounds =
new Rectangle(min, max - min);
422 if (selectedModule.PreviousModule !=
null)
424 selectedModule.PreviousGap = GetGap(entities[selectedModule.PreviousModule], GetOpposingGapPosition(selectedModule.ThisGapPosition));
425 if (selectedModule.PreviousGap ==
null)
427 DebugConsole.ThrowError($
"Error during outpost generation: {GetOpposingGapPosition(selectedModule.ThisGapPosition)} gap not found in module {selectedModule.PreviousModule.Info.Name}.");
428 generationFailed =
true;
429 return new List<MapEntity>();
431 selectedModule.ThisGap = GetGap(moduleEntities, selectedModule.ThisGapPosition);
432 if (selectedModule.ThisGap ==
null)
434 DebugConsole.ThrowError($
"Error during outpost generation: {selectedModule.ThisGapPosition} gap not found in module {selectedModule.Info.Name}.");
435 generationFailed =
true;
436 return new List<MapEntity>();
439 Vector2 moveDir = GetMoveDir(selectedModule.ThisGapPosition);
440 selectedModule.Offset =
441 (selectedModule.PreviousGap.WorldPosition + selectedModule.PreviousModule.Offset) -
442 selectedModule.ThisGap.WorldPosition;
443 if (selectedModule.PreviousGap.ConnectedDoor !=
null || selectedModule.ThisGap.ConnectedDoor !=
null)
445 selectedModule.Offset += moveDir * generationParams.MinHallwayLength;
448 entities[selectedModule] = moduleEntities;
451 bool overlapsFound =
true;
453 while (overlapsFound)
455 overlapsFound =
false;
456 foreach (PlacedModule placedModule
in selectedModules)
458 if (placedModule.PreviousModule ==
null) {
continue; }
460 List<PlacedModule> subsequentModules =
new List<PlacedModule>();
461 GetSubsequentModules(placedModule, selectedModules, ref subsequentModules);
462 List<PlacedModule> otherModules = selectedModules.Except(subsequentModules).ToList();
464 int remainingTries = 10;
465 while (FindOverlap(subsequentModules, otherModules, out var module1, out var module2) && remainingTries > 0)
467 overlapsFound =
true;
468 if (FindOverlapSolution(subsequentModules, module1, module2, selectedModules, out Dictionary<PlacedModule, Vector2> solution))
470 foreach (KeyValuePair<PlacedModule, Vector2> kvp
in solution)
472 kvp.Key.Offset += kvp.Value;
485 generationFailed =
true;
490 List<MapEntity> allEntities =
new List<MapEntity>();
491 foreach (List<MapEntity> entityList
in entities.Values)
493 allEntities.AddRange(entityList);
496 if (!generationFailed)
498 foreach (PlacedModule module
in selectedModules)
500 Submarine.RepositionEntities(module.Offset + sub.HiddenSubPosition, entities[module]);
503 allEntities.AddRange(GenerateHallways(sub, locationType, selectedModules, outpostModules, entities, generationParams is RuinGeneration.RuinGenerationParams));
504 LinkOxygenGenerators(allEntities);
505 if (generationParams.LockUnusedDoors)
507 LockUnusedDoors(selectedModules, entities, generationParams.RemoveUnusedGaps);
509 if (generationParams.DrawBehindSubs)
511 foreach (var entity
in allEntities)
513 if (entity is Structure structure)
516 structure.SpriteDepth = MathHelper.Lerp(0.999f, 0.9999f, structure.SpriteDepth);
518 foreach (var light
in structure.Lights)
520 light.IsBackground =
true;
526 AlignLadders(selectedModules, entities);
527 if (generationParams.MaxWaterPercentage > 0.0f)
529 foreach (var entity
in allEntities)
531 if (entity is Hull hull)
533 float diff = generationParams.MaxWaterPercentage - generationParams.MinWaterPercentage;
537 hull.WaterVolume = hull.Volume * 2;
541 hull.WaterVolume = hull.Volume * Rand.Range(generationParams.MinWaterPercentage, generationParams.MaxWaterPercentage, Rand.RandSync.ServerAndClient) * 0.01f;
555 private static List<Identifier> SelectModules(IEnumerable<SubmarineInfo> modules, Location location, OutpostGenerationParams generationParams)
557 int totalModuleCount = generationParams.TotalModuleCount;
558 int totalModuleCountExcludingOptional = totalModuleCount - generationParams.ModuleCounts.Count(m => m.Probability < 1.0f);
559 var pendingModuleFlags =
new List<Identifier>();
560 bool availableModulesFound =
true;
562 Identifier initialModuleFlag = generationParams.ModuleCounts.FirstOrDefault().Identifier;
563 pendingModuleFlags.Add(initialModuleFlag);
564 while (pendingModuleFlags.Count < totalModuleCountExcludingOptional && availableModulesFound)
566 availableModulesFound =
false;
567 foreach (var moduleCount
in generationParams.ModuleCounts)
569 float? difficulty = Level.ForcedDifficulty ?? location?.LevelData?.Difficulty;
570 if (difficulty.HasValue)
572 if (difficulty.Value < moduleCount.MinDifficulty || difficulty.Value > moduleCount.MaxDifficulty)
580 if (GameMain.GameSession?.ForceOutpostModule ==
null ||
581 !GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.ModuleFlags.Contains(moduleCount.Identifier))
583 if (moduleCount.Probability < 1.0f &&
584 Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) > moduleCount.Probability)
588 if (!moduleCount.RequiredFaction.IsEmpty &&
589 location?.Faction?.Prefab.Identifier != moduleCount.RequiredFaction &&
590 location?.SecondaryFaction?.Prefab.Identifier != moduleCount.RequiredFaction)
595 if (pendingModuleFlags.Count(m => m == moduleCount.Identifier) >= generationParams.GetModuleCount(moduleCount.Identifier))
599 if (!modules.Any(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleCount.Identifier)))
601 DebugConsole.ThrowError($
"Failed to add a module to the outpost (no modules with the flag \"{moduleCount.Identifier}\" found).");
604 availableModulesFound =
true;
605 pendingModuleFlags.Add(moduleCount.Identifier);
608 pendingModuleFlags.OrderBy(f => generationParams.ModuleCounts.First(m => m.Identifier == f).Order).ThenBy(f => Rand.Value(Rand.RandSync.ServerAndClient));
609 while (pendingModuleFlags.Count < totalModuleCount && generationParams.AppendToReachTotalModuleCount)
614 pendingModuleFlags.Insert(Rand.Int(pendingModuleFlags.Count - 1, Rand.RandSync.ServerAndClient),
"none".ToIdentifier());
618 pendingModuleFlags.Remove(initialModuleFlag);
619 pendingModuleFlags.Insert(0, initialModuleFlag);
621 if (pendingModuleFlags.Count > totalModuleCount)
623 DebugConsole.ThrowError($
"Error during outpost generation. {pendingModuleFlags.Count} modules set to be used the outpost, but total module count is only {totalModuleCount}. Leaving out some of the modules...");
624 int removeCount = pendingModuleFlags.Count - totalModuleCount;
625 for (
int i = 0; i < removeCount; i++)
627 pendingModuleFlags.Remove(pendingModuleFlags.Last());
631 return pendingModuleFlags;
646 private static bool AppendToModule(PlacedModule currentModule,
647 List<SubmarineInfo> availableModules,
648 List<Identifier> pendingModuleFlags,
649 List<PlacedModule> selectedModules,
650 LocationType locationType,
651 bool tryReplacingCurrentModule =
true,
652 bool allowExtendBelowInitialModule =
false,
653 bool allowDifferentLocationType =
false)
655 if (pendingModuleFlags.Count == 0) {
return true; }
657 List<PlacedModule> placedModules =
new List<PlacedModule>();
658 foreach (OutpostModuleInfo.GapPosition gapPosition in GapPositions.Randomize(Rand.RandSync.ServerAndClient))
660 if (currentModule.UsedGapPositions.HasFlag(gapPosition)) {
continue; }
661 if (DisallowBelowAirlock(allowExtendBelowInitialModule, gapPosition, currentModule)) {
continue; }
663 PlacedModule newModule =
null;
665 if (currentModule.Info.OutpostModuleInfo.GapPositions.HasFlag(gapPosition))
667 newModule = AppendModule(currentModule, GetOpposingGapPosition(gapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
670 if (newModule !=
null)
672 placedModules.Add(newModule);
677 foreach (PlacedModule otherModule
in selectedModules)
679 if (otherModule == currentModule) {
continue; }
680 foreach (OutpostModuleInfo.GapPosition otherGapPosition in
681 GapPositions.Where(g => !otherModule.UsedGapPositions.HasFlag(g) && otherModule.Info.OutpostModuleInfo.GapPositions.HasFlag(g)))
683 if (DisallowBelowAirlock(allowExtendBelowInitialModule, otherGapPosition, otherModule)) {
continue; }
684 newModule = AppendModule(otherModule, GetOpposingGapPosition(otherGapPosition), availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType);
685 if (newModule !=
null)
687 placedModules.Add(newModule);
691 if (newModule !=
null) {
break; }
694 if (pendingModuleFlags.Count == 0) {
return true; }
698 if (placedModules.Count == 0 && tryReplacingCurrentModule && currentModule.PreviousModule !=
null && !selectedModules.Any(m => m != currentModule && m.PreviousModule == currentModule))
701 for (
int i = 0; i < 10; i++)
703 selectedModules.Remove(currentModule);
704 assertAllPreviousModulesPresent();
706 pendingModuleFlags.AddRange(currentModule.FulfilledModuleTypes);
707 if (!availableModules.Contains(currentModule.Info)) { availableModules.Add(currentModule.Info); }
709 currentModule = AppendModule(currentModule.PreviousModule, currentModule.ThisGapPosition, availableModules, pendingModuleFlags, selectedModules, locationType, allowDifferentLocationType:
true);
710 assertAllPreviousModulesPresent();
711 if (currentModule ==
null) {
break; }
712 if (AppendToModule(currentModule, availableModules, pendingModuleFlags, selectedModules, locationType, tryReplacingCurrentModule:
false, allowExtendBelowInitialModule, allowDifferentLocationType))
714 assertAllPreviousModulesPresent();
721 foreach (PlacedModule placedModule
in placedModules)
723 AppendToModule(placedModule, availableModules, pendingModuleFlags, selectedModules, locationType, tryReplacingCurrentModule:
true, allowExtendBelowInitialModule, allowDifferentLocationType);
725 return placedModules.Count > 0;
727 void assertAllPreviousModulesPresent()
729 System.Diagnostics.Debug.Assert(selectedModules.All(m => m.PreviousModule ==
null || selectedModules.Contains(m.PreviousModule)));
732 static bool DisallowBelowAirlock(
bool allowExtendBelowInitialModule, OutpostModuleInfo.GapPosition gapPosition, PlacedModule currentModule)
734 if (!allowExtendBelowInitialModule)
737 if (gapPosition == OutpostModuleInfo.GapPosition.Bottom && currentModule.Offset.Y <= 1) {
return true; }
751 private static PlacedModule AppendModule(
752 PlacedModule currentModule,
753 OutpostModuleInfo.GapPosition gapPosition,
754 List<SubmarineInfo> availableModules,
755 List<Identifier> pendingModuleFlags,
756 List<PlacedModule> selectedModules,
757 LocationType locationType,
758 bool allowDifferentLocationType)
760 if (pendingModuleFlags.Count == 0) {
return null; }
762 Identifier flagToPlace =
"none".ToIdentifier();
763 SubmarineInfo nextModule =
null;
764 foreach (Identifier moduleFlag
in pendingModuleFlags.OrderByDescending(f => currentModule?.Info?.OutpostModuleInfo.AllowAttachToModules.Contains(f) ??
false))
766 flagToPlace = moduleFlag;
767 nextModule = GetRandomModule(currentModule?.Info?.OutpostModuleInfo, availableModules, flagToPlace, gapPosition, locationType, allowDifferentLocationType);
768 if (nextModule !=
null) {
break; }
771 if (nextModule !=
null)
773 var newModule =
new PlacedModule(nextModule, currentModule, gapPosition)
775 Offset = currentModule.Offset + GetMoveDir(gapPosition),
777 foreach (Identifier moduleFlag
in nextModule.OutpostModuleInfo.ModuleFlags)
779 if (!pendingModuleFlags.Contains(moduleFlag)) {
continue; }
780 if (moduleFlag !=
"none" || flagToPlace ==
"none")
782 newModule.FulfilledModuleTypes.Add(moduleFlag);
783 pendingModuleFlags.Remove(moduleFlag);
786 selectedModules.Add(newModule);
787 if (selectedModules.Count(m => m.Info == nextModule) >= nextModule.OutpostModuleInfo.MaxCount)
789 availableModules.Remove(nextModule);
799 private static bool FindOverlap(IEnumerable<PlacedModule> modules1, IEnumerable<PlacedModule> modules2, out PlacedModule module1, out PlacedModule module2)
803 foreach (PlacedModule module
in modules1)
805 foreach (PlacedModule otherModule
in modules2)
807 if (module == otherModule) {
continue; }
808 if (module.PreviousModule == otherModule && module.PreviousGap.ConnectedDoor ==
null && module.ThisGap.ConnectedDoor ==
null) {
continue; }
809 if (ModulesOverlap(module, otherModule))
812 module2 = otherModule;
823 private static bool ModulesOverlap(PlacedModule module1, PlacedModule module2)
826 bounds1.Location += (module1.Offset + module1.MoveOffset).ToPoint();
828 bounds2.Location += (module2.Offset + module2.MoveOffset).ToPoint();
831 if (module1.PreviousModule == module2 || module2.PreviousModule == module1)
833 bounds1.Inflate(-16, -16);
834 bounds2.Inflate(-16, -16);
837 Rectangle hullBounds1 = module1.HullBounds;
838 hullBounds1.Location += (module1.Offset + module1.MoveOffset).ToPoint();
839 Rectangle hullBounds2 = module2.HullBounds;
840 hullBounds2.Location += (module2.Offset + module2.MoveOffset).ToPoint();
842 hullBounds1.Inflate(-32, -32);
843 hullBounds2.Inflate(-32, -32);
845 return hullBounds1.Intersects(hullBounds2) || hullBounds1.Intersects(bounds2) || hullBounds2.Intersects(bounds1);
851 private static bool ModuleOverlapsWithModuleConnections(IEnumerable<PlacedModule> modules)
853 foreach (PlacedModule module
in modules)
856 rect.Location += (module.Offset + module.MoveOffset).ToPoint();
857 rect.Y += module.Bounds.Height;
859 Vector2? selfGapPos1 =
null;
860 Vector2? selfGapPos2 =
null;
861 if (module.PreviousModule !=
null)
863 selfGapPos1 = module.Offset + module.ThisGap.Position + module.MoveOffset;
864 selfGapPos2 = module.PreviousModule.Offset + module.PreviousGap.Position + module.PreviousModule.MoveOffset;
867 foreach (PlacedModule otherModule
in modules)
869 if (otherModule == module || otherModule.PreviousModule ==
null || otherModule.PreviousModule == module) {
continue; }
872 for (
int i = -1; i <= 1; i += 2)
874 Vector2 gapEdgeOffset =
875 otherModule.ThisGap.IsHorizontal ?
876 Vector2.UnitY * otherModule.ThisGap.Rect.Height / 2 * i * 0.9f :
877 Vector2.UnitX * otherModule.ThisGap.Rect.Width / 2 * i * 0.9f;
879 Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset;
880 Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset;
881 if (
Submarine.RectContains(rect, gapPos1) ||
883 MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
889 if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
890 !gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
891 MathUtils.LineSegmentsIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
912 private static bool FindOverlapSolution(IEnumerable<PlacedModule> movableModules, PlacedModule module1, PlacedModule module2, IEnumerable<PlacedModule> allmodules, out Dictionary<PlacedModule, Vector2> solution)
914 solution =
new Dictionary<PlacedModule, Vector2>();
915 foreach (PlacedModule module
in movableModules)
917 solution[module] = Vector2.Zero;
920 Vector2 shortestMove =
new Vector2(
float.MaxValue,
float.MaxValue);
921 bool solutionFound =
false;
922 foreach (PlacedModule module
in movableModules)
924 if (module.ThisGap.ConnectedDoor ==
null && module.PreviousGap.ConnectedDoor ==
null) {
continue; }
925 Vector2 moveDir = GetMoveDir(module.ThisGapPosition);
926 Vector2 moveStep = moveDir * 50.0f;
927 Vector2 currentMove = Vector2.Zero;
928 float maxMoveAmount = 2000.0f;
930 List<PlacedModule> subsequentModules2 =
new List<PlacedModule>();
931 GetSubsequentModules(module, movableModules, ref subsequentModules2);
932 while (currentMove.LengthSquared() < maxMoveAmount * maxMoveAmount)
934 currentMove += moveStep;
935 foreach (PlacedModule movedModule
in subsequentModules2)
937 movedModule.MoveOffset = currentMove;
939 if (!ModulesOverlap(module1, module2) &&
940 !ModuleOverlapsWithModuleConnections(allmodules) &&
941 currentMove.LengthSquared() < shortestMove.LengthSquared())
943 shortestMove = currentMove;
944 foreach (PlacedModule movedModule
in allmodules)
946 solution[movedModule] = subsequentModules2.Contains(movedModule) ? currentMove : Vector2.Zero;
947 solutionFound =
true;
952 foreach (PlacedModule movedModule
in allmodules)
954 movedModule.MoveOffset = Vector2.Zero;
958 return solutionFound;
961 private static SubmarineInfo GetRandomModule(IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, LocationType locationType)
963 IEnumerable<SubmarineInfo> availableModules =
null;
964 if (moduleFlag.IsEmpty || moduleFlag ==
"none")
966 availableModules = modules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || m.OutpostModuleInfo.ModuleFlags.Contains(
"none".ToIdentifier()));
970 availableModules = modules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
971 if (moduleFlag !=
"hallwayhorizontal" && moduleFlag !=
"hallwayvertical")
973 availableModules = availableModules.Where(m => !m.OutpostModuleInfo.ModuleFlags.Contains(
"hallwayhorizontal".ToIdentifier()) && !m.OutpostModuleInfo.ModuleFlags.Contains(
"hallwayvertical".ToIdentifier()));
977 if (!availableModules.Any()) {
return null; }
980 var modulesSuitableForLocationType =
981 availableModules.Where(m => m.OutpostModuleInfo.IsAllowedInLocationType(locationType));
984 if (!modulesSuitableForLocationType.Any())
986 modulesSuitableForLocationType = availableModules.Where(m => m.OutpostModuleInfo.IsAllowedInAnyLocationType());
989 if (!modulesSuitableForLocationType.Any())
991 DebugConsole.NewMessage($
"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
992 return ToolBox.SelectWeightedRandom(availableModules.ToList(), availableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
996 return ToolBox.SelectWeightedRandom(modulesSuitableForLocationType.ToList(), modulesSuitableForLocationType.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
1000 private static SubmarineInfo GetRandomModule(OutpostModuleInfo prevModule, IEnumerable<SubmarineInfo> modules, Identifier moduleFlag, OutpostModuleInfo.GapPosition gapPosition, LocationType locationType,
bool allowDifferentLocationType)
1002 IEnumerable<SubmarineInfo> modulesWithCorrectFlags =
null;
1003 if (moduleFlag.IsEmpty || moduleFlag.Equals(
"none"))
1005 modulesWithCorrectFlags = modules
1006 .Where(m => !m.OutpostModuleInfo.ModuleFlags.Any() || (m.OutpostModuleInfo.ModuleFlags.Count() == 1 && m.OutpostModuleInfo.ModuleFlags.Contains(
"none".ToIdentifier())));
1010 modulesWithCorrectFlags = modules
1011 .Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
1013 modulesWithCorrectFlags = modulesWithCorrectFlags.Where(m => m.OutpostModuleInfo.GapPositions.HasFlag(gapPosition) && m.OutpostModuleInfo.CanAttachToPrevious.HasFlag(gapPosition));
1015 var suitableModules = GetSuitableModules(modulesWithCorrectFlags, requireAllowAttachToPrevious:
true, requireCorrectLocationType:
true, disallowNonLocationTypeSpecific:
true);
1016 var suitableModulesForAnyOutpost = GetSuitableModules(modulesWithCorrectFlags, requireAllowAttachToPrevious:
true, requireCorrectLocationType:
true, disallowNonLocationTypeSpecific:
false);
1017 if (!suitableModules.Any())
1020 suitableModules = suitableModulesForAnyOutpost;
1022 if (!suitableModules.Any())
1024 suitableModules = GetSuitableModules(modulesWithCorrectFlags, requireAllowAttachToPrevious:
false, requireCorrectLocationType:
true, disallowNonLocationTypeSpecific:
true);
1027 if (!suitableModules.Any())
1029 suitableModules = GetSuitableModules(modulesWithCorrectFlags, requireAllowAttachToPrevious:
false, requireCorrectLocationType:
true, disallowNonLocationTypeSpecific:
false);
1033 if (!suitableModules.Any())
1035 if (allowDifferentLocationType && modulesWithCorrectFlags.Any())
1037 DebugConsole.NewMessage($
"Could not find a suitable module for the location type {locationType}. Module flag: {moduleFlag}.", Color.Orange);
1038 return ToolBox.SelectWeightedRandom(modulesWithCorrectFlags.ToList(), modulesWithCorrectFlags.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
1047 var suitableModule = ToolBox.SelectWeightedRandom(suitableModules.ToList(), suitableModules.Select(m => m.OutpostModuleInfo.Commonness).ToList(), Rand.RandSync.ServerAndClient);
1049 if (GameMain.GameSession?.ForceOutpostModule !=
null)
1051 if (suitableModules.Any(module => module.OutpostModuleInfo.Name == GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.Name) ||
1052 suitableModulesForAnyOutpost.Any(module => module.OutpostModuleInfo.Name == GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.Name))
1054 var forceOutpostModule = GameMain.GameSession.ForceOutpostModule;
1055 System.Diagnostics.Debug.WriteLine($
"Inserting Force outpost module in Outpost generation: {forceOutpostModule.OutpostModuleInfo.Name}");
1056 GameMain.GameSession.ForceOutpostModule =
null;
1057 usedForceOutpostModule = forceOutpostModule;
1058 return forceOutpostModule;
1060 else if (GameMain.GameSession.ForceOutpostModule.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag))
1068 return suitableModule;
1071 IEnumerable<SubmarineInfo> GetSuitableModules(IEnumerable<SubmarineInfo> modules,
bool requireAllowAttachToPrevious,
bool requireCorrectLocationType,
bool disallowNonLocationTypeSpecific)
1073 IEnumerable<SubmarineInfo> suitable = modules;
1074 if (requireCorrectLocationType)
1076 if (disallowNonLocationTypeSpecific)
1079 suitable = modules.Where(m => m.OutpostModuleInfo.AllowedLocationTypes.Contains(locationType.Identifier));
1083 suitable = modules.Where(m => m.OutpostModuleInfo.IsAllowedInLocationType(locationType));
1086 if (requireAllowAttachToPrevious && prevModule !=
null)
1088 suitable = suitable.Where(m => CanAttachTo(m.OutpostModuleInfo, prevModule));
1097 private static void GetSubsequentModules(PlacedModule startModule, IEnumerable<PlacedModule> allModules, ref List<PlacedModule> subsequentModules)
1099 System.Diagnostics.Debug.Assert(!subsequentModules.Contains(startModule));
1100 subsequentModules.Add(startModule);
1101 foreach (PlacedModule module
in allModules)
1103 if (module.PreviousModule == startModule)
1105 GetSubsequentModules(module, allModules, ref subsequentModules);
1110 private readonly
static OutpostModuleInfo.GapPosition[] GapPositions =
new[]
1112 OutpostModuleInfo.GapPosition.Right,
1113 OutpostModuleInfo.GapPosition.Left,
1114 OutpostModuleInfo.GapPosition.Top,
1115 OutpostModuleInfo.GapPosition.Bottom
1118 private static OutpostModuleInfo.GapPosition GetOpposingGapPosition(OutpostModuleInfo.GapPosition thisGapPosition)
1120 return thisGapPosition
switch
1122 OutpostModuleInfo.GapPosition.Right => OutpostModuleInfo.GapPosition.Left,
1123 OutpostModuleInfo.GapPosition.Left => OutpostModuleInfo.GapPosition.Right,
1124 OutpostModuleInfo.GapPosition.Bottom => OutpostModuleInfo.GapPosition.Top,
1125 OutpostModuleInfo.GapPosition.Top => OutpostModuleInfo.GapPosition.Bottom,
1126 OutpostModuleInfo.GapPosition.None => OutpostModuleInfo.GapPosition.None,
1127 _ =>
throw new ArgumentException()
1131 private static Vector2 GetMoveDir(OutpostModuleInfo.GapPosition thisGapPosition)
1133 return thisGapPosition
switch
1135 OutpostModuleInfo.GapPosition.Right => -Vector2.UnitX,
1136 OutpostModuleInfo.GapPosition.Left => Vector2.UnitX,
1137 OutpostModuleInfo.GapPosition.Bottom => Vector2.UnitY,
1138 OutpostModuleInfo.GapPosition.Top => -Vector2.UnitY,
1139 OutpostModuleInfo.GapPosition.None => Vector2.Zero,
1140 _ =>
throw new ArgumentException()
1144 private static Gap GetGap(IEnumerable<MapEntity> entities, OutpostModuleInfo.GapPosition gapPosition)
1146 Gap selectedGap =
null;
1147 foreach (MapEntity entity
in entities)
1149 if (!(entity is Gap gap)) {
continue; }
1150 if (gap.ConnectedDoor !=
null && !gap.ConnectedDoor.UseBetweenOutpostModules) {
continue; }
1151 switch (gapPosition)
1153 case OutpostModuleInfo.GapPosition.Right:
1154 if (gap.IsHorizontal && (selectedGap ==
null || gap.WorldPosition.X > selectedGap.WorldPosition.X) &&
1155 !entities.Any(e => e is Hull && e.WorldPosition.X > gap.WorldPosition.X && gap.WorldRect.Y - gap.WorldRect.Height <= e.WorldRect.Y && gap.WorldRect.Y >= e.WorldRect.Y - e.WorldRect.Height))
1160 case OutpostModuleInfo.GapPosition.Left:
1161 if (gap.IsHorizontal && (selectedGap ==
null || gap.WorldPosition.X < selectedGap.WorldPosition.X) &&
1162 !entities.Any(e => e is Hull && e.WorldPosition.X < gap.WorldPosition.X && gap.WorldRect.Y - gap.WorldRect.Height <= e.WorldRect.Y && gap.WorldRect.Y >= e.WorldRect.Y - e.WorldRect.Height))
1167 case OutpostModuleInfo.GapPosition.Top:
1168 if (!gap.IsHorizontal && (selectedGap ==
null || gap.WorldPosition.Y > selectedGap.WorldPosition.Y) &&
1169 !entities.Any(e => e is Hull && e.WorldPosition.Y > gap.WorldPosition.Y && gap.WorldRect.Right >= e.WorldRect.X && gap.WorldRect.X <= e.WorldRect.Right))
1174 case OutpostModuleInfo.GapPosition.Bottom:
1175 if (!gap.IsHorizontal && (selectedGap ==
null || gap.WorldPosition.Y < selectedGap.WorldPosition.Y) &&
1176 !entities.Any(e => e is Hull && e.WorldPosition.Y < gap.WorldPosition.Y && gap.WorldRect.Right >= e.WorldRect.X && gap.WorldRect.X <= e.WorldRect.Right))
1186 private static bool CanAttachTo(OutpostModuleInfo from, OutpostModuleInfo to)
1188 if (!from.AllowAttachToModules.Any() || from.AllowAttachToModules.All(s => s ==
"any")) {
return true; }
1189 return from.AllowAttachToModules.Any(s => to.ModuleFlags.Contains(s));
1192 private static List<MapEntity> GenerateHallways(Submarine sub, LocationType locationType, IEnumerable<PlacedModule> placedModules, IEnumerable<SubmarineInfo> availableModules, Dictionary<PlacedModule, List<MapEntity>> allEntities,
bool isRuin)
1195 const float MinTwoDoorHallwayLength = 32.0f;
1197 List<MapEntity> placedEntities =
new List<MapEntity>();
1198 foreach (PlacedModule module
in placedModules)
1200 if (module.PreviousModule ==
null) {
continue; }
1203 var previousJunctionBox =
Powered.
PoweredList.FirstOrDefault(p => p is
PowerTransfer pt && IsLinked(module.PreviousGap, pt))?.Item?.GetComponent<ConnectionPanel>();
1207 if (junctionBox.
Item.
linkedTo.Contains(gap)) {
return true; }
1208 if (gap.ConnectedDoor !=
null && junctionBox.
Item.
linkedTo.Contains(gap.ConnectedDoor.Item)) {
return true; }
1209 if (gap.linkedTo.Contains(junctionBox.
Item)) {
return true; }
1210 if (gap.ConnectedDoor !=
null && gap.ConnectedDoor.Item.linkedTo.Contains(junctionBox.
Item)) {
return true; }
1214 if (thisJunctionBox !=
null && previousJunctionBox !=
null)
1216 for (
int i = 0; i < thisJunctionBox.Connections.Count && i < previousJunctionBox.Connections.Count; i++)
1218 var wirePrefab = MapEntityPrefab.FindByIdentifier((thisJunctionBox.Connections[i].IsPower ?
"redwire" :
"bluewire").ToIdentifier()) as ItemPrefab;
1219 var wire =
new Item(wirePrefab, thisJunctionBox.Item.Position, sub).GetComponent<
Wire>();
1221 if (!thisJunctionBox.Connections[i].TryAddLink(wire))
1223 DebugConsole.AddWarning($
"Failed to connect junction boxes between outpost modules (not enough free connections in module \"{module.Info.Name}\")");
1226 if (!previousJunctionBox.Connections[i].TryAddLink(wire))
1228 DebugConsole.AddWarning($
"Failed to connect junction boxes between outpost modules (not enough free connections in module \"{module.PreviousModule.Info.Name}\")");
1231 wire.
TryConnect(thisJunctionBox.Connections[i], addNode:
false);
1232 wire.TryConnect(previousJunctionBox.Connections[i], addNode:
false);
1233 wire.SetNodes(
new List<Vector2>());
1238 module.ThisGapPosition == OutpostModuleInfo.GapPosition.Left ||
1239 module.ThisGapPosition == OutpostModuleInfo.GapPosition.Right;
1241 if (!module.ThisGap.linkedTo.Any())
1243 DebugConsole.ThrowError($
"Error during outpost generation: {module.ThisGapPosition} gap in module \"{module.Info.Name}\" was not linked to any hulls.");
1246 if (!module.PreviousGap.linkedTo.Any())
1248 DebugConsole.ThrowError($
"Error during outpost generation: {GetOpposingGapPosition(module.ThisGapPosition)} gap in module \"{module.PreviousModule.Info.Name}\" was not linked to any hulls.");
1252 MapEntity leftHull = module.ThisGap.Position.X < module.PreviousGap.Position.X ? module.ThisGap.linkedTo[0] : module.PreviousGap.linkedTo[0];
1253 MapEntity rightHull = module.ThisGap.Position.X > module.PreviousGap.Position.X ?
1254 module.ThisGap.linkedTo.Count == 1 ? module.ThisGap.linkedTo[0] : module.ThisGap.linkedTo[1] :
1255 module.PreviousGap.linkedTo.Count == 1 ? module.PreviousGap.linkedTo[0] : module.PreviousGap.linkedTo[1];
1256 MapEntity topHull = module.ThisGap.Position.Y > module.PreviousGap.Position.Y ? module.ThisGap.linkedTo[0] : module.PreviousGap.linkedTo[0];
1257 MapEntity bottomHull = module.ThisGap.Position.Y < module.PreviousGap.Position.Y ?
1258 module.ThisGap.linkedTo.Count == 1 ? module.ThisGap.linkedTo[0] : module.ThisGap.linkedTo[1] :
1259 module.PreviousGap.linkedTo.Count == 1 ? module.PreviousGap.linkedTo[0] : module.PreviousGap.linkedTo[1];
1261 float hallwayLength = isHorizontal ?
1262 rightHull.WorldRect.X - leftHull.WorldRect.Right :
1263 topHull.WorldRect.Y - topHull.RectHeight - bottomHull.WorldRect.Y;
1265 if (module.ThisGap !=
null && module.ThisGap.ConnectedDoor ==
null)
1268 foreach (var otherEntity
in allEntities[module])
1270 if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse &&
1271 Submarine.RectContains(structure.WorldRect, module.ThisGap.WorldPosition))
1277 if (module.PreviousGap !=
null && module.PreviousGap.ConnectedDoor ==
null)
1280 foreach (var otherEntity
in allEntities[module.PreviousModule])
1282 if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse &&
1283 Submarine.RectContains(structure.WorldRect, module.PreviousGap.WorldPosition))
1291 if (hallwayLength <= MinTwoDoorHallwayLength)
1293 if (module.ThisGap !=
null && module.PreviousGap !=
null)
1295 var gapToRemove = module.ThisGap.ConnectedDoor ==
null ? module.ThisGap : module.PreviousGap;
1296 var otherGap = gapToRemove == module.ThisGap ? module.PreviousGap : module.ThisGap;
1298 gapToRemove.ConnectedDoor?.Item.linkedTo.ForEachMod(lt => (lt as Structure)?.Remove());
1299 if (gapToRemove.ConnectedDoor?.Item.Connections !=
null)
1303 c.
Wires.ToArray().ForEach(w => w?.
Item.Remove());
1307 WayPoint thisWayPoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == gapToRemove);
1308 WayPoint previousWayPoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == otherGap);
1309 if (thisWayPoint !=
null && previousWayPoint !=
null)
1311 foreach (MapEntity me
in thisWayPoint.linkedTo)
1313 if (me is WayPoint wayPoint && !previousWayPoint.linkedTo.Contains(wayPoint))
1315 previousWayPoint.linkedTo.Add(wayPoint);
1318 thisWayPoint.Remove();
1322 if (thisWayPoint ==
null)
1324 DebugConsole.ThrowError($
"Failed to connect waypoints between outpost modules. No waypoint in the {module.ThisGapPosition.ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
1326 if (previousWayPoint ==
null)
1328 DebugConsole.ThrowError($
"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
1332 gapToRemove.ConnectedDoor?.Item.Remove();
1333 if (hallwayLength <= 1.0f) { gapToRemove?.Remove(); }
1337 if (hallwayLength <= 1.0f) {
continue; }
1339 Identifier moduleFlag = (isHorizontal ?
"hallwayhorizontal" :
"hallwayvertical").ToIdentifier();
1340 var hallwayModules = availableModules.Where(m => m.OutpostModuleInfo.ModuleFlags.Contains(moduleFlag));
1342 var suitableHallwayModules = hallwayModules.Where(m =>
1343 m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.Info.OutpostModuleInfo.ModuleFlags.Contains(s)) &&
1344 m.OutpostModuleInfo.AllowAttachToModules.Any(s => module.PreviousModule.Info.OutpostModuleInfo.ModuleFlags.Contains(s)));
1345 if (suitableHallwayModules.None())
1347 suitableHallwayModules = hallwayModules.Where(m =>
1348 !m.OutpostModuleInfo.AllowAttachToModules.Any() ||
1349 m.OutpostModuleInfo.AllowAttachToModules.All(s => s ==
"any"));
1352 var hallwayInfo = GetRandomModule(suitableHallwayModules, moduleFlag, locationType);
1353 if (hallwayInfo ==
null)
1355 DebugConsole.ThrowError($
"Generating hallways between outpost modules failed. No {(isHorizontal ? "horizontal
" : "vertical
")} hallway modules suitable for use between the modules \"{module.Info.DisplayName}\" and \"{module.PreviousModule.Info.DisplayName}\".");
1356 return placedEntities;
1359 var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath, -1);
1362 moduleEntities.Where(e => e is Item item && item.GetComponent<
Door>() ==
null && (isHorizontal ? e.Rect.Width : e.Rect.Height) > hallwayLength).ForEach(e => e.Remove());
1366 Vector2 hullCenter = Vector2.Zero;
1368 float largestHullVolume = 0.0f;
1369 foreach (MapEntity me
in moduleEntities)
1371 if (me is Hull hull)
1373 if (hull.Volume > largestHullVolume)
1375 largestHullVolume = hull.Volume;
1376 hullCenter = hull.WorldPosition;
1379 Math.Min(hullBounds.X, me.WorldRect.X),
1380 Math.Min(hullBounds.Y, me.WorldRect.Y - me.WorldRect.Height),
1381 Math.Max(hullBounds.Width, me.WorldRect.Right),
1382 Math.Max(hullBounds.Height, me.WorldRect.Y));
1385 hullBounds.Width -= hullBounds.X;
1386 hullBounds.Height -= hullBounds.Y;
1388 float scaleFactor = isHorizontal ?
1389 hallwayLength / (float)hullBounds.Width :
1390 hallwayLength / (
float)hullBounds.Height;
1391 System.Diagnostics.Debug.Assert(scaleFactor > 0.0f);
1393 placedEntities.AddRange(moduleEntities);
1394 MapEntity.InitializeLoadedLinks(moduleEntities);
1395 Vector2 moveAmount = (module.ThisGap.Position + module.PreviousGap.Position) / 2 - hullCenter;
1396 Submarine.RepositionEntities(moveAmount, moduleEntities);
1397 hullBounds.Location += moveAmount.ToPoint();
1400 foreach (MapEntity me
in moduleEntities)
1404 if (hallwayLength <= MinTwoDoorHallwayLength)
1409 int midX = (leftHull.Rect.Right + rightHull.Rect.X) / 2;
1410 leftHull.Rect =
new Rectangle(leftHull.Rect.X, leftHull.Rect.Y, midX - leftHull.Rect.X, leftHull.Rect.Height);
1411 rightHull.Rect =
new Rectangle(midX, rightHull.Rect.Y, rightHull.Rect.Right - midX, rightHull.Rect.Height);
1415 int midY = (topHull.Rect.Y - topHull.Rect.Height + bottomHull.Rect.Y) / 2;
1416 topHull.Rect =
new Rectangle(topHull.Rect.X, topHull.Rect.Y, topHull.Rect.Width, topHull.Rect.Y - midY);
1417 bottomHull.Rect =
new Rectangle(bottomHull.Rect.X, midY, bottomHull.Rect.Width, midY - (bottomHull.Rect.Y - bottomHull.Rect.Height));
1426 me.Rect =
new Rectangle(leftHull.Rect.Right, me.Rect.Y, rightHull.Rect.X - leftHull.Rect.Right, me.Rect.Height);
1431 me.Rect =
new Rectangle(me.Rect.X, topHull.Rect.Y - topHull.Rect.Height, me.Rect.Width, topHull.Rect.Y - topHull.Rect.Height - bottomHull.Rect.Y);
1435 else if (me is Structure || (me is Item item && item.GetComponent<
Door>() ==
null))
1439 if (!me.ResizeHorizontal)
1441 int xPos = (int)(leftHull.WorldRect.Right + (me.WorldPosition.X - hullBounds.X) * scaleFactor);
1442 me.Rect =
new Rectangle(xPos - me.RectWidth / 2, me.Rect.Y, me.Rect.Width, me.Rect.Height);
1446 int minX = (int)(leftHull.WorldRect.Right + (me.WorldRect.X - hullBounds.X) * scaleFactor);
1447 int maxX = (int)(leftHull.WorldRect.Right + (me.WorldRect.Right - hullBounds.X) * scaleFactor);
1448 me.Rect =
new Rectangle(minX, me.Rect.Y, Math.Max(maxX - minX, 16), me.Rect.Height);
1453 if (!me.ResizeVertical)
1455 int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.Y - hullBounds.Bottom) * scaleFactor);
1456 me.Rect =
new Rectangle(me.Rect.X, yPos + me.RectHeight / 2, me.Rect.Width, me.Rect.Height);
1460 int minY = (int)(bottomHull.WorldRect.Y + (me.WorldRect.Y - me.RectHeight - hullBounds.Y) * scaleFactor);
1461 int maxY = (int)(bottomHull.WorldRect.Y + (me.WorldRect.Y - hullBounds.Y) * scaleFactor);
1462 me.Rect =
new Rectangle(me.Rect.X, maxY, me.Rect.Width, Math.Max(maxY - minY, 16));
1468 if (hallwayLength > MinTwoDoorHallwayLength)
1471 var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.ThisGap);
1472 if (startWaypoint ==
null)
1474 DebugConsole.ThrowError($
"Failed to connect waypoints between outpost modules. No waypoint in the {module.ThisGapPosition.ToString().ToLower()} gap of the module \"{module.Info.Name}\".");
1477 var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == module.PreviousGap);
1478 if (endWaypoint ==
null)
1480 DebugConsole.ThrowError($
"Failed to connect waypoints between outpost modules. No waypoint in the {GetOpposingGapPosition(module.ThisGapPosition).ToString().ToLower()} gap of the module \"{module.PreviousModule.Info.Name}\".");
1484 if (startWaypoint.WorldPosition.X > endWaypoint.WorldPosition.X)
1486 (endWaypoint, startWaypoint) = (startWaypoint, endWaypoint);
1491 const float distanceBetweenWaypoints = 100.0f;
1492 if (hallwayLength > distanceBetweenWaypoints)
1494 WayPoint prevWayPoint = startWaypoint;
1495 WayPoint firstWayPoint =
null;
1498 for (
float x = leftHull.Rect.Right + distanceBetweenWaypoints / 2; x < rightHull.Rect.X - distanceBetweenWaypoints / 2; x += distanceBetweenWaypoints)
1500 var newWayPoint =
new WayPoint(
new Vector2(x, hullBounds.Y + 110.0f),
SpawnType.Path, sub);
1501 firstWayPoint ??= newWayPoint;
1502 prevWayPoint.linkedTo.Add(newWayPoint);
1503 newWayPoint.linkedTo.Add(prevWayPoint);
1504 prevWayPoint = newWayPoint;
1507 else if (startWaypoint.Ladders ==
null)
1509 float bottom = bottomHull.Rect.Y;
1510 float top = topHull.Rect.Y - topHull.Rect.Height;
1511 for (
float y = bottom + distanceBetweenWaypoints; y < top - distanceBetweenWaypoints; y += distanceBetweenWaypoints)
1513 var newWayPoint =
new WayPoint(
new Vector2(startWaypoint.Position.X, y),
SpawnType.Path, sub);
1514 firstWayPoint ??= newWayPoint;
1515 prevWayPoint.linkedTo.Add(newWayPoint);
1516 newWayPoint.linkedTo.Add(prevWayPoint);
1517 prevWayPoint = newWayPoint;
1522 startWaypoint.linkedTo.Add(endWaypoint);
1523 endWaypoint.linkedTo.Add(startWaypoint);
1525 if (firstWayPoint !=
null)
1527 firstWayPoint.linkedTo.Add(startWaypoint);
1528 startWaypoint.linkedTo.Add(firstWayPoint);
1530 if (prevWayPoint !=
null)
1532 prevWayPoint.linkedTo.Add(endWaypoint);
1533 endWaypoint.linkedTo.Add(prevWayPoint);
1538 startWaypoint.linkedTo.Add(endWaypoint);
1539 endWaypoint.linkedTo.Add(startWaypoint);
1543 return placedEntities;
1546 private static void LinkOxygenGenerators(IEnumerable<MapEntity> entities)
1548 List<OxygenGenerator> oxygenGenerators =
new List<OxygenGenerator>();
1549 List<Vent> vents =
new List<Vent>();
1550 foreach (MapEntity e
in entities)
1555 if (oxygenGenerator !=
null) { oxygenGenerators.Add(oxygenGenerator); }
1556 var vent = item.GetComponent<
Vent>();
1557 if (vent !=
null) { vents.Add(vent); }
1562 foreach (
Vent vent
in vents)
1565 float closestDist =
float.MaxValue;
1568 float dist = Vector2.DistanceSquared(oxygenGenerator.
Item.
WorldPosition, vent.Item.WorldPosition);
1569 if (dist < closestDist)
1571 closestOxygenGenerator = oxygenGenerator;
1575 if (closestOxygenGenerator !=
null && !closestOxygenGenerator.
Item.
linkedTo.Contains(vent.Item))
1582 private static void EnableFactionSpecificEntities(Submarine sub, Location location)
1584 sub.EnableFactionSpecificEntities(location?.Faction?.Prefab.Identifier ?? Identifier.Empty);
1587 private static void LockUnusedDoors(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities,
bool removeUnusedGaps)
1589 foreach (PlacedModule module
in placedModules)
1591 foreach (MapEntity me
in entities[module])
1593 if (me is not Gap gap) {
continue; }
1594 var door = gap.ConnectedDoor;
1595 if (door !=
null && !door.UseBetweenOutpostModules) {
continue; }
1596 if (placedModules.Any(m => m.PreviousGap == gap || m.ThisGap == gap))
1599 if (gap.ConnectedDoor ==
null)
1601 foreach (var otherEntity
in entities[module])
1603 if (otherEntity is Structure structure && structure.HasBody && !structure.IsPlatform && structure.RemoveIfLinkedOutpostDoorInUse &&
1604 Submarine.RectContains(structure.WorldRect, gap.WorldPosition))
1606 RemoveLinkedEntity(otherEntity);
1610 door?.Item.linkedTo.Where(lt => ShouldRemoveLinkedEntity(lt, doorInUse:
true, module: module)).ForEachMod(lt => RemoveLinkedEntity(lt));
1613 if (door !=
null &&
DockingPort.
List.Any(d =>
Submarine.RectContains(d.Item.WorldRect, door.Item.WorldPosition))) {
continue; }
1616 if (gap.linkedTo.Count == 2 &&
1617 entities[module].Contains(gap.linkedTo[0]) &&
1618 entities[module].Contains(gap.linkedTo[1]))
1624 if (door.Item.linkedTo.Any(lt => lt is Structure))
1627 door.Item.linkedTo.Where(lt => ShouldRemoveLinkedEntity(lt, doorInUse:
false, module: module)).ForEachMod(lt => RemoveLinkedEntity(lt));
1628 WayPoint.WayPointList.Where(wp => wp.ConnectedDoor == door).ForEachMod(wp => wp.Remove());
1629 RemoveLinkedEntity(door.Item);
1634 door.Stuck = 100.0f;
1635 door.Item.NonInteractable =
true;
1637 if (connectionPanel !=
null) { connectionPanel.
Locked =
true; }
1640 else if (removeUnusedGaps)
1643 WayPoint.WayPointList.Where(wp => wp.ConnectedGap == gap).ForEachMod(wp => wp.Remove());
1646 entities[module].RemoveAll(e => e.Removed);
1649 static bool ShouldRemoveLinkedEntity(MapEntity e,
bool doorInUse, PlacedModule module)
1651 if (e is Item { IsLadder:
true } ladderItem)
1653 int linkedToLadderCount =
Door.
DoorList.Count(otherDoor => otherDoor.Item.linkedTo.Contains(ladderItem));
1654 if (linkedToLadderCount > 1)
1660 return ladderItem.RemoveIfLinkedOutpostDoorInUse == doorInUse;
1663 if (e is Structure structure)
1665 return structure.RemoveIfLinkedOutpostDoorInUse == doorInUse;
1667 else if (e is Item item)
1669 if (item.GetComponent<
PowerTransfer>() !=
null) {
return false; }
1670 return item.RemoveIfLinkedOutpostDoorInUse == doorInUse;
1675 static void RemoveLinkedEntity(MapEntity linked)
1677 if (linked is Item linkedItem)
1679 if (linkedItem.Connections !=
null)
1681 foreach (
Connection connection
in linkedItem.Connections)
1683 foreach (
Wire w
in connection.
Wires.ToArray())
1690 if (linkedItem.GetComponent<
Ladder>() is
Ladder ladder)
1692 var ladderWaypoints = WayPoint.WayPointList.FindAll(wp => wp.Ladders == ladder);
1693 foreach (var ladderWaypoint
in ladderWaypoints)
1697 for (
int i = 0; i < ladderWaypoint.linkedTo.Count; i++)
1699 if (ladderWaypoint.linkedTo[i] is not WayPoint waypoint1 || waypoint1.Ladders == ladder) {
continue; }
1700 for (
int j = i + 1; j < ladderWaypoint.linkedTo.Count; j++)
1702 if (ladderWaypoint.linkedTo[j] is not WayPoint waypoint2 || waypoint2.Ladders == ladder) {
continue; }
1703 waypoint1.ConnectTo(waypoint2);
1707 ladderWaypoints.ForEach(wp => wp.Remove());
1714 private static void AlignLadders(IEnumerable<PlacedModule> placedModules, Dictionary<PlacedModule, List<MapEntity>> entities)
1717 float horizontalTolerance = 30.0f;
1718 foreach (PlacedModule module
in placedModules)
1721 module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ?
1722 module.PreviousModule :
1723 placedModules.FirstOrDefault(m => m.PreviousModule == module && m.ThisGapPosition == OutpostModuleInfo.GapPosition.Bottom);
1724 if (topModule ==
null) {
continue; }
1726 var topGap = module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ? module.ThisGap : topModule.ThisGap;
1727 var bottomGap = module.ThisGapPosition == OutpostModuleInfo.GapPosition.Top ? module.PreviousGap : topModule.PreviousGap;
1729 foreach (MapEntity me
in entities[module])
1731 var ladder = (me as
Item)?.GetComponent<Ladder>();
1732 if (ladder ==
null) {
continue; }
1733 if (ladder.Item.WorldRect.Right < topGap.WorldRect.X || ladder.Item.WorldPosition.X > topGap.WorldRect.Right) {
continue; }
1735 var topLadder = entities[topModule].Find(e =>
1736 (e as Item)?.GetComponent<
Ladder>() !=
null &&
1737 Math.Abs(e.WorldPosition.X - me.WorldPosition.X) < horizontalTolerance);
1739 int topLadderDiff = 0;
1740 int topLadderBottom = (int)(topModule.HullBounds.Y + topModule.Offset.Y + topModule.MoveOffset.Y + ladder.Item.Submarine.HiddenSubPosition.Y);
1741 if (topLadder !=
null)
1743 topLadderBottom = topLadder.WorldRect.Y - topLadder.WorldRect.Height;
1747 ladder.Item.Rect.X + topLadderDiff,
1749 ladder.Item.Rect.Width,
1750 topLadderBottom - (ladder.Item.WorldRect.Y - ladder.Item.WorldRect.Height));
1752 Rectangle testOverlapRect =
new Rectangle(newLadderRect.X, newLadderRect.Y + 30, newLadderRect.Width, newLadderRect.Height - 60);
1753 if (testOverlapRect.Height <= 0) {
continue; }
1756 if (entities[module].
Any(e => e is Structure structure && structure.HasBody && !structure.IsPlatform &&
Submarine.RectsOverlap(testOverlapRect, structure.Rect)))
1760 ladder.Item.Rect = newLadderRect;
1762 if (topGap !=
null && bottomGap !=
null)
1764 var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == bottomGap);
1765 var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == topGap);
1767 if (startWaypoint !=
null && endWaypoint !=
null)
1769 WayPoint prevWaypoint = startWaypoint;
1770 for (
float y = bottomGap.Position.Y + margin; y <= topGap.Position.Y - margin; y += WayPoint.LadderWaypointInterval)
1772 var wayPoint =
new WayPoint(
new Vector2(startWaypoint.Position.X, y),
SpawnType.Path, ladder.Item.Submarine)
1776 prevWaypoint.ConnectTo(wayPoint);
1777 prevWaypoint = wayPoint;
1779 prevWaypoint.ConnectTo(endWaypoint);
1786 public static void PowerUpOutpost(Submarine sub)
1790 var entities = MapEntity.MapEntityList.Where(me => me.Submarine == sub).ToList();
1792 foreach (MapEntity e
in entities)
1794 if (e is not Item item) {
continue; }
1795 var reactor = item.GetComponent<
Reactor>();
1796 if (reactor !=
null)
1799 reactor.AutoTemp =
true;
1803 for (
int i = 0; i < 600; i++)
1806 foreach (MapEntity e
in entities)
1808 if (e is not Item item || item.GetComponent<
Powered>() ==
null) {
continue; }
1809 item.Update((
float)Timing.Step, GameMain.GameScreen.Cam);
1814 public static void SpawnNPCs(Location location, Submarine outpost)
1816 if (outpost?.Info?.OutpostGenerationParams ==
null) {
return; }
1818 List<HumanPrefab> killedCharacters =
new List<HumanPrefab>();
1819 List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)> selectedCharacters
1820 =
new List<(HumanPrefab HumanPrefab, CharacterInfo CharacterInfo)>();
1822 List<FactionPrefab> factions =
new List<FactionPrefab>();
1823 if (location?.Faction !=
null) { factions.Add(location.Faction.Prefab); }
1824 if (location?.SecondaryFaction !=
null) { factions.Add(location.SecondaryFaction.Prefab); }
1826 var humanPrefabs = outpost.Info.OutpostGenerationParams.GetHumanPrefabs(factions, outpost, Rand.RandSync.ServerAndClient);
1827 foreach (HumanPrefab humanPrefab
in humanPrefabs)
1829 if (humanPrefab is
null) {
continue; }
1830 var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
1831 if (location !=
null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
1833 killedCharacters.Add(humanPrefab);
1836 selectedCharacters.Add((humanPrefab, characterInfo));
1840 foreach (HumanPrefab killedCharacter
in killedCharacters)
1842 for (
int tries = 0; tries < 100; tries++)
1844 var characterInfo = killedCharacter.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
1845 if (location !=
null && !location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier()))
1847 selectedCharacters.Add((killedCharacter, characterInfo));
1853 foreach ((var humanPrefab, var characterInfo) in selectedCharacters)
1855 Rand.SetSyncedSeed(ToolBox.StringToInt(characterInfo.Name));
1857 ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost,
SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags());
1858 if (gotoTarget ==
null)
1860 gotoTarget = outpost.GetHulls(
true).GetRandom(Rand.RandSync.ServerAndClient);
1863 var npc =
Character.Create(characterInfo.SpeciesName, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi:
true, createNetworkEvent:
true);
1864 npc.AnimController.FindHull(gotoTarget.WorldPosition, setSubmarine:
true);
1866 npc.HumanPrefab = humanPrefab;
1867 outpost.Info.AddOutpostNPCIdentifierOrTag(npc, humanPrefab.Identifier);
1868 foreach (Identifier tag
in humanPrefab.GetTags())
1870 outpost.Info.AddOutpostNPCIdentifierOrTag(npc, tag);
1872 if (GameMain.NetworkMember?.ServerSettings !=
null && !GameMain.NetworkMember.ServerSettings.KillableNPCs)
1874 npc.CharacterHealth.Unkillable =
true;
1876 humanPrefab.GiveItems(npc, outpost, gotoTarget as WayPoint, Rand.RandSync.ServerAndClient);
1877 foreach (Item item
in npc.Inventory.FindAllItems(it => it !=
null, recursive:
true))
1879 item.AllowStealing = outpost.Info.OutpostGenerationParams.AllowStealing;
1880 item.SpawnedInCurrentOutpost =
true;
1882 humanPrefab.InitializeCharacter(npc, gotoTarget);
List< Connection > Connections
IReadOnlyCollection< Wire > Wires
static IEnumerable< DockingPort > List
static IReadOnlyCollection< Door > DoorList
static void UpdatePower(float deltaTime)
Update the power calculations of all devices and grids Updates grids in the order of ConnCurrConsumpt...
static IEnumerable< Powered > PoweredList
bool TryConnect(Connection newConnection, bool addNode=true, bool sendNetworkEvent=false)
Tries to add the given connection to this wire. Note that this only affects the wire - adding the wir...
readonly List< MapEntity > linkedTo
@ Character
Characters only
@ Structure
Structures and hulls, but also items (for backwards support)!