Release 1.10.5.0 - Autumn Update 2025
This commit is contained in:
@@ -312,9 +312,10 @@ namespace Barotrauma
|
||||
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
|
||||
}
|
||||
targetSlot = i;
|
||||
//slot free, continue
|
||||
var otherItem = targetInventory.GetItemAt(i);
|
||||
//slot free, continue
|
||||
if (otherItem == null) { continue; }
|
||||
if (!otherItem.IsInteractable(Character)) { return false; }
|
||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot))
|
||||
{
|
||||
|
||||
@@ -1092,6 +1092,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isFleeing)
|
||||
{
|
||||
CheckForDraggedCorpses();
|
||||
|
||||
foreach (Character target in Character.CharacterList)
|
||||
{
|
||||
if (target.CurrentHull != hull) { continue; }
|
||||
@@ -1209,6 +1211,34 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForDraggedCorpses()
|
||||
{
|
||||
if (Character.IsOnPlayerTeam) { return; }
|
||||
if (Character.Submarine is not { Info.IsOutpost: true }) { return; }
|
||||
|
||||
//find corpses in the same team
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (otherCharacter.SelectedCharacter == null ||
|
||||
!otherCharacter.SelectedCharacter.IsDead ||
|
||||
otherCharacter.SelectedCharacter.TeamID != Character.TeamID ||
|
||||
otherCharacter.IsInstigator)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Character.CanSeeTarget(otherCharacter)) { continue; }
|
||||
|
||||
// Player is dragging a corpse from our team
|
||||
string dialogTag = Character.IsSecurity ? "dialogdraggingcorpsereactionsecurity" : "dialogdraggingcorpsereaction";
|
||||
Character.Speak(TextManager.Get(dialogTag).Value, messageType: null,
|
||||
delay: Rand.Range(0.5f, 1.0f), identifier: "dialogdraggingcorpsereaction".ToIdentifier(), minDurationBetweenSimilar: 10.0f);
|
||||
|
||||
AddCombatObjective(Character.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat, otherCharacter);
|
||||
break; // Only react to one at a time
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnHealed(Character healer, float healAmount)
|
||||
{
|
||||
@@ -1660,6 +1690,10 @@ namespace Barotrauma
|
||||
public AIObjective SetForcedOrder(Order order)
|
||||
{
|
||||
var objective = ObjectiveManager.CreateObjective(order);
|
||||
if (order != null && !order.IsDismissal)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(objective != null);
|
||||
}
|
||||
ObjectiveManager.SetForcedOrder(objective);
|
||||
return objective;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Barotrauma
|
||||
private static List<Identifier> GetCurrentFlags(Character speaker)
|
||||
{
|
||||
var currentFlags = new List<Identifier>();
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); }
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtCosmeticDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); }
|
||||
|
||||
if (GameMain.GameSession != null && Level.Loaded != null)
|
||||
{
|
||||
@@ -84,7 +84,6 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
||||
speaker?.CurrentHull != null &&
|
||||
GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f &&
|
||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||
{
|
||||
currentFlags.Add("EnterOutpost".ToIdentifier());
|
||||
|
||||
+1
@@ -101,6 +101,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true, bool requireValidContainer = true, bool ignoreItemsMarkedForDeconstruction = true)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.GetComponents<Pickable>().None(c => c is not Door && c.CanBePicked)) { return false; }
|
||||
if (item.DontCleanUp) { return false; }
|
||||
if (item.Illegitimate == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
|
||||
+11
-8
@@ -65,7 +65,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly AIObjectiveFindSafety findSafety;
|
||||
private readonly HashSet<ItemComponent> weapons = new HashSet<ItemComponent>();
|
||||
private readonly HashSet<Item> ignoredWeapons = new HashSet<Item>();
|
||||
private readonly HashSet<ItemComponent> ignoredWeapons = new HashSet<ItemComponent>();
|
||||
|
||||
private AIObjectiveContainItem seekAmmunitionObjective;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
@@ -503,7 +503,8 @@ namespace Barotrauma
|
||||
HashSet<ItemComponent> allWeapons = FindWeaponsFromInventory();
|
||||
while (allWeapons.Any())
|
||||
{
|
||||
Weapon = GetWeapon(allWeapons, out _weaponComponent);
|
||||
Weapon = GetWeapon(allWeapons, out ItemComponent newWeaponComponent);
|
||||
_weaponComponent = newWeaponComponent;
|
||||
if (Weapon == null)
|
||||
{
|
||||
// No weapons
|
||||
@@ -512,7 +513,7 @@ namespace Barotrauma
|
||||
if (!character.Inventory.Contains(Weapon) || WeaponComponent == null)
|
||||
{
|
||||
// Not in the inventory anymore or cannot find the weapon component
|
||||
allWeapons.Remove(WeaponComponent);
|
||||
allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon);
|
||||
Weapon = null;
|
||||
continue;
|
||||
}
|
||||
@@ -540,7 +541,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// No ammo and should not try to seek ammo.
|
||||
allWeapons.Remove(WeaponComponent);
|
||||
allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon);
|
||||
Weapon = null;
|
||||
}
|
||||
}
|
||||
@@ -980,7 +981,6 @@ namespace Barotrauma
|
||||
weapons.Clear();
|
||||
foreach (var item in character.Inventory.AllItems)
|
||||
{
|
||||
if (ignoredWeapons.Contains(item)) { continue; }
|
||||
GetWeapons(item, weapons);
|
||||
if (item.OwnInventory != null)
|
||||
{
|
||||
@@ -990,11 +990,12 @@ namespace Barotrauma
|
||||
return weapons;
|
||||
}
|
||||
|
||||
private static void GetWeapons(Item item, ICollection<ItemComponent> weaponList)
|
||||
private void GetWeapons(Item item, ICollection<ItemComponent> weaponList)
|
||||
{
|
||||
if (item == null) { return; }
|
||||
foreach (var component in item.Components)
|
||||
{
|
||||
if (ignoredWeapons.Contains(component)) { continue; }
|
||||
if (component.CombatPriority > 0)
|
||||
{
|
||||
weaponList.Add(component);
|
||||
@@ -1332,19 +1333,21 @@ namespace Barotrauma
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||
ignoredWeapons.Add(Weapon);
|
||||
ignoredWeapons.Add(WeaponComponent);
|
||||
Weapon = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the ammunition found in the inventory.
|
||||
/// If seekAmmo is true, tries to get find the ammo elsewhere.
|
||||
/// </summary>
|
||||
/// <returns>True if the weapon was reloaded successfully.</returns>
|
||||
private bool Reload(bool seekAmmo)
|
||||
{
|
||||
if (WeaponComponent == null) { return false; }
|
||||
if (Weapon.OwnInventory == null) { return true; }
|
||||
if (!Weapon.IsInteractable(character)) { return false; }
|
||||
HumanAIController.UnequipEmptyItems(Weapon, allowDestroying: !character.IsOnPlayerTeam);
|
||||
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
||||
if (WeaponComponent.RequiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
|
||||
+7
@@ -443,6 +443,10 @@ namespace Barotrauma
|
||||
newCurrentObjective.Abandoned += () => DismissSelf(order);
|
||||
CurrentOrders.Add(order.WithObjective(newCurrentObjective));
|
||||
}
|
||||
else if (!order.IsDismissal)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to create an objective for the order: {order.Name}");
|
||||
}
|
||||
if (!HasOrders())
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
@@ -458,6 +462,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AI objective based on the order. Note that the method can return null in the case of e.g. Dismissal orders or orders that erroneously target something non-interactable.
|
||||
/// </summary>
|
||||
public AIObjective CreateObjective(Order order, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null || order.IsDismissal) { return null; }
|
||||
|
||||
@@ -341,6 +341,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (component?.GetType() is Type componentType)
|
||||
{
|
||||
// Items used via a controller (i.e. turrets) are not selectable but should still be valid targets.
|
||||
if (!UseController && !component.CanBeSelected) { continue; }
|
||||
if (componentType == ItemComponentType) { return component; }
|
||||
if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using static Barotrauma.CharacterParams;
|
||||
|
||||
@@ -434,6 +435,21 @@ namespace Barotrauma
|
||||
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
|
||||
if (petBehavior == null) { continue; }
|
||||
|
||||
//never save hostile pets or pets left outside
|
||||
if (c.TeamID == CharacterTeamType.None ||
|
||||
c.TeamID == CharacterTeamType.Team2 ||
|
||||
c.Submarine == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//pets must be in a player sub or owned by someone to be persistent
|
||||
if (c.Submarine is not { Info.IsPlayer: true } &&
|
||||
petBehavior.Owner is not { IsOnPlayerTeam: true })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
XElement petElement = new XElement("pet",
|
||||
new XAttribute("speciesname", c.SpeciesName),
|
||||
new XAttribute("ownerhash", petBehavior.Owner?.Info?.GetIdentifier() ?? 0),
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Barotrauma
|
||||
{
|
||||
enemyAi.PetBehavior?.Update(deltaTime);
|
||||
}
|
||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated)
|
||||
if (IsDead || IsUnconscious || Stun > 0.0f || IsIncapacitated)
|
||||
{
|
||||
//don't enable simple physics on dead/incapacitated characters
|
||||
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
||||
|
||||
@@ -8,8 +8,15 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class AnimController : Ragdoll
|
||||
abstract class AnimController : Ragdoll, ISerializableEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Most of the properties in this class are read-only, but can be useful for conditionals
|
||||
/// </summary>
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
public string Name => nameof(AnimController);
|
||||
|
||||
public Vector2 RightHandIKPos { get; protected set; }
|
||||
public Vector2 LeftHandIKPos { get; protected set; }
|
||||
|
||||
@@ -200,7 +207,10 @@ namespace Barotrauma
|
||||
|
||||
public float WalkPos { get; protected set; }
|
||||
|
||||
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
|
||||
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
}
|
||||
|
||||
public void UpdateAnimations(float deltaTime)
|
||||
{
|
||||
|
||||
@@ -2411,7 +2411,9 @@ namespace Barotrauma
|
||||
|
||||
if (Inventory != null)
|
||||
{
|
||||
if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false })
|
||||
//this doesn't need to be run by the server, clients sync the contents of their inventory with the server instead of the inputs used to manipulate the inventory
|
||||
#if CLIENT
|
||||
if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false } && CharacterHUD.ShouldDrawInventory(this))
|
||||
{
|
||||
foreach (Item item in HeldItems)
|
||||
{
|
||||
@@ -2429,6 +2431,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
||||
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
||||
@@ -2632,6 +2635,7 @@ namespace Barotrauma
|
||||
public bool Unequip(Item item)
|
||||
{
|
||||
if (!HasEquippedItem(item)) { return false; }
|
||||
if (!item.IsInteractable(this)) { return false; }
|
||||
if (!TryPutItemInAnySlot(item))
|
||||
{
|
||||
if (!TryPutItemInBag(item))
|
||||
@@ -2642,7 +2646,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited)
|
||||
public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets)
|
||||
{
|
||||
if (!CanInteract || inventory.Locked) { return false; }
|
||||
|
||||
@@ -2686,7 +2690,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Is the inventory accessible to the character? Doesn't check if the character can actually interact with it (distance checks etc).
|
||||
/// </summary>
|
||||
public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited)
|
||||
public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets)
|
||||
{
|
||||
if (Removed || Inventory == null) { return false; }
|
||||
if (!Inventory.AccessibleWhenAlive && !IsDead)
|
||||
@@ -2701,9 +2705,9 @@ namespace Barotrauma
|
||||
if (IsKnockedDownOrRagdolled || LockHands) { return true; }
|
||||
return accessLevel switch
|
||||
{
|
||||
CharacterInventory.AccessLevel.Restricted => false,
|
||||
CharacterInventory.AccessLevel.Limited => (IsBot && IsOnSameTeam()) || IsFriendlyPet(),
|
||||
CharacterInventory.AccessLevel.Allowed => IsOnSameTeam() || IsFriendlyPet(),
|
||||
CharacterInventory.AccessLevel.OnlyIfIncapacitated => false,
|
||||
CharacterInventory.AccessLevel.AllowBotsAndPets => (IsBot && IsOnSameTeam()) || IsFriendlyPet(),
|
||||
CharacterInventory.AccessLevel.AllowFriendly => IsOnSameTeam() || IsFriendlyPet(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
@@ -5714,7 +5718,7 @@ namespace Barotrauma
|
||||
|
||||
public bool HasRecipeForItem(Identifier recipeIdentifier)
|
||||
{
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.UnlockedRecipes.Contains(recipeIdentifier)) { return true; }
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.HasUnlockedRecipe(this, recipeIdentifier)) { return true; }
|
||||
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace Barotrauma
|
||||
private Affliction stunAffliction;
|
||||
public Affliction BloodlossAffliction { get => bloodlossAffliction; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the character dead or below 0 vitality and not able to stay conscious?
|
||||
/// </summary>
|
||||
public bool IsUnconscious
|
||||
{
|
||||
get { return Character.IsDead || (Vitality <= 0.0f && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)); }
|
||||
|
||||
Reference in New Issue
Block a user