Unstable 0.1400.3.0 (Bad sleep schedule edition)

This commit is contained in:
Markus Isberg
2021-06-03 03:34:18 +03:00
parent 0b3fb5e440
commit de04525d51
47 changed files with 703 additions and 213 deletions
@@ -66,7 +66,7 @@ namespace Barotrauma
private float Sight => AIParams.Sight;
private float Hearing => AIParams.Hearing;
private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
private bool AggressiveBoarding => AIParams.AggressiveBoarding;
private bool IsAggressiveBoarder => AIParams.AggressiveBoarding;
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
@@ -1271,7 +1271,7 @@ namespace Barotrauma
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - attackLimbPos;
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
if (wallTarget != null)
if (wallTarget != null && Character.Submarine == null)
{
if (wallTarget.Structure.Submarine != null)
{
@@ -1440,10 +1440,11 @@ namespace Barotrauma
else
{
pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
return;
}
}
@@ -1674,16 +1675,6 @@ namespace Barotrauma
}
if (canAttack)
{
if (SelectedAiTarget.Entity is Item targetItem)
{
var door = targetItem.GetComponent<Door>();
if (door != null && door.CanBeTraversed)
{
ResetAITarget();
State = PreviousState;
return;
}
}
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
{
IgnoreTarget(SelectedAiTarget);
@@ -2292,25 +2283,24 @@ namespace Barotrauma
var section = s.Sections[i];
if (section.gap == null) { continue; }
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
isInnerWall = isInnerWall || !leadsInside;
if (Character.AnimController.CanEnterSubmarine)
{
if (!isCharacterInside)
{
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0;
}
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
else if (IsAggressiveBoarder && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open;
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open * 0.25f;
}
}
else
{
// Inside
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
if (!isInnerWall)
{
@@ -2327,6 +2317,10 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else
{
valueModifier = 0.1f;
}
}
else
{
@@ -2350,7 +2344,7 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else if (AggressiveBoarding)
else if (IsAggressiveBoarder)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
// (Bonethreshers)
@@ -2384,17 +2378,24 @@ namespace Barotrauma
// Ignore broken and open doors, if cannot enter submarine
continue;
}
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
// Increase the priority if the character is outside and the door is from outside to inside
if (character.CurrentHull == null)
{
valueModifier *= isOpen ? 5 : 1;
// Increase the priority if the character is outside and the door is from outside to inside
if (door.CanBeTraversed)
{
valueModifier = 3;
}
else if (door.LinkedGap != null)
{
valueModifier = 1 + door.LinkedGap.Open;
}
}
else
{
// Inside -> ignore open doors and outer doors
valueModifier *= isOpen || isOutdoor ? 0 : 1;
valueModifier = isOpen || isOutdoor ? 0 : 1;
}
}
}
@@ -2643,9 +2644,9 @@ namespace Barotrauma
private void UpdateWallTarget(int requiredHoleCount)
{
wallTarget = null;
if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; }
if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; }
if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear();
Structure wall = null;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
@@ -2729,9 +2730,12 @@ namespace Barotrauma
{
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
{
bool isTargetingDoor = SelectedAiTarget.Entity is Item i && i.GetComponent<Door>() != null;
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
if (!isTargetingDoor)
{
ResetAITarget();
}
}
else
{
@@ -2741,7 +2745,6 @@ namespace Barotrauma
else
{
// Blocked by a disabled wall.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
}
}
@@ -1926,26 +1926,33 @@ namespace Barotrauma
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
{
operatingCharacter = null;
float highestPriority = -1.0f;
float highestPriorityModifier = -1.0f;
foreach (Character c in Character.CharacterList)
{
if (c.Removed) { continue; }
if (c.TeamID != team) { continue; }
if (c.IsIncapacitated) { continue; }
bool isOperated = c.SelectedConstruction == target.Item;
if (!isOperated)
{
if (c.AIController is HumanAIController humanAI)
{
isOperated = humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveOperateItem operateObjective && operateObjective.Component.Item == target.Item);
}
}
operatingCharacter = c;
if (isOperated)
if (c.SelectedConstruction == target.Item)
{
operatingCharacter = c;
return true;
}
if (c.AIController is HumanAIController humanAI)
{
foreach (var objective in humanAI.ObjectiveManager.Objectives)
{
if (!(objective is AIObjectiveOperateItem operateObjective)) { continue; }
if (operateObjective.Component.Item != target.Item) { continue; }
if (operateObjective.Priority < highestPriority) { continue; }
if (operateObjective.PriorityModifier < highestPriorityModifier) { continue; }
operatingCharacter = c;
highestPriority = operateObjective.Priority;
highestPriorityModifier = operateObjective.PriorityModifier;
}
}
}
return false;
return operatingCharacter != null;
}
// There's some duplicate logic in the two methods below, but making them use the same code would require some changes in the target classes so that we could use exactly the same checks.
@@ -637,7 +637,7 @@ namespace Barotrauma
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
}
}
return true;
return !f2.IsSensor;
}
Vector2 colliderBottom = GetColliderBottom();
@@ -1541,6 +1541,7 @@ namespace Barotrauma
case Physics.CollisionLevel:
if (!fixture.CollidesWith.HasFlag(Physics.CollisionCharacter)) { return -1; }
if (fixture.Body.UserData is Submarine && character.Submarine != null) { return -1; }
if (fixture.IsSensor) { return -1; }
if (fraction < standOnFloorFraction)
{
standOnFloorFraction = fraction;
@@ -2377,6 +2377,10 @@ namespace Barotrauma
var item = FindItemAtPosition(mouseSimPos, aimAssist);
focusedItem = CanInteract ? item : null;
if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None)
{
FocusedCharacter = null;
}
findFocusedTimer = 0.05f;
}
}
@@ -23,7 +23,17 @@ namespace Barotrauma
protected PropertyConditional.OperatorType Operator { get; set; }
public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
{
if (string.IsNullOrEmpty(Condition))
{
Condition = element.GetAttributeString("value", string.Empty);
if (string.IsNullOrEmpty(Condition))
{
DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).");
}
}
}
protected override bool? DetermineSuccess()
{
@@ -213,7 +213,14 @@ namespace Barotrauma
if (dialogOpened)
{
#if CLIENT
Character.DisableControls = true;
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
{
Character.DisableControls = true;
}
else
{
Reset();
}
#endif
if (ShouldInterrupt())
{
@@ -20,11 +20,7 @@ namespace Barotrauma
{
if (string.IsNullOrWhiteSpace(ItemIdentifier))
{
ItemIdentifier = element.GetAttributeString("itemidentifiers", "");
}
if (string.IsNullOrWhiteSpace(ItemIdentifier))
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - RemoveItemAction without an item identifier.");
ItemIdentifier = element.GetAttributeString("itemidentifiers", null) ?? element.GetAttributeString("identifier", "");
}
}
@@ -66,7 +62,7 @@ namespace Barotrauma
var item = inventory.FindItem(it =>
it != null &&
!removedItems.Contains(it) &&
it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase), recursive: true);
(string.IsNullOrEmpty(ItemIdentifier) || it.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase)), recursive: true);
if (item == null) { break; }
Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item);
@@ -74,7 +70,7 @@ namespace Barotrauma
}
else if (target is Item item)
{
if (item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
if (string.IsNullOrEmpty(ItemIdentifier) || item.Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
{
Entity.Spawner.AddToRemoveQueue(item);
removedItems.Add(item);
@@ -128,8 +128,8 @@ namespace Barotrauma
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
#else
npc.SetCustomInteract(
Trigger,
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.Get("CampaignInteraction.Talk"));
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
#endif
@@ -185,7 +185,7 @@ namespace Barotrauma
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
{
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
for (int i = 0; i < count; i++)
{
LoadHuman(humanPrefab, element, submarine);
@@ -42,7 +42,7 @@ namespace Barotrauma
{
this.sub = sub;
itemConfig = prefab.ConfigElement.Element("Items");
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f);
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
DetermineCargo();
}
@@ -84,8 +84,7 @@ namespace Barotrauma
{
characters.Clear();
characterItems.Clear();
// VIP transport mission characters stay in the same location; other characters roam at will
// could be replaced with a designated waypoint for VIPs, such as cargo or crew
WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
Rand.RandSync randSync = Rand.RandSync.Server;
@@ -95,12 +94,30 @@ namespace Barotrauma
randSync = Rand.RandSync.Unsynced;
}
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
foreach (XElement element in characterConfig.Elements())
{
var humanPrefab = GetHumanPrefabFromElement(element);
if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; }
var jobPrefab = humanPrefab.GetJobPrefab();
if (jobPrefab != null)
{
var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub);
if (jobSpecificSpawnPos != null)
{
explicitStayInHullPos = jobSpecificSpawnPos;
break;
}
}
}
foreach (XElement element in characterConfig.Elements())
{
int count = CalculateScalingEscortedCharacterCount(inMission: true);
for (int i = 0; i < count; i++)
{
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
if (spawnedCharacter.AIController is HumanAIController humanAI)
{
humanAI.InitMentalStateManager();
@@ -293,6 +293,7 @@ namespace Barotrauma
/// </summary>
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
{
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
if (trigger.Delay > 0)
{
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
@@ -311,6 +312,7 @@ namespace Barotrauma
/// </summary>
private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
{
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase));
if (eventPrefab == null)
{
@@ -398,7 +400,7 @@ namespace Barotrauma
public virtual void AdjustLevelData(LevelData levelData) { }
// putting these here since both escort and pirate missions need them. could be tucked away into another class that they can inherit from (or use composition)
protected HumanPrefab CreateHumanPrefabFromElement(XElement element)
protected HumanPrefab GetHumanPrefabFromElement(XElement element)
{
if (element.Attribute("name") != null)
{
@@ -117,6 +117,9 @@ namespace Barotrauma
[Serialize(0.0f, true)]
public float Delay { get; private set; }
[Serialize(false, true)]
public bool CampaignOnly { get; private set; }
public TriggerEvent(XElement element)
{
SerializableProperty.DeserializeProperties(this, element);
@@ -191,6 +191,8 @@ namespace Barotrauma
}
enemySub.EnableMaintainPosition();
enemySub.TeamID = CharacterTeamType.None;
//make the enemy sub withstand atleast the same depth as the player sub
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
}
private void InitPirates()
@@ -230,7 +232,7 @@ namespace Barotrauma
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
if (!commanderAssigned)
{
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
@@ -431,7 +431,15 @@ namespace Barotrauma
float scatterAmount = scatter;
if (spawnPosType.HasFlag(Level.PositionType.SidePath))
{
scatterAmount = Math.Min(scatter, Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath).Min(t => t.MinWidth) / 2);
var sidePaths = Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath);
if (sidePaths.Any())
{
scatterAmount = Math.Min(scatter, sidePaths.Min(t => t.MinWidth) / 2);
}
else
{
scatterAmount = scatter;
}
}
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
{
@@ -46,6 +46,7 @@ namespace Barotrauma
public List<PurchasedItem> ItemsInBuyCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> ItemsInSellCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> ItemsInSellFromSubCrate { get; } = new List<PurchasedItem>();
public List<PurchasedItem> PurchasedItems { get; } = new List<PurchasedItem>();
public List<SoldItem> SoldItems { get; } = new List<SoldItem>();
@@ -55,6 +56,7 @@ namespace Barotrauma
public Action OnItemsInBuyCrateChanged;
public Action OnItemsInSellCrateChanged;
public Action OnItemsInSellFromSubCrateChanged;
public Action OnPurchasedItemsChanged;
public Action OnSoldItemsChanged;
@@ -75,6 +77,12 @@ namespace Barotrauma
OnItemsInSellCrateChanged?.Invoke();
}
public void ClearItemsInSellFromSubCrate()
{
ItemsInSellFromSubCrate.Clear();
OnItemsInSellFromSubCrateChanged?.Invoke();
}
public void SetPurchasedItems(List<PurchasedItem> items)
{
PurchasedItems.Clear();
@@ -417,7 +417,7 @@ namespace Barotrauma
if (isControlledCharacterNull) { return null; }
#endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemTargetedBySomeone(order.TargetItemComponent, controlledCharacter != null ? controlledCharacter.TeamID : CharacterTeamType.Team1, out Character operatingCharacter) &&
(isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
(isControlledCharacterNull || operatingCharacter.CanHearCharacter(controlledCharacter)))
{
return operatingCharacter;
}
@@ -558,12 +558,14 @@ namespace Barotrauma
{
CargoManager.ClearItemsInBuyCrate();
CargoManager.ClearItemsInSellCrate();
CargoManager.ClearItemsInSellFromSubCrate();
}
else
{
if (GameMain.NetworkMember.IsServer)
{
CargoManager?.ClearItemsInBuyCrate();
// TODO: CargoManager?.ClearItemsInSellFromSubCrate();
}
else if (GameMain.NetworkMember.IsClient)
{
@@ -8,7 +8,6 @@ namespace Barotrauma.Items.Components
{
partial class MotionSensor : ItemComponent
{
private const float UpdateInterval = 0.1f;
private float rangeX, rangeY;
private Vector2 detectOffset;
@@ -75,6 +74,13 @@ namespace Barotrauma.Items.Components
}
}
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 100.0f, DecimalCount = 2), Serialize(0.1f, true, description: "How often the sensor checks if there's something moving near it. Higher values are better for performance.", alwaysUseInstanceValues: true)]
public float UpdateInterval
{
get;
set;
}
private int maxOutputLength;
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
public int MaxOutputLength
@@ -135,6 +141,9 @@ namespace Barotrauma.Items.Components
{
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
}
//randomize update timer so all sensors aren't updated during the same frame
updateTimer = Rand.Range(0.0f, UpdateInterval);
}
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
@@ -17,7 +17,7 @@ namespace Barotrauma
public readonly string Identifier;
public readonly int OriginalContainerIndex;
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerIndex, ushort moduleIndex)
public TakenItem(string identifier, UInt16 originalID, int originalContainerIndex, ushort moduleIndex)
{
OriginalID = originalID;
OriginalContainerIndex = originalContainerIndex;
@@ -345,7 +345,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
continue;
}
if (!ushort.TryParse(takenItemSplit[2], out ushort containerIndex))
if (!int.TryParse(takenItemSplit[2], out int containerIndex))
{
DebugConsole.ThrowError($"Error in saved location: could not parse taken container index \"{takenItemSplit[2]}\"");
continue;
@@ -577,6 +577,10 @@ namespace Barotrauma
{
weight /= missionCount * 2;
}
if (destination.IsRadiated())
{
weight *= 0.001f;
}
}
return weight;
}
@@ -53,7 +53,16 @@ namespace Barotrauma
//dimensions of the wall sections' physics bodies (only used for debug rendering)
private readonly List<Vector2> bodyDebugDimensions = new List<Vector2>();
public bool Indestructible;
#if DEBUG
[Serialize(false, true), Editable]
#else
[Serialize(false, true)]
#endif
public bool Indestructible
{
get;
set;
}
//sections of the wall that are supposed to be rendered
public WallSection[] Sections
@@ -798,7 +807,7 @@ namespace Barotrauma
public void AddDamage(int sectionIndex, float damage, Character attacker = null)
{
if (!Prefab.Body || Prefab.Platform || Indestructible ) { return; }
if (!Prefab.Body || Prefab.Platform || Indestructible) { return; }
if (sectionIndex < 0 || sectionIndex > Sections.Length - 1) { return; }
@@ -23,7 +23,7 @@ namespace Barotrauma
HideInMenus = 2
}
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation }
public enum SubmarineType { Player, Outpost, OutpostModule, Wreck, BeaconStation, EnemySubmarine }
public enum SubmarineClass { Undefined, Scout, Attack, Transport, DeepDiver }
partial class SubmarineInfo : IDisposable
@@ -766,14 +766,14 @@ namespace Barotrauma
if (!wayPoint2.linkedTo.Contains(this)) { wayPoint2.linkedTo.Add(this); }
}
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, Job assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, Ruin ruin = null, bool useSyncedRand = false)
{
return WayPointList.GetRandom(wp =>
wp.Submarine == sub &&
wp.ParentRuin == ruin &&
wp.spawnType == spawnType &&
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob.Prefab))
, useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
(assignedJob == null || (assignedJob != null && wp.AssignedJob == assignedJob)),
useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced);
}
public static WayPoint[] SelectCrewSpawnPoints(List<CharacterInfo> crew, Submarine submarine)