Merge https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -95,10 +95,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (visibleHulls == null)
|
||||
{
|
||||
visibleHulls = Character.GetVisibleHulls();
|
||||
}
|
||||
visibleHulls ??= Character.GetVisibleHulls();
|
||||
return visibleHulls;
|
||||
}
|
||||
private set
|
||||
@@ -425,14 +422,9 @@ namespace Barotrauma
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door != null)
|
||||
{
|
||||
if (!door.CanBeTraversed)
|
||||
if (!pathSteering.CanAccessDoor(door))
|
||||
{
|
||||
if (!door.HasAccess(Character))
|
||||
{
|
||||
if (!canAttackDoors) { continue; }
|
||||
// Treat doors that don't have access to like they were farther, because it will take time to break them.
|
||||
multiplier = 5;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -473,7 +465,7 @@ namespace Barotrauma
|
||||
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
||||
float sqrDist = diff.LengthSquared();
|
||||
bool isClose = sqrDist < MathUtils.Pow2(100);
|
||||
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
|
||||
if (Character.CurrentHull == null || (isClose && !isClosedDoor) || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
|
||||
{
|
||||
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
|
||||
Character.ReleaseSecondaryItem();
|
||||
|
||||
@@ -369,7 +369,13 @@ namespace Barotrauma
|
||||
}
|
||||
else if (targetCharacter.AIController is EnemyAIController enemy)
|
||||
{
|
||||
if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
if (enemy.PetBehavior != null && (PetBehavior != null || AIParams.HasTag("pet")))
|
||||
{
|
||||
// Pets see other pets as pets by default.
|
||||
// Monsters see them only as pet only when they have a matching ai target. Otherwise they use the other tags, specified below.
|
||||
targetingTag = "pet";
|
||||
}
|
||||
else if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
{
|
||||
targetingTag = "husk";
|
||||
}
|
||||
@@ -695,6 +701,9 @@ namespace Barotrauma
|
||||
// Can't target characters of same species/group because that would make us hostile to all friendly characters in the same species/group.
|
||||
if (Character.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
if (targetCharacter.IsSameSpeciesOrGroup(c)) { return false; }
|
||||
//don't try to attack targets in a sub that belongs to a different team
|
||||
//(for example, targets in an outpost if we're in the main sub)
|
||||
if (c.Submarine?.TeamID != Character.Submarine?.TeamID) { return false; }
|
||||
if (c.IsPlayer || Character.IsOnFriendlyTeam(c))
|
||||
{
|
||||
return a.Damage >= selectedTargetingParams.Threshold;
|
||||
@@ -894,7 +903,7 @@ namespace Barotrauma
|
||||
_previousAttackLimb?.attack is Attack previousAttack && (previousAttack.AfterAttack != AIBehaviorAfterAttack.FallBack || previousAttack.CoolDownTimer <= 0)))
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target, false);
|
||||
var memory = GetTargetMemory(target);
|
||||
if (memory != null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
@@ -981,7 +990,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.SetPath(path);
|
||||
PathSteering.SetPath(patrolTarget.SimPosition, path);
|
||||
patrolTimerMargin = 0;
|
||||
newPatrolTargetTimer = newPatrolTargetIntervalMax * Rand.Range(0.5f, 1.5f);
|
||||
searchingNewHull = false;
|
||||
@@ -1088,13 +1097,13 @@ namespace Barotrauma
|
||||
Character owner = GetOwner(item);
|
||||
if (owner != null)
|
||||
{
|
||||
if (Character.IsFriendly(owner))
|
||||
if (Character.IsFriendly(owner) || owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else if (!owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
else
|
||||
{
|
||||
SelectedAiTarget = owner.AiTarget;
|
||||
}
|
||||
@@ -2186,7 +2195,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true, keepAlive: true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
@@ -2531,8 +2540,10 @@ namespace Barotrauma
|
||||
if (Math.Abs(limbDiff.X) < itemBodyExtent &&
|
||||
Math.Abs(limbDiff.Y) < Character.AnimController.Collider.GetMaxExtent() + Character.AnimController.ColliderHeightFromFloor)
|
||||
{
|
||||
Vector2 velocity = limbDiff;
|
||||
if (limbDiff.LengthSquared() > 0.01f) { velocity = Vector2.Normalize(velocity); }
|
||||
item.body.LinearVelocity *= 0.9f;
|
||||
item.body.LinearVelocity -= limbDiff * 0.25f;
|
||||
item.body.LinearVelocity -= velocity * 0.25f;
|
||||
bool wasBroken = item.Condition <= 0.0f;
|
||||
item.AddDamage(Character, item.WorldPosition, new Attack(0.0f, 0.0f, 0.0f, 0.0f, 0.02f * Character.Params.EatingSpeed), deltaTime);
|
||||
Character.ApplyStatusEffects(ActionType.OnEating, deltaTime);
|
||||
@@ -2924,7 +2935,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0)
|
||||
//no need to eat if the character is already in full health (except if it's a pet - pets actually need to eat to stay alive, not just to regain health)
|
||||
if (targetParams.State == AIState.Eat && Character.Params.Health.HealthRegenerationWhenEating > 0 && !Character.IsPet)
|
||||
{
|
||||
valueModifier *= MathHelper.Lerp(1f, 0.1f, Character.HealthPercentage / 100f);
|
||||
}
|
||||
@@ -3021,7 +3033,7 @@ namespace Barotrauma
|
||||
//if the target is very close, the distance doesn't make much difference
|
||||
// -> just ignore the distance and target whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true, keepAlive: SelectedAiTarget != aiTarget);
|
||||
if (Character.Submarine != null && !Character.Submarine.Info.IsRuin && Character.CurrentHull != null)
|
||||
{
|
||||
float diff = Math.Abs(toTarget.Y) - Character.CurrentHull.Size.Y;
|
||||
@@ -3090,12 +3102,20 @@ namespace Barotrauma
|
||||
if (aiTarget.Entity is Item i)
|
||||
{
|
||||
Character owner = GetOwner(i);
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (owner == Character) { continue; }
|
||||
if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
|
||||
if (owner != null)
|
||||
{
|
||||
continue;
|
||||
if (owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)) { continue; }
|
||||
if (Character.IsFriendly(owner))
|
||||
{
|
||||
// Don't target items that we own. This is a rare case, and almost entirely related to Humanhusks (in the vanilla game).
|
||||
continue;
|
||||
}
|
||||
if (owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
{
|
||||
// ignore if owner is tagged to be explicitly ignored (Feign Death)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
@@ -3418,7 +3438,7 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound)
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound = false, bool keepAlive = false)
|
||||
{
|
||||
if (!targetMemories.TryGetValue(target, out AITargetMemory memory))
|
||||
{
|
||||
@@ -3428,9 +3448,8 @@ namespace Barotrauma
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
}
|
||||
if (addIfNotFound)
|
||||
if (keepAlive)
|
||||
{
|
||||
// Keep the memory alive.
|
||||
memory.Priority = Math.Max(memory.Priority, minPriority);
|
||||
}
|
||||
return memory;
|
||||
@@ -3446,7 +3465,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (CanPerceive(_selectedAiTarget, checkVisibility: false))
|
||||
{
|
||||
var memory = GetTargetMemory(_selectedAiTarget, false);
|
||||
var memory = GetTargetMemory(_selectedAiTarget);
|
||||
if (memory != null)
|
||||
{
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
@@ -3504,10 +3523,10 @@ namespace Barotrauma
|
||||
private readonly float stateResetCooldown = 10;
|
||||
private float stateResetTimer;
|
||||
private bool isStateChanged;
|
||||
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
|
||||
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
|
||||
private readonly Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams>();
|
||||
private readonly HashSet<StatusEffect.AITrigger> inactiveTriggers = new HashSet<StatusEffect.AITrigger>();
|
||||
|
||||
public void LaunchTrigger(AITrigger trigger)
|
||||
public void LaunchTrigger(StatusEffect.AITrigger trigger)
|
||||
{
|
||||
if (trigger.IsTriggered) { return; }
|
||||
if (activeTriggers.ContainsKey(trigger)) { return; }
|
||||
@@ -3527,7 +3546,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var triggerObject in activeTriggers)
|
||||
{
|
||||
AITrigger trigger = triggerObject.Key;
|
||||
StatusEffect.AITrigger trigger = triggerObject.Key;
|
||||
if (trigger.IsPermanent) { continue; }
|
||||
trigger.UpdateTimer(deltaTime);
|
||||
if (!trigger.IsActive)
|
||||
@@ -3537,7 +3556,7 @@ namespace Barotrauma
|
||||
inactiveTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
foreach (AITrigger trigger in inactiveTriggers)
|
||||
foreach (StatusEffect.AITrigger trigger in inactiveTriggers)
|
||||
{
|
||||
activeTriggers.Remove(trigger);
|
||||
}
|
||||
@@ -3643,7 +3662,11 @@ namespace Barotrauma
|
||||
{
|
||||
isStateChanged = true;
|
||||
SetStateResetTimer();
|
||||
ChangeParams(target.SpeciesName, state, priority, ignoreAttacksIfNotInSameSub: !target.IsHuman);
|
||||
if (!Character.IsPet || !target.IsHuman)
|
||||
{
|
||||
//don't turn pets hostile to all humans when attacked by one
|
||||
ChangeParams(target.SpeciesName, state, priority, ignoreAttacksIfNotInSameSub: !target.IsHuman);
|
||||
}
|
||||
if (target.IsHuman)
|
||||
{
|
||||
priority = GetTargetParams("human")?.Priority;
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Barotrauma
|
||||
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
|
||||
public readonly List<Item> IgnoredItems = new List<Item>();
|
||||
|
||||
private readonly HashSet<Hull> dirtyHullSafetyCalculations = new HashSet<Hull>();
|
||||
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
private bool wasConscious;
|
||||
@@ -436,6 +438,7 @@ namespace Barotrauma
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
PropagateHullSafety(Character, h);
|
||||
dirtyHullSafetyCalculations.Remove(h);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -443,9 +446,15 @@ namespace Barotrauma
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
RefreshHullSafety(h);
|
||||
dirtyHullSafetyCalculations.Remove(h);
|
||||
}
|
||||
}
|
||||
foreach (Hull h in dirtyHullSafetyCalculations)
|
||||
{
|
||||
RefreshHullSafety(h);
|
||||
}
|
||||
}
|
||||
dirtyHullSafetyCalculations.Clear();
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
{
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.Submarine.TeamID == Character.OriginalTeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
@@ -615,7 +624,7 @@ namespace Barotrauma
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
|
||||
Character.CurrentHull.IsWetRoom;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo { IsWaitOrder: true };
|
||||
bool removeDivingSuit = !shouldKeepTheGearOn && !IsOrderedToWait();
|
||||
if (shouldActOnSuffocation && Character.CurrentHull.Oxygen > 0 && (!isCurrentObjectiveFindSafety || Character.OxygenAvailable < 1))
|
||||
{
|
||||
@@ -900,7 +909,7 @@ namespace Barotrauma
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer() ?? container.Item;
|
||||
var rootContainer = container.Item.RootContainer ?? container.Item;
|
||||
if (rootContainer.GetComponent<Fabricator>() != null || rootContainer.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
@@ -1145,7 +1154,7 @@ namespace Barotrauma
|
||||
string msgId = "DialogLowOxygen";
|
||||
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
if (Character.Bleeding > 2.0f && !Character.IsMedic)
|
||||
if (Character.Bleeding > AfflictionPrefab.Bleeding.TreatmentThreshold && !Character.IsMedic)
|
||||
{
|
||||
string msgId = "DialogBleeding";
|
||||
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||
@@ -1658,7 +1667,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static bool HasDivingSuit(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||
=> HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, requireOxygenTank ? AIObjectiveFindDivingGear.OXYGEN_SOURCE : Identifier.Empty, conditionPercentage, requireEquipped: true,
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes));
|
||||
predicate: (Item item) => character.HasEquippedItem(item, InvSlotType.OuterClothes | InvSlotType.InnerClothes));
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
@@ -1891,7 +1900,7 @@ namespace Barotrauma
|
||||
private static float GetReactionTime() => reactionTime * Rand.Range(0.75f, 1.25f);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threads.
|
||||
/// Updates the hull safety for all ai characters in the team. The idea is that the crew communicates (magically) via radio about the threats.
|
||||
/// The safety levels need to be calculated for each bot individually, because the formula takes into account things like current orders.
|
||||
/// There's now a cached value per each hull, which should prevent too frequent calculations.
|
||||
/// </summary>
|
||||
@@ -1900,9 +1909,13 @@ namespace Barotrauma
|
||||
DoForEachBot(character, (humanAi) => humanAi.RefreshHullSafety(hull));
|
||||
}
|
||||
|
||||
public void AskToRecalculateHullSafety(Hull hull) => dirtyHullSafetyCalculations.Add(hull);
|
||||
|
||||
private void RefreshHullSafety(Hull hull)
|
||||
{
|
||||
if (GetHullSafety(hull, Character, VisibleHulls) > HULL_SAFETY_THRESHOLD)
|
||||
var visibleHulls = dirtyHullSafetyCalculations.Contains(hull) ? hull.GetConnectedHulls(includingThis: true, searchDepth: 1) : VisibleHulls;
|
||||
float hullSafety = GetHullSafety(hull, Character, visibleHulls);
|
||||
if (hullSafety > HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
UnsafeHulls.Remove(hull);
|
||||
}
|
||||
|
||||
+123
-115
@@ -22,7 +22,10 @@ namespace Barotrauma
|
||||
|
||||
private readonly Character character;
|
||||
|
||||
private Vector2 currentTarget;
|
||||
/// <summary>
|
||||
/// In sim units.
|
||||
/// </summary>
|
||||
private Vector2 currentTargetPos;
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
@@ -40,11 +43,6 @@ namespace Barotrauma
|
||||
get { return pathFinder; }
|
||||
}
|
||||
|
||||
public Vector2 CurrentTarget
|
||||
{
|
||||
get { return currentTarget; }
|
||||
}
|
||||
|
||||
public bool IsPathDirty
|
||||
{
|
||||
get;
|
||||
@@ -54,9 +52,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns true if any node in the path is in stairs
|
||||
/// </summary>
|
||||
public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
|
||||
public bool PathHasStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
|
||||
|
||||
public bool IsCurrentNodeLadder => currentPath?.CurrentNode?.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character);
|
||||
public bool IsCurrentNodeLadder => GetCurrentLadder() != null;
|
||||
|
||||
public bool IsNextNodeLadder => GetNextLadder() != null;
|
||||
|
||||
@@ -64,14 +62,9 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentPath == null) { return false; }
|
||||
if (currentPath.CurrentNode == null) { return false; }
|
||||
if (currentPath.NextNode == null) { return false; }
|
||||
var currentLadder = currentPath.CurrentNode.Ladders;
|
||||
var currentLadder = GetCurrentLadder();
|
||||
if (currentLadder == null) { return false; }
|
||||
if (!currentLadder.Item.IsInteractable(character)) { return false; }
|
||||
var nextLadder = GetNextLadder();
|
||||
return nextLadder != null && nextLadder == currentLadder;
|
||||
return currentLadder == GetNextLadder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +100,10 @@ namespace Barotrauma
|
||||
findPathTimer -= step;
|
||||
}
|
||||
|
||||
public void SetPath(SteeringPath path)
|
||||
public void SetPath(Vector2 targetPos, SteeringPath path)
|
||||
{
|
||||
currentTargetPos = targetPos;
|
||||
currentPath = path;
|
||||
if (path.Nodes.Any())
|
||||
{
|
||||
currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
|
||||
}
|
||||
findPathTimer = Math.Min(findPathTimer, 1.0f);
|
||||
IsPathDirty = false;
|
||||
}
|
||||
@@ -136,46 +126,17 @@ namespace Barotrauma
|
||||
steering += addition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeks the ladder from the next and next + 1 nodes.
|
||||
/// </summary>
|
||||
public Ladder GetNextLadder()
|
||||
{
|
||||
if (currentPath == null) { return null; }
|
||||
if (currentPath.NextNode == null) { return null; }
|
||||
if (currentPath.NextNode.Ladders != null && currentPath.NextNode.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return currentPath.NextNode.Ladders;
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = currentPath.CurrentIndex + 2;
|
||||
if (currentPath.Nodes.Count > index)
|
||||
{
|
||||
var node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
if (node.Ladders != null && node.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
//if the next node is a hatch, check if the node after that is a ladder
|
||||
else if (node.ConnectedDoor != null && node.ConnectedDoor.IsHorizontal)
|
||||
{
|
||||
index++;
|
||||
if (currentPath.Nodes.Count > index)
|
||||
{
|
||||
node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
if (node.Ladders != null && node.Ladders.Item.IsInteractable(character))
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
}
|
||||
}
|
||||
public Ladder GetCurrentLadder() => GetLadder(currentPath?.CurrentNode);
|
||||
|
||||
}
|
||||
return null;
|
||||
public Ladder GetNextLadder() => GetLadder(currentPath?.NextNode);
|
||||
|
||||
private Ladder GetLadder(WayPoint wp)
|
||||
{
|
||||
if (wp?.Ladders?.Item is Item item && item.IsInteractable(character))
|
||||
{
|
||||
return wp.Ladders;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||
@@ -183,19 +144,10 @@ namespace Barotrauma
|
||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.CurrentNode == null;
|
||||
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
||||
{
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
|
||||
{
|
||||
//target in a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
|
||||
if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
|
||||
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
}
|
||||
}
|
||||
// If the target has moved, we need a new path.
|
||||
// Different subs are already taken into account before setting the target.
|
||||
// Triggers when either the target or we have changed subs, but only once (until the new path has been accepted).
|
||||
Vector2 targetDiff = target - currentTargetPos;
|
||||
if (targetDiff.LengthSquared() > 1)
|
||||
{
|
||||
needsNewPath = true;
|
||||
@@ -205,14 +157,14 @@ namespace Barotrauma
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
IsPathDirty = true;
|
||||
if (!needsNewPath && findPathTimer < -1)
|
||||
if (!needsNewPath && currentPath?.CurrentNode is WayPoint wp)
|
||||
{
|
||||
if (character.Submarine != null && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0)
|
||||
if (character.Submarine != null && wp.Ladders == null && wp.ConnectedDoor == null && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0)
|
||||
{
|
||||
// Not moving -> need a new path.
|
||||
needsNewPath = true;
|
||||
}
|
||||
if (character.Submarine == null && currentPath?.CurrentNode is WayPoint wp && wp.CurrentHull != null)
|
||||
if (character.Submarine == null && wp.CurrentHull != null)
|
||||
{
|
||||
// Current node inside, while we are outside
|
||||
// -> Check that the current node is not too far (can happen e.g. if someone controls the character in the meanwhile)
|
||||
@@ -226,7 +178,7 @@ namespace Barotrauma
|
||||
if (findPathTimer < 0)
|
||||
{
|
||||
SkipCurrentPathNodes();
|
||||
currentTarget = target;
|
||||
currentTargetPos = target;
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||
@@ -252,6 +204,14 @@ namespace Barotrauma
|
||||
useNewPath = Vector2.DistanceSquared(character.WorldPosition, currentPath.CurrentNode.WorldPosition) > Math.Pow(Vector2.Distance(character.WorldPosition, newPath.Nodes.First().WorldPosition) * 3, 2);
|
||||
}
|
||||
}
|
||||
if (!useNewPath && !character.CanSeeTarget(currentPath.CurrentNode))
|
||||
{
|
||||
// If we are set to disregard the new path, ensure that we can actually see the current node of the old path,
|
||||
// because it's possible that there's e.g. a closed door between us and the current node,
|
||||
// and in that case we'd want to use the new path instead of the old.
|
||||
// There's visibility checks in the pathfinder calls, so the new path should always be ok.
|
||||
useNewPath = true;
|
||||
}
|
||||
|
||||
bool IsIdenticalPath()
|
||||
{
|
||||
@@ -330,6 +290,7 @@ namespace Barotrauma
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.Height / 2 + collider.Radius)
|
||||
{
|
||||
// TODO: might cause some edge cases -> do we need this?
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
if (diff == Vector2.Zero) { return Vector2.Zero; }
|
||||
@@ -346,12 +307,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (currentPath.Finished)
|
||||
{
|
||||
Vector2 pos2 = host.SimPosition;
|
||||
Vector2 hostPosition = host.SimPosition;
|
||||
if (character != null && character.Submarine == null && CurrentPath.Nodes.Count > 0 && CurrentPath.Nodes.Last().Submarine != null)
|
||||
{
|
||||
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
hostPosition -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
}
|
||||
return currentTarget - pos2;
|
||||
return currentTargetPos - hostPosition;
|
||||
}
|
||||
bool doorsChecked = false;
|
||||
checkDoorsTimer = Math.Min(checkDoorsTimer, GetDoorCheckTime());
|
||||
@@ -371,14 +332,46 @@ namespace Barotrauma
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController && !character.LockHands;
|
||||
Ladder currentLadder = currentPath.CurrentNode.Ladders;
|
||||
if (currentLadder != null && !currentLadder.Item.IsInteractable(character))
|
||||
{
|
||||
currentLadder = null;
|
||||
}
|
||||
Ladder currentLadder = GetCurrentLadder();
|
||||
Ladder nextLadder = GetNextLadder();
|
||||
var ladders = currentLadder ?? nextLadder;
|
||||
bool useLadders = canClimb && ladders != null && steering.LengthSquared() > 0.1f && (!isDiving || steering.Y > 1);
|
||||
bool useLadders = canClimb && ladders != null;
|
||||
var collider = character.AnimController.Collider;
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
if (useLadders)
|
||||
{
|
||||
if (character.IsClimbing && Math.Abs(diff.X) - ConvertUnits.ToDisplayUnits(colliderSize.X) > Math.Abs(diff.Y))
|
||||
{
|
||||
// If the current node is horizontally farther from us than vertically, we don't want to keep climbing the ladders.
|
||||
useLadders = false;
|
||||
}
|
||||
else if (!character.IsClimbing && currentPath.NextNode != null && nextLadder == null)
|
||||
{
|
||||
Vector2 diffToNextNode = currentPath.NextNode.WorldPosition - pos;
|
||||
if (Math.Abs(diffToNextNode.X) > Math.Abs(diffToNextNode.Y))
|
||||
{
|
||||
// If the next node is horizontally farther from us than vertically, we don't want to start climbing.
|
||||
useLadders = false;
|
||||
}
|
||||
}
|
||||
else if (isDiving && steering.Y < 1)
|
||||
{
|
||||
// When diving, only use ladders to get upwards (towards the surface), otherwise we can just ignore them.
|
||||
useLadders = false;
|
||||
}
|
||||
}
|
||||
if (character.IsClimbing && !useLadders)
|
||||
{
|
||||
if (currentPath.IsAtEndNode && canClimb && ladders != null)
|
||||
{
|
||||
// Don't release the ladders when ending a path in ladders.
|
||||
useLadders = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
}
|
||||
if (useLadders && character.SelectedSecondaryItem != ladders.Item)
|
||||
{
|
||||
if (character.CanInteractWith(ladders.Item))
|
||||
@@ -398,40 +391,28 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !useLadders)
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
if (character.IsClimbing && useLadders)
|
||||
{
|
||||
if (currentLadder == null && nextLadder != null)
|
||||
if (currentLadder == null && nextLadder != null && character.SelectedSecondaryItem == nextLadder.Item)
|
||||
{
|
||||
// Climbing a ladder but the path is still on the node next to the ladder -> Skip the node.
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
||||
if (nextLadderSameAsCurrent || currentLadder != null && nextLadder != null && Math.Abs(currentLadder.Item.Position.X - nextLadder.Item.Position.X) < 50)
|
||||
bool nextLadderSameAsCurrent = currentLadder == nextLadder;
|
||||
if (currentLadder != null && nextLadder != null)
|
||||
{
|
||||
//climbing ladders -> don't move horizontally
|
||||
diff.X = 0.0f;
|
||||
}
|
||||
//at the same height as the waypoint
|
||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||
float colliderSize = (collider.Height / 2 + collider.Radius) * 1.25f;
|
||||
if (heightDiff < colliderSize)
|
||||
float colliderHeight = collider.Height / 2 + collider.Radius;
|
||||
float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X);
|
||||
if (heightDiff < colliderHeight * 1.25f)
|
||||
{
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
bool isAboveFloor = heightFromFloor > -0.1f;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||
@@ -442,12 +423,36 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)
|
||||
bool isAboveFloor;
|
||||
if (diff.Y < 0)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
// When climbing down, let's use the collider bottom to prevent getting stuck at the bottom of the ladders.
|
||||
float colliderBottom = character.AnimController.Collider.SimPosition.Y;
|
||||
float floorY = character.AnimController.FloorY;
|
||||
isAboveFloor = colliderBottom > floorY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When climbing up, let's use the lowest collider (feet).
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative,
|
||||
// when a foot is still below the platform.
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
isAboveFloor = heightFromFloor > -0.1f;
|
||||
}
|
||||
if (isAboveFloor)
|
||||
{
|
||||
if (Math.Abs(diff.Y) < distanceMargin)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
else if (!currentPath.IsAtEndNode && (nextLadder == null || (currentLadder != null && Math.Abs(currentLadder.Item.WorldPosition.X - nextLadder.Item.WorldPosition.X) > distanceMargin)))
|
||||
{
|
||||
// Can't skip the node -> Release the ladders, because the next node is not on a ladder or it's horizontally too far.
|
||||
character.StopClimbing();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (nextLadder != null)
|
||||
else if (currentLadder != null && currentPath.NextNode != null)
|
||||
{
|
||||
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
||||
{
|
||||
@@ -466,7 +471,6 @@ namespace Barotrauma
|
||||
if (door == null || door.CanBeTraversed)
|
||||
{
|
||||
float margin = MathHelper.Lerp(1, 5, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
float targetDistance = Math.Max(Math.Max(colliderSize.X, colliderSize.Y) / 2 * margin, 0.5f);
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
@@ -485,7 +489,6 @@ namespace Barotrauma
|
||||
{
|
||||
// Walking horizontally
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
Vector2 velocity = collider.LinearVelocity;
|
||||
// If the character is very short, it would fail to use the waypoint nodes because they are always too high.
|
||||
// If the character is very thin, it would often fail to reach the waypoints, because the horizontal distance is too small.
|
||||
@@ -512,9 +515,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
|
||||
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow && currentLadder == null && (door == null || door.CanBeTraversed))
|
||||
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow)
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
if (door is not { CanBeTraversed: false } && (currentLadder == null || nextLadder == null))
|
||||
{
|
||||
NextNode(!doorsChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentPath.CurrentNode == null)
|
||||
@@ -533,9 +539,9 @@ namespace Barotrauma
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
public bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsBroken) { return true; }
|
||||
if (door.CanBeTraversed) { return true; }
|
||||
if (door.IsClosed)
|
||||
{
|
||||
if (!door.Item.IsInteractable(character)) { return false; }
|
||||
@@ -631,10 +637,12 @@ namespace Barotrauma
|
||||
{
|
||||
//the node we're heading towards is the last one in the path, and at a door
|
||||
//the door needs to be open for the character to reach the node
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap != null)
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap is Gap linkedGap)
|
||||
{
|
||||
// Keep the airlock doors closed, but not in ruins/wrecks
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom && currentWaypoint.CurrentHull is { IsWetRoom: false } || currentWaypoint.Submarine == null || currentWaypoint.Submarine.Info.IsRuin || currentWaypoint.Submarine.Info.IsWreck)
|
||||
if (currentWaypoint.Submarine == null ||
|
||||
currentWaypoint.Submarine.Info is { IsPlayer: false } ||
|
||||
!linkedGap.IsRoomToRoom ||
|
||||
(linkedGap.IsRoomToRoom && currentWaypoint.CurrentHull is { IsWetRoom: false }))
|
||||
{
|
||||
shouldBeOpen = true;
|
||||
door = currentWaypoint.ConnectedDoor;
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Voronoi2.GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 potentialAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
float distSqr = Vector2.DistanceSquared(character.SimPosition, potentialAttachPos);
|
||||
|
||||
@@ -506,6 +506,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SpeakAfterOrderReceived() { }
|
||||
|
||||
protected static bool CanEquip(Character character, Item item, bool allowWearing)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
|
||||
+6
-2
@@ -14,6 +14,10 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<Item> prioritizedItems = new List<Item>();
|
||||
|
||||
public static readonly Identifier AllowCleanupTag = "allowcleanup".ToIdentifier();
|
||||
|
||||
protected override int MaxTargets => 100;
|
||||
|
||||
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, Item prioritizedItem = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -81,8 +85,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidContainer(Item container, Character character, bool allowUnloading = true) =>
|
||||
allowUnloading &&
|
||||
container.HasTag(AllowCleanupTag) &&
|
||||
container.HasAccess(character) &&
|
||||
container.HasTag("allowcleanup") &&
|
||||
container.ParentInventory == null && container.OwnInventory != null && container.OwnInventory.AllItems.Any() &&
|
||||
container.GetComponent<ItemContainer>() != null &&
|
||||
IsItemInsideValidSubmarine(container, character) &&
|
||||
@@ -91,7 +95,6 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
@@ -102,6 +105,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!IsValidContainer(item.Container, character, allowUnloading)) { return false; }
|
||||
}
|
||||
if (!item.HasAccess(character)) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
|
||||
+10
-2
@@ -995,10 +995,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
|
||||
|
||||
//prefer using handcuffs already on the enemy's inventory
|
||||
if (!HumanAIController.HasItem(Enemy, "handlocker".ToIdentifier(), out IEnumerable<Item> matchingItems))
|
||||
{
|
||||
HumanAIController.HasItem(character, "handlocker".ToIdentifier(), out matchingItems);
|
||||
}
|
||||
|
||||
if (matchingItems.Any() &&
|
||||
!Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy) && !Enemy.LockHands)
|
||||
{
|
||||
var handCuffs = matchingItems.First();
|
||||
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
|
||||
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true, wear: true))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red);
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@ namespace Barotrauma
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || !container.Item.HasAccess(character) ||
|
||||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
|
||||
(container.Item.RootContainer?.OwnInventory?.Locked ?? false) ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
||||
|
||||
+4
-3
@@ -30,7 +30,8 @@ namespace Barotrauma
|
||||
public static readonly Identifier DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors".ToIdentifier();
|
||||
public static readonly Identifier OXYGEN_SOURCE = "oxygensource".ToIdentifier();
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head);
|
||||
protected override bool CheckObjectiveSpecific() =>
|
||||
targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head);
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needsDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
@@ -51,7 +52,7 @@ namespace Barotrauma
|
||||
TrySetTargetItem(character.Inventory.FindItemByTag(HEAVY_DIVING_GEAR, true));
|
||||
}
|
||||
if (targetItem == null ||
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes) &&
|
||||
!character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head) &&
|
||||
targetItem.ContainedItems.Any(it => IsSuitableContainedOxygenSource(it)))
|
||||
{
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
@@ -65,7 +66,7 @@ namespace Barotrauma
|
||||
AllowStealing = HumanAIController.NeedsDivingGear(character.CurrentHull, out _),
|
||||
AllowToFindDivingGear = false,
|
||||
AllowDangerousPressure = true,
|
||||
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.Head | InvSlotType.InnerClothes,
|
||||
EquipSlotType = InvSlotType.OuterClothes | InvSlotType.InnerClothes | InvSlotType.Head,
|
||||
Wear = true
|
||||
};
|
||||
},
|
||||
|
||||
+1
-1
@@ -406,7 +406,7 @@ namespace Barotrauma
|
||||
if (allowChangingSubmarine || !potentialHull.OutpostModuleTags.Any(t => t == "airlock"))
|
||||
{
|
||||
// Don't allow to go outside if not already outside.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, potentialHull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, character.GetRelativeSimPosition(potentialHull), character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
hullSafety = 0;
|
||||
|
||||
+2
-2
@@ -376,7 +376,7 @@ namespace Barotrauma
|
||||
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
|
||||
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
|
||||
// Only allow one path find call per frame.
|
||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
|
||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrder);
|
||||
}
|
||||
bool checkPath = CheckPathForEachItem;
|
||||
// Reset if the character has switched subs.
|
||||
@@ -536,7 +536,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (itemCandidates.FirstOrDefault() is { } itemCandidate)
|
||||
{
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, itemCandidate.item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, character.GetRelativeSimPosition(itemCandidate.item), character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
// Remove the invalid candidates and continue on the next frame.
|
||||
|
||||
+303
-274
@@ -26,7 +26,8 @@ namespace Barotrauma
|
||||
|
||||
public Func<float> PriorityGetter;
|
||||
|
||||
public bool IsFollowOrderObjective;
|
||||
public bool IsFollowOrder;
|
||||
public bool IsWaitOrder;
|
||||
public bool Mimic;
|
||||
|
||||
public bool SpeakIfFails { get; set; } = true;
|
||||
@@ -59,7 +60,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsFollowOrderObjective && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
|
||||
if (IsFollowOrder && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
|
||||
{
|
||||
// Keep close when the target is going inside/outside
|
||||
return minDistance;
|
||||
@@ -220,15 +221,45 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
Hull targetHull = GetTargetHull();
|
||||
if (!IsFollowOrderObjective)
|
||||
if (!IsFollowOrder)
|
||||
{
|
||||
// Abandon if going through unsafe paths. Note ignores unsafe nodes when following an order or when the objective is set to ignore unsafe hulls.
|
||||
bool containsUnsafeNodes = character.IsDismissed && !HumanAIController.ObjectiveManager.CurrentObjective.IgnoreUnsafeHulls
|
||||
&& PathSteering != null && PathSteering.CurrentPath != null
|
||||
&& PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
|
||||
if (containsUnsafeNodes || HumanAIController.UnreachableHulls.Contains(targetHull))
|
||||
bool isUnreachable = HumanAIController.UnreachableHulls.Contains(targetHull);
|
||||
if (!objectiveManager.CurrentObjective.IgnoreUnsafeHulls)
|
||||
{
|
||||
Abandon = true;
|
||||
if (HumanAIController.UnsafeHulls.Contains(targetHull))
|
||||
{
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(targetHull);
|
||||
}
|
||||
else if (PathSteering?.CurrentPath != null)
|
||||
{
|
||||
foreach (WayPoint wp in PathSteering.CurrentPath.Nodes)
|
||||
{
|
||||
if (wp.CurrentHull == null) { continue; }
|
||||
if (HumanAIController.UnsafeHulls.Contains(wp.CurrentHull))
|
||||
{
|
||||
isUnreachable = true;
|
||||
HumanAIController.AskToRecalculateHullSafety(wp.CurrentHull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isUnreachable)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (PathSteering?.CurrentPath != null)
|
||||
{
|
||||
PathSteering.CurrentPath.Unreachable = true;
|
||||
}
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
@@ -250,6 +281,7 @@ namespace Barotrauma
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,310 +294,285 @@ namespace Barotrauma
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
}
|
||||
}
|
||||
if (!Abandon)
|
||||
if (Abandon) { return; }
|
||||
if (getDivingGearIfNeeded)
|
||||
{
|
||||
if (getDivingGearIfNeeded)
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (character.LockHands)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
tryToGetDivingGear = true;
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (character.LockHands)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repeat)
|
||||
{
|
||||
if (IsCloseEnough)
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
if (character.CanSeeTarget(Target))
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxGapDistance = 500;
|
||||
Character targetCharacter = Target as Character;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (character.CurrentHull == null ||
|
||||
IsFollowOrderObjective &&
|
||||
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
|
||||
{
|
||||
if (seekGapsTimer > 0)
|
||||
{
|
||||
seekGapsTimer -= deltaTime;
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
|
||||
bool isEitherOneInside = isInside || Target.Submarine != null;
|
||||
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
|
||||
{
|
||||
SeekGaps(maxGapDistance);
|
||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||
if (TargetGap != null)
|
||||
// Try again without requiring the diving suit
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
// Check that nothing is blocking the way
|
||||
Vector2 rayStart = character.SimPosition;
|
||||
Vector2 rayEnd = TargetGap.SimPosition;
|
||||
if (TargetGap.Submarine != null && character.Submarine == null)
|
||||
{
|
||||
rayStart -= TargetGap.Submarine.SimPosition;
|
||||
}
|
||||
else if (TargetGap.Submarine == null && character.Submarine != null)
|
||||
{
|
||||
rayEnd -= character.Submarine.SimPosition;
|
||||
}
|
||||
var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
if (closestBody != null)
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
}
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repeat && IsCloseEnough)
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
{
|
||||
if (character.CanSeeTarget(Target) && (!character.IsClimbing || IsFollowOrder))
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
float maxGapDistance = 500;
|
||||
Character targetCharacter = Target as Character;
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (character.CurrentHull == null ||
|
||||
IsFollowOrder &&
|
||||
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
|
||||
{
|
||||
if (seekGapsTimer > 0)
|
||||
{
|
||||
seekGapsTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
if (TargetGap != null)
|
||||
{
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrderObjective ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
|
||||
bool isEitherOneInside = isInside || Target.Submarine != null;
|
||||
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
return;
|
||||
SeekGaps(maxGapDistance);
|
||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||
if (TargetGap != null)
|
||||
{
|
||||
// Check that nothing is blocking the way
|
||||
Vector2 rayStart = character.SimPosition;
|
||||
Vector2 rayEnd = TargetGap.SimPosition;
|
||||
if (TargetGap.Submarine != null && character.Submarine == null)
|
||||
{
|
||||
rayStart -= TargetGap.Submarine.SimPosition;
|
||||
}
|
||||
else if (TargetGap.Submarine == null && character.Submarine != null)
|
||||
{
|
||||
rayEnd -= character.Submarine.SimPosition;
|
||||
}
|
||||
var closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
if (closestBody != null)
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
}
|
||||
}
|
||||
if (checkScooterTimer <= 0)
|
||||
{
|
||||
useScooter = false;
|
||||
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
|
||||
Identifier scooterTag = "scooter".ToIdentifier();
|
||||
Identifier batteryTag = "mobilebattery".ToIdentifier();
|
||||
Item scooter = null;
|
||||
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false);
|
||||
if (!shouldUseScooter)
|
||||
{
|
||||
float threshold = 500;
|
||||
if (isInside)
|
||||
{
|
||||
Vector2 diff = Target.WorldPosition - character.WorldPosition;
|
||||
shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
|
||||
}
|
||||
}
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
|
||||
{
|
||||
// Currently equipped scooter
|
||||
scooter = equippedScooters.FirstOrDefault();
|
||||
}
|
||||
else if (shouldUseScooter)
|
||||
{
|
||||
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
||||
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
||||
bool handsFull =
|
||||
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
|
||||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
|
||||
if (!handsFull)
|
||||
{
|
||||
bool hasBattery = false;
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false))
|
||||
{
|
||||
// Non-equipped scooter with a battery
|
||||
scooter = nonEquippedScooters.FirstOrDefault();
|
||||
hasBattery = true;
|
||||
}
|
||||
else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
|
||||
{
|
||||
// Non-equipped scooter without a battery
|
||||
scooter = _nonEquippedScooters.FirstOrDefault();
|
||||
// Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
|
||||
hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
|
||||
}
|
||||
if (scooter != null && hasBattery)
|
||||
{
|
||||
// Equip only if we have a battery available
|
||||
HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scooter != null && character.HasEquippedItem(scooter))
|
||||
{
|
||||
if (shouldUseScooter)
|
||||
{
|
||||
useScooter = true;
|
||||
// Check the battery
|
||||
if (scooter.ContainedItems.None(i => i.Condition > 0))
|
||||
{
|
||||
// Try to switch batteries
|
||||
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
|
||||
{
|
||||
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
|
||||
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
|
||||
{
|
||||
useScooter = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useScooter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!useScooter)
|
||||
{
|
||||
// Unequip
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkScooterTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
useScooter = false;
|
||||
checkScooterTimer = 0;
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
if (TargetGap != null)
|
||||
{
|
||||
Vector2 targetPos = character.GetRelativeSimPosition(Target);
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
if (isInside && !AllowGoingOutside)
|
||||
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, IsFollowOrder ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||
}
|
||||
else if (!isInside)
|
||||
{
|
||||
if (HumanAIController.UseOutsideWaypoints)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
|
||||
}
|
||||
}
|
||||
if (!isInside && !UsePathingOutside)
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.SteeringSeek(targetPos, weight: 1,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
|
||||
endNodeFilter: endNodeFilter,
|
||||
nodeFilter: nodeFilter,
|
||||
checkVisiblity: Target is Item || Target is Character);
|
||||
TargetGap = null;
|
||||
}
|
||||
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
|
||||
}
|
||||
if (checkScooterTimer <= 0)
|
||||
{
|
||||
useScooter = false;
|
||||
checkScooterTimer = checkScooterTime * Rand.Range(0.75f, 1.25f);
|
||||
Identifier scooterTag = "scooter".ToIdentifier();
|
||||
Identifier batteryTag = "mobilebattery".ToIdentifier();
|
||||
Item scooter = null;
|
||||
bool shouldUseScooter = Mimic && targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false);
|
||||
if (!shouldUseScooter)
|
||||
{
|
||||
if (useScooter)
|
||||
float threshold = 500;
|
||||
if (isInside)
|
||||
{
|
||||
UseScooter(Target.WorldPosition);
|
||||
Vector2 diff = Target.WorldPosition - character.WorldPosition;
|
||||
shouldUseScooter = Math.Abs(diff.X) > threshold || Math.Abs(diff.Y) > 150;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
shouldUseScooter = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) > threshold * threshold;
|
||||
}
|
||||
}
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
|
||||
{
|
||||
// Currently equipped scooter
|
||||
scooter = equippedScooters.FirstOrDefault();
|
||||
}
|
||||
else if (shouldUseScooter)
|
||||
{
|
||||
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
||||
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
||||
bool handsFull =
|
||||
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
|
||||
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
|
||||
if (!handsFull)
|
||||
{
|
||||
bool hasBattery = false;
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> nonEquippedScooters, containedTag: batteryTag, conditionPercentage: 1, requireEquipped: false))
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
|
||||
// Non-equipped scooter with a battery
|
||||
scooter = nonEquippedScooters.FirstOrDefault();
|
||||
hasBattery = true;
|
||||
}
|
||||
else if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> _nonEquippedScooters, requireEquipped: false))
|
||||
{
|
||||
// Non-equipped scooter without a battery
|
||||
scooter = _nonEquippedScooters.FirstOrDefault();
|
||||
// Non-recursive so that the bots won't take batteries from other items. Also means that they can't find batteries inside containers. Not sure how to solve this.
|
||||
hasBattery = HumanAIController.HasItem(character, batteryTag, out _, requireEquipped: false, conditionPercentage: 1, recursive: false);
|
||||
}
|
||||
if (scooter != null && hasBattery)
|
||||
{
|
||||
// Equip only if we have a battery available
|
||||
HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
|
||||
if (scooter != null && character.HasEquippedItem(scooter))
|
||||
{
|
||||
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
|
||||
if (shouldUseScooter)
|
||||
{
|
||||
useScooter = true;
|
||||
// Check the battery
|
||||
if (scooter.ContainedItems.None(i => i.Condition > 0))
|
||||
{
|
||||
// Try to switch batteries
|
||||
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
|
||||
{
|
||||
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
|
||||
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
|
||||
{
|
||||
useScooter = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useScooter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!useScooter)
|
||||
{
|
||||
// Unequip
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkScooterTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetGap = null;
|
||||
useScooter = false;
|
||||
checkScooterTimer = 0;
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
Vector2 targetPos = character.GetRelativeSimPosition(Target);
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
if (isInside && !AllowGoingOutside)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||
}
|
||||
else if (!isInside)
|
||||
{
|
||||
if (HumanAIController.UseOutsideWaypoints)
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
|
||||
}
|
||||
}
|
||||
if (!isInside && !UsePathingOutside)
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.SteeringSeek(targetPos, weight: 1,
|
||||
startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (character.CurrentHull == null),
|
||||
endNodeFilter: endNodeFilter,
|
||||
nodeFilter: nodeFilter,
|
||||
checkVisiblity: Target is Item || Target is Character);
|
||||
}
|
||||
if (!isInside && (PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable))
|
||||
{
|
||||
if (useScooter)
|
||||
{
|
||||
@@ -574,13 +581,33 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
|
||||
{
|
||||
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useScooter)
|
||||
{
|
||||
UseScooter(Target.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.ReleaseSecondaryItem();
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UseScooter(Vector2 targetWorldPos)
|
||||
@@ -595,7 +622,7 @@ namespace Barotrauma
|
||||
}
|
||||
Vector2 diff = character.CursorPosition - character.Position;
|
||||
Vector2 dir = Vector2.Normalize(diff);
|
||||
if (character.CurrentHull == null && IsFollowOrderObjective)
|
||||
if (character.CurrentHull == null && IsFollowOrder)
|
||||
{
|
||||
float sqrDist = diff.LengthSquared();
|
||||
if (sqrDist > MathUtils.Pow2(CloseEnough * 1.5f))
|
||||
@@ -674,7 +701,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (gap.Open < 1) { continue; }
|
||||
if (gap.Submarine == null) { continue; }
|
||||
if (!IsFollowOrderObjective)
|
||||
if (!IsFollowOrder)
|
||||
{
|
||||
if (gap.FlowTargetHull == null) { continue; }
|
||||
if (gap.Submarine != Target.Submarine) { continue; }
|
||||
@@ -792,8 +819,10 @@ namespace Barotrauma
|
||||
// Release ladders when ordered to wait at a spawnpoint.
|
||||
// This is a special case specifically meant for NPCs that spawn in outposts with a wait order.
|
||||
// Otherwise they might keep holding to the ladders when the target is just next to it.
|
||||
// Releasing too early should be handled inside the IsCloseEnough property.
|
||||
character.ReleaseSecondaryItem();
|
||||
if (character.IsClimbing && character.AnimController.IsAboveFloor)
|
||||
{
|
||||
character.StopClimbing();
|
||||
}
|
||||
}
|
||||
base.OnCompleted();
|
||||
}
|
||||
|
||||
+5
-4
@@ -168,7 +168,7 @@ namespace Barotrauma
|
||||
CleanupItems(deltaTime);
|
||||
}
|
||||
|
||||
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null)
|
||||
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null && !IsForbidden(character.CurrentHull))
|
||||
{
|
||||
TargetHull = character.CurrentHull;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ namespace Barotrauma
|
||||
IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid && !HumanAIController.UnsafeHulls.Contains(TargetHull))
|
||||
if (behavior == BehaviorType.StayInHull && TargetHull != null && !IsForbidden(TargetHull) && !currentTargetIsInvalid && !HumanAIController.UnsafeHulls.Contains(TargetHull))
|
||||
{
|
||||
currentTarget = TargetHull;
|
||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||
@@ -258,7 +258,8 @@ namespace Barotrauma
|
||||
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
bool isInWrongSub = (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted) && character.Submarine.TeamID != character.TeamID;
|
||||
bool isCurrentHullAllowed = !isInWrongSub && !IsForbidden(character.CurrentHull);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, character.Submarine, nodeFilter: node =>
|
||||
Vector2 targetPos = character.GetRelativeSimPosition(currentTarget);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, targetPos, character.Submarine, nodeFilter: node =>
|
||||
{
|
||||
if (node.Waypoint.CurrentHull == null) { return false; }
|
||||
// Check that there is no unsafe hulls on the way to the target
|
||||
@@ -278,7 +279,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
character.AIController.SelectTarget(currentTarget.AiTarget);
|
||||
PathSteering.SetPath(path);
|
||||
PathSteering.SetPath(targetPos, path);
|
||||
SetTargetTimerNormal();
|
||||
searchingNewHull = false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var item in itemContainer.ContainableItems)
|
||||
{
|
||||
if (CheckStatusEffects(item.statusEffects) == CheckStatus.Finished)
|
||||
if (CheckStatusEffects(item.StatusEffects) == CheckStatus.Finished)
|
||||
{
|
||||
return CheckStatus.Finished;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace Barotrauma
|
||||
protected virtual bool ResetWhenClearingIgnoreList => true;
|
||||
protected virtual bool ForceOrderPriority => true;
|
||||
|
||||
protected virtual int MaxTargets => int.MaxValue;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -188,6 +190,10 @@ namespace Barotrauma
|
||||
if (!ignoreList.Contains(target))
|
||||
{
|
||||
Targets.Add(target);
|
||||
if (Targets.Count > MaxTargets)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -436,7 +436,7 @@ namespace Barotrauma
|
||||
ExtraDistanceWhileSwimming = 100,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
IsFollowOrderObjective = true,
|
||||
IsFollowOrder = true,
|
||||
Mimic = character.IsOnPlayerTeam,
|
||||
DialogueIdentifier = "dialogcannotreachplace".ToIdentifier()
|
||||
};
|
||||
@@ -444,7 +444,11 @@ namespace Barotrauma
|
||||
case "wait":
|
||||
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
AllowGoingOutside = true,
|
||||
IsWaitOrder = true,
|
||||
DebugLogWhenFails = false,
|
||||
SpeakIfFails = false,
|
||||
CloseEnough = 100
|
||||
};
|
||||
break;
|
||||
case "return":
|
||||
|
||||
+48
-12
@@ -13,6 +13,8 @@ namespace Barotrauma
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private readonly HashSet<Character> charactersWithMinorInjuries = new HashSet<Character>();
|
||||
|
||||
private const float vitalityThreshold = 75;
|
||||
private const float vitalityThresholdForOrders = 90;
|
||||
public static float GetVitalityThreshold(AIObjectiveManager manager, Character character, Character target)
|
||||
@@ -23,17 +25,34 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer && target.HealthPercentage < 100 ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? vitalityThresholdForOrders : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
|
||||
public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Character target) => IsValidTarget(target, character);
|
||||
protected override bool Filter(Character target)
|
||||
{
|
||||
if (!IsValidTarget(target, character, requireTreatableAfflictions: false)) { return false; }
|
||||
if (GetTreatableAfflictions(target).Any())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//the target might be at a low enough health to be considered a valid target,
|
||||
//but if all afflictions are below treatment thresholds, the bot won't (and shouldn't) treat them
|
||||
// -> make the bot speak to make it clear the bot intentionally ignores very minor injuries
|
||||
if (!charactersWithMinorInjuries.Contains(character))
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("dialogignoreminorinjuries", "[targetname]", target.Name).Value,
|
||||
null, 1.0f, $"notreatableafflictions{target.Name}".ToIdentifier(), 10.0f);
|
||||
charactersWithMinorInjuries.Add(character);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
|
||||
@@ -68,7 +87,7 @@ namespace Barotrauma
|
||||
float vitality = 100;
|
||||
vitality -= character.Bleeding * 2;
|
||||
vitality += Math.Min(character.Oxygen, 0);
|
||||
foreach (Affliction affliction in GetTreatableAfflictions(character))
|
||||
foreach (Affliction affliction in GetTreatableAfflictions(character, ignoreTreatmentThreshold: true))
|
||||
{
|
||||
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
@@ -84,12 +103,19 @@ namespace Barotrauma
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character, bool ignoreTreatmentThreshold = false)
|
||||
{
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (affliction.Prefab.IsBuff) { continue; }
|
||||
if (!ignoreTreatmentThreshold)
|
||||
{
|
||||
//other afflictions of the same type increase the "treatability"
|
||||
// e.g. we might want to ignore burns below 5%, but not if the character has them on all limbs
|
||||
float totalAfflictionStrength = character.CharacterHealth.GetTotalAdjustedAfflictionStrength(affliction);
|
||||
if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
}
|
||||
if (affliction.Prefab.TreatmentSuitability.None(kvp => kvp.Value > 0)) { continue; }
|
||||
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
|
||||
yield return affliction;
|
||||
@@ -102,7 +128,7 @@ namespace Barotrauma
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Character target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveRescueAll, Character>(character, target);
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character)
|
||||
public static bool IsValidTarget(Character target, Character character, bool requireTreatableAfflictions = true)
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target.IsInstigator) { return false; }
|
||||
@@ -112,7 +138,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
@@ -127,6 +153,10 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (requireTreatableAfflictions && GetTreatableAfflictions(target).None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -159,5 +189,11 @@ namespace Barotrauma
|
||||
}
|
||||
return character.GetDamageDoneByAttacker(target) <= 0;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
charactersWithMinorInjuries.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsRemotePlayer && !(AIController is HumanAIController))
|
||||
if (!IsRemotePlayer && AIController is not HumanAIController)
|
||||
{
|
||||
float characterDistSqr = GetDistanceSqrToClosestPlayer();
|
||||
if (characterDistSqr > MathUtils.Pow2(Params.DisableDistance * 0.5f))
|
||||
@@ -63,6 +63,10 @@ namespace Barotrauma
|
||||
AnimController.SimplePhysicsEnabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimController.SimplePhysicsEnabled = false;
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
if (Controlled == this) { return; }
|
||||
|
||||
+13
-10
@@ -1,10 +1,10 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1550,6 +1550,10 @@ namespace Barotrauma
|
||||
target.AnimController.ResetPullJoints();
|
||||
}
|
||||
|
||||
bool targetPoseControlled =
|
||||
target.SelectedItem?.GetComponent<Controller>() is { ControlCharacterPose: true } ||
|
||||
target.SelectedSecondaryItem?.GetComponent<Controller>() is { ControlCharacterPose: true };
|
||||
|
||||
if (IsClimbing)
|
||||
{
|
||||
//cannot drag up ladders if the character is conscious
|
||||
@@ -1725,13 +1729,12 @@ namespace Barotrauma
|
||||
targetForce = 5000.0f;
|
||||
}
|
||||
|
||||
targetLimb.PullJointEnabled = true;
|
||||
targetLimb.PullJointMaxForce = targetForce;
|
||||
targetLimb.PullJointWorldAnchorB = targetAnchor;
|
||||
targetLimb.Disabled = true;
|
||||
|
||||
if (diff.LengthSquared() > 0.1f)
|
||||
if (!targetPoseControlled)
|
||||
{
|
||||
targetLimb.PullJointEnabled = true;
|
||||
targetLimb.PullJointMaxForce = targetForce;
|
||||
targetLimb.PullJointWorldAnchorB = targetAnchor;
|
||||
targetLimb.Disabled = true;
|
||||
target.AnimController.movement = -diff;
|
||||
}
|
||||
}
|
||||
@@ -1757,7 +1760,7 @@ namespace Barotrauma
|
||||
target.AnimController.IgnorePlatforms = IgnorePlatforms;
|
||||
target.AnimController.TargetMovement = TargetMovement;
|
||||
}
|
||||
else if (target is AICharacter && target != Character.Controlled)
|
||||
else if (target is AICharacter && target != Character.Controlled && !targetPoseControlled)
|
||||
{
|
||||
if (target.AnimController.Dir > 0 == WorldPosition.X > target.WorldPosition.X)
|
||||
{
|
||||
|
||||
@@ -1205,7 +1205,7 @@ namespace Barotrauma
|
||||
{
|
||||
inWater = false;
|
||||
headInWater = false;
|
||||
RefreshFloorY(ignoreStairs: Stairs == null);
|
||||
RefreshFloorY(deltaTime, ignoreStairs: Stairs == null);
|
||||
}
|
||||
//ragdoll isn't in any room -> it's in the water
|
||||
else if (currentHull == null)
|
||||
@@ -1217,7 +1217,7 @@ namespace Barotrauma
|
||||
{
|
||||
headInWater = false;
|
||||
inWater = false;
|
||||
RefreshFloorY(ignoreStairs: Stairs == null);
|
||||
RefreshFloorY(deltaTime, ignoreStairs: Stairs == null);
|
||||
if (currentHull.WaterPercentage > 0.001f)
|
||||
{
|
||||
(float waterSurfaceDisplayUnits, float ceilingDisplayUnits) = GetWaterSurfaceAndCeilingY();
|
||||
@@ -1562,15 +1562,24 @@ namespace Barotrauma
|
||||
lastFloorCheckPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
private void RefreshFloorY(Limb refLimb = null, bool ignoreStairs = false)
|
||||
// Force check floor y at least once a second so that we'll drop through gaps that we are standing upon.
|
||||
private const float FloorYStaleTime = 1;
|
||||
private float floorYCheckTimer;
|
||||
private void RefreshFloorY(float deltaTime, Limb refLimb = null, bool ignoreStairs = false)
|
||||
{
|
||||
floorYCheckTimer -= deltaTime;
|
||||
PhysicsBody refBody = refLimb == null ? Collider : refLimb.body;
|
||||
if (Vector2.DistanceSquared(lastFloorCheckPos, refBody.SimPosition) > 0.1f * 0.1f || lastFloorCheckIgnoreStairs != ignoreStairs || lastFloorCheckIgnorePlatforms != IgnorePlatforms)
|
||||
if (floorYCheckTimer < 0 ||
|
||||
lastFloorCheckIgnoreStairs != ignoreStairs ||
|
||||
lastFloorCheckIgnorePlatforms != IgnorePlatforms ||
|
||||
Vector2.DistanceSquared(lastFloorCheckPos, refBody.SimPosition) > 0.1f * 0.1f)
|
||||
{
|
||||
floorY = GetFloorY(refBody.SimPosition, ignoreStairs);
|
||||
lastFloorCheckPos = refBody.SimPosition;
|
||||
lastFloorCheckIgnoreStairs = ignoreStairs;
|
||||
lastFloorCheckIgnorePlatforms = IgnorePlatforms;
|
||||
// Add some randomness to prevent all stationary characters doing the checks at the same frame.
|
||||
floorYCheckTimer = FloorYStaleTime * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1854,6 +1863,7 @@ namespace Barotrauma
|
||||
|
||||
|
||||
private bool collisionsDisabled;
|
||||
private double lastObstacleRayCastTime;
|
||||
|
||||
protected void CheckDistFromCollider()
|
||||
{
|
||||
@@ -1861,15 +1871,28 @@ namespace Barotrauma
|
||||
allowedDist = Math.Max(allowedDist, 1.0f);
|
||||
float resetDist = allowedDist * 5.0f;
|
||||
|
||||
float obstacleCheckDist = 0.3f;
|
||||
|
||||
Vector2 diff = Collider.SimPosition - MainLimb.SimPosition;
|
||||
float distSqrd = diff.LengthSquared();
|
||||
|
||||
if (distSqrd > resetDist * resetDist)
|
||||
bool shouldReset = distSqrd > resetDist * resetDist;
|
||||
if (!shouldReset && distSqrd > obstacleCheckDist * obstacleCheckDist)
|
||||
{
|
||||
if (Timing.TotalTime > lastObstacleRayCastTime + 1 &&
|
||||
Submarine.PickBody(Collider.SimPosition, MainLimb.SimPosition, collisionCategory: Physics.CollisionWall) != null)
|
||||
{
|
||||
shouldReset = true;
|
||||
lastObstacleRayCastTime = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldReset)
|
||||
{
|
||||
//ragdoll way too far, reset position
|
||||
SetPosition(Collider.SimPosition, lerp: true, forceMainLimbToCollider: true);
|
||||
}
|
||||
if (distSqrd > allowedDist * allowedDist)
|
||||
else if (distSqrd > allowedDist * allowedDist)
|
||||
{
|
||||
//ragdoll too far from the collider, disable collisions until it's close enough
|
||||
//(in case the ragdoll has gotten stuck somewhere)
|
||||
@@ -1891,7 +1914,7 @@ namespace Barotrauma
|
||||
collisionsDisabled = false;
|
||||
//force collision categories to be updated
|
||||
prevCollisionCategory = Category.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos);
|
||||
|
||||
@@ -1068,7 +1068,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
return SelectedItem == null || (SelectedItem.GetComponent<Controller>()?.AllowAiming ?? false);
|
||||
return (SelectedItem == null || SelectedItem.GetComponent<Controller>() is { AllowAiming: true }) && !IsIncapacitated && !IsRagdolled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1131,6 +1131,7 @@ namespace Barotrauma
|
||||
public HashSet<Identifier> MarkedAsLooted = new();
|
||||
|
||||
public bool IsInFriendlySub => Submarine != null && Submarine.TeamID == TeamID;
|
||||
public bool IsInPlayerSub => Submarine != null && Submarine.Info.IsPlayer;
|
||||
|
||||
public float AITurretPriority
|
||||
{
|
||||
@@ -2710,27 +2711,6 @@ namespace Barotrauma
|
||||
CustomInteractHUDText = hudText;
|
||||
}
|
||||
|
||||
private void TransformCursorPos()
|
||||
{
|
||||
if (Submarine == null)
|
||||
{
|
||||
//character is outside but cursor position inside
|
||||
if (cursorPosition.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
var sub = Submarine.FindContaining(cursorPosition);
|
||||
if (sub != null) cursorPosition += sub.Position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//character is inside but cursor position is outside
|
||||
if (cursorPosition.Y < Level.Loaded.Size.Y)
|
||||
{
|
||||
cursorPosition -= Submarine.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectCharacter(Character character)
|
||||
{
|
||||
if (character == null || character == this) { return; }
|
||||
@@ -4335,7 +4315,7 @@ namespace Barotrauma
|
||||
if (statusEffect.type == ActionType.OnDamaged)
|
||||
{
|
||||
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
|
||||
if (statusEffect.OnlyPlayerTriggered)
|
||||
if (statusEffect.OnlyWhenDamagedByPlayer)
|
||||
{
|
||||
if (LastAttacker == null || !LastAttacker.IsPlayer)
|
||||
{
|
||||
@@ -4393,6 +4373,10 @@ namespace Barotrauma
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, this);
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.Hull) && CurrentHull != null)
|
||||
{
|
||||
statusEffect.Apply(actionType, deltaTime, this, CurrentHull);
|
||||
}
|
||||
}
|
||||
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
|
||||
{
|
||||
@@ -4670,6 +4654,13 @@ namespace Barotrauma
|
||||
|
||||
CharacterList.Remove(this);
|
||||
|
||||
foreach (var attachedProjectile in AttachedProjectiles.ToList())
|
||||
{
|
||||
attachedProjectile.Unstick();
|
||||
}
|
||||
Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
|
||||
Latchers.Clear();
|
||||
|
||||
if (Inventory != null)
|
||||
{
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
@@ -4756,6 +4747,8 @@ namespace Barotrauma
|
||||
}
|
||||
#if SERVER
|
||||
newItem.GetComponent<Terminal>()?.SyncHistory();
|
||||
if (newItem.GetComponent<WifiComponent>() is WifiComponent wifiComponent) { newItem.CreateServerEvent(wifiComponent); }
|
||||
if (newItem.GetComponent<GeneticMaterial>() is GeneticMaterial geneticMaterial) { newItem.CreateServerEvent(geneticMaterial); }
|
||||
#endif
|
||||
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
|
||||
if (!slotIndices.Any())
|
||||
|
||||
@@ -386,6 +386,8 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (AfflictionPrefab.PeriodicEffect periodicEffect in Prefab.PeriodicEffects)
|
||||
{
|
||||
if (Strength <= periodicEffect.MinStrength) { continue; }
|
||||
if (periodicEffect.MaxStrength > 0 && Strength > periodicEffect.MaxStrength) { continue; }
|
||||
PeriodicEffectTimers[periodicEffect] -= deltaTime;
|
||||
if (PeriodicEffectTimers[periodicEffect] <= 0.0f)
|
||||
{
|
||||
@@ -498,6 +500,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void SetStrength(float strength)
|
||||
{
|
||||
if (!MathUtils.IsValid(strength))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Attempted to set an affliction to an invalid strength ({strength})\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
_nonClampedStrength = strength;
|
||||
_strength = _nonClampedStrength;
|
||||
activeEffectDirty |= !MathUtils.NearlyEqual(_strength, prevActiveEffectStrength);
|
||||
|
||||
+4
-4
@@ -730,13 +730,13 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// How high the strength has to be for the affliction icon to be shown with a health scanner
|
||||
/// </summary>
|
||||
public readonly float ShowInHealthScannerThreshold = 0.05f;
|
||||
public readonly float ShowInHealthScannerThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// How strong the affliction needs to be before bots attempt to treat it.
|
||||
/// Also effects when the affliction is shown in the suitable treatments list.
|
||||
/// </summary>
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
public readonly float TreatmentThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Bots will not try to treat the affliction if the character has any of these afflictions
|
||||
@@ -847,7 +847,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var itemPrefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
float suitability = Math.Max(itemPrefab.GetTreatmentSuitability(Identifier), itemPrefab.GetTreatmentSuitability(AfflictionType));
|
||||
float suitability = itemPrefab.GetTreatmentSuitability(Identifier) + itemPrefab.GetTreatmentSuitability(AfflictionType);
|
||||
if (!MathUtils.NearlyEqual(suitability, 0.0f))
|
||||
{
|
||||
yield return new KeyValuePair<Identifier, float>(itemPrefab.Identifier, suitability);
|
||||
@@ -915,7 +915,7 @@ namespace Barotrauma
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat(nameof(ShowInHealthScannerThreshold),
|
||||
Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
|
||||
TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 5.0f));
|
||||
TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 10.0f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -422,9 +423,13 @@ namespace Barotrauma
|
||||
return strength;
|
||||
}
|
||||
|
||||
public void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking = true)
|
||||
public void ApplyAffliction(Limb targetLimb, Affliction affliction, bool allowStacking = true, bool ignoreUnkillability = false)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff && Unkillable || Character.GodMode) { return; }
|
||||
if (Character.GodMode) { return; }
|
||||
if (!ignoreUnkillability)
|
||||
{
|
||||
if (!affliction.Prefab.IsBuff && Unkillable) { return; }
|
||||
}
|
||||
if (affliction.Prefab.LimbSpecific)
|
||||
{
|
||||
if (targetLimb == null)
|
||||
@@ -455,11 +460,7 @@ namespace Barotrauma
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier);
|
||||
}
|
||||
|
||||
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(afflictionPrefab));
|
||||
if (resistance > 1f) { resistance = 1f; }
|
||||
|
||||
return resistance;
|
||||
return 1 - ((1 - resistance) * Character.GetAbilityResistance(afflictionPrefab));
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
@@ -990,7 +991,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private float GetVitalityMultiplier(Affliction affliction, LimbHealth limbHealth)
|
||||
private static float GetVitalityMultiplier(Affliction affliction, LimbHealth limbHealth)
|
||||
{
|
||||
float multiplier = 1.0f;
|
||||
if (limbHealth.VitalityMultipliers.TryGetValue(affliction.Prefab.Identifier, out float vitalityMultiplier))
|
||||
@@ -1132,7 +1133,11 @@ namespace Barotrauma
|
||||
strength = GetPredictedStrength(affliction, predictFutureDuration, limb);
|
||||
}
|
||||
|
||||
if (strength <= affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
//other afflictions of the same type increase the "treatability"
|
||||
// e.g. we might want to ignore burns below 5%, but not if the character has them on all limbs
|
||||
float totalAfflictionStrength = strength + GetTotalAdjustedAfflictionStrength(affliction, includeSameAffliction: false);
|
||||
if (totalAfflictionStrength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
|
||||
if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) { continue; }
|
||||
|
||||
if (ignoreHiddenAfflictions)
|
||||
@@ -1149,13 +1154,20 @@ namespace Barotrauma
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> treatment in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
float suitability = treatment.Value * strength;
|
||||
if (treatment.Value > strength)
|
||||
{
|
||||
//avoid using very effective meds on small injuries
|
||||
float overtreatmentFactor = MathHelper.Clamp(treatment.Value / strength, 1.0f, 10.0f);
|
||||
suitability /= overtreatmentFactor;
|
||||
}
|
||||
if (!treatmentSuitability.ContainsKey(treatment.Key))
|
||||
{
|
||||
treatmentSuitability[treatment.Key] = treatment.Value * strength;
|
||||
treatmentSuitability[treatment.Key] = suitability;
|
||||
}
|
||||
else
|
||||
{
|
||||
treatmentSuitability[treatment.Key] += treatment.Value * strength;
|
||||
treatmentSuitability[treatment.Key] += suitability;
|
||||
}
|
||||
minSuitability = Math.Min(treatmentSuitability[treatment.Key], minSuitability);
|
||||
maxSuitability = Math.Max(treatmentSuitability[treatment.Key], maxSuitability);
|
||||
@@ -1171,6 +1183,28 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the total strength of instances of the same affliction on all the characters limbs,
|
||||
/// with a smaller weight given to the other afflictions on other limbs
|
||||
/// </summary>
|
||||
/// <param name="otherAfflictionMultiplier">Multiplier on the strengths of the afflictions on other limbs.</param>
|
||||
/// <param name="includeSameAffliction">Should the strength of the provided affliction be included too?</param>
|
||||
public float GetTotalAdjustedAfflictionStrength(Affliction affliction, float otherAfflictionMultiplier = 0.3f, bool includeSameAffliction = true)
|
||||
{
|
||||
float totalAfflictionStrength = includeSameAffliction ? affliction.Strength : 0;
|
||||
if (affliction.Prefab.LimbSpecific)
|
||||
{
|
||||
foreach (Affliction otherAffliction in afflictions.Keys)
|
||||
{
|
||||
if (affliction.Prefab == otherAffliction.Prefab && affliction != otherAffliction)
|
||||
{
|
||||
totalAfflictionStrength += otherAffliction.Strength * otherAfflictionMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalAfflictionStrength;
|
||||
}
|
||||
|
||||
private readonly HashSet<Identifier> afflictionTags = new HashSet<Identifier>();
|
||||
public IEnumerable<Identifier> GetActiveAfflictionTags()
|
||||
{
|
||||
|
||||
@@ -182,7 +182,9 @@ namespace Barotrauma
|
||||
{
|
||||
humanAI.ObjectiveManager.SetForcedOrder(new AIObjectiveGoTo(positionToStayIn, npc, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200)
|
||||
{
|
||||
DebugLogWhenFails = false
|
||||
DebugLogWhenFails = false,
|
||||
IsWaitOrder = true,
|
||||
CloseEnough = 100
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,7 +1226,7 @@ namespace Barotrauma
|
||||
if (statusEffect.type == ActionType.OnDamaged)
|
||||
{
|
||||
if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
|
||||
if (statusEffect.OnlyPlayerTriggered)
|
||||
if (statusEffect.OnlyWhenDamagedByPlayer)
|
||||
{
|
||||
if (character.LastAttacker == null || !character.LastAttacker.IsPlayer)
|
||||
{
|
||||
|
||||
@@ -231,12 +231,17 @@ namespace Barotrauma
|
||||
protected void CreateSubParams()
|
||||
{
|
||||
SubParams.Clear();
|
||||
var health = MainElement.GetChildElement("health");
|
||||
if (health != null)
|
||||
var healthElement = MainElement.GetChildElement("health");
|
||||
if (healthElement != null)
|
||||
{
|
||||
Health = new HealthParams(health, this);
|
||||
SubParams.Add(Health);
|
||||
Health = new HealthParams(healthElement, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"No health parameters defined for character \"{(SpeciesName)}\".");
|
||||
Health = new HealthParams(null, this);
|
||||
}
|
||||
SubParams.Add(Health);
|
||||
// TODO: support for multiple ai elements?
|
||||
var ai = MainElement.GetChildElement("ai");
|
||||
if (ai != null)
|
||||
|
||||
+7
-2
@@ -29,7 +29,7 @@ namespace Barotrauma.Abilities
|
||||
nearbyCharactersAppliesToEnemies = abilityElement.GetAttributeBool("nearbycharactersappliestoenemies", true);
|
||||
}
|
||||
|
||||
protected void ApplyEffectSpecific(Character targetCharacter)
|
||||
protected void ApplyEffectSpecific(Character targetCharacter, Limb targetLimb = null)
|
||||
{
|
||||
//prevent an infinite loop if an effect triggers itself
|
||||
//(e.g. a talent that triggers when an affliction is applied, and applies that same affliction)
|
||||
@@ -66,6 +66,11 @@ namespace Barotrauma.Abilities
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb) && targetLimb != null)
|
||||
{
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetLimb);
|
||||
}
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
statusEffect.SetUser(Character);
|
||||
@@ -99,7 +104,7 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter && !applyToSelf)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
ApplyEffectSpecific(targetCharacter, targetLimb: (abilityObject as AbilityApplyTreatment)?.TargetLimb);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+5
-7
@@ -22,15 +22,13 @@
|
||||
{
|
||||
foreach (Identifier afflictionIdentifier in afflictionIdentifiers)
|
||||
{
|
||||
if (affliction.Identifier != afflictionIdentifier) { continue; }
|
||||
affliction.Strength *= 1 + addedMultiplier;
|
||||
if (affliction.Identifier != afflictionIdentifier) { continue; }
|
||||
AfflictionPrefab afflictionPrefab = affliction.Prefab;
|
||||
if (!replaceWith.IsEmpty)
|
||||
{
|
||||
if (AfflictionPrefab.Prefabs.TryGet(replaceWith, out AfflictionPrefab afflictionPrefab))
|
||||
{
|
||||
abilityAffliction.Affliction = new Affliction(afflictionPrefab, abilityAffliction.Affliction.Strength);
|
||||
}
|
||||
}
|
||||
AfflictionPrefab.Prefabs.TryGet(replaceWith, out afflictionPrefab);
|
||||
}
|
||||
abilityAffliction.Affliction = new Affliction(afflictionPrefab, affliction.Strength * (1 + addedMultiplier));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -64,9 +63,14 @@ namespace Barotrauma
|
||||
|
||||
public Identifier GetAttributeIdentifier(string key, string def) => Element.GetAttributeIdentifier(key, def);
|
||||
public Identifier GetAttributeIdentifier(string key, Identifier def) => Element.GetAttributeIdentifier(key, def);
|
||||
public Identifier[]? GetAttributeIdentifierArray(string key, Identifier[] def, bool trim = true) => Element.GetAttributeIdentifierArray(key, def, trim);
|
||||
[return:NotNullIfNotNull("def")]
|
||||
public ImmutableHashSet<Identifier>? GetAttributeIdentifierImmutableHashSet(string key, ImmutableHashSet<Identifier>? def, bool trim = true) => Element.GetAttributeIdentifierImmutableHashSet(key, def, trim);
|
||||
|
||||
[return: NotNullIfNotNull("def")]
|
||||
public Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys) => Element.GetAttributeIdentifierArray(def, keys);
|
||||
[return: NotNullIfNotNull("def")]
|
||||
public Identifier[] GetAttributeIdentifierArray(string key, Identifier[] def, bool trim = true) => Element.GetAttributeIdentifierArray(key, def, trim);
|
||||
[return: NotNullIfNotNull("def")]
|
||||
public ImmutableHashSet<Identifier> GetAttributeIdentifierImmutableHashSet(string key, ImmutableHashSet<Identifier>? def, bool trim = true) => Element.GetAttributeIdentifierImmutableHashSet(key, def, trim);
|
||||
|
||||
public string? GetAttributeString(string key, string? def) => Element.GetAttributeString(key, def);
|
||||
public string GetAttributeStringUnrestricted(string key, string def) => Element.GetAttributeStringUnrestricted(key, def);
|
||||
public string[]? GetAttributeStringArray(string key, string[]? def, bool convertToLowerInvariant = false) => Element.GetAttributeStringArray(key, def, convertToLowerInvariant);
|
||||
|
||||
@@ -1879,7 +1879,9 @@ namespace Barotrauma
|
||||
commands.Add(new Command("lighting|lights", "Toggle lighting on/off (client-only).", null, isCheat: true));
|
||||
commands.Add(new Command("ambientlight", "ambientlight [color]: Change the color of the ambient light in the level.", null, isCheat: true));
|
||||
commands.Add(new Command("debugdraw", "Toggle the debug drawing mode on/off (client-only).", null, isCheat: true));
|
||||
commands.Add(new Command("debugwiring", "Toggle the wiring debug mode on/off (client-only).", null, isCheat: true));
|
||||
commands.Add(new Command("debugdrawlocalization", "Toggle the localization debug drawing mode on/off (client-only). Colors all text that hasn't been fetched from a localization file magenta, making it easier to spot hard-coded or missing texts.", null, isCheat: false));
|
||||
commands.Add(new Command("debugdrawlos", "Toggle the los debug drawing mode on/off (client-only).", null, isCheat: true));
|
||||
commands.Add(new Command("togglevoicechatfilters", "Toggle the radio/muffle filters in the voice chat (client-only).", null, isCheat: false));
|
||||
commands.Add(new Command("togglehud|hud", "Toggle the character HUD (inventories, icons, buttons, etc) on/off (client-only).", null));
|
||||
commands.Add(new Command("toggleupperhud", "Toggle the upper part of the ingame HUD (chatbox, crewmanager) on/off (client-only).", null));
|
||||
|
||||
@@ -12,21 +12,112 @@ namespace Barotrauma
|
||||
Exponential
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ActionTypes define when a <see cref="StatusEffect"/> is executed.
|
||||
/// </summary>
|
||||
public enum ActionType
|
||||
{
|
||||
Always = 0, OnPicked = 1, OnUse = 2, OnSecondaryUse = 3,
|
||||
OnWearing = 4, OnContaining = 5, OnContained = 6, OnNotContained = 7,
|
||||
OnActive = 8, OnFailure = 9, OnBroken = 10,
|
||||
OnFire = 11, InWater = 12, NotInWater = 13,
|
||||
/// <summary>
|
||||
/// Executes every frame regardless of the state of the entity.
|
||||
/// </summary>
|
||||
Always = 0,
|
||||
/// <summary>
|
||||
/// Executes when the item is picked up. Only valid for items.
|
||||
/// </summary>
|
||||
OnPicked = 1,
|
||||
/// <summary>
|
||||
/// Executes when the item is used. The meaning of "using" an item depends on the item, but generally it means the action that happens when holding the item and clicking LMB. Only valid for items.
|
||||
/// </summary>
|
||||
OnUse = 2,
|
||||
/// <summary>
|
||||
/// Executes when an item is held and the aim key is held. Only valid for items.
|
||||
/// </summary>
|
||||
OnSecondaryUse = 3,
|
||||
/// <summary>
|
||||
/// Executes continuously while the item is being worn. Only valid for wearable items.
|
||||
/// </summary>
|
||||
OnWearing = 4,
|
||||
/// <summary>
|
||||
/// Executes continuously when a specific Containable is inside an ItemContainer. Only valid for Containables defined in an ItemContainer component.
|
||||
/// </summary>
|
||||
OnContaining = 5,
|
||||
/// <summary>
|
||||
/// Executes continuously when the item is contained in some inventory. Only valid for items.
|
||||
/// </summary>
|
||||
OnContained = 6,
|
||||
/// <summary>
|
||||
/// Executes continuously when the item is NOT contained in an inventory. Only valid for items.
|
||||
/// </summary>
|
||||
OnNotContained = 7,
|
||||
/// <summary>
|
||||
/// Executes continuously when the item is active. The meaning of "active" depends on the item, but generally means the item is on, powered, and doing the thing it's intended for. Only valid for items.
|
||||
/// </summary>
|
||||
OnActive = 8,
|
||||
/// <summary>
|
||||
/// Executes when using the item fails due to a failed skill check. Only valid for items.
|
||||
/// </summary>
|
||||
OnFailure = 9,
|
||||
/// <summary>
|
||||
/// Executes when using the item's condition drops to 0. Only valid for items.
|
||||
/// </summary>
|
||||
OnBroken = 10,
|
||||
/// <summary>
|
||||
/// Executes continuously when the entity is within the damage range of fire. Valid for items and characters.
|
||||
/// </summary>
|
||||
OnFire = 11,
|
||||
/// <summary>
|
||||
/// Executes continuously when the entity is submerged. Valid for items and characters.
|
||||
/// </summary>
|
||||
InWater = 12,
|
||||
/// <summary>
|
||||
/// Executes continuously when the entity is NOT submerged. Valid for items and characters.
|
||||
/// </summary>
|
||||
NotInWater = 13,
|
||||
/// <summary>
|
||||
/// Executes when the entity hits something hard enough. For items, the threshold is determined by <see cref="ItemPrefab.ImpactTolerance"/>,
|
||||
/// for characters by <see cref="Ragdoll.ImpactTolerance"/>. Valid for items and characters.
|
||||
/// </summary>
|
||||
OnImpact = 14,
|
||||
/// <summary>
|
||||
/// Executes continuously when the character is eating another character. Only valid for characters.
|
||||
/// </summary>
|
||||
OnEating = 15,
|
||||
/// <summary>
|
||||
/// Executes when the entity receives damage from an external source (i.e. an affliction that increases in severity, or an item degrading by itself don't count).
|
||||
/// Valid for items and characters.
|
||||
/// </summary>
|
||||
OnDamaged = 16,
|
||||
/// <summary>
|
||||
/// Executes when the limb gets severed. Only valid for limbs.
|
||||
/// </summary>
|
||||
OnSevered = 17,
|
||||
/// <summary>
|
||||
/// Executes when a <see cref="Items.Components.Growable"/> produces an item (e.g. when a plant grows a fruit). Only valid for Growable items.
|
||||
/// </summary>
|
||||
OnProduceSpawned = 18,
|
||||
OnOpen = 19, OnClose = 20,
|
||||
/// <summary>
|
||||
/// Executes when a <see cref="Items.Components.Door"/> is opened. Only valid for doors.
|
||||
/// </summary>
|
||||
OnOpen = 19,
|
||||
/// <summary>
|
||||
/// Executes when a <see cref="Items.Components.Door"/> is closed. Only valid for doors.
|
||||
/// </summary>
|
||||
OnClose = 20,
|
||||
/// <summary>
|
||||
/// Executes when the entity spawns. Valid for items and characters.
|
||||
/// </summary>
|
||||
OnSpawn = 21,
|
||||
/// <summary>
|
||||
/// Executes when using the item succeeds based on a skill check. Only valid for items.
|
||||
/// </summary>
|
||||
OnSuccess = 22,
|
||||
/// <summary>
|
||||
/// Executes when an Ability (an effect from a talent) triggers the status effect. Only valid in Abilities, the target can be either a character or an item depending on the type of Ability.
|
||||
/// </summary>
|
||||
OnAbility = 23,
|
||||
/// <summary>
|
||||
/// Executes when the character dies. Only valid for characters.
|
||||
/// </summary>
|
||||
OnDeath = OnBroken
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma
|
||||
var limb = character.AnimController.GetLimb(LimbType);
|
||||
if (Strength > 0.0f)
|
||||
{
|
||||
character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(Strength));
|
||||
character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(Strength), ignoreUnkillability: true);
|
||||
}
|
||||
else if (Strength < 0.0f)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Strength > 0.0f)
|
||||
{
|
||||
character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(Strength));
|
||||
character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(Strength), ignoreUnkillability: true);
|
||||
}
|
||||
else if (Strength < 0.0f)
|
||||
{
|
||||
|
||||
+21
-4
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckSelectedItemAction : BinaryOptionAction
|
||||
class CheckSelectedAction : BinaryOptionAction
|
||||
{
|
||||
public enum SelectedItemType { Primary, Secondary, Any };
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
|
||||
public SelectedItemType ItemType { get; set; }
|
||||
|
||||
public CheckSelectedItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
public CheckSelectedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
@@ -34,7 +34,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
Error($"{nameof(CheckSelectedAction)} error: {GetEventName()} uses a {nameof(CheckSelectedAction)} but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
if (!TargetTag.IsEmpty)
|
||||
@@ -42,11 +42,16 @@ namespace Barotrauma
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (targets.None())
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
Error($"{nameof(CheckSelectedAction)} error: {GetEventName()} uses a {nameof(CheckSelectedAction)} but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
if (ItemType == SelectedItemType.Any && character.SelectedCharacter == targetCharacter) { return true; }
|
||||
continue;
|
||||
}
|
||||
if (target is not Item targetItem)
|
||||
{
|
||||
continue;
|
||||
@@ -79,6 +84,18 @@ namespace Barotrauma
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
#if DEBUG
|
||||
void Error(string errorMsg)
|
||||
{
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
}
|
||||
#else
|
||||
|
||||
void Error(string errorMsg)
|
||||
{
|
||||
DebugConsole.LogError(errorMsg);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckSelectedAction : BinaryOptionAction
|
||||
{
|
||||
public enum SelectedItemType { Primary, Secondary, Any };
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CharacterTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
|
||||
public SelectedItemType ItemType { get; set; }
|
||||
|
||||
public CheckSelectedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character character = null;
|
||||
if (!CharacterTag.IsEmpty)
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(CharacterTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
character = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid character was found for tag \"{CharacterTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
IEnumerable<Entity> targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (targets.None())
|
||||
{
|
||||
DebugConsole.LogError($"CheckSelectedItemAction error: {GetEventName()} uses a CheckSelectedItemAction but no valid targets were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
if (ItemType == SelectedItemType.Any && character.SelectedCharacter == targetCharacter) { return true; }
|
||||
continue;
|
||||
}
|
||||
if (target is not Item targetItem)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (IsSelected(targetItem))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
bool IsSelected(Item item)
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => character.IsAnySelectedItem(item),
|
||||
SelectedItemType.Primary => character.SelectedItem == item,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == item,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemType switch
|
||||
{
|
||||
SelectedItemType.Any => !character.HasSelectedAnyItem,
|
||||
SelectedItemType.Primary => character.SelectedItem == null,
|
||||
SelectedItemType.Secondary => character.SelectedSecondaryItem == null,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
{
|
||||
return ParentEvent?.Prefab?.Identifier is { IsEmpty: false } identifier ? $"the event \"{identifier}\"" : "an unknown event";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,18 +60,42 @@ namespace Barotrauma
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
Identifier missionDebugId = (MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier);
|
||||
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
Mission unlockedMission = null;
|
||||
var unlockLocation = FindUnlockLocation(MinLocationDistance, UnlockFurtherOnMap, LocationTypes);
|
||||
var unlockLocation = FindUnlockLocation(MinLocationDistance, UnlockFurtherOnMap, LocationTypes, mustAllowLocationTypeChanges: false);
|
||||
|
||||
if (unlockLocation == null && UnlockFurtherOnMap)
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" further on the map. Attempting to find a location earlier on the map...");
|
||||
unlockLocation = FindUnlockLocation(MinLocationDistance, unlockFurtherOnMap: false, LocationTypes, mustAllowLocationTypeChanges: false);
|
||||
}
|
||||
|
||||
if (unlockLocation == null && CreateLocationIfNotFound)
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\". Attempting to change the type of an empty location to create a suitable location...");
|
||||
//find an empty location at least 3 steps away, further on the map
|
||||
var emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: true, "none".ToIdentifier().ToEnumerable());
|
||||
var emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: true, "none".ToIdentifier().ToEnumerable(),
|
||||
mustAllowLocationTypeChanges: true,
|
||||
requireCorrectFaction: false);
|
||||
if (emptyLocation == null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to find a suitable empty location further on the map. Attempting to find a location earlier on the map...");
|
||||
emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: false, "none".ToIdentifier().ToEnumerable(),
|
||||
mustAllowLocationTypeChanges: true,
|
||||
requireCorrectFaction: false);
|
||||
}
|
||||
if (emptyLocation != null)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!emptyLocation.LocationTypeChangesBlocked);
|
||||
emptyLocation.ChangeType(campaign, LocationType.Prefabs[LocationTypes[0]]);
|
||||
unlockLocation = emptyLocation;
|
||||
if (!RequiredFaction.IsEmpty)
|
||||
{
|
||||
emptyLocation.Faction = campaign.Factions.Find(f => f.Prefab.Identifier == RequiredFaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,13 +139,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationTypes}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
|
||||
DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private Location FindUnlockLocation(int minDistance, bool unlockFurtherOnMap, IEnumerable<Identifier> locationTypes)
|
||||
private Location FindUnlockLocation(int minDistance, bool unlockFurtherOnMap, IEnumerable<Identifier> locationTypes, bool mustAllowLocationTypeChanges, bool requireCorrectFaction = true)
|
||||
{
|
||||
var campaign = GameMain.GameSession.GameMode as CampaignMode;
|
||||
if (LocationTypes.Length == 0 && minDistance <= 1)
|
||||
@@ -140,7 +164,7 @@ namespace Barotrauma
|
||||
foreach (var location in currentLocations)
|
||||
{
|
||||
checkedLocations.Add(location);
|
||||
if (IsLocationValid(currentLocation, location, unlockFurtherOnMap, distance, minDistance, locationTypes))
|
||||
if (IsLocationValid(currentLocation, location, unlockFurtherOnMap, distance, minDistance, locationTypes, mustAllowLocationTypeChanges, requireCorrectFaction))
|
||||
{
|
||||
return location;
|
||||
}
|
||||
@@ -160,9 +184,13 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsLocationValid(Location currLocation, Location location, bool unlockFurtherOnMap, int distance, int minDistance, IEnumerable<Identifier> locationTypes)
|
||||
private bool IsLocationValid(Location currLocation, Location location, bool unlockFurtherOnMap, int distance, int minDistance, IEnumerable<Identifier> locationTypes, bool mustAllowLocationTypeChanges, bool requireCorrectFaction)
|
||||
{
|
||||
if (!RequiredFaction.IsEmpty)
|
||||
if (mustAllowLocationTypeChanges && location.LocationTypeChangesBlocked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (requireCorrectFaction && !RequiredFaction.IsEmpty)
|
||||
{
|
||||
if (location.Faction?.Prefab.Identifier != RequiredFaction &&
|
||||
location.SecondaryFaction?.Prefab.Identifier != RequiredFaction)
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AddToCrew { get; set; }
|
||||
|
||||
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RemoveFromCrew { get; set; }
|
||||
|
||||
@@ -38,6 +40,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
bool isPlayerTeam = TeamID == CharacterTeamType.Team1 || TeamID == CharacterTeamType.Team2;
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
@@ -49,9 +53,13 @@ namespace Barotrauma
|
||||
if (idCard != null)
|
||||
{
|
||||
idCard.TeamID = TeamID;
|
||||
if (isPlayerTeam)
|
||||
{
|
||||
idCard.SubmarineSpecificID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AddToCrew && (TeamID == CharacterTeamType.Team1 || TeamID == CharacterTeamType.Team2))
|
||||
if (AddToCrew && isPlayerTeam)
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(npc);
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f,
|
||||
IsFollowOrderObjective = true
|
||||
IsFollowOrder = true
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(newObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
|
||||
@@ -35,7 +35,9 @@ namespace Barotrauma
|
||||
AIObjectiveGoTo.GetTargetHull(npc) as ISpatialEntity ?? npc, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f,
|
||||
SourceEventAction = this
|
||||
SourceEventAction = this,
|
||||
IsWaitOrder = true,
|
||||
CloseEnough = 100
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(gotoObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerEventAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool NextRound { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TriggerEventAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -26,17 +27,24 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
if (NextRound)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
GameMain.GameSession.EventManager.QueuedEventsForNextRound.Enqueue(Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -75,7 +76,6 @@ namespace Barotrauma
|
||||
|
||||
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
|
||||
private readonly HashSet<Identifier> nonRepeatableEvents = new HashSet<Identifier>();
|
||||
private readonly HashSet<EventSet> usedUniqueSets = new HashSet<EventSet>();
|
||||
|
||||
|
||||
#if DEBUG && SERVER
|
||||
@@ -102,7 +102,9 @@ namespace Barotrauma
|
||||
|
||||
public readonly Queue<Event> QueuedEvents = new Queue<Event>();
|
||||
|
||||
private struct TimeStamp
|
||||
public readonly Queue<Identifier> QueuedEventsForNextRound = new Queue<Identifier>();
|
||||
|
||||
private readonly struct TimeStamp
|
||||
{
|
||||
public readonly double Time;
|
||||
public readonly Event Event;
|
||||
@@ -223,6 +225,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
while (QueuedEventsForNextRound.TryDequeue(out var id))
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(id);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in EventManager.StartRound - could not find an event with the identifier {id}.");
|
||||
continue;
|
||||
}
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
{
|
||||
QueuedEvents.Enqueue(ev);
|
||||
}
|
||||
}
|
||||
|
||||
PreloadContent(GetFilesToPreload());
|
||||
|
||||
roundDuration = 0.0f;
|
||||
@@ -355,7 +372,6 @@ namespace Barotrauma
|
||||
QueuedEvents.Clear();
|
||||
finishedEvents.Clear();
|
||||
nonRepeatableEvents.Clear();
|
||||
usedUniqueSets.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
@@ -1153,5 +1169,20 @@ namespace Barotrauma
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
{
|
||||
foreach (var id in element.GetAttributeIdentifierArray(nameof(QueuedEventsForNextRound), Array.Empty<Identifier>()))
|
||||
{
|
||||
QueuedEventsForNextRound.Enqueue(id);
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement("eventmanager",
|
||||
new XAttribute(nameof(QueuedEventsForNextRound),
|
||||
string.Join(',', QueuedEventsForNextRound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Barotrauma
|
||||
if (IsClient) { return; }
|
||||
if (!swarmSpawned && level.CheckBeaconActive())
|
||||
{
|
||||
List<Submarine> connectedSubs = level.BeaconStation.GetConnectedSubs();
|
||||
IEnumerable<Submarine> connectedSubs = level.BeaconStation.GetConnectedSubs();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine) || item.Submarine?.Info is { IsPlayer: true }) { continue; }
|
||||
|
||||
@@ -320,7 +320,7 @@ namespace Barotrauma
|
||||
private static bool IsItemDelivered(Item item)
|
||||
{
|
||||
if (item.Removed || item.Condition <= 0.0f || Submarine.MainSub == null) { return false; }
|
||||
var submarine = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
var submarine = item.Submarine ?? item.RootContainer?.Submarine;
|
||||
return submarine == Submarine.MainSub || Submarine.MainSub.GetConnectedSubs().Contains(submarine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
|
||||
@@ -11,20 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
private struct ResourceCluster
|
||||
{
|
||||
public int Amount;
|
||||
public float Rotation;
|
||||
|
||||
public ResourceCluster(int amount, float rotation)
|
||||
{
|
||||
Amount = amount;
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public static implicit operator ResourceCluster((int amount, float rotation) tuple) => new ResourceCluster(tuple.amount, tuple.rotation);
|
||||
}
|
||||
private readonly Dictionary<Identifier, ResourceCluster> resourceClusters = new Dictionary<Identifier, ResourceCluster>();
|
||||
private readonly Dictionary<Identifier, int> resourceAmounts = new Dictionary<Identifier, int>();
|
||||
private readonly Dictionary<Identifier, List<Item>> spawnedResources = new Dictionary<Identifier, List<Item>>();
|
||||
private readonly Dictionary<Identifier, Item[]> relevantLevelResources = new Dictionary<Identifier, Item[]>();
|
||||
private readonly List<(Identifier Identifier, Vector2 Position)> missionClusterPositions = new List<(Identifier Identifier, Vector2 Position)>();
|
||||
@@ -81,13 +68,13 @@ namespace Barotrauma
|
||||
{
|
||||
var identifier = c.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier.IsEmpty) { continue; }
|
||||
if (resourceClusters.ContainsKey(identifier))
|
||||
if (resourceAmounts.ContainsKey(identifier))
|
||||
{
|
||||
resourceClusters[identifier] = (resourceClusters[identifier].Amount + 1, resourceClusters[identifier].Rotation);
|
||||
resourceAmounts[identifier]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceClusters.Add(identifier, (1, 0.0f));
|
||||
resourceAmounts.Add(identifier, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +115,7 @@ namespace Barotrauma
|
||||
|
||||
if (IsClient) { return; }
|
||||
|
||||
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
|
||||
foreach ((Identifier identifier, int amount) in resourceAmounts)
|
||||
{
|
||||
if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab)
|
||||
{
|
||||
@@ -136,10 +123,10 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation, caves);
|
||||
if (spawnedResources.Count < cluster.Amount)
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves);
|
||||
if (spawnedResources.Count < amount)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}");
|
||||
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}");
|
||||
}
|
||||
|
||||
if (spawnedResources.None()) { continue; }
|
||||
@@ -194,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
// When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
|
||||
var handoverResources = new List<Item>();
|
||||
foreach (Identifier identifier in resourceClusters.Keys)
|
||||
foreach (Identifier identifier in resourceAmounts.Keys)
|
||||
{
|
||||
if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
|
||||
{
|
||||
@@ -231,11 +218,11 @@ namespace Barotrauma
|
||||
private void FindRelevantLevelResources()
|
||||
{
|
||||
relevantLevelResources.Clear();
|
||||
foreach (var identifier in resourceClusters.Keys)
|
||||
foreach (var identifier in resourceAmounts.Keys)
|
||||
{
|
||||
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
|
||||
i.Submarine == null && i.ParentInventory == null &&
|
||||
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
|
||||
(i.GetComponent<Holdable>() is not Holdable h || (h.Attachable && h.Attached)))
|
||||
.ToArray();
|
||||
relevantLevelResources.Add(identifier, items);
|
||||
}
|
||||
@@ -243,12 +230,12 @@ namespace Barotrauma
|
||||
|
||||
private bool EnoughHaveBeenCollected()
|
||||
{
|
||||
foreach (var kvp in resourceClusters)
|
||||
foreach (var kvp in resourceAmounts)
|
||||
{
|
||||
if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
{
|
||||
var collected = availableResources.Count(HasBeenCollected);
|
||||
var needed = kvp.Value.Amount;
|
||||
var needed = kvp.Value;
|
||||
if (collected < needed) { return false; }
|
||||
}
|
||||
else
|
||||
@@ -299,10 +286,10 @@ namespace Barotrauma
|
||||
protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
|
||||
{
|
||||
int i = 1;
|
||||
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
|
||||
foreach ((Identifier identifier, int amount) in resourceAmounts)
|
||||
{
|
||||
Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
|
||||
Replace($"[resourcequantity{i}]", cluster.Amount.ToString());
|
||||
Replace($"[resourcequantity{i}]", amount.ToString());
|
||||
i++;
|
||||
}
|
||||
Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
|
||||
|
||||
@@ -363,7 +363,7 @@ namespace Barotrauma
|
||||
//make body dynamic when picked up
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||
var root = target.Item?.RootContainer ?? target.Item;
|
||||
if (root == null) { continue; }
|
||||
if (target.Item.ParentInventory != null && target.Item.body != null) { target.Item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
}
|
||||
@@ -389,7 +389,7 @@ namespace Barotrauma
|
||||
{
|
||||
TrySetRetrievalState(Target.RetrievalState.Interact);
|
||||
}
|
||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||
var root = target.Item?.RootContainer ?? target.Item;
|
||||
if (root.ParentInventory?.Owner is Character character && character.TeamID == CharacterTeamType.Team1)
|
||||
{
|
||||
TrySetRetrievalState(Target.RetrievalState.PickedUp);
|
||||
|
||||
@@ -335,7 +335,7 @@ namespace Barotrauma
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
|
||||
{
|
||||
SpawnedInCurrentOutpost = validContainer.Key.Item.SpawnedInCurrentOutpost,
|
||||
AllowStealing = validContainer.Key.Item.AllowStealing,
|
||||
AllowStealing = validContainer.Key.Item.AllowStealing || validContainer.Key.Item.Prefab.AllowStealingContainedItems,
|
||||
Quality = quality,
|
||||
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
|
||||
OriginalContainerIndex =
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections;
|
||||
using System.Collections.Immutable;
|
||||
#if SERVER
|
||||
using Barotrauma.Networking;
|
||||
#endif
|
||||
@@ -415,14 +416,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return Submarine.MainSub.GetItems(true).FindAll(item =>
|
||||
return FindAllSellableItems().Where(it => IsItemSellable(it, confirmedSoldEntities)).ToList();
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<Item> FindAllItemsOnPlayerAndSub(Character character)
|
||||
{
|
||||
List<Item> allItems = new();
|
||||
if (character?.Inventory is { } inv)
|
||||
{
|
||||
allItems.AddRange(inv.FindAllItems(recursive: true));
|
||||
}
|
||||
allItems.AddRange(FindAllSellableItems());
|
||||
return allItems;
|
||||
}
|
||||
|
||||
public static IEnumerable<Item> FindAllSellableItems()
|
||||
{
|
||||
if (Submarine.MainSub is null) { return Enumerable.Empty<Item>(); }
|
||||
|
||||
return Submarine.MainSub.GetItems(true).FindAll(static item =>
|
||||
{
|
||||
if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
|
||||
if (item.GetRootInventoryOwner() is Character) { return false; }
|
||||
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
|
||||
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
|
||||
if (!item.Components.All(static c => c is not Holdable { Attachable: true, Attached: true })) { return false; }
|
||||
if (!item.Components.All(static c => c is not Wire w || w.Connections.All(static c => c is null))) { return false; }
|
||||
if (!ItemAndAllContainersInteractable(item)) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
if (item.RootContainer is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
return true;
|
||||
}).Distinct();
|
||||
|
||||
@@ -592,7 +610,7 @@ namespace Barotrauma
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(PurchasedItem purchased, Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
Submarine sub = item.Submarine ?? item.RootContainer?.Submarine;
|
||||
if (sub != null)
|
||||
{
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
|
||||
@@ -143,6 +143,8 @@ namespace Barotrauma
|
||||
public virtual bool PurchasedLostShuttles { get; set; }
|
||||
public virtual bool PurchasedItemRepairs { get; set; }
|
||||
|
||||
public bool DivingSuitWarningShown;
|
||||
|
||||
private static bool AnyOneAllowedToManageCampaign(ClientPermissions permissions)
|
||||
{
|
||||
if (GameMain.NetworkMember == null) { return true; }
|
||||
@@ -738,9 +740,11 @@ namespace Barotrauma
|
||||
//if there's a sub docked to the outpost, we can leave the level
|
||||
if (Level.Loaded.StartOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.StartOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
foreach (var dockedSub in Level.Loaded.StartOutpost.DockedTo)
|
||||
{
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
@@ -776,9 +780,11 @@ namespace Barotrauma
|
||||
//if there's a sub docked to the outpost, we can leave the level
|
||||
if (Level.Loaded.EndOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.EndOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
foreach (var dockedSub in Level.Loaded.EndOutpost.DockedTo)
|
||||
{
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
@@ -930,6 +936,9 @@ namespace Barotrauma
|
||||
CampaignMetadata.SetValue("campaign.endings".ToIdentifier(), loops + 1);
|
||||
}
|
||||
|
||||
//no tutorials after finishing the campaign once
|
||||
Settings.TutorialEnabled = false;
|
||||
|
||||
GameAnalyticsManager.AddProgressionEvent(
|
||||
GameAnalyticsManager.ProgressionStatus.Complete,
|
||||
Preset?.Identifier.Value ?? "none");
|
||||
@@ -985,7 +994,7 @@ namespace Barotrauma
|
||||
if (characterInfo == null) { return false; }
|
||||
if (characterInfo.MinReputationToHire.factionId != Identifier.Empty)
|
||||
{
|
||||
if (GetReputation(characterInfo.MinReputationToHire.factionId) < characterInfo.MinReputationToHire.reputation)
|
||||
if (MathF.Round(GetReputation(characterInfo.MinReputationToHire.factionId)) < characterInfo.MinReputationToHire.reputation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1204,15 +1213,17 @@ namespace Barotrauma
|
||||
{
|
||||
TotalPlayTime = element.GetAttributeDouble(nameof(TotalPlayTime).ToLowerInvariant(), 0);
|
||||
TotalPassedLevels = element.GetAttributeInt(nameof(TotalPassedLevels).ToLowerInvariant(), 0);
|
||||
DivingSuitWarningShown = element.GetAttributeBool(nameof(DivingSuitWarningShown).ToLowerInvariant(), false);
|
||||
}
|
||||
|
||||
protected XElement SaveStats()
|
||||
{
|
||||
return new XElement("stats",
|
||||
new XAttribute(nameof(TotalPlayTime).ToLowerInvariant(), TotalPlayTime),
|
||||
new XAttribute(nameof(TotalPassedLevels).ToLowerInvariant(), TotalPassedLevels));
|
||||
new XAttribute(nameof(TotalPassedLevels).ToLowerInvariant(), TotalPassedLevels),
|
||||
new XAttribute(nameof(DivingSuitWarningShown).ToLowerInvariant(), DivingSuitWarningShown));
|
||||
}
|
||||
|
||||
|
||||
public void LogState()
|
||||
{
|
||||
DebugConsole.NewMessage("********* CAMPAIGN STATUS *********", Color.White);
|
||||
|
||||
+19
-10
@@ -223,6 +223,9 @@ namespace Barotrauma
|
||||
case "stats":
|
||||
LoadStats(subElement);
|
||||
break;
|
||||
case "eventmanager":
|
||||
GameMain.GameSession.EventManager.Load(subElement);
|
||||
break;
|
||||
case Wallet.LowerCaseSaveElementName:
|
||||
Bank = new Wallet(Option<Character>.None(), subElement);
|
||||
break;
|
||||
@@ -272,29 +275,35 @@ namespace Barotrauma
|
||||
bool isSubmarineVisible(SubmarineInfo s)
|
||||
=> !GameMain.NetworkMember.ServerSettings.HiddenSubs.Any(h
|
||||
=> s.Name.Equals(h, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
List<SubmarineInfo> availableSubs =
|
||||
SubmarineInfo.SavedSubmarines
|
||||
|
||||
var availableSubs = SubmarineInfo.SavedSubmarines;
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
availableSubs = GameMain.Client.ServerSubmarines;
|
||||
}
|
||||
#endif
|
||||
|
||||
List<SubmarineInfo> campaignSubs =
|
||||
availableSubs
|
||||
.Where(s =>
|
||||
s.IsCampaignCompatible
|
||||
&& isSubmarineVisible(s))
|
||||
.ToList();
|
||||
|
||||
if (!availableSubs.Any())
|
||||
if (!campaignSubs.Any())
|
||||
{
|
||||
//None of the available subs were marked as campaign-compatible, just include all visible subs
|
||||
availableSubs.AddRange(
|
||||
SubmarineInfo.SavedSubmarines
|
||||
.Where(isSubmarineVisible));
|
||||
campaignSubs.AddRange(availableSubs.Where(isSubmarineVisible));
|
||||
}
|
||||
|
||||
if (!availableSubs.Any())
|
||||
if (!campaignSubs.Any())
|
||||
{
|
||||
//No subs are visible at all! Just make the selected one available
|
||||
availableSubs.Add(GameMain.NetLobbyScreen.SelectedSub);
|
||||
campaignSubs.Add(GameMain.NetLobbyScreen.SelectedSub);
|
||||
}
|
||||
|
||||
return availableSubs;
|
||||
return campaignSubs;
|
||||
}
|
||||
|
||||
private static void WriteItems(IWriteMessage msg, Dictionary<Identifier, List<PurchasedItem>> purchasedItems)
|
||||
|
||||
@@ -328,11 +328,11 @@ namespace Barotrauma
|
||||
Campaign!.TransferItemsOnSubSwitch = transferItems;
|
||||
}
|
||||
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
public bool TryPurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
{
|
||||
if (Campaign is null) { return; }
|
||||
if (Campaign is null) { return false; }
|
||||
int price = newSubmarine.GetPrice();
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.TryPurchase(client, price)) { return; }
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.TryPurchase(client, price)) { return false; }
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
@@ -341,6 +341,7 @@ namespace Barotrauma
|
||||
(Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.SubList);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsSubmarineOwned(SubmarineInfo query)
|
||||
@@ -504,7 +505,8 @@ namespace Barotrauma
|
||||
}
|
||||
GameAnalyticsManager.AddDesignEvent($"{eventId}HintManager:{(HintManager.Enabled ? "Enabled" : "Disabled")}");
|
||||
#endif
|
||||
if (GameMode is CampaignMode campaignMode)
|
||||
var campaignMode = GameMode as CampaignMode;
|
||||
if (campaignMode != null)
|
||||
{
|
||||
if (campaignMode.Map?.Radiation != null && campaignMode.Map.Radiation.Enabled)
|
||||
{
|
||||
@@ -532,7 +534,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
#if CLIENT
|
||||
if (GameMode is CampaignMode && levelData != null) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); }
|
||||
if (campaignMode != null && levelData != null) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); }
|
||||
|
||||
var existingRoundSummary = GUIMessageBox.MessageBoxes.Find(mb => mb.UserData is RoundSummary)?.UserData as RoundSummary;
|
||||
if (existingRoundSummary?.ContinueButton != null)
|
||||
@@ -577,6 +579,14 @@ namespace Barotrauma
|
||||
|
||||
GameMain.LuaCs.Hook.Call("roundStart");
|
||||
#endif
|
||||
if (campaignMode is { DivingSuitWarningShown: false } &&
|
||||
Level.Loaded != null && Level.Loaded.GetRealWorldDepth(0) > 4000)
|
||||
{
|
||||
#if CLIENT
|
||||
CoroutineManager.Invoke(() => new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("hint.upgradedivingsuits")), delay: 5.0f);
|
||||
#endif
|
||||
campaignMode.DivingSuitWarningShown = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeLevel(Level? level)
|
||||
@@ -693,7 +703,7 @@ namespace Barotrauma
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine == level.StartOutpost)
|
||||
{
|
||||
if (port.DockingTarget == null)
|
||||
if (port.DockingTarget == null || (outPostPort != null && !outPostPort.MainDockingPort && port.MainDockingPort))
|
||||
{
|
||||
outPostPort = port;
|
||||
}
|
||||
@@ -982,7 +992,7 @@ namespace Barotrauma
|
||||
Dictionary<ItemPrefab, int> submarineInventory = new Dictionary<ItemPrefab, int>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var rootContainer = item.GetRootContainer() ?? item;
|
||||
var rootContainer = item.RootContainer ?? item;
|
||||
if (rootContainer.Submarine?.Info == null || rootContainer.Submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (rootContainer.Submarine != Submarine.MainSub && !Submarine.MainSub.DockedTo.Contains(rootContainer.Submarine)) { continue; }
|
||||
|
||||
|
||||
@@ -85,6 +85,22 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
var itemElements = element.Elements().Where(e => e.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase));
|
||||
int itemCount = itemElements.Count();
|
||||
if (itemCount > capacity)
|
||||
{
|
||||
DebugConsole.ThrowError($"Character \"{character.SpeciesName}\" is configured to spawn with more items than it has inventory capacity for.");
|
||||
}
|
||||
#if DEBUG
|
||||
else if (itemCount > capacity - 2)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Character \"{character.SpeciesName}\" is configured to spawn with so many items it will have less than 2 free inventory slots. " +
|
||||
"This can cause issues with talents that spawn extra loot in monsters' inventories."
|
||||
+ " Consider increasing the inventory size.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!spawnInitialItems) { return; }
|
||||
|
||||
#if CLIENT
|
||||
@@ -92,10 +108,8 @@ namespace Barotrauma
|
||||
if (GameMain.Client != null) { return; }
|
||||
#endif
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
foreach (var subElement in itemElements)
|
||||
{
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!ItemPrefab.Prefabs.TryGet(itemIdentifier, out var itemPrefab))
|
||||
{
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Barotrauma.Lights;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -243,10 +245,12 @@ namespace Barotrauma.Items.Components
|
||||
if (!target.item.Submarine.DockedTo.Contains(item.Submarine))
|
||||
{
|
||||
target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
|
||||
target.item.Submarine.RefreshConnectedSubs();
|
||||
}
|
||||
if (!item.Submarine.DockedTo.Contains(target.item.Submarine))
|
||||
{
|
||||
item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
|
||||
item.Submarine.RefreshConnectedSubs();
|
||||
}
|
||||
|
||||
DockingTarget = target;
|
||||
@@ -507,9 +511,9 @@ namespace Barotrauma.Items.Components
|
||||
wire.RemoveConnection(DockingTarget.item);
|
||||
|
||||
powerConnection.TryAddLink(wire);
|
||||
wire.Connect(powerConnection, false, false);
|
||||
wire.TryConnect(powerConnection, addNode: false);
|
||||
recipient.TryAddLink(wire);
|
||||
wire.Connect(recipient, false, false);
|
||||
wire.TryConnect(recipient, addNode: false);
|
||||
|
||||
//Flag connections to be updated
|
||||
Powered.ChangedConnections.Add(powerConnection);
|
||||
@@ -558,6 +562,7 @@ namespace Barotrauma.Items.Components
|
||||
var subs = new Submarine[] { item.Submarine, DockingTarget.item.Submarine };
|
||||
|
||||
bodies = new Body[4];
|
||||
RemoveConvexHulls();
|
||||
|
||||
if (DockingTarget.Door != null)
|
||||
{
|
||||
@@ -648,8 +653,10 @@ namespace Barotrauma.Items.Components
|
||||
hullRects[i].X -= expand;
|
||||
hullRects[i].Width += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
|
||||
hulls[i] = new Hull(hullRects[i], subs[i]);
|
||||
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
|
||||
hulls[i] = new Hull(hullRects[i], subs[i])
|
||||
{
|
||||
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch"
|
||||
};
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
hulls[i].FreeID();
|
||||
|
||||
@@ -661,6 +668,15 @@ namespace Barotrauma.Items.Components
|
||||
BodyType.Static);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
convexHulls[i] =
|
||||
new ConvexHull(new Rectangle(
|
||||
new Point((int)item.Position.X, item.Rect.Y - item.Rect.Height * i),
|
||||
new Point((int)(DockingTarget.item.WorldPosition.X - item.WorldPosition.X), 0)), IsHorizontal, item);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rightHullDiff <= 100 && hulls[0].Submarine != null)
|
||||
{
|
||||
@@ -764,15 +780,17 @@ namespace Barotrauma.Items.Components
|
||||
hullRects[1].Height += midHullDiff / 2 + 1;
|
||||
}
|
||||
|
||||
|
||||
int expand = 5;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].Y += expand;
|
||||
hullRects[i].Height += expand * 2;
|
||||
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
|
||||
hulls[i] = new Hull(hullRects[i], subs[i]);
|
||||
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
|
||||
hulls[i] = new Hull(hullRects[i], subs[i])
|
||||
{
|
||||
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch",
|
||||
AvoidStaying = true
|
||||
};
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
hulls[i].FreeID();
|
||||
|
||||
@@ -784,6 +802,15 @@ namespace Barotrauma.Items.Components
|
||||
BodyType.Static);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
convexHulls[i] =
|
||||
new ConvexHull(new Rectangle(
|
||||
new Point(item.Rect.X + item.Rect.Width * i, (int)item.Position.Y),
|
||||
new Point(0, (int)(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y))), IsHorizontal, item);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (midHullDiff <= 100 && hulls[0].Submarine != null)
|
||||
{
|
||||
@@ -822,6 +849,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
partial void RemoveConvexHulls();
|
||||
|
||||
private void LinkHullsToGaps()
|
||||
{
|
||||
if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null)
|
||||
@@ -916,7 +945,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
|
||||
DockingTarget.item.Submarine.RefreshConnectedSubs();
|
||||
item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
|
||||
item.Submarine.RefreshConnectedSubs();
|
||||
|
||||
if (Door != null && DockingTarget.Door != null)
|
||||
{
|
||||
@@ -976,6 +1007,8 @@ namespace Barotrauma.Items.Components
|
||||
hulls[0]?.Remove(); hulls[0] = null;
|
||||
hulls[1]?.Remove(); hulls[1] = null;
|
||||
|
||||
RemoveConvexHulls();
|
||||
|
||||
if (gap != null)
|
||||
{
|
||||
gap.Remove();
|
||||
@@ -1091,6 +1124,7 @@ namespace Barotrauma.Items.Components
|
||||
hulls[0]?.Remove(); hulls[0] = null;
|
||||
hulls[1]?.Remove(); hulls[1] = null;
|
||||
gap?.Remove(); gap = null;
|
||||
RemoveConvexHulls();
|
||||
|
||||
overlaySprite?.Remove();
|
||||
overlaySprite = null;
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool isBroken;
|
||||
|
||||
public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck && !Impassable;
|
||||
public bool CanBeTraversed => !Impassable && (IsBroken || IsOpen);
|
||||
|
||||
public bool IsBroken
|
||||
{
|
||||
@@ -186,13 +186,19 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
get { return openState; }
|
||||
set
|
||||
{
|
||||
{
|
||||
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
float size = IsHorizontal ? item.Rect.Width : item.Rect.Height;
|
||||
if (Math.Abs(lastConvexHullState - openState) * size < 5.0f) { return; }
|
||||
UpdateConvexHulls();
|
||||
lastConvexHullState = openState;
|
||||
//refresh convex hulls if the body of the door has moved by 5 pixels,
|
||||
//or if it becomes fully closed or fully open
|
||||
if (Math.Abs(lastConvexHullState - openState) * size > 5.0f ||
|
||||
(openState <= 0.0f && lastConvexHullState > 0.0f) ||
|
||||
(openState >= 1.0f && lastConvexHullState < 1.0f))
|
||||
{
|
||||
UpdateConvexHulls();
|
||||
lastConvexHullState = openState;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -523,11 +529,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshLinkedGap();
|
||||
#if CLIENT
|
||||
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
|
||||
|
||||
convexHull = new ConvexHull(corners, Color.Black, item);
|
||||
if (Window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
|
||||
|
||||
convexHull = new ConvexHull(doorRect, IsHorizontal, item);
|
||||
if (Window != Rectangle.Empty)
|
||||
{
|
||||
convexHull2 = new ConvexHull(doorRect, IsHorizontal, item);
|
||||
}
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -96,11 +95,16 @@ namespace Barotrauma.Items.Components
|
||||
if (selectedEffect != null)
|
||||
{
|
||||
targetCharacter = character;
|
||||
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
|
||||
ApplyStatusEffects(ActionType.OnWearing, 1.0f, targetCharacter);
|
||||
float selectedEffectStrength = GetCombinedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null) { affliction.Strength = selectedEffectStrength; }
|
||||
if (affliction != null)
|
||||
{
|
||||
affliction.Strength = selectedEffectStrength;
|
||||
//force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing
|
||||
affliction.SetStrength(selectedEffectStrength);
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
@@ -110,7 +114,12 @@ namespace Barotrauma.Items.Components
|
||||
float selectedTaintedEffectStrength = GetCombinedTaintedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (affliction != null) { affliction.Strength = selectedTaintedEffectStrength; }
|
||||
if (affliction != null)
|
||||
{
|
||||
affliction.Strength = selectedTaintedEffectStrength;
|
||||
//force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing
|
||||
affliction.SetStrength(selectedTaintedEffectStrength);
|
||||
}
|
||||
targetCharacter = character;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
@@ -127,7 +136,7 @@ namespace Barotrauma.Items.Components
|
||||
base.Update(deltaTime, cam);
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
var rootContainer = item.GetRootContainer();
|
||||
var rootContainer = item.RootContainer;
|
||||
if (!targetCharacter.HasEquippedItem(item) &&
|
||||
(rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface)))
|
||||
{
|
||||
@@ -220,7 +229,7 @@ namespace Barotrauma.Items.Components
|
||||
return MathHelper.Clamp(probability, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private float GetTaintedProbabilityOnCombine(Character user)
|
||||
private static float GetTaintedProbabilityOnCombine(Character user)
|
||||
{
|
||||
if (user == null) { return 1.0f; }
|
||||
float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine);
|
||||
|
||||
@@ -409,6 +409,7 @@ namespace Barotrauma.Items.Components
|
||||
private int leafVariants;
|
||||
private int[] flowerTiles;
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes)]
|
||||
public float Health
|
||||
{
|
||||
get => health;
|
||||
|
||||
@@ -321,12 +321,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
Drop(true, dropper);
|
||||
Drop(true, dropper, setTransform);
|
||||
}
|
||||
|
||||
private void Drop(bool dropConnectedWires, Character dropper)
|
||||
private void Drop(bool dropConnectedWires, Character dropper, bool setTransform = true)
|
||||
{
|
||||
GetRope()?.Snap();
|
||||
if (dropConnectedWires)
|
||||
@@ -343,8 +343,11 @@ namespace Barotrauma.Items.Components
|
||||
DeattachFromWall();
|
||||
}
|
||||
|
||||
if (Pusher != null) { Pusher.Enabled = false; }
|
||||
if (item.body != null) { item.body.Enabled = true; }
|
||||
if (setTransform)
|
||||
{
|
||||
if (Pusher != null) { Pusher.Enabled = false; }
|
||||
if (item.body != null) { item.body.Enabled = true; }
|
||||
}
|
||||
IsActive = false;
|
||||
attachTargetCell = null;
|
||||
|
||||
@@ -357,7 +360,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
if (item.body != null)
|
||||
if (item.body != null && setTransform)
|
||||
{
|
||||
if (item.body.Removed)
|
||||
{
|
||||
@@ -599,6 +602,10 @@ namespace Barotrauma.Items.Components
|
||||
throw new InvalidOperationException($"Tried to attach an item with no physics body to a wall ({item.Prefab.Identifier}).");
|
||||
}
|
||||
|
||||
body.Enabled = false;
|
||||
body.SetTransformIgnoreContacts(body.SimPosition, rotation: 0.0f);
|
||||
item.body = null;
|
||||
|
||||
//outside hulls/subs -> we need to check if the item is being attached on a structure outside the sub
|
||||
if (item.CurrentHull == null && item.Submarine == null)
|
||||
{
|
||||
@@ -638,9 +645,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
body.Enabled = false;
|
||||
item.body = null;
|
||||
|
||||
DisplayMsg = prevMsg;
|
||||
PickKey = prevPickKey;
|
||||
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(prevRequiredItems);
|
||||
@@ -812,7 +816,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var edge in cell.Edges)
|
||||
{
|
||||
if (!edge.IsSolid) { continue; }
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, user.WorldPosition, attachPos, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, user.WorldPosition, attachPos, out Vector2 intersection))
|
||||
{
|
||||
attachPos = intersection;
|
||||
edgeFound = true;
|
||||
|
||||
+14
-5
@@ -97,11 +97,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (holdable != null && !holdable.Attached)
|
||||
{
|
||||
trigger.Enabled = false;
|
||||
if (trigger != null)
|
||||
{
|
||||
trigger.Enabled = false;
|
||||
}
|
||||
IsActive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (trigger == null)
|
||||
{
|
||||
CreateTriggerBody();
|
||||
}
|
||||
if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
|
||||
{
|
||||
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||
@@ -123,12 +130,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
holdable.PickingTime = float.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTriggerBody()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(trigger == null, "LevelResource trigger already created!");
|
||||
var body = item.body ?? holdable.Body;
|
||||
|
||||
if (body != null)
|
||||
if (body != null && Attached)
|
||||
{
|
||||
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
|
||||
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
|
||||
body.Density,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
@@ -143,7 +153,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
if (trigger != null)
|
||||
{
|
||||
trigger.Remove();
|
||||
|
||||
@@ -170,9 +170,9 @@ namespace Barotrauma.Items.Components
|
||||
return characterUsable || character == null;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
base.Drop(dropper, setTransform);
|
||||
hitting = false;
|
||||
hitPos = 0.0f;
|
||||
}
|
||||
|
||||
@@ -241,12 +241,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
if (picker == null)
|
||||
{
|
||||
picker = dropper;
|
||||
}
|
||||
picker ??= dropper;
|
||||
|
||||
Vector2 bodyDropPos = Vector2.Zero;
|
||||
|
||||
@@ -255,8 +252,7 @@ namespace Barotrauma.Items.Components
|
||||
if (item.ParentInventory != null && item.ParentInventory.Owner != null && !item.ParentInventory.Owner.Removed)
|
||||
{
|
||||
bodyDropPos = item.ParentInventory.Owner.SimPosition;
|
||||
|
||||
if (item.body != null) item.body.ResetDynamics();
|
||||
item.body?.ResetDynamics();
|
||||
}
|
||||
}
|
||||
else if (!picker.Removed)
|
||||
@@ -270,7 +266,7 @@ namespace Barotrauma.Items.Components
|
||||
picker = null;
|
||||
}
|
||||
|
||||
if (item.body != null && !item.body.Enabled)
|
||||
if (item.body != null && !item.body.Enabled && setTransform)
|
||||
{
|
||||
if (item.body.Removed)
|
||||
{
|
||||
|
||||
@@ -912,17 +912,16 @@ namespace Barotrauma.Items.Components
|
||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||
foreach (ISerializableEntity target in currentTargets)
|
||||
{
|
||||
if (!(target is Door door)) { continue; }
|
||||
if (target is not Door door) { continue; }
|
||||
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
||||
for (int i = 0; i < effect.propertyNames.Length; i++)
|
||||
foreach (var propertyEffect in effect.PropertyEffects)
|
||||
{
|
||||
Identifier propertyName = effect.propertyNames[i];
|
||||
if (propertyName != "stuck") { continue; }
|
||||
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
|
||||
if (propertyEffect.propertyName != "stuck") { continue; }
|
||||
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyEffect.propertyName, out SerializableProperty property)) { continue; }
|
||||
object value = property.GetValue(target);
|
||||
if (door.Stuck > 0)
|
||||
{
|
||||
bool isCutting = effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0;
|
||||
bool isCutting = propertyEffect.value is float and < 0;
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White,
|
||||
textTag: isCutting ? "progressbar.cutting" : "progressbar.welding");
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
|
||||
@@ -56,9 +56,9 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
base.Drop(dropper, setTransform);
|
||||
throwState = ThrowState.None;
|
||||
throwAngle = ThrowAngleStart;
|
||||
Item.ResetWaterDragCoefficient();
|
||||
|
||||
@@ -442,7 +442,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
/// <summary>a Character has dropped the item</summary>
|
||||
public virtual void Drop(Character dropper) { }
|
||||
public virtual void Drop(Character dropper, bool setTransform = true) { }
|
||||
|
||||
/// <returns>true if the operation was completed</returns>
|
||||
public virtual bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
@@ -724,20 +724,50 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsBot && item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (character.Inventory != null)
|
||||
if (requiredItems.Count == 0) { return true; }
|
||||
if (character.Inventory != null && requiredItems.TryGetValue(RelatedItem.RelationType.Picked, out List<RelatedItem> relatedItems))
|
||||
{
|
||||
foreach (Item item in character.Inventory.AllItems)
|
||||
foreach (RelatedItem relatedItem in relatedItems)
|
||||
{
|
||||
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
|
||||
foreach (Item otherItem in character.Inventory.AllItems)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (relatedItem.MatchesItem(otherItem))
|
||||
{
|
||||
if (otherItem.GetComponent<IdCard>() is IdCard idCard)
|
||||
{
|
||||
if (!CheckIdCardAccess(relatedItem, idCard))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presumes that matching is already checked.
|
||||
/// </summary>
|
||||
private bool CheckIdCardAccess(RelatedItem relatedItem, IdCard idCard)
|
||||
{
|
||||
if (item.Submarine != null)
|
||||
{
|
||||
//id cards don't work in enemy subs (except on items that only require the default "idcard" tag)
|
||||
if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
if (requiredItems.None()) { return true; }
|
||||
@@ -773,23 +803,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
bool CheckItems(RelatedItem relatedItem, IEnumerable<Item> itemList)
|
||||
{
|
||||
bool Predicate(Item it)
|
||||
bool Predicate(Item it)
|
||||
{
|
||||
if (it == null || it.Condition <= 0.0f || !relatedItem.MatchesItem(it)) { return false; }
|
||||
if (item.Submarine != null)
|
||||
if (it.GetComponent<IdCard>() is IdCard idCard)
|
||||
{
|
||||
var idCard = it.GetComponent<IdCard>();
|
||||
if (idCard != null)
|
||||
if (!CheckIdCardAccess(relatedItem, idCard))
|
||||
{
|
||||
//id cards don't work in enemy subs (except on items that only require the default "idcard" tag)
|
||||
if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1029,7 +1050,7 @@ namespace Barotrauma.Items.Components
|
||||
prevRequiredItems[newRequiredItem.Type].Find(ri => ri.JoinedIdentifiers == newRequiredItem.JoinedIdentifiers) : null;
|
||||
if (prevRequiredItem != null)
|
||||
{
|
||||
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
|
||||
newRequiredItem.StatusEffects = prevRequiredItem.StatusEffects;
|
||||
newRequiredItem.Msg = prevRequiredItem.Msg;
|
||||
newRequiredItem.IsOptional = prevRequiredItem.IsOptional;
|
||||
newRequiredItem.IgnoreInEditor = prevRequiredItem.IgnoreInEditor;
|
||||
|
||||
@@ -20,11 +20,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public readonly int MaxStackSize;
|
||||
public List<RelatedItem> ContainableItems;
|
||||
public readonly bool AutoInject;
|
||||
|
||||
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems)
|
||||
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems, bool autoInject)
|
||||
{
|
||||
MaxStackSize = maxStackSize;
|
||||
ContainableItems = containableItems;
|
||||
AutoInject = autoInject;
|
||||
}
|
||||
|
||||
public bool MatchesItem(Item item)
|
||||
@@ -269,7 +271,7 @@ namespace Barotrauma.Items.Components
|
||||
List<SlotRestrictions> newSlotRestrictions = new List<SlotRestrictions>(totalCapacity);
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems));
|
||||
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems, autoInject: false));
|
||||
}
|
||||
|
||||
int subContainerIndex = capacity;
|
||||
@@ -279,6 +281,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
int subCapacity = subElement.GetAttributeInt("capacity", 1);
|
||||
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
|
||||
bool autoInject = subElement.GetAttributeBool("autoinject", false);
|
||||
|
||||
var subContainableItems = new List<RelatedItem>();
|
||||
foreach (var subSubElement in subElement.Elements())
|
||||
@@ -298,7 +301,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
|
||||
{
|
||||
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems));
|
||||
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems, autoInject));
|
||||
}
|
||||
subContainerIndex += subCapacity;
|
||||
}
|
||||
@@ -351,7 +354,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var containableItem in slotRestrictions[index].ContainableItems)
|
||||
{
|
||||
if (!containableItem.MatchesItem(containedItem)) { continue; }
|
||||
foreach (StatusEffect effect in containableItem.statusEffects)
|
||||
foreach (StatusEffect effect in containableItem.StatusEffects)
|
||||
{
|
||||
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition));
|
||||
}
|
||||
@@ -466,7 +469,7 @@ namespace Barotrauma.Items.Components
|
||||
prevContainedItemPositions = item.Position;
|
||||
}
|
||||
|
||||
if (AutoInject)
|
||||
if (AutoInject || slotRestrictions.Any(s => s.AutoInject))
|
||||
{
|
||||
//normally autoinjection should delete the (medical) item, so it only gets applied once
|
||||
//but in multiplayer clients aren't allowed to remove items themselves, so they may be able to trigger this dozens of times
|
||||
@@ -480,7 +483,21 @@ namespace Barotrauma.Items.Components
|
||||
ownerCharacter.HealthPercentage / 100f <= AutoInjectThreshold &&
|
||||
ownerCharacter.HasEquippedItem(item))
|
||||
{
|
||||
foreach (Item item in Inventory.AllItemsMod)
|
||||
if (AutoInject)
|
||||
{
|
||||
Inventory.AllItemsMod.ForEach(i => Inject(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < slotRestrictions.Length; i++)
|
||||
{
|
||||
if (slotRestrictions[i].AutoInject)
|
||||
{
|
||||
Inventory.GetItemsAt(i).ForEachMod(i => Inject(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
void Inject(Item item)
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter, useTarget: ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter, useTarget: ownerCharacter);
|
||||
@@ -632,7 +649,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
IsActive = true;
|
||||
SetContainedActive(false);
|
||||
|
||||
@@ -98,6 +98,20 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can another character select this controller when another character has already selected it?")]
|
||||
public bool AllowSelectingWhenSelectedByOther
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can another character select this controller when a bot has already selected it?")]
|
||||
public bool AllowSelectingWhenSelectedByBot
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool ControlCharacterPose
|
||||
{
|
||||
get { return limbPositions.Count > 0; }
|
||||
@@ -466,8 +480,18 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = false;
|
||||
CancelUsing(user);
|
||||
user = null;
|
||||
return false;
|
||||
}
|
||||
else if (user.IsBot && !activator.IsBot)
|
||||
{
|
||||
if (AllowSelectingWhenSelectedByBot)
|
||||
{
|
||||
CancelUsing(user);
|
||||
user = activator;
|
||||
IsActive = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return AllowSelectingWhenSelectedByOther;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -10,6 +10,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private float force;
|
||||
|
||||
/// <summary>
|
||||
/// Latest signal the set_force connection received, used to set <see cref="targetForce"/> in the Update method.
|
||||
/// We use a separate variable, because otherwise specific item update orders and sending multiple signals to set_force would lead to bugs:
|
||||
/// targetForce could be set to 0, then a power grid might update as if the engine was off and mark the voltage of the grid as 1,
|
||||
/// then another item could set the targetForce to 100 and make it run without power.
|
||||
/// </summary>
|
||||
private float? lastReceivedTargetForce;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of force the engine is aiming for (the actual force may be less than this,
|
||||
/// depending on the amount of power, the condition of the engine or boosts from talents)
|
||||
/// </summary>
|
||||
private float targetForce;
|
||||
|
||||
private float maxForce;
|
||||
@@ -58,7 +70,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float CurrentVolume
|
||||
{
|
||||
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
|
||||
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage, 1.0f))); }
|
||||
}
|
||||
|
||||
public float CurrentBrokenVolume
|
||||
@@ -110,7 +122,10 @@ namespace Barotrauma.Items.Components
|
||||
hasPower = Voltage > MinVoltage;
|
||||
}
|
||||
|
||||
|
||||
if (lastReceivedTargetForce.HasValue)
|
||||
{
|
||||
targetForce = lastReceivedTargetForce.Value;
|
||||
}
|
||||
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, deltaTime * 10.0f);
|
||||
if (Math.Abs(Force) > 1.0f)
|
||||
{
|
||||
@@ -254,7 +269,7 @@ namespace Barotrauma.Items.Components
|
||||
if (float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
|
||||
{
|
||||
controlLockTimer = 0.1f;
|
||||
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
|
||||
lastReceivedTargetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
|
||||
User = signal.sender;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,8 +784,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
float condition1 = MathUtils.IsValid(item1.Condition) ? item1.Condition : 0;
|
||||
float condition2 = MathUtils.IsValid(item2.Condition) ? item2.Condition : 0;
|
||||
//prefer items in worse condition
|
||||
return Math.Sign(item2.Condition - item1.Condition);
|
||||
return Math.Sign(condition2 - condition1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -775,7 +775,6 @@ namespace Barotrauma.Items.Components
|
||||
if (shutDown)
|
||||
{
|
||||
PowerOn = false;
|
||||
AutoTemp = false;
|
||||
TargetFissionRate = 0.0f;
|
||||
TargetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Barotrauma.Items.Components
|
||||
public bool UseDirectionalPing => useDirectionalPing;
|
||||
private bool useDirectionalPing = false;
|
||||
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
|
||||
private bool useMineralScanner;
|
||||
|
||||
private bool aiPingCheckPending;
|
||||
|
||||
@@ -133,6 +132,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool UseMineralScanner { get; set; }
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get { return zoom; }
|
||||
@@ -366,7 +368,7 @@ namespace Barotrauma.Items.Components
|
||||
bool isActive = msg.ReadBoolean();
|
||||
bool directionalPing = useDirectionalPing;
|
||||
float zoomT = zoom, pingDirectionT = 0.0f;
|
||||
bool mineralScanner = useMineralScanner;
|
||||
bool mineralScanner = UseMineralScanner;
|
||||
if (isActive)
|
||||
{
|
||||
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
@@ -391,13 +393,13 @@ namespace Barotrauma.Items.Components
|
||||
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
|
||||
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
|
||||
}
|
||||
useMineralScanner = mineralScanner;
|
||||
UseMineralScanner = mineralScanner;
|
||||
#if CLIENT
|
||||
zoomSlider.BarScroll = zoomT;
|
||||
directionalModeSwitch.Selected = useDirectionalPing;
|
||||
if (mineralScannerSwitch != null)
|
||||
{
|
||||
mineralScannerSwitch.Selected = useMineralScanner;
|
||||
mineralScannerSwitch.Selected = UseMineralScanner;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -418,7 +420,7 @@ namespace Barotrauma.Items.Components
|
||||
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
|
||||
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
|
||||
}
|
||||
msg.WriteBoolean(useMineralScanner);
|
||||
msg.WriteBoolean(UseMineralScanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ namespace Barotrauma.Items.Components
|
||||
if (connectedSubUpdateTimer <= 0.0f)
|
||||
{
|
||||
connectedSubs.Clear();
|
||||
connectedSubs = controlledSub?.GetConnectedSubs();
|
||||
connectedSubs.AddRange(controlledSub.GetConnectedSubs());
|
||||
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 diff = controlledSub.WorldPosition - intersection;
|
||||
//far enough -> ignore
|
||||
|
||||
@@ -435,7 +435,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
if (ic is PowerTransfer && !(ic is RelayComponent)) { continue; }
|
||||
if (ic is PowerTransfer && ic is not RelayComponent) { continue; }
|
||||
ic.ReceiveSignal(signal, recipient);
|
||||
}
|
||||
|
||||
|
||||
@@ -709,7 +709,7 @@ namespace Barotrauma.Items.Components
|
||||
return hits;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
Item.ResetWaterDragCoefficient();
|
||||
if (dropper != null)
|
||||
@@ -717,7 +717,7 @@ namespace Barotrauma.Items.Components
|
||||
DisableProjectileCollisions();
|
||||
Unstick();
|
||||
}
|
||||
base.Drop(dropper);
|
||||
base.Drop(dropper, setTransform);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -939,7 +939,7 @@ namespace Barotrauma.Items.Components
|
||||
Character character = null;
|
||||
if (target.Body.UserData is Submarine submarine && target.UserData is not Barotrauma.Item)
|
||||
{
|
||||
item.Move(-submarine.Position);
|
||||
item.Move(-submarine.Position, ignoreContacts: false);
|
||||
item.Submarine = submarine;
|
||||
item.body.Submarine = submarine;
|
||||
return !Hitscan;
|
||||
|
||||
@@ -339,18 +339,19 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (user != null && targetCharacter != null && !user.AnimController.InWater)
|
||||
bool lerpForces = LerpForces;
|
||||
if (!lerpForces && user != null && targetCharacter != null && !user.AnimController.InWater)
|
||||
{
|
||||
// Prevents rubberbanding horizontally when dragging a corpse.
|
||||
if ((forceDir.X < 0) != (user.AnimController.Dir < 0))
|
||||
{
|
||||
forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f);
|
||||
// Prevents rubberbanding horizontally when dragging a corpse.
|
||||
lerpForces = true;
|
||||
}
|
||||
}
|
||||
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
|
||||
float force = lerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
|
||||
targetBody?.ApplyForce(-forceDir * force);
|
||||
var targetRagdoll = targetCharacter?.AnimController;
|
||||
if (targetRagdoll != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
|
||||
if (targetRagdoll?.Collider != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
|
||||
{
|
||||
targetRagdoll.Collider.ApplyForce(-forceDir * force * 3);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ namespace Barotrauma.Items.Components
|
||||
private readonly Item item;
|
||||
|
||||
public readonly bool IsOutput;
|
||||
|
||||
|
||||
public readonly List<StatusEffect> Effects;
|
||||
|
||||
public readonly List<ushort> LoadedWireIds;
|
||||
public readonly List<(ushort wireId, int? connectionIndex)> LoadedWires;
|
||||
|
||||
//The grid the connection is a part of
|
||||
public GridInfo Grid;
|
||||
@@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components
|
||||
//Priority in which power output will be handled - load is unaffected
|
||||
public PowerPriority Priority = PowerPriority.Default;
|
||||
|
||||
public Signal LastSentSignal { get; private set; }
|
||||
public Signal LastReceivedSignal {get; private set;}
|
||||
|
||||
public bool IsPower
|
||||
{
|
||||
get;
|
||||
@@ -151,16 +154,20 @@ namespace Barotrauma.Items.Components
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
|
||||
LoadedWireIds = new List<ushort>();
|
||||
LoadedWires = new List<(ushort wireId, int? connectionIndex)>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "link":
|
||||
int id = subElement.GetAttributeInt("w", 0);
|
||||
int? i = null;
|
||||
if (subElement.GetAttribute("i") != null)
|
||||
{
|
||||
i = subElement.GetAttributeInt("i", 0);
|
||||
}
|
||||
if (id < 0) { id = 0; }
|
||||
if (LoadedWireIds.Count < MaxWires) { LoadedWireIds.Add(idRemap.GetOffsetId(id)); }
|
||||
|
||||
if (LoadedWires.Count < MaxWires) { LoadedWires.Add((idRemap.GetOffsetId(id), i)); }
|
||||
break;
|
||||
case "statuseffect":
|
||||
Effects ??= new List<StatusEffect>();
|
||||
@@ -288,6 +295,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SendSignal(Signal signal)
|
||||
{
|
||||
LastSentSignal = signal;
|
||||
enumeratingWires = true;
|
||||
foreach (var wire in wires)
|
||||
{
|
||||
@@ -298,6 +306,10 @@ namespace Barotrauma.Items.Components
|
||||
signal.source?.LastSentSignalRecipients.Add(recipient);
|
||||
|
||||
Connection connection = recipient;
|
||||
connection.LastReceivedSignal = signal;
|
||||
#if CLIENT
|
||||
wire.RegisterSignal(signal, source: this);
|
||||
#endif
|
||||
|
||||
object[] obj = new object[] { signal, connection };
|
||||
GameMain.LuaCs.Hook.Call("signalReceived", obj);
|
||||
@@ -355,22 +367,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void InitializeFromLoaded()
|
||||
{
|
||||
if (LoadedWireIds.Count == 0) { return; }
|
||||
if (LoadedWires.Count == 0) { return; }
|
||||
|
||||
for (int i = 0; i < LoadedWireIds.Count; i++)
|
||||
foreach ((ushort wireId, int? connectionIndex) in LoadedWires)
|
||||
{
|
||||
if (!(Entity.FindEntityByID(LoadedWireIds[i]) is Item wireItem)) { continue; }
|
||||
if (Entity.FindEntityByID(wireId) is not Item wireItem) { continue; }
|
||||
|
||||
var wire = wireItem.GetComponent<Wire>();
|
||||
if (wire != null && TryAddLink(wire))
|
||||
{
|
||||
if (wire.Item.body != null) wire.Item.body.Enabled = false;
|
||||
wire.Connect(this, false, false);
|
||||
if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
|
||||
if (connectionIndex.HasValue)
|
||||
{
|
||||
wire.Connect(this, connectionIndex.Value, addNode: false, sendNetworkEvent: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.TryConnect(this, addNode: false, sendNetworkEvent: false);
|
||||
}
|
||||
wire.FixNodeEnds();
|
||||
recipientsDirty = true;
|
||||
}
|
||||
}
|
||||
LoadedWireIds.Clear();
|
||||
LoadedWires.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -381,7 +400,8 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var wire in wires.OrderBy(w => w.Item.ID))
|
||||
{
|
||||
newElement.Add(new XElement("link",
|
||||
new XAttribute("w", wire.Item.ID.ToString())));
|
||||
new XAttribute("w", wire.Item.ID.ToString()),
|
||||
new XAttribute("i", wire.Connections[0] == this ? 0 : 1)));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
|
||||
@@ -295,8 +295,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
|
||||
{
|
||||
Connections[i].LoadedWireIds.Clear();
|
||||
Connections[i].LoadedWireIds.AddRange(loadedConnections[i].LoadedWireIds);
|
||||
Connections[i].LoadedWires.Clear();
|
||||
Connections[i].LoadedWires.AddRange(loadedConnections[i].LoadedWires);
|
||||
}
|
||||
|
||||
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty<ushort>()).ToList();
|
||||
|
||||
@@ -82,8 +82,10 @@ namespace Barotrauma.Items.Components
|
||||
signalOut.SendDuration -= 1;
|
||||
item.SendSignal(new Signal(signalOut.Signal.value, sender: signalOut.Signal.sender, strength: signalOut.Signal.strength), "signal_out");
|
||||
if (signalOut.SendDuration <= 0)
|
||||
{
|
||||
signalQueue.Dequeue();
|
||||
{
|
||||
//check the queue isn't empty again, because sending the signal may empty it
|
||||
//if this component is set to reset when it receives a signal and the signal is routed back to this component
|
||||
signalQueue.TryDequeue(out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -173,6 +173,8 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
lightColor = value;
|
||||
//reset previously received signal to force updating the color if we receive a set_color signal after the color has been modified manually
|
||||
prevColorSignal = string.Empty;
|
||||
#if CLIENT
|
||||
if (Light != null)
|
||||
{
|
||||
@@ -249,6 +251,11 @@ namespace Barotrauma.Items.Components
|
||||
base.OnItemLoaded();
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
turret = item.GetComponent<Turret>();
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.FarseerBody.OnEnabled += CheckIfNeedsUpdate;
|
||||
item.body.FarseerBody.OnDisabled += CheckIfNeedsUpdate;
|
||||
}
|
||||
#if CLIENT
|
||||
Drawable = AlphaBlend && Light.LightSprite != null;
|
||||
if (Screen.Selected.IsEditor)
|
||||
@@ -277,15 +284,24 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
|
||||
if ((item.body == null || !item.body.Enabled) &&
|
||||
powerConsumption <= 0.0f && Parent == null && turret == null &&
|
||||
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
|
||||
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
|
||||
{
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
if (item.body != null && !item.body.Enabled)
|
||||
{
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
}
|
||||
isOn = true;
|
||||
SetLightSourceTransformProjSpecific();
|
||||
base.IsActive = false;
|
||||
isOn = true;
|
||||
#if CLIENT
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
|
||||
@@ -222,10 +222,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Vector2 e1 = edge.Point1 + cell.Translation;
|
||||
Vector2 e2 = edge.Point2 + cell.Translation;
|
||||
if (MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
|
||||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
|
||||
if (MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
|
||||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
|
||||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
|
||||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,9 @@ namespace Barotrauma.Items.Components
|
||||
public Item source;
|
||||
public float power;
|
||||
public float strength;
|
||||
public readonly double CreationTime;
|
||||
|
||||
public double TimeSinceCreated => Timing.TotalTimeUnpaused - CreationTime;
|
||||
|
||||
public Signal(string value, int stepsTaken = 0, Character sender = null,
|
||||
Item source = null, float power = 0.0f, float strength = 1.0f)
|
||||
@@ -18,6 +21,7 @@ namespace Barotrauma.Items.Components
|
||||
this.source = source;
|
||||
this.power = power;
|
||||
this.strength = strength;
|
||||
CreationTime = Timing.TotalTimeUnpaused;
|
||||
}
|
||||
|
||||
internal Signal WithStepsTaken(int stepsTaken)
|
||||
|
||||
@@ -64,9 +64,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private bool linkToChat = false;
|
||||
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat)]
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat, onlyInEditors: false)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
|
||||
"as chat messages in the chatbox of the player holding the item.", alwaysUseInstanceValues: true)]
|
||||
public bool LinkToChat
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
private Vector2 end;
|
||||
|
||||
private readonly float angle;
|
||||
private readonly float length;
|
||||
public readonly float Length;
|
||||
|
||||
public Vector2 Start
|
||||
{
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
|
||||
this.end = end;
|
||||
|
||||
angle = MathUtils.VectorToAngle(end - start);
|
||||
length = Vector2.Distance(start, end);
|
||||
Length = Vector2.Distance(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma.Items.Components
|
||||
private List<Vector2> nodes;
|
||||
private readonly List<WireSection> sections;
|
||||
|
||||
private Connection[] connections;
|
||||
private readonly Connection[] connections;
|
||||
|
||||
private bool canPlaceNode;
|
||||
private Vector2 newNodePos;
|
||||
@@ -81,6 +81,8 @@ namespace Barotrauma.Items.Components
|
||||
get { return connections; }
|
||||
}
|
||||
|
||||
public float Length { get; private set; }
|
||||
|
||||
[Serialize(5000.0f, IsPropertySaveable.No, description: "The maximum distance the wire can extend (in pixels).")]
|
||||
public float MaxLength
|
||||
{
|
||||
@@ -162,14 +164,50 @@ namespace Barotrauma.Items.Components
|
||||
SetConnectedDirty();
|
||||
}
|
||||
|
||||
public bool Connect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
|
||||
/// <summary>
|
||||
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
||||
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
||||
/// </summary>
|
||||
|
||||
public bool TryConnect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
|
||||
{
|
||||
if (connections[0] == null)
|
||||
{
|
||||
return Connect(newConnection, 0, addNode, sendNetworkEvent);
|
||||
}
|
||||
else if (connections[1] == null)
|
||||
{
|
||||
return Connect(newConnection, 1, addNode, sendNetworkEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
||||
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
||||
/// </summary>
|
||||
/// <param name="connectionIndex">Which end of the wire to add the connection to? 0 or 1.
|
||||
/// Normally doesn't make a difference, but matters if we're copying/loading a wire,
|
||||
/// in which case the 1st node should be located at the same item as the 1st connection.</param>
|
||||
/// <returns></returns>
|
||||
public bool Connect(Connection newConnection, int connectionIndex, bool addNode = true, bool sendNetworkEvent = false)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (connections[i] == newConnection) { return false; }
|
||||
}
|
||||
|
||||
if (!connections.Any(c => c == null)) { return false; }
|
||||
if (connectionIndex < 0 || connectionIndex > 1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: {connectionIndex} is not a valid index.");
|
||||
return false;
|
||||
}
|
||||
if (connections[connectionIndex] != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: a wire is already connected to the index {connectionIndex}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
@@ -183,70 +221,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
newConnection.ConnectionPanel.DisconnectedWires.Remove(this);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
connections[connectionIndex] = newConnection;
|
||||
FixNodeEnds();
|
||||
|
||||
if (addNode)
|
||||
{
|
||||
if (connections[i] != null) { continue; }
|
||||
|
||||
connections[i] = newConnection;
|
||||
FixNodeEnds();
|
||||
|
||||
if (!addNode) { break; }
|
||||
|
||||
Submarine refSub = newConnection.Item.Submarine;
|
||||
if (refSub == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
|
||||
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
|
||||
{
|
||||
connections[i] = null;
|
||||
continue;
|
||||
}
|
||||
refSub = attachTarget?.Submarine;
|
||||
}
|
||||
|
||||
Vector2 nodePos = refSub == null ?
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
|
||||
|
||||
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
|
||||
int newNodeIndex = 0;
|
||||
if (nodes.Count > 1)
|
||||
{
|
||||
if (connections[0] != null && connections[0] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (connections[1] != null && connections[1] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
if (newNodeIndex == 0 && nodes.Count > 1)
|
||||
{
|
||||
nodes.Insert(0, nodePos);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(nodePos);
|
||||
}
|
||||
|
||||
break;
|
||||
AddNode(newConnection, connectionIndex);
|
||||
}
|
||||
|
||||
SetConnectedDirty();
|
||||
@@ -258,7 +238,7 @@ namespace Barotrauma.Items.Components
|
||||
if (ic == this) { continue; }
|
||||
ic.Drop(null);
|
||||
}
|
||||
if (item.Container != null) { item.Container.RemoveContained(this.item); }
|
||||
item.Container?.RemoveContained(item);
|
||||
if (item.body != null) { item.body.Enabled = false; }
|
||||
|
||||
IsActive = false;
|
||||
@@ -286,6 +266,63 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddNode(Connection newConnection, int selectedIndex)
|
||||
{
|
||||
Submarine refSub = newConnection.Item.Submarine;
|
||||
if (refSub == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
|
||||
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
|
||||
{
|
||||
connections[selectedIndex] = null;
|
||||
return;
|
||||
}
|
||||
refSub = attachTarget?.Submarine;
|
||||
}
|
||||
|
||||
Vector2 nodePos = refSub == null ?
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { return; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { return; }
|
||||
|
||||
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
|
||||
int newNodeIndex = 0;
|
||||
if (nodes.Count > 1)
|
||||
{
|
||||
if (connections[0] != null && connections[0] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (connections[1] != null && connections[1] != newConnection)
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
||||
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
|
||||
{
|
||||
newNodeIndex = nodes.Count;
|
||||
}
|
||||
}
|
||||
|
||||
if (newNodeIndex == 0 && nodes.Count > 1)
|
||||
{
|
||||
nodes.Insert(0, nodePos);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(nodePos);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
if (shouldClearConnections) { ClearConnections(character); }
|
||||
@@ -298,7 +335,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
if (shouldClearConnections) { ClearConnections(dropper); }
|
||||
IsActive = false;
|
||||
@@ -528,6 +565,7 @@ namespace Barotrauma.Items.Components
|
||||
sections.Add(new WireSection(nodes[i], nodes[i + 1]));
|
||||
}
|
||||
Drawable = IsActive || sections.Count > 0;
|
||||
Length = sections.Count > 0 ? sections.Sum(s => s.Length) : 0;
|
||||
CalculateExtents();
|
||||
}
|
||||
|
||||
|
||||
@@ -845,8 +845,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public readonly Item Projectile;
|
||||
|
||||
public EventData(Item projectile)
|
||||
public EventData(Item projectile, Turret turret)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(projectile != null, $"Tried to create Turret {nameof(EventData)} with no projectile.");
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Turret.EventData:entitynull"+ turret.Item.Prefab.Identifier,
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
$"Turret \"{turret.Item.Prefab.Identifier}\" tried to create {nameof(EventData)} with no projectile.");
|
||||
Projectile = projectile;
|
||||
}
|
||||
}
|
||||
@@ -918,7 +923,7 @@ namespace Barotrauma.Items.Components
|
||||
projectile.Container?.RemoveContained(projectile);
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this, new EventData(projectile));
|
||||
item.CreateServerEvent(this, new EventData(projectile, this));
|
||||
#endif
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
|
||||
@@ -1314,7 +1319,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
// Don't aim monsters that are inside any submarine.
|
||||
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
// Don't shoot at captured enemies.
|
||||
if (enemy.LockHands) { continue; }
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDistance) { continue; }
|
||||
if (dist < shootDistance * shootDistance)
|
||||
@@ -1413,7 +1420,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
// The closest point can't be targeted -> get a point directly in front of the turret
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (MathUtils.GetLineIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
|
||||
if (MathUtils.GetLineSegmentIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
|
||||
{
|
||||
closestPoint = intersection;
|
||||
if (!CheckTurretAngle(closestPoint)) { continue; }
|
||||
@@ -1889,22 +1896,27 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (TryExtractEventData(extraData, out EventData eventData))
|
||||
{
|
||||
msg.WriteUInt16(eventData.Projectile.ID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
msg.WriteUInt16(eventData.Projectile?.ID ?? Entity.NullEntityID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(wrapAngle(rotation), minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.WriteUInt16((ushort)0);
|
||||
float wrappedTargetRotation = targetRotation;
|
||||
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(wrapAngle(targetRotation), minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
|
||||
float wrapAngle(float angle)
|
||||
{
|
||||
float wrappedAngle = angle;
|
||||
while (wrappedAngle < minRotation && MathUtils.IsValid(wrappedAngle))
|
||||
{
|
||||
wrappedTargetRotation += MathHelper.TwoPi;
|
||||
wrappedAngle += MathHelper.TwoPi;
|
||||
}
|
||||
while (wrappedTargetRotation > maxRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
while (wrappedAngle > maxRotation && MathUtils.IsValid(wrappedAngle))
|
||||
{
|
||||
wrappedTargetRotation -= MathHelper.TwoPi;
|
||||
wrappedAngle -= MathHelper.TwoPi;
|
||||
}
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(wrappedTargetRotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
return wrappedAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,11 +482,11 @@ namespace Barotrauma.Items.Components
|
||||
character.OnWearablesChanged();
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
Character previousPicker = picker;
|
||||
Unequip(picker);
|
||||
base.Drop(dropper);
|
||||
base.Drop(dropper, setTransform);
|
||||
previousPicker?.OnWearablesChanged();
|
||||
picker = null;
|
||||
IsActive = false;
|
||||
|
||||
@@ -580,8 +580,8 @@ namespace Barotrauma
|
||||
|
||||
if (removeItem)
|
||||
{
|
||||
item.Drop(user);
|
||||
if (item.ParentInventory != null) { item.ParentInventory.RemoveItem(item); }
|
||||
item.Drop(user, setTransform: false);
|
||||
item.ParentInventory?.RemoveItem(item);
|
||||
}
|
||||
|
||||
slots[i].Add(item);
|
||||
@@ -845,13 +845,31 @@ namespace Barotrauma
|
||||
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
TryPutAndForce(existingItems, this, index);
|
||||
TryPutAndForce(stackedItems, otherInventory, otherIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
TryPutAndForce(stackedItems, otherInventory, otherIndex);
|
||||
TryPutAndForce(existingItems, this, index);
|
||||
}
|
||||
|
||||
void TryPutAndForce(IEnumerable<Item> items, Inventory inventory, int slotIndex)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!inventory.TryPutItem(item, slotIndex, false, false, user, createNetworkEvent, ignoreCondition: true) &&
|
||||
!inventory.GetItemsAt(slotIndex).Contains(item))
|
||||
{
|
||||
inventory.ForceToSlot(item, slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (createNetworkEvent)
|
||||
{
|
||||
CreateNetworkEvent();
|
||||
otherInventory.CreateNetworkEvent();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
|
||||
@@ -44,6 +44,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Item> CleanableItems => cleanableItems;
|
||||
|
||||
private static readonly List<Item> sonarVisibleItems = new List<Item>();
|
||||
|
||||
/// <summary>
|
||||
/// Items whose <see cref="ItemPrefab.SonarSize"/> is larger than 0
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Item> SonarVisibleItems => sonarVisibleItems;
|
||||
|
||||
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
||||
|
||||
public static bool ShowLinks = true;
|
||||
@@ -127,7 +134,8 @@ namespace Barotrauma
|
||||
private float condition;
|
||||
|
||||
private bool inWater;
|
||||
private readonly bool hasWaterStatusEffects;
|
||||
private readonly bool hasInWaterStatusEffects;
|
||||
private readonly bool hasNotInWaterStatusEffects;
|
||||
|
||||
private Inventory parentInventory;
|
||||
private readonly ItemInventory ownInventory;
|
||||
@@ -207,6 +215,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Item RootContainer { get; private set; }
|
||||
|
||||
private bool inWaterProofContainer;
|
||||
|
||||
private Item container;
|
||||
public Item Container
|
||||
{
|
||||
@@ -218,6 +230,8 @@ namespace Barotrauma
|
||||
container = value;
|
||||
CheckCleanable();
|
||||
SetActiveSprite();
|
||||
|
||||
RefreshRootContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,14 +423,15 @@ namespace Barotrauma
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.Pickable)]
|
||||
public Color InventoryIconColor
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")]
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.Pickable)]
|
||||
public Color ContainerColor
|
||||
{
|
||||
get;
|
||||
@@ -700,14 +715,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool FireProof
|
||||
{
|
||||
get { return Prefab.FireProof; }
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private bool waterProof;
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool WaterProof
|
||||
{
|
||||
get { return Prefab.WaterProof; }
|
||||
get { return waterProof; }
|
||||
private set
|
||||
{
|
||||
if (waterProof == value) { return; }
|
||||
waterProof = value;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshInWaterProofContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseInHealthInterface
|
||||
@@ -736,7 +763,7 @@ namespace Barotrauma
|
||||
{
|
||||
//if the item has an active physics body, inWater is updated in the Update method
|
||||
if (body != null && body.Enabled) { return inWater; }
|
||||
if (hasWaterStatusEffects) { return inWater; }
|
||||
if (hasInWaterStatusEffects) { return inWater; }
|
||||
|
||||
//if not, we'll just have to check
|
||||
return IsInWater();
|
||||
@@ -1068,7 +1095,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
hasWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.InWater] || hasStatusEffectsOfType[(int)ActionType.NotInWater];
|
||||
hasInWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.InWater];
|
||||
hasNotInWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.NotInWater];
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
@@ -1124,6 +1152,7 @@ namespace Barotrauma
|
||||
ItemList.Add(this);
|
||||
if (Prefab.IsDangerous) { dangerousItems.Add(this); }
|
||||
if (Repairables.Any()) { repairableItems.Add(this); }
|
||||
if (Prefab.SonarSize > 0.0f) { sonarVisibleItems.Add(this); }
|
||||
CheckCleanable();
|
||||
|
||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||
@@ -1419,7 +1448,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -1427,7 +1456,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
base.Move(amount);
|
||||
base.Move(amount, ignoreContacts);
|
||||
|
||||
if (ItemList != null && body != null)
|
||||
{
|
||||
@@ -1511,17 +1540,51 @@ namespace Barotrauma
|
||||
return CurrentHull;
|
||||
}
|
||||
|
||||
public Item GetRootContainer()
|
||||
private void RefreshRootContainer()
|
||||
{
|
||||
if (Container == null) { return null; }
|
||||
Item rootContainer = Container;
|
||||
while (rootContainer.Container != null)
|
||||
Item newRootContainer = null;
|
||||
inWaterProofContainer = false;
|
||||
if (Container != null)
|
||||
{
|
||||
rootContainer = rootContainer.Container;
|
||||
Item rootContainer = Container;
|
||||
inWaterProofContainer |= Container.WaterProof;
|
||||
|
||||
while (rootContainer.Container != null)
|
||||
{
|
||||
rootContainer = rootContainer.Container;
|
||||
inWaterProofContainer |= rootContainer.WaterProof;
|
||||
}
|
||||
newRootContainer = rootContainer;
|
||||
}
|
||||
if (newRootContainer != RootContainer)
|
||||
{
|
||||
RootContainer = newRootContainer;
|
||||
isActive = true;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshRootContainer();
|
||||
}
|
||||
}
|
||||
return rootContainer;
|
||||
}
|
||||
|
||||
|
||||
private void RefreshInWaterProofContainer()
|
||||
{
|
||||
inWaterProofContainer = false;
|
||||
if (container == null) { return; }
|
||||
if (container.WaterProof || container.inWaterProofContainer)
|
||||
{
|
||||
inWaterProofContainer = true;
|
||||
}
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshInWaterProofContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by the AI to check whether they can (in principle) and are allowed (in practice) to interact with an object or not.
|
||||
/// Unlike CanInteractWith(), this method doesn't check the distance, the triggers, or anything like that.
|
||||
/// </summary>
|
||||
public bool HasAccess(Character character)
|
||||
{
|
||||
if (character.IsBot && IgnoreByAI(character)) { return false; }
|
||||
@@ -1529,6 +1592,7 @@ namespace Barotrauma
|
||||
var itemContainer = GetComponent<ItemContainer>();
|
||||
if (itemContainer != null && !itemContainer.HasAccess(character)) { return false; }
|
||||
if (Container != null && !Container.HasAccess(character)) { return false; }
|
||||
if (GetComponent<Pickable>() is { CanBePicked: false }) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1538,9 +1602,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (ParentInventory == null) { return this; }
|
||||
if (ParentInventory.Owner is Character) { return ParentInventory.Owner; }
|
||||
var rootContainer = GetRootContainer();
|
||||
if (rootContainer?.ParentInventory?.Owner is Character) { return rootContainer.ParentInventory.Owner; }
|
||||
return rootContainer ?? this;
|
||||
if (RootContainer?.ParentInventory?.Owner is Character) { return RootContainer.ParentInventory.Owner; }
|
||||
return RootContainer ?? this;
|
||||
}
|
||||
|
||||
public Inventory FindParentInventory(Func<Inventory, bool> predicate)
|
||||
@@ -1784,8 +1847,23 @@ namespace Barotrauma
|
||||
|
||||
bool wasInFullCondition = IsFullCondition;
|
||||
|
||||
float diff = value - condition;
|
||||
if (GetComponent<Door>() is Door door && door.IsStuck && diff < 0)
|
||||
{
|
||||
float dmg = -diff;
|
||||
// When the door is fully welded shut, reduce the welded state instead of the condition.
|
||||
float prevStuck = door.Stuck;
|
||||
door.Stuck -= dmg;
|
||||
if (door.IsStuck) { return; }
|
||||
// Reduce the damage by the amount we just adjusted the welded state by.
|
||||
float damageReduction = dmg - prevStuck;
|
||||
if (damageReduction < 0) { return; }
|
||||
value -= damageReduction;
|
||||
}
|
||||
|
||||
condition = MathHelper.Clamp(value, 0.0f, MaxCondition);
|
||||
if (MathUtils.NearlyEqual(prevCondition, condition, epsilon: 0.000001f)) { return; }
|
||||
|
||||
if (MathUtils.NearlyEqual(prevCondition, value, epsilon: 0.000001f)) { return; }
|
||||
|
||||
RecalculateConditionValues();
|
||||
|
||||
@@ -2008,7 +2086,7 @@ namespace Barotrauma
|
||||
|
||||
if (Removed) { return; }
|
||||
|
||||
bool needsWaterCheck = hasWaterStatusEffects;
|
||||
bool needsWaterCheck = hasInWaterStatusEffects || hasNotInWaterStatusEffects;
|
||||
if (body != null && body.Enabled)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(body.FarseerBody.FixtureList != null);
|
||||
@@ -2037,7 +2115,7 @@ namespace Barotrauma
|
||||
if (needsWaterCheck)
|
||||
{
|
||||
bool wasInWater = inWater;
|
||||
inWater = IsInWater() && !WaterProof;
|
||||
inWater = !inWaterProofContainer && IsInWater() && !WaterProof;
|
||||
if (inWater)
|
||||
{
|
||||
//the item has gone through the surface of the water
|
||||
@@ -2050,36 +2128,29 @@ namespace Barotrauma
|
||||
body.LinearVelocity *= 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
Item container = this.Container;
|
||||
while (container != null)
|
||||
{
|
||||
if (container.WaterProof)
|
||||
{
|
||||
inWater = false;
|
||||
break;
|
||||
}
|
||||
container = container.Container;
|
||||
}
|
||||
}
|
||||
if (hasWaterStatusEffects && condition > 0.0f)
|
||||
if ((hasInWaterStatusEffects || hasNotInWaterStatusEffects) && condition > 0.0f)
|
||||
{
|
||||
ApplyStatusEffects(inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (updateableComponents.Count == 0 &&
|
||||
(aiTarget == null || !aiTarget.NeedsUpdate) &&
|
||||
!hasStatusEffectsOfType[(int)ActionType.Always] &&
|
||||
(body == null || !body.Enabled))
|
||||
if (inWaterProofContainer && !hasNotInWaterStatusEffects)
|
||||
{
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
needsWaterCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsWaterCheck &&
|
||||
updateableComponents.Count == 0 &&
|
||||
(aiTarget == null || !aiTarget.NeedsUpdate) &&
|
||||
!hasStatusEffectsOfType[(int)ActionType.Always] &&
|
||||
(body == null || !body.Enabled))
|
||||
{
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void Splash();
|
||||
@@ -2877,7 +2948,7 @@ namespace Barotrauma
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var abilityItem = new AbilityApplyTreatment(user, character, this);
|
||||
var abilityItem = new AbilityApplyTreatment(user, character, this, targetLimb);
|
||||
user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem);
|
||||
}
|
||||
|
||||
@@ -2938,7 +3009,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ItemComponent ic in components) { ic.Drop(dropper); }
|
||||
foreach (ItemComponent ic in components) { ic.Drop(dropper, setTransform); }
|
||||
|
||||
if (Container != null)
|
||||
{
|
||||
@@ -3577,7 +3648,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("healthmultiplier", HealthMultiplier.ToString("G", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
Item rootContainer = GetRootContainer() ?? this;
|
||||
Item rootContainer = RootContainer ?? this;
|
||||
System.Diagnostics.Debug.Assert(Submarine != null || rootContainer.ParentInventory?.Owner is Character);
|
||||
|
||||
Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition;
|
||||
@@ -3758,6 +3829,7 @@ namespace Barotrauma
|
||||
ItemList.Remove(this);
|
||||
dangerousItems.Remove(this);
|
||||
repairableItems.Remove(this);
|
||||
sonarVisibleItems.Remove(this);
|
||||
cleanableItems.Remove(this);
|
||||
}
|
||||
|
||||
@@ -3781,12 +3853,14 @@ namespace Barotrauma
|
||||
public Character Character { get; set; }
|
||||
public Character User { get; set; }
|
||||
public Item Item { get; set; }
|
||||
public Limb TargetLimb { get; set; }
|
||||
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item)
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item, Limb limb)
|
||||
{
|
||||
Character = target;
|
||||
User = user;
|
||||
Item = item;
|
||||
TargetLimb = limb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +88,15 @@ namespace Barotrauma
|
||||
|
||||
public abstract ItemPrefab FirstMatchingPrefab { get; }
|
||||
|
||||
public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition)
|
||||
public LocalizedString OverrideDescription { get; }
|
||||
|
||||
public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription)
|
||||
{
|
||||
Amount = amount;
|
||||
MinCondition = minCondition;
|
||||
MaxCondition = maxCondition;
|
||||
UseCondition = useCondition;
|
||||
OverrideDescription = overrideDescription;
|
||||
}
|
||||
public readonly int Amount;
|
||||
public readonly float MinCondition;
|
||||
@@ -129,12 +132,14 @@ namespace Barotrauma
|
||||
|
||||
public override ItemPrefab FirstMatchingPrefab => ItemPrefab;
|
||||
|
||||
|
||||
public override bool MatchesItem(Item item)
|
||||
{
|
||||
return item?.Prefab.Identifier == ItemPrefabIdentifier;
|
||||
}
|
||||
|
||||
public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
|
||||
public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription) :
|
||||
base(amount, minCondition, maxCondition, useCondition, overrideDescription)
|
||||
{
|
||||
ItemPrefabIdentifier = itemPrefab;
|
||||
using MD5 md5 = MD5.Create();
|
||||
@@ -163,7 +168,8 @@ namespace Barotrauma
|
||||
return item.HasTag(Tag);
|
||||
}
|
||||
|
||||
public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
|
||||
public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription)
|
||||
: base(amount, minCondition, maxCondition, useCondition, overrideDescription)
|
||||
{
|
||||
Tag = tag;
|
||||
using MD5 md5 = MD5.Create();
|
||||
@@ -260,6 +266,12 @@ namespace Barotrauma
|
||||
bool useCondition = subElement.GetAttributeBool("usecondition", true);
|
||||
int amount = subElement.GetAttributeInt("count", subElement.GetAttributeInt("amount", 1));
|
||||
|
||||
LocalizedString description = string.Empty;
|
||||
if (subElement.GetAttributeString("description", string.Empty) is string texTag && !texTag.IsNullOrEmpty())
|
||||
{
|
||||
description = TextManager.Get(texTag);
|
||||
}
|
||||
|
||||
if (requiredItemIdentifier != Identifier.Empty)
|
||||
{
|
||||
var existing = requiredItems.FindIndex(r =>
|
||||
@@ -272,7 +284,7 @@ namespace Barotrauma
|
||||
amount += requiredItems[existing].Amount;
|
||||
requiredItems.RemoveAt(existing);
|
||||
}
|
||||
requiredItems.Add(new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition));
|
||||
requiredItems.Add(new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition, description));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -286,7 +298,7 @@ namespace Barotrauma
|
||||
amount += requiredItems[existing].Amount;
|
||||
requiredItems.RemoveAt(existing);
|
||||
}
|
||||
requiredItems.Add(new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition));
|
||||
requiredItems.Add(new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition, description));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -669,8 +681,19 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float OffsetOnSelected { get; private set; }
|
||||
|
||||
private float health;
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.No)]
|
||||
public float Health { get; private set; }
|
||||
public float Health
|
||||
{
|
||||
get { return health; }
|
||||
private set
|
||||
{
|
||||
//don't allow health values higher than this, because they lead to various issues:
|
||||
//e.g. integer overflows when we're casting to int to display a health value, value being set to float.Infinity if it's high enough
|
||||
health = Math.Min(value, 1000000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AllowSellingWhenBroken { get; private set; }
|
||||
@@ -702,12 +725,6 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DamagedByMonsters { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool FireProof { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool WaterProof { get; private set; }
|
||||
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ImpactTolerance
|
||||
@@ -813,10 +830,13 @@ namespace Barotrauma
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with slow turrets, like railguns? Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making.")]
|
||||
public float AISlowTurretPriority { get; private set; }
|
||||
|
||||
|
||||
[Serialize(float.PositiveInfinity, IsPropertySaveable.No, description: "The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
|
||||
public float AITurretTargetingMaxDistance { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, taking items from this container is never considered stealing.")]
|
||||
public bool AllowStealingContainedItems { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
@@ -1437,6 +1457,22 @@ namespace Barotrauma
|
||||
"Specify the amount in the variant to fix this.");
|
||||
}
|
||||
}
|
||||
if (originalElement?.Name.ToIdentifier() == "Deconstruct" &&
|
||||
variantElement?.Name.ToIdentifier() == "Deconstruct")
|
||||
{
|
||||
if (originalElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
|
||||
variantElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
|
||||
$"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly.");
|
||||
}
|
||||
if (variantElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
|
||||
originalElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item \"{parent.Identifier}\": " +
|
||||
$"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,11 @@ namespace Barotrauma
|
||||
/// The item this relation is defined in must be inside a specific kind of container.
|
||||
/// Can for example by used to make an item do something when it's inside some other type of item.
|
||||
/// </summary>
|
||||
Container
|
||||
Container,
|
||||
/// <summary>
|
||||
/// Signifies an error (type could not be parsed)
|
||||
/// </summary>
|
||||
Invalid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,9 +64,9 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Identifier> ExcludedIdentifiers { get; private set; }
|
||||
|
||||
private RelationType type;
|
||||
private readonly RelationType type;
|
||||
|
||||
public List<StatusEffect> statusEffects;
|
||||
public List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
/// <summary>
|
||||
/// Only valid for the RequiredItems of an ItemComponent. A message displayed if the required item isn't found (e.g. a notification about lack of ammo or fuel).
|
||||
@@ -198,8 +202,121 @@ namespace Barotrauma
|
||||
{
|
||||
this.Identifiers = identifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
|
||||
this.ExcludedIdentifiers = excludedIdentifiers.Select(id => id.Value.Trim().ToIdentifier()).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public RelatedItem(ContentXElement element, string parentDebugName)
|
||||
{
|
||||
Identifier[] identifiers;
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
//backwards compatibility + a console warning
|
||||
DebugConsole.ThrowError($"Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.");
|
||||
Identifier[] itemNames = element.GetAttributeIdentifierArray("name", Array.Empty<Identifier>());
|
||||
//attempt to convert to identifiers and tags
|
||||
List<Identifier> convertedIdentifiers = new List<Identifier>();
|
||||
foreach (Identifier itemName in itemNames)
|
||||
{
|
||||
var matchingItem = ItemPrefab.Prefabs.Find(me => me.Name == itemName.Value);
|
||||
if (matchingItem != null)
|
||||
{
|
||||
convertedIdentifiers.Add(matchingItem.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
//no matching item found, this must be a tag
|
||||
convertedIdentifiers.Add(itemName);
|
||||
}
|
||||
}
|
||||
identifiers = convertedIdentifiers.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("items", null) ?? element.GetAttributeIdentifierArray("item", null);
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("identifiers", null) ?? element.GetAttributeIdentifierArray("tags", null);
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("identifier", null) ?? element.GetAttributeIdentifierArray("tag", Array.Empty<Identifier>());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Identifiers = identifiers.ToImmutableHashSet();
|
||||
|
||||
Identifier[] excludedIdentifiers = element.GetAttributeIdentifierArray("excludeditems", null) ?? element.GetAttributeIdentifierArray("excludeditem", null);
|
||||
if (excludedIdentifiers == null)
|
||||
{
|
||||
excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifiers", null) ?? element.GetAttributeIdentifierArray("excludedtags", null);
|
||||
if (excludedIdentifiers == null)
|
||||
{
|
||||
excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifier", null) ?? element.GetAttributeIdentifierArray("excludedtag", Array.Empty<Identifier>());
|
||||
}
|
||||
}
|
||||
this.ExcludedIdentifiers = excludedIdentifiers.ToImmutableHashSet();
|
||||
|
||||
ExcludeBroken = element.GetAttributeBool("excludebroken", true);
|
||||
RequireEmpty = element.GetAttributeBool("requireempty", false);
|
||||
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false);
|
||||
AllowVariants = element.GetAttributeBool("allowvariants", true);
|
||||
Rotation = element.GetAttributeFloat("rotation", 0f);
|
||||
SetActive = element.GetAttributeBool("setactive", false);
|
||||
|
||||
if (element.GetAttribute(nameof(Hide)) != null)
|
||||
{
|
||||
Hide = element.GetAttributeBool(nameof(Hide), false);
|
||||
}
|
||||
if (element.GetAttribute(nameof(ItemPos)) != null)
|
||||
{
|
||||
ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
|
||||
}
|
||||
string typeStr = element.GetAttributeString("type", "");
|
||||
if (string.IsNullOrEmpty(typeStr))
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "containable":
|
||||
typeStr = "Contained";
|
||||
break;
|
||||
case "suitablefertilizer":
|
||||
case "suitableseed":
|
||||
typeStr = "None";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Enum.TryParse(typeStr, true, out type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type.");
|
||||
type = RelationType.Invalid;
|
||||
}
|
||||
|
||||
MsgTag = element.GetAttributeIdentifier("msg", Identifier.Empty);
|
||||
LocalizedString msg = TextManager.Get(MsgTag);
|
||||
if (!msg.Loaded)
|
||||
{
|
||||
Msg = MsgTag.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
|
||||
{
|
||||
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType));
|
||||
}
|
||||
Msg = msg;
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
|
||||
}
|
||||
|
||||
IsOptional = element.GetAttributeBool("optional", false);
|
||||
IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
|
||||
MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
|
||||
TargetSlot = element.GetAttributeInt("targetslot", -1);
|
||||
|
||||
statusEffects = new List<StatusEffect>();
|
||||
}
|
||||
|
||||
public bool CheckRequirements(Character character, Item parentItem)
|
||||
@@ -301,120 +418,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public static RelatedItem Load(ContentXElement element, bool returnEmpty, string parentDebugName)
|
||||
{
|
||||
Identifier[] identifiers;
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
//backwards compatibility + a console warning
|
||||
DebugConsole.ThrowError("Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.");
|
||||
Identifier[] itemNames = element.GetAttributeIdentifierArray("name", Array.Empty<Identifier>());
|
||||
//attempt to convert to identifiers and tags
|
||||
List<Identifier> convertedIdentifiers = new List<Identifier>();
|
||||
foreach (Identifier itemName in itemNames)
|
||||
{
|
||||
var matchingItem = ItemPrefab.Prefabs.Find(me => me.Name == itemName.Value);
|
||||
if (matchingItem != null)
|
||||
{
|
||||
convertedIdentifiers.Add(matchingItem.Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
//no matching item found, this must be a tag
|
||||
convertedIdentifiers.Add(itemName);
|
||||
}
|
||||
}
|
||||
identifiers = convertedIdentifiers.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("items", null) ?? element.GetAttributeIdentifierArray("item", null);
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("identifiers", null) ?? element.GetAttributeIdentifierArray("tags", null);
|
||||
if (identifiers == null)
|
||||
{
|
||||
identifiers = element.GetAttributeIdentifierArray("identifier", null) ?? element.GetAttributeIdentifierArray("tag", Array.Empty<Identifier>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Identifier[] excludedIdentifiers = element.GetAttributeIdentifierArray("excludeditems", null) ?? element.GetAttributeIdentifierArray("excludeditem", null);
|
||||
if (excludedIdentifiers == null)
|
||||
{
|
||||
excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifiers", null) ?? element.GetAttributeIdentifierArray("excludedtags", null);
|
||||
if (excludedIdentifiers == null)
|
||||
{
|
||||
excludedIdentifiers = element.GetAttributeIdentifierArray("excludedidentifier", null) ?? element.GetAttributeIdentifierArray("excludedtag", Array.Empty<Identifier>());
|
||||
}
|
||||
}
|
||||
|
||||
if (identifiers.Length == 0 && excludedIdentifiers.Length == 0 && !returnEmpty) { return null; }
|
||||
|
||||
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
|
||||
{
|
||||
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
|
||||
RequireEmpty = element.GetAttributeBool("requireempty", false),
|
||||
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
|
||||
AllowVariants = element.GetAttributeBool("allowvariants", true),
|
||||
Rotation = element.GetAttributeFloat("rotation", 0f),
|
||||
SetActive = element.GetAttributeBool("setactive", false)
|
||||
};
|
||||
if (element.GetAttribute(nameof(Hide)) != null)
|
||||
{
|
||||
ri.Hide = element.GetAttributeBool(nameof(Hide), false);
|
||||
}
|
||||
if (element.GetAttribute(nameof(ItemPos)) != null)
|
||||
{
|
||||
ri.ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
|
||||
}
|
||||
string typeStr = element.GetAttributeString("type", "");
|
||||
if (string.IsNullOrEmpty(typeStr))
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "containable":
|
||||
typeStr = "Contained";
|
||||
break;
|
||||
case "suitablefertilizer":
|
||||
case "suitableseed":
|
||||
typeStr = "None";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Enum.TryParse(typeStr, true, out ri.type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in RelatedItem config (" + parentDebugName + ") - \"" + typeStr + "\" is not a valid relation type.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ri.MsgTag = element.GetAttributeIdentifier("msg", Identifier.Empty);
|
||||
LocalizedString msg = TextManager.Get(ri.MsgTag);
|
||||
if (!msg.Loaded)
|
||||
{
|
||||
ri.Msg = ri.MsgTag.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
|
||||
{
|
||||
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameSettings.CurrentConfig.KeyMap.KeyBindText(inputType));
|
||||
}
|
||||
ri.Msg = msg;
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
ri.statusEffects.Add(StatusEffect.Load(subElement, parentDebugName));
|
||||
}
|
||||
|
||||
ri.IsOptional = element.GetAttributeBool("optional", false);
|
||||
ri.IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
|
||||
ri.MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
|
||||
ri.TargetSlot = element.GetAttributeInt("targetslot", -1);
|
||||
|
||||
{
|
||||
RelatedItem ri = new RelatedItem(element, parentDebugName);
|
||||
if (ri.Type == RelationType.Invalid) { return null; }
|
||||
if (ri.Identifiers.None() && ri.ExcludedIdentifiers.None() && !returnEmpty) { return null; }
|
||||
return ri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,9 @@ namespace Barotrauma
|
||||
var lightComponent = item.GetComponent<LightComponent>();
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor, 10.0f);
|
||||
//multiply by 10 to make the effect more noticeable
|
||||
//(a strength of 1 is already enough to kill power and shut down the lights, but we want weaker EMPs to make the lights flicker noticeably)
|
||||
lightComponent.TemporaryFlickerTimer = Math.Min(EmpStrength * distFactor * 10.0f, 10.0f);
|
||||
}
|
||||
|
||||
//discharge batteries
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Barotrauma
|
||||
d.ForceRefreshFadeTimer(Math.Min(d.FadeTimer, d.FadeInTime));
|
||||
}
|
||||
|
||||
UpdateProjSpecific(growModifier);
|
||||
UpdateProjSpecific(growModifier, deltaTime);
|
||||
|
||||
|
||||
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
@@ -273,7 +273,7 @@ namespace Barotrauma
|
||||
position.X -= GrowSpeed * growModifier * 0.5f * deltaTime;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float growModifier);
|
||||
partial void UpdateProjSpecific(float growModifier, float deltaTime);
|
||||
|
||||
private void OnChangeHull(Vector2 pos, Hull particleHull)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool IsDiagonal { get; }
|
||||
|
||||
public readonly float GlowEffectT;
|
||||
|
||||
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
|
||||
private float open;
|
||||
|
||||
@@ -194,6 +196,8 @@ namespace Barotrauma
|
||||
GapList.Add(this);
|
||||
InsertToList();
|
||||
|
||||
GlowEffectT = Rand.Range(0.0f, 1.0f);
|
||||
|
||||
float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2;
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize,
|
||||
BodyType.Static,
|
||||
@@ -216,7 +220,7 @@ namespace Barotrauma
|
||||
return new Gap(rect, IsHorizontal, Submarine);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -224,7 +228,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
base.Move(amount);
|
||||
base.Move(amount, ignoreContacts);
|
||||
|
||||
if (!DisableHullRechecks) { FindHulls(); }
|
||||
}
|
||||
@@ -337,8 +341,28 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private int updateCount;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
int updateInterval = 4;
|
||||
float flowMagnitude = flowForce.LengthSquared();
|
||||
if (flowMagnitude < 1.0f)
|
||||
{
|
||||
//very sparse updates if there's practically no water moving
|
||||
updateInterval = 8;
|
||||
}
|
||||
else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
|
||||
{
|
||||
//frequent updates if water is moving between hulls
|
||||
updateInterval = 1;
|
||||
}
|
||||
|
||||
updateCount++;
|
||||
if (updateCount < updateInterval) { return; }
|
||||
deltaTime *= updateCount;
|
||||
updateCount = 0;
|
||||
|
||||
flowForce = Vector2.Zero;
|
||||
outsideColliderRaycastTimer -= deltaTime;
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ namespace Barotrauma
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -851,7 +851,21 @@ namespace Barotrauma
|
||||
{
|
||||
decal.Update(deltaTime);
|
||||
}
|
||||
decals.RemoveAll(d => d.FadeTimer >= d.LifeTime || d.BaseAlpha <= 0.001f);
|
||||
//clients don't remove decals unless the server says so
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
for (int i = decals.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var decal = decals[i];
|
||||
if (decal.FadeTimer >= decal.LifeTime || decal.BaseAlpha <= 0.001f)
|
||||
{
|
||||
decals.RemoveAt(i);
|
||||
#if SERVER
|
||||
decalUpdatePending = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aiTarget != null)
|
||||
{
|
||||
@@ -1509,9 +1523,8 @@ namespace Barotrauma
|
||||
public void CleanSection(BackgroundSection section, float cleanVal, bool updateRequired)
|
||||
{
|
||||
bool decalsCleaned = false;
|
||||
for (int i = 0; i < decals.Count; i++)
|
||||
foreach (Decal decal in decals)
|
||||
{
|
||||
Decal decal = decals[i];
|
||||
if (decal.AffectsSection(section))
|
||||
{
|
||||
decal.Clean(cleanVal);
|
||||
@@ -1672,5 +1685,9 @@ namespace Barotrauma
|
||||
return element;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Name ?? "unnamed"})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Barotrauma
|
||||
{
|
||||
me.Move(position);
|
||||
me.Submarine = sub;
|
||||
if (!(me is Item item)) { continue; }
|
||||
if (me is not Item item) { continue; }
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
//Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition;
|
||||
if (wire != null)
|
||||
|
||||
@@ -296,9 +296,9 @@ namespace Barotrauma
|
||||
Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3;
|
||||
foreach (GraphEdge nearbyEdge in nearbyCell.Edges)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
|
||||
!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
|
||||
if (!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) &&
|
||||
!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) &&
|
||||
!MathUtils.LineSegmentsIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2))
|
||||
{
|
||||
isInside = true;
|
||||
break;
|
||||
|
||||
@@ -1454,7 +1454,7 @@ namespace Barotrauma
|
||||
if (node2.X <= pathNodes.Last().X) { continue; }
|
||||
if (MathUtils.NearlyEqual(node1.X, pathNodes.Last().X)) { continue; }
|
||||
if (Math.Abs(node1.Y - nodePos.Y) > tunnel.MinWidth && Math.Abs(node2.Y - nodePos.Y) > tunnel.MinWidth &&
|
||||
!MathUtils.LinesIntersect(node1.ToVector2(), node2.ToVector2(), pathNodes.Last().ToVector2(), nodePos.ToVector2()))
|
||||
!MathUtils.LineSegmentsIntersect(node1.ToVector2(), node2.ToVector2(), pathNodes.Last().ToVector2(), nodePos.ToVector2()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1550,7 +1550,7 @@ namespace Barotrauma
|
||||
foreach (GraphEdge edge in tunnel.Cells[i].Edges)
|
||||
{
|
||||
if (edge.AdjacentCell(tunnel.Cells[i])?.CellType == CellType.Solid &&
|
||||
MathUtils.LinesIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2))
|
||||
MathUtils.LineSegmentsIntersect(newWaypoint.WorldPosition, prevWayPoint.WorldPosition, edge.Point1, edge.Point2))
|
||||
{
|
||||
solidCellBetween = true;
|
||||
break;
|
||||
@@ -2801,7 +2801,7 @@ namespace Barotrauma
|
||||
if (Vector2.DistanceSquared(c.EdgeCenter, validLocation.EdgeCenter) > (intervalRange.X * intervalRange.X)) { return true; }
|
||||
// If there is a line from a previous path point to one of its existing cluster locations
|
||||
// which intersects with the line from this path point to the new possible cluster location
|
||||
if (MathUtils.LinesIntersect(anotherPathPoint.Position, c.EdgeCenter, pathPoint.Position, validLocation.EdgeCenter)) { return true; }
|
||||
if (MathUtils.LineSegmentsIntersect(anotherPathPoint.Position, c.EdgeCenter, pathPoint.Position, validLocation.EdgeCenter)) { return true; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2975,13 +2975,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <param name="rotation">Used by clients to set the rotation for the resources</param>
|
||||
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, out float rotation, IEnumerable<Cave> targetCaves = null)
|
||||
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, IEnumerable<Cave> targetCaves = null)
|
||||
{
|
||||
var allValidLocations = GetAllValidClusterLocations();
|
||||
var placedResources = new List<Item>();
|
||||
rotation = 0.0f;
|
||||
|
||||
if (allValidLocations.None()) { return placedResources; } // TODO: WHAT?!
|
||||
// if there are no valid locations, don't place anything
|
||||
if (allValidLocations.None()) { return placedResources; }
|
||||
|
||||
// Make sure not to pick a spot that already has other level resources
|
||||
for (int i = allValidLocations.Count - 1; i >= 0; i--)
|
||||
@@ -3077,7 +3077,6 @@ namespace Barotrauma
|
||||
}
|
||||
PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources);
|
||||
Vector2 edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
|
||||
rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
|
||||
return placedResources;
|
||||
|
||||
static bool IsOnMainPath(ClusterLocation location) => location.Edge.NextToMainPath;
|
||||
@@ -3182,13 +3181,11 @@ namespace Barotrauma
|
||||
Vector2 edgeNormal = location.Edge.GetNormal(location.Cell);
|
||||
float moveAmount = (item.body == null ? item.Rect.Height / 2 : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent() * 0.7f));
|
||||
moveAmount += (item.GetComponent<LevelResource>()?.RandomOffsetFromWall ?? 0.0f) * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient);
|
||||
item.Move(edgeNormal * moveAmount, ignoreContacts: true);
|
||||
item.Move(edgeNormal * moveAmount);
|
||||
if (item.GetComponent<Holdable>() is Holdable h)
|
||||
{
|
||||
h.AttachToWall();
|
||||
#if CLIENT
|
||||
item.Rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
|
||||
#endif
|
||||
}
|
||||
else if (item.body != null)
|
||||
{
|
||||
@@ -3509,7 +3506,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (GraphEdge e in cell.Edges)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(closestPathCell.Center, pos.ToVector2(), e.Point1, e.Point2)) { continue; }
|
||||
if (!MathUtils.LineSegmentsIntersect(closestPathCell.Center, pos.ToVector2(), e.Point1, e.Point2)) { continue; }
|
||||
|
||||
cell.CellType = CellType.Removed;
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
|
||||
@@ -294,40 +294,37 @@ namespace Barotrauma
|
||||
throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}");
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
SetStartLocation(location);
|
||||
}
|
||||
FindStartLocation(l => l.Type.Identifier == "outpost");
|
||||
//if no outpost was found (using a mod that replaces the outpost location type?), find any type of outpost
|
||||
if (CurrentLocation == null)
|
||||
{
|
||||
FindStartLocation(l => l.Type.HasOutpost);
|
||||
}
|
||||
|
||||
void FindStartLocation(Func<Location, bool> predicate)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.HasOutpost) { continue; }
|
||||
SetStartLocation(location);
|
||||
if (!predicate(location)) { continue; }
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetStartLocation(Location location)
|
||||
|
||||
StartLocation.SecondaryFaction = null;
|
||||
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
|
||||
if (startOutpostFaction != null)
|
||||
{
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
StartLocation.Faction = startOutpostFaction;
|
||||
foreach (var connection in StartLocation.Connections)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
StartLocation.SecondaryFaction = null;
|
||||
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
|
||||
if (startOutpostFaction != null)
|
||||
var otherLocation = connection.OtherLocation(StartLocation);
|
||||
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
StartLocation.Faction = startOutpostFaction;
|
||||
foreach (var connection in StartLocation.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(StartLocation);
|
||||
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
otherLocation.Faction = startOutpostFaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
otherLocation.Faction = startOutpostFaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
public List<ushort> unresolvedLinkedToID;
|
||||
|
||||
public static int MapEntityUpdateInterval = 1;
|
||||
public static int GapUpdateInterval = 4;
|
||||
public static int GapUpdateInterval = 1;
|
||||
public static int PoweredUpdateInterval = 1;
|
||||
private static int mapEntityUpdateTick;
|
||||
|
||||
@@ -317,7 +317,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public virtual void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
rect.X += (int)amount.X;
|
||||
rect.Y += (int)amount.Y;
|
||||
@@ -454,7 +454,7 @@ namespace Barotrauma
|
||||
List<Wire> orphanedWires = new List<Wire>();
|
||||
for (int i = 0; i < clones.Count; i++)
|
||||
{
|
||||
if (!(clones[i] is Item cloneItem)) { continue; }
|
||||
if (clones[i] is not Item cloneItem) { continue; }
|
||||
|
||||
var door = cloneItem.GetComponent<Door>();
|
||||
door?.RefreshLinkedGap();
|
||||
@@ -509,10 +509,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
(clones[itemIndex] as Item).Connections[connectionIndex].TryAddLink(cloneWire);
|
||||
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false);
|
||||
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], n, addNode: false);
|
||||
}
|
||||
|
||||
if ((cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) && cloneItem.GetComponent<DockingPort>() == null)
|
||||
if (originalWire.Connections.Any(c => c != null) &&
|
||||
(cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) &&
|
||||
cloneItem.GetComponent<DockingPort>() == null)
|
||||
{
|
||||
if (!clones.Any(c => (c as Item)?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ?? false))
|
||||
{
|
||||
|
||||
@@ -786,7 +786,7 @@ namespace Barotrauma
|
||||
//check if the connection overlaps with this module's connection
|
||||
if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
|
||||
!gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
|
||||
MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
|
||||
MathUtils.LineSegmentsIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1105,8 +1105,8 @@ namespace Barotrauma
|
||||
DebugConsole.AddWarning($"Failed to connect junction boxes between outpost modules (not enough free connections in module \"{module.PreviousModule.Info.Name}\")");
|
||||
continue;
|
||||
}
|
||||
wire.Connect(thisJunctionBox.Connections[i], addNode: false);
|
||||
wire.Connect(previousJunctionBox.Connections[i], addNode: false);
|
||||
wire.TryConnect(thisJunctionBox.Connections[i], addNode: false);
|
||||
wire.TryConnect(previousJunctionBox.Connections[i], addNode: false);
|
||||
wire.SetNodes(new List<Vector2>());
|
||||
}
|
||||
}
|
||||
@@ -1374,11 +1374,6 @@ namespace Barotrauma
|
||||
endWaypoint.linkedTo.Add(prevWayPoint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
startWaypoint.linkedTo.Add(endWaypoint);
|
||||
endWaypoint.linkedTo.Add(startWaypoint);
|
||||
}
|
||||
|
||||
WayPoint closestWaypoint = null;
|
||||
float closestDistSqr = 30.0f * 30.0f;
|
||||
@@ -1595,10 +1590,11 @@ namespace Barotrauma
|
||||
{
|
||||
var startWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == bottomGap);
|
||||
var endWaypoint = WayPoint.WayPointList.Find(wp => wp.ConnectedGap == topGap);
|
||||
float margin = 100;
|
||||
if (startWaypoint != null && endWaypoint != null)
|
||||
{
|
||||
WayPoint prevWaypoint = startWaypoint;
|
||||
for (float y = startWaypoint.Position.Y + WayPoint.LadderWaypointInterval; y <= endWaypoint.Position.Y - WayPoint.LadderWaypointInterval; y += WayPoint.LadderWaypointInterval)
|
||||
for (float y = bottomGap.Position.Y + margin; y <= topGap.Position.Y - margin; y += WayPoint.LadderWaypointInterval)
|
||||
{
|
||||
var wayPoint = new WayPoint(new Vector2(startWaypoint.Position.X, y), SpawnType.Path, ladder.Item.Submarine)
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Barotrauma
|
||||
private static Explosion explosionOnBroken;
|
||||
|
||||
#if DEBUG
|
||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
|
||||
#else
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
#endif
|
||||
@@ -104,9 +104,11 @@ namespace Barotrauma
|
||||
|
||||
public List<Body> Bodies { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
|
||||
public bool CastShadow
|
||||
{
|
||||
get { return Prefab.CastShadow; }
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsHorizontal { get; private set; }
|
||||
@@ -118,7 +120,7 @@ namespace Barotrauma
|
||||
|
||||
private float? maxHealth;
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0)]
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody, MinValueFloat = 0)]
|
||||
public float MaxHealth
|
||||
{
|
||||
get => maxHealth ?? Prefab.Health;
|
||||
@@ -189,14 +191,14 @@ namespace Barotrauma
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody), Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool UseDropShadow
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody), Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the drop shadow relative to the structure. If set to zero, the shadow is positioned automatically so that it points towards the sub's center of mass.")]
|
||||
public Vector2 DropShadowOffset
|
||||
{
|
||||
get;
|
||||
@@ -367,7 +369,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -375,7 +377,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
base.Move(amount);
|
||||
base.Move(amount, ignoreContacts);
|
||||
|
||||
for (int i = 0; i < Sections.Length; i++)
|
||||
{
|
||||
@@ -440,13 +442,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float width = BodyWidth > 0.0f ? BodyWidth : rect.Width;
|
||||
float height = BodyHeight > 0.0f ? BodyHeight : rect.Height;
|
||||
if (BodyWidth > 0.0f && BodyHeight > 0.0f)
|
||||
{
|
||||
IsHorizontal = BodyWidth > BodyHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsHorizontal = (rect.Width > rect.Height);
|
||||
IsHorizontal = width > height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,29 +455,28 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific();
|
||||
|
||||
if (!HiddenInGame)
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
if (element?.GetAttribute(nameof(CastShadow)) == null)
|
||||
{
|
||||
if (Prefab.Body)
|
||||
{
|
||||
Bodies = new List<Body>();
|
||||
WallList.Add(this);
|
||||
|
||||
CreateSections();
|
||||
UpdateSections();
|
||||
}
|
||||
else
|
||||
{
|
||||
Sections = new WallSection[1];
|
||||
Sections[0] = new WallSection(rect, this);
|
||||
|
||||
if (StairDirection != Direction.None)
|
||||
{
|
||||
CreateStairBodies();
|
||||
}
|
||||
}
|
||||
CastShadow = Prefab.CastShadow;
|
||||
}
|
||||
|
||||
SerializableProperties = element != null ? SerializableProperty.DeserializeProperties(this, element) : SerializableProperty.GetProperties(this);
|
||||
if (Prefab.Body)
|
||||
{
|
||||
Bodies = new List<Body>();
|
||||
WallList.Add(this);
|
||||
CreateSections();
|
||||
UpdateSections();
|
||||
}
|
||||
else if (StairDirection != Direction.None)
|
||||
{
|
||||
CreateStairBodies();
|
||||
}
|
||||
if (Sections == null)
|
||||
{
|
||||
Sections = new WallSection[1];
|
||||
Sections[0] = new WallSection(rect, this);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
foreach (var subElement in sp.ConfigElement.Elements())
|
||||
@@ -1546,16 +1545,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("flippedx", false)) { s.FlipX(false); }
|
||||
if (element.GetAttributeBool("flippedy", false)) { s.FlipY(false); }
|
||||
if (element.GetAttributeBool(nameof(FlippedX), false)) { s.FlipX(false); }
|
||||
if (element.GetAttributeBool(nameof(FlippedY), false)) { s.FlipY(false); }
|
||||
|
||||
//structures with a body drop a shadow by default
|
||||
if (element.GetAttribute("usedropshadow") == null)
|
||||
if (element.GetAttribute(nameof(UseDropShadow)) == null)
|
||||
{
|
||||
s.UseDropShadow = prefab.Body;
|
||||
}
|
||||
|
||||
if (element.GetAttribute("noaitarget") == null)
|
||||
if (element.GetAttribute(nameof(NoAITarget)) == null)
|
||||
{
|
||||
s.NoAITarget = prefab.NoAITarget;
|
||||
}
|
||||
@@ -1604,12 +1603,12 @@ namespace Barotrauma
|
||||
(int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," +
|
||||
width + "," + height));
|
||||
|
||||
if (FlippedX) element.Add(new XAttribute("flippedx", true));
|
||||
if (FlippedY) element.Add(new XAttribute("flippedy", true));
|
||||
if (FlippedX) { element.Add(new XAttribute("flippedx", true)); }
|
||||
if (FlippedY) { element.Add(new XAttribute("flippedy", true)); }
|
||||
|
||||
for (int i = 0; i < Sections.Length; i++)
|
||||
{
|
||||
if (Sections[i].damage == 0.0f) continue;
|
||||
if (Sections[i].damage == 0.0f) { continue; }
|
||||
var sectionElement =
|
||||
new XElement("section",
|
||||
new XAttribute("i", i),
|
||||
@@ -1619,6 +1618,11 @@ namespace Barotrauma
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element);
|
||||
|
||||
if (CastShadow == Prefab.CastShadow)
|
||||
{
|
||||
element.GetAttribute(nameof(CastShadow))?.Remove();
|
||||
}
|
||||
|
||||
foreach (var upgrade in Upgrades)
|
||||
{
|
||||
upgrade.Save(element);
|
||||
|
||||
@@ -7,6 +7,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
@@ -427,17 +428,20 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
|
||||
/// </summary>
|
||||
public Rectangle GetDockedBorders()
|
||||
public Rectangle GetDockedBorders(bool allowDifferentTeam = true)
|
||||
{
|
||||
checkSubmarineBorders.Clear();
|
||||
return GetDockedBordersRecursive();
|
||||
return GetDockedBordersRecursive(allowDifferentTeam);
|
||||
}
|
||||
|
||||
private Rectangle GetDockedBordersRecursive()
|
||||
private Rectangle GetDockedBordersRecursive(bool allowDifferentTeam)
|
||||
{
|
||||
Rectangle dockedBorders = Borders;
|
||||
checkSubmarineBorders.Add(this);
|
||||
var connectedSubs = DockedTo.Where(s => !checkSubmarineBorders.Contains(s) && !s.Info.IsOutpost);
|
||||
var connectedSubs = DockedTo.Where(s =>
|
||||
!checkSubmarineBorders.Contains(s) &&
|
||||
!s.Info.IsOutpost &&
|
||||
(allowDifferentTeam || s.TeamID == TeamID));
|
||||
foreach (Submarine dockedSub in connectedSubs)
|
||||
{
|
||||
//use docking ports instead of world position to determine
|
||||
@@ -446,7 +450,7 @@ namespace Barotrauma
|
||||
Vector2? expectedLocation = CalculateDockOffset(this, dockedSub);
|
||||
if (expectedLocation == null) { continue; }
|
||||
|
||||
Rectangle dockedSubBorders = dockedSub.GetDockedBordersRecursive();
|
||||
Rectangle dockedSubBorders = dockedSub.GetDockedBordersRecursive(allowDifferentTeam);
|
||||
dockedSubBorders.Location += MathUtils.ToPoint(expectedLocation.Value);
|
||||
|
||||
dockedBorders.Y = -dockedBorders.Y;
|
||||
@@ -458,23 +462,23 @@ namespace Barotrauma
|
||||
return dockedBorders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Don't use this directly, because the list is updated only when GetConnectedSubs() is called. The method is called so frequently that we don't want to create new list here.
|
||||
/// </summary>
|
||||
private readonly List<Submarine> connectedSubs = new List<Submarine>(2);
|
||||
private readonly HashSet<Submarine> connectedSubs;
|
||||
/// <summary>
|
||||
/// Returns a list of all submarines that are connected to this one via docking ports, including this sub.
|
||||
/// </summary>
|
||||
public List<Submarine> GetConnectedSubs()
|
||||
public IEnumerable<Submarine> GetConnectedSubs()
|
||||
{
|
||||
return connectedSubs;
|
||||
}
|
||||
|
||||
public void RefreshConnectedSubs()
|
||||
{
|
||||
connectedSubs.Clear();
|
||||
connectedSubs.Add(this);
|
||||
GetConnectedSubsRecursive(connectedSubs);
|
||||
|
||||
return connectedSubs;
|
||||
}
|
||||
|
||||
private void GetConnectedSubsRecursive(List<Submarine> subs)
|
||||
private void GetConnectedSubsRecursive(HashSet<Submarine> subs)
|
||||
{
|
||||
foreach (Submarine dockedSub in DockedTo)
|
||||
{
|
||||
@@ -1067,6 +1071,8 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
RefreshConnectedSubs();
|
||||
|
||||
if (Info.IsWreck)
|
||||
{
|
||||
WreckAI?.Update(deltaTime);
|
||||
@@ -1393,6 +1399,13 @@ namespace Barotrauma
|
||||
|
||||
public Submarine(SubmarineInfo info, bool showErrorMessages = true, Func<Submarine, List<MapEntity>> loadEntities = null, IdRemap linkedRemap = null) : base(null, Entity.NullEntityID)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
connectedSubs = new HashSet<Submarine>(2)
|
||||
{
|
||||
this
|
||||
};
|
||||
|
||||
upgradeEventIdentifier = new Identifier($"Submarine{ID}");
|
||||
Loading = true;
|
||||
GameMain.World.Enabled = false;
|
||||
@@ -1489,8 +1502,14 @@ namespace Barotrauma
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = info.OutpostGenerationParams != null;
|
||||
item.AllowStealing = info.OutpostGenerationParams?.AllowStealing ?? true;
|
||||
item.AllowStealing = true;
|
||||
if (info.OutpostGenerationParams != null)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing =
|
||||
info.OutpostGenerationParams.AllowStealing ||
|
||||
item.RootContainer is { Prefab: { AllowStealingContainedItems: true } };
|
||||
}
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
@@ -1569,6 +1588,7 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
GameMain.LightManager.OnMapLoaded();
|
||||
Lights.ConvexHull.RecalculateAll(this);
|
||||
#endif
|
||||
//if the sub was made using an older version,
|
||||
//halve the brightness of the lights to make them look (almost) right on the new lighting formula
|
||||
@@ -1596,6 +1616,10 @@ namespace Barotrauma
|
||||
Loading = false;
|
||||
GameMain.World.Enabled = true;
|
||||
}
|
||||
sw.Stop();
|
||||
string debugMsg = $"Loading {Info?.Name ?? "unknown"} took {sw.ElapsedMilliseconds} ms.";
|
||||
DebugConsole.Log(debugMsg);
|
||||
System.Diagnostics.Debug.WriteLine(debugMsg);
|
||||
}
|
||||
|
||||
protected override ushort DetermineID(ushort id, Submarine submarine)
|
||||
@@ -1745,8 +1769,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
if (e.Submarine != this) { continue; }
|
||||
var rootContainer = item.GetRootContainer();
|
||||
if (rootContainer != null && rootContainer.Submarine != this) { continue; }
|
||||
if (item.RootContainer != null && item.RootContainer.Submarine != this) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user