Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git into Regalis11-master

This commit is contained in:
Evil Factory
2021-12-15 14:45:31 -03:00
388 changed files with 12646 additions and 8136 deletions
@@ -130,6 +130,7 @@ namespace Barotrauma
}
public readonly HashSet<LatchOntoAI> Latchers = new HashSet<LatchOntoAI>();
public readonly HashSet<Projectile> AttachedProjectiles = new HashSet<Projectile>();
protected readonly Dictionary<string, ActiveTeamChange> activeTeamChanges = new Dictionary<string, ActiveTeamChange>();
protected ActiveTeamChange currentTeamChange;
@@ -265,7 +266,7 @@ namespace Barotrauma
private CharacterPrefab prefab;
public readonly CharacterParams Params;
public string SpeciesName => Params.SpeciesName;
public string SpeciesName => Params?.SpeciesName ?? "null";
public string Group => Params.Group;
public bool IsHumanoid => Params.Humanoid;
public bool IsHusk => Params.Husk;
@@ -611,8 +612,7 @@ namespace Barotrauma
get
{
if (IsUnconscious) { return true; }
if (IsDead) { return true; }
return CharacterHealth.Afflictions.Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength);
return CharacterHealth.GetAllAfflictions().Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength);
}
}
@@ -621,6 +621,11 @@ namespace Barotrauma
get { return CharacterHealth.IsUnconscious; }
}
public bool IsArrested
{
get { return IsHuman && HasEquippedItem("handlocker"); }
}
public bool IsPet
{
get { return AIController is EnemyAIController enemyController && enemyController.PetBehavior != null; }
@@ -642,6 +647,11 @@ namespace Barotrauma
set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public float HullOxygenPercentage
{
get { return CurrentHull?.OxygenPercentage ?? 0.0f; }
}
public bool UseHullOxygen { get; set; } = true;
public float Stun
@@ -690,12 +700,12 @@ namespace Barotrauma
{
get
{
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) return 100.0f;
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) { return 100.0f; }
return speechImpediment;
}
set
{
if (value < speechImpediment) return;
if (value < speechImpediment) { return; }
speechImpedimentSet = true;
speechImpediment = MathHelper.Clamp(value, 0.0f, 100.0f);
}
@@ -807,7 +817,7 @@ namespace Barotrauma
{
if (!canBeDragged) { return false; }
if (Removed || !AnimController.Draggable) { return false; }
return IsKnockedDown || LockHands || IsPet;
return IsKnockedDown || LockHands || IsPet || CanInventoryBeAccessed;
}
set { canBeDragged = value; }
}
@@ -825,7 +835,7 @@ namespace Barotrauma
}
else
{
return IsKnockedDown || LockHands;
return IsKnockedDown || LockHands || IsBot && TeamID != CharacterTeamType.FriendlyNPC;
}
}
set { canInventoryBeAccessed = value; }
@@ -854,7 +864,7 @@ namespace Barotrauma
{
if (!accessRemovedCharacterErrorShown)
{
string errorMsg = "Attempted to access a potentially removed character. Character: " + Name + ", id: " + ID + ", removed: " + Removed + ".";
string errorMsg = "Attempted to access a potentially removed character. Character: [name], id: " + ID + ", removed: " + Removed + ".";
if (AnimController == null)
{
errorMsg += " AnimController == null";
@@ -864,11 +874,11 @@ namespace Barotrauma
errorMsg += " AnimController.Collider == null";
}
errorMsg += '\n' + Environment.StackTrace.CleanupStackTrace();
DebugConsole.NewMessage(errorMsg, Color.Red);
DebugConsole.NewMessage(errorMsg.Replace("[name]", Name), Color.Red);
GameAnalyticsManager.AddErrorEventOnce(
"Character.SimPosition:AccessRemoved",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg.Replace("[name]", SpeciesName) + "\n" + Environment.StackTrace.CleanupStackTrace());
accessRemovedCharacterErrorShown = true;
}
return Vector2.Zero;
@@ -1362,7 +1372,11 @@ namespace Barotrauma
public override string ToString()
{
#if DEBUG
return (info != null && !string.IsNullOrWhiteSpace(info.Name)) ? info.Name : SpeciesName;
#else
return SpeciesName;
#endif
}
public void GiveJobItems(WayPoint spawnPoint = null)
@@ -1988,7 +2002,7 @@ namespace Barotrauma
public bool CanSeeTarget(ISpatialEntity target, ISpatialEntity seeingEntity = null)
{
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this as ISpatialEntity : GetSeeingLimb() as ISpatialEntity;
seeingEntity ??= AnimController.SimplePhysicsEnabled ? this : GetSeeingLimb() as ISpatialEntity;
if (seeingEntity == null) { return false; }
ISpatialEntity sourceEntity = seeingEntity ;
// TODO: Could we just use the method below? If not, let's refactor it so that we can.
@@ -2023,16 +2037,11 @@ namespace Barotrauma
{
return wall != target;
}
else if (body.UserData is Item item && item != target)
else if (body.UserData is Item item)
{
// TODO: The door collider should be disabled, so this check is probably unnecessary.
var door = item.GetComponent<Door>();
if (door != null)
{
return !door.CanBeTraversed;
}
return item != target;
}
return false;
return true;
}
}
@@ -2041,18 +2050,23 @@ namespace Barotrauma
/// </summary>
public bool IsFacing(Vector2 targetWorldPos) => AnimController.Dir > 0 && targetWorldPos.X > WorldPosition.X || AnimController.Dir < 0 && targetWorldPos.X < WorldPosition.X;
public bool HasItem(Item item, bool requireEquipped = false, InvSlotType? slotType = null) => requireEquipped ? HasEquippedItem(item) : item.IsOwnedBy(this);
public bool HasItem(Item item, bool requireEquipped = false, InvSlotType? slotType = null) => requireEquipped ? HasEquippedItem(item, slotType) : item.IsOwnedBy(this);
public bool HasEquippedItem(Item item, InvSlotType? slotType = null)
public bool HasEquippedItem(Item item, InvSlotType? slotType = null, Func<InvSlotType, bool> predicate = null)
{
if (Inventory == null) { return false; }
for (int i = 0; i < Inventory.Capacity; i++)
{
InvSlotType slot = Inventory.SlotTypes[i];
if (predicate != null)
{
if (!predicate(slot)) { continue; }
}
if (slotType.HasValue)
{
if (!slotType.Value.HasFlag(Inventory.SlotTypes[i])) { continue; }
if (!slotType.Value.HasFlag(slot)) { continue; }
}
else if (Inventory.SlotTypes[i] == InvSlotType.Any)
else if (slot == InvSlotType.Any)
{
continue;
}
@@ -2239,6 +2253,10 @@ namespace Barotrauma
{
return wire.Connections[0] == null;
}
if (SelectedConstruction?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)
{
return wire.Connections[0] == null && wire.Connections[1] == null;
}
}
if (checkLinked && item.DisplaySideBySideWhenLinked)
@@ -2472,7 +2490,10 @@ namespace Barotrauma
{
minDist = dist;
nearbyLadder = ladder;
if (isControlled) ladder.Item.IsHighlighted = true;
if (isControlled)
{
ladder.Item.IsHighlighted = true;
}
break;
}
}
@@ -2480,7 +2501,10 @@ namespace Barotrauma
if (nearbyLadder != null && climbInput)
{
if (nearbyLadder.Select(this)) SelectedConstruction = nearbyLadder.Item;
if (nearbyLadder.Select(this))
{
SelectedConstruction = nearbyLadder.Item;
}
}
}
@@ -2502,14 +2526,20 @@ namespace Barotrauma
{
DeselectCharacter();
#if CLIENT
if (Controlled == this) CharacterHealth.OpenHealthWindow = null;
if (Controlled == this)
{
CharacterHealth.OpenHealthWindow = null;
}
#endif
}
else
{
SelectCharacter(FocusedCharacter);
#if CLIENT
if (Controlled == this) CharacterHealth.OpenHealthWindow = FocusedCharacter.CharacterHealth;
if (Controlled == this)
{
CharacterHealth.OpenHealthWindow = FocusedCharacter.CharacterHealth;
}
#endif
}
}
@@ -2644,7 +2674,7 @@ namespace Barotrauma
ApplyStatusEffects(ActionType.Always, deltaTime);
PreviousHull = CurrentHull;
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull, true);
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull, useWorldCoordinates: true);
speechBubbleTimer = Math.Max(0.0f, speechBubbleTimer - deltaTime);
@@ -2714,7 +2744,7 @@ namespace Barotrauma
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Implode();
if (IsDead) { return; }
if (IsDead) { return; }
}
}
}
@@ -2723,7 +2753,9 @@ namespace Barotrauma
PressureTimer = 0.0f;
}
}
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && WorldPosition.Y < CharacterHealth.CrushDepth)
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) &&
PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) &&
WorldPosition.Y < CharacterHealth.CrushDepth)
{
//implode if below crush depth, and either outside or in a high-pressure hull
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
@@ -2874,6 +2906,18 @@ namespace Barotrauma
}
}
public float GetDamageDoneByAttacker(Character otherCharacter)
{
if (otherCharacter == null) { return 0; }
float dmg = 0;
Attacker attacker = LastAttackers.LastOrDefault(a => a.Character == otherCharacter);
if (attacker != null)
{
dmg = attacker.Damage;
}
return dmg;
}
private void UpdateAttackers(float deltaTime)
{
//slowly forget about damage done by attackers
@@ -3128,47 +3172,53 @@ namespace Barotrauma
//set the character order only if the character is close enough to hear the message
if (!force && orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
if (order != null && order.OrderGiver != orderGiver)
if (order != null)
{
order.OrderGiver = orderGiver;
}
switch (order?.Category)
{
case OrderCategory.Operate when order?.TargetEntity != null:
// If there's another character operating the same device, make them dismiss themself
foreach (var character in CharacterList)
if (order.OrderGiver != orderGiver)
{
order.OrderGiver = orderGiver;
}
if (order.AutoDismiss)
{
switch (order.Category)
{
if (character == this) { continue; }
if (character.TeamID != TeamID) { continue; }
if (!(character.AIController is HumanAIController)) { continue; }
if (!HumanAIController.IsActive(character)) { continue; }
foreach (var currentOrder in character.CurrentOrders)
{
if (currentOrder.Order == null) { continue; }
if (currentOrder.Order.Category != OrderCategory.Operate) { continue; }
if (currentOrder.Order.Identifier != order.Identifier) { continue; }
if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; }
character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force);
case OrderCategory.Operate when order.TargetEntity != null:
// If there's another character operating the same device, make them dismiss themself
foreach (var character in CharacterList)
{
if (character == this) { continue; }
if (character.TeamID != TeamID) { continue; }
if (!(character.AIController is HumanAIController)) { continue; }
if (!HumanAIController.IsActive(character)) { continue; }
foreach (var currentOrder in character.CurrentOrders)
{
if (currentOrder.Order == null) { continue; }
if (currentOrder.Order.Category != OrderCategory.Operate) { continue; }
if (currentOrder.Order.Identifier != order.Identifier) { continue; }
if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; }
if (!currentOrder.Order.AutoDismiss) { continue; }
character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force);
break;
}
}
break;
case OrderCategory.Movement:
// If there character has another movement order, dismiss that order
OrderInfo? orderToReplace = null;
foreach (var currentOrder in CurrentOrders)
{
if (currentOrder.Order == null) { continue; }
if (currentOrder.Order.Category != OrderCategory.Movement) { continue; }
orderToReplace = currentOrder;
break;
}
if (orderToReplace.HasValue && orderToReplace.Value.Order.AutoDismiss)
{
SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force);
}
break;
}
}
break;
case OrderCategory.Movement:
// If there character has another movement order, dismiss that order
OrderInfo? orderToReplace = null;
foreach (var currentOrder in CurrentOrders)
{
if (currentOrder.Order == null) { continue; }
if (currentOrder.Order.Category != OrderCategory.Movement) { continue; }
orderToReplace = currentOrder;
break;
}
if (orderToReplace.HasValue)
{
SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force);
}
break;
}
}
// Prevent adding duplicate orders
@@ -3324,6 +3374,8 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(message)) { return; }
if (SpeechImpediment >= 100.0f) { return; }
if (prevAiChatMessages.ContainsKey(identifier) &&
prevAiChatMessages[identifier] < Timing.TotalTime - minDurationBetweenSimilar)
{
@@ -3341,13 +3393,13 @@ namespace Barotrauma
private void UpdateAIChatMessages(float deltaTime)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
List<AIChatMessage> sentMessages = new List<AIChatMessage>();
foreach (AIChatMessage message in aiChatMessageQueue)
{
message.SendDelay -= deltaTime;
if (message.SendDelay > 0.0f) continue;
if (message.SendDelay > 0.0f) { continue; }
if (message.MessageType == null)
{
@@ -3424,9 +3476,9 @@ namespace Barotrauma
{
if (Removed)
{
string errorMsg = "Tried to apply an attack to a removed character (" + Name + ").\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Character.ApplyAttack:RemovedCharacter", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
string errorMsg = "Tried to apply an attack to a removed character ([name]).\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg.Replace("[name]", Name));
GameAnalyticsManager.AddErrorEventOnce("Character.ApplyAttack:RemovedCharacter", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName));
return new AttackResult();
}
@@ -3615,7 +3667,7 @@ namespace Barotrauma
// {
// string errorMsg = $"Character {Name} received damage from outside the sub while inside (attacker: {attacker.Name})";
// GameAnalyticsManager.AddErrorEventOnce("Character.DamageLimb:DamageFromOutside" + Name + attacker.Name,
// GameAnalyticsSDK.Net.EGAErrorSeverity.Warning,
// GameAnalyticsManager.ErrorSeverity.Warning,
// errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
//#if DEBUG
// DebugConsole.ThrowError(errorMsg);
@@ -3628,7 +3680,7 @@ namespace Barotrauma
{
if (attacker.TeamID == TeamID)
{
afflictions = afflictions.Where(a => !a.Prefab.IsBuff);
afflictions = afflictions.Where(a => a.Prefab.IsBuff);
if (!afflictions.Any()) { return new AttackResult(); }
}
}
@@ -3845,7 +3897,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to apply an invalid impulse to a limb in Character.BreakJoints (" + diff + "). Limb position: " + limb.SimPosition + ", center of mass: " + centerOfMass + ".";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.GetCenterOfMass", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
@@ -3890,7 +3942,7 @@ namespace Barotrauma
AnimController.Frozen = false;
if (GameSettings.SendUserStatistics)
if (GameAnalyticsManager.SendUserStatistics)
{
string characterType = "Unknown";
@@ -3940,6 +3992,7 @@ namespace Barotrauma
}
SelectedConstruction = null;
SelectedCharacter = null;
AnimController.ResetPullJoints();
@@ -4068,10 +4121,15 @@ namespace Barotrauma
public void TeleportTo(Vector2 worldPos)
{
CurrentHull = null;
AnimController.CurrentHull = null;
Submarine = null;
AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), false);
AnimController.FindHull(worldPos, true);
AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), lerp: false);
AnimController.FindHull(worldPos, setSubmarine: true);
if (AIController is HumanAIController humanAI)
{
humanAI.PathSteering?.ResetPath();
}
}
public static void SaveInventory(Inventory inventory, XElement parentElement)
@@ -4442,6 +4500,10 @@ namespace Barotrauma
public static IEnumerable<Character> GetFriendlyCrew(Character character)
{
if (character is null)
{
return Enumerable.Empty<Character>();
}
return CharacterList.Where(c => HumanAIController.IsFriendly(character, c, onlySameTeam: true) && !c.IsDead);
}