Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -23,7 +23,7 @@ namespace Barotrauma
{
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerPositionSync, IClientSerializable
{
public static List<Item> ItemList = new List<Item>();
public static readonly List<Item> ItemList = new List<Item>();
private static readonly HashSet<Item> dangerousItems = new HashSet<Item>();
@@ -77,17 +77,17 @@ namespace Barotrauma
public CampaignMode.InteractionType CampaignInteractionType
{
get { return campaignInteractionType; }
set
{
if (campaignInteractionType != value)
{
campaignInteractionType = value;
AssignCampaignInteractionTypeProjSpecific(campaignInteractionType);
}
}
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType);
public void AssignCampaignInteractionType(CampaignMode.InteractionType interactionType, IEnumerable<Client> targetClients = null)
{
if (campaignInteractionType == interactionType) { return; }
campaignInteractionType = interactionType;
AssignCampaignInteractionTypeProjSpecific(campaignInteractionType, targetClients);
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType, IEnumerable<Client> targetClients);
public bool Visible = true;
@@ -105,6 +105,12 @@ namespace Barotrauma
private readonly List<IDrawableComponent> drawableComponents;
private bool hasComponentsToDraw;
/// <summary>
/// Has everything in the item been loaded/instantiated/initialized (basically, can be used to check if the whole constructor/Load method has run).
/// Most commonly used to avoid creating network events when some value changes if the item is being initialized.
/// </summary>
public bool FullyInitialized { get; private set; }
public PhysicsBody body;
private readonly float originalWaterDragCoefficient;
private float? overrideWaterDragCoefficient;
@@ -114,6 +120,21 @@ namespace Barotrauma
set => overrideWaterDragCoefficient = value;
}
/// <summary>
/// Can be used by StatusEffects to set the type of the body (if the item has one)
/// </summary>
public BodyType BodyType
{
get { return body?.BodyType ?? BodyType.Dynamic; }
set
{
if (body != null)
{
body.BodyType = value;
}
}
}
/// <summary>
/// Removes the override value -> falls back to using the original value defined in the xml.
/// </summary>
@@ -125,9 +146,10 @@ namespace Barotrauma
private bool transformDirty = true;
private static readonly List<Item> itemsWithPendingConditionUpdates = new List<Item>();
private float lastSentCondition;
private float sendConditionUpdateTimer;
private bool conditionUpdatePending;
private float prevCondition;
private float condition;
@@ -207,7 +229,11 @@ namespace Barotrauma
set
{
parentInventory = value;
if (parentInventory != null) { Container = parentInventory.Owner as Item; }
if (parentInventory != null)
{
Container = parentInventory.Owner as Item;
RemoveFromDroppedStack(allowClientExecute: false);
}
#if SERVER
PreviousParentInventory = value;
#endif
@@ -229,7 +255,6 @@ namespace Barotrauma
container = value;
CheckCleanable();
SetActiveSprite();
RefreshRootContainer();
}
}
@@ -247,6 +272,37 @@ namespace Barotrauma
set { description = value; }
}
private string descriptionTag;
[Serialize("", IsPropertySaveable.Yes, alwaysUseInstanceValues: true), ConditionallyEditable(ConditionallyEditable.ConditionType.OnlyByStatusEffectsAndNetwork)]
/// <summary>
/// Can be used to set a localized description via StatusEffects
/// </summary>
public string DescriptionTag
{
get { return descriptionTag; }
set
{
if (value == descriptionTag) { return; }
if (value.IsNullOrEmpty())
{
descriptionTag = null;
description = null;
}
else
{
description = TextManager.Get(value).Value;
descriptionTag = value;
}
if (FullyInitialized &&
SerializableProperties != null &&
SerializableProperties.TryGetValue(nameof(DescriptionTag).ToIdentifier(), out SerializableProperty property))
{
GameMain.NetworkMember?.CreateEntityEvent(this, new ChangePropertyEventData(property, this));
}
}
}
[Editable, Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
public bool NonInteractable
{
@@ -675,6 +731,8 @@ namespace Barotrauma
set { allowStealing = value; }
}
public bool IsSalvageMissionItem;
private string originalOutpost;
[Serialize("", IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
public string OriginalOutpost
@@ -875,8 +933,8 @@ namespace Barotrauma
{
get { return allPropertyObjects; }
}
public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam;
public bool IgnoreByAI(Character character) => HasTag(Barotrauma.Tags.ItemIgnoredByAI) || OrderedToBeIgnored && character.IsOnPlayerTeam;
public bool OrderedToBeIgnored { get; set; }
public bool HasBallastFloraInHull
@@ -896,6 +954,9 @@ namespace Barotrauma
}
}
public bool InPlayerSubmarine => Submarine?.Info is { IsPlayer: true };
public bool InBeaconStation => Submarine?.Info is { Type: SubmarineType.BeaconStation };
public bool IsLadder { get; }
public bool IsSecondaryItem { get; }
@@ -910,6 +971,11 @@ namespace Barotrauma
}
}
/// <summary>
/// Timing.TotalTimeUnpaused when some character was last eating the item
/// </summary>
public float LastEatenTime { get; set; }
public Action<Character> OnDeselect;
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID, bool callOnItemLoaded = true)
@@ -1157,10 +1223,15 @@ namespace Barotrauma
DebugConsole.Log("Created " + Name + " (" + ID + ")");
if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; }
if (HasTag("logic")) { isLogic = true; }
if (HasTag(Barotrauma.Tags.LogicItem)) { isLogic = true; }
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
RecalculateConditionValues();
if (callOnItemLoaded)
{
FullyInitialized = true;
}
#if CLIENT
Submarine.ForceVisibilityRecheck();
#endif
@@ -1179,7 +1250,7 @@ namespace Barotrauma
};
foreach (KeyValuePair<Identifier, SerializableProperty> property in SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
if (property.Value.Attributes.OfType<Serialize>().None()) { continue; }
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
}
@@ -1194,9 +1265,10 @@ namespace Barotrauma
for (int i = 0; i < components.Count && i < clone.components.Count; i++)
{
foreach (KeyValuePair<Identifier, SerializableProperty> property in components[i].SerializableProperties)
//order the properties to get them to be applied in a consistent order (may matter for properties that are interconnected somehow, like IsOn/IsActive)
foreach (KeyValuePair<Identifier, SerializableProperty> property in components[i].SerializableProperties.OrderBy(s => s.Key))
{
if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
if (property.Value.Attributes.OfType<Serialize>().None()) { continue; }
clone.components[i].SerializableProperties[property.Key].TrySetValue(clone.components[i], property.Value.GetValue(components[i]));
}
@@ -1219,18 +1291,48 @@ namespace Barotrauma
if (FlippedX) clone.FlipX(false);
if (FlippedY) clone.FlipY(false);
foreach (ItemComponent component in clone.components)
{
component.OnItemLoaded();
}
foreach (Item containedItem in ContainedItems)
Dictionary<ushort, Item> clonedContainedItems = new();
for (int i = 0; i < components.Count && i < clone.components.Count; i++)
{
var containedClone = containedItem.Clone();
clone.ownInventory.TryPutItem(containedClone as Item, null);
ItemComponent component = components[i],
cloneComp = clone.components[i];
if (component is not ItemContainer origInv ||
cloneComp is not ItemContainer cloneInv)
{
continue;
}
foreach (var containedItem in origInv.Inventory.AllItems)
{
var containedClone = (Item)containedItem.Clone();
cloneInv.Inventory.TryPutItem(containedClone, null);
clonedContainedItems.Add(containedItem.ID, containedClone);
}
}
for (int i = 0; i < components.Count && i < clone.components.Count; i++)
{
ItemComponent component = components[i],
cloneComp = clone.components[i];
if (component is not CircuitBox origBox || cloneComp is not CircuitBox cloneBox)
{
continue;
}
cloneBox.CloneFrom(origBox, clonedContainedItems);
}
clone.FullyInitialized = true;
return clone;
}
@@ -1432,7 +1534,7 @@ namespace Barotrauma
var pickable = GetComponent<Pickable>();
if (pickable != null && !pickable.IsAttached &&
Prefab.PreferredContainers.Any() &&
(container == null || container.HasTag("allowcleanup")))
(container == null || container.HasTag(Barotrauma.Tags.AllowCleanup)))
{
if (!cleanableItems.Contains(this))
{
@@ -1638,12 +1740,6 @@ namespace Barotrauma
tags.Add(tag);
}
public bool HasTag(string tag)
{
return HasTag(tag.ToIdentifier());
}
public bool HasTag(Identifier tag)
{
if (tag == null) { return true; }
@@ -1690,7 +1786,7 @@ namespace Barotrauma
public bool ConditionalMatches(PropertyConditional conditional)
{
if (string.IsNullOrEmpty(conditional.TargetItemComponentName))
if (string.IsNullOrEmpty(conditional.TargetItemComponent))
{
if (!conditional.Matches(this)) { return false; }
}
@@ -1698,7 +1794,7 @@ namespace Barotrauma
{
foreach (ItemComponent component in components)
{
if (component.Name != conditional.TargetItemComponentName) { continue; }
if (component.Name != conditional.TargetItemComponent) { continue; }
if (!conditional.Matches(component)) { return false; }
}
}
@@ -1796,7 +1892,7 @@ namespace Barotrauma
}
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
targets.AddRange(character.AnimController.Limbs.ToList());
targets.AddRange(character.AnimController.Limbs);
}
if (effect.HasTargetType(StatusEffect.TargetType.Limb) && limb == null && effect.targetLimbs != null)
{
@@ -1811,7 +1907,7 @@ namespace Barotrauma
targets.Add(limb);
}
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.Add(Container); }
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.AddRange(Container.AllPropertyObjects); }
effect.Apply(type, deltaTime, this, targets, worldPosition);
}
@@ -1897,21 +1993,20 @@ namespace Barotrauma
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
if (Math.Abs(lastSentCondition - condition) > 1.0f)
{
conditionUpdatePending = true;
isActive = true;
}
else if (wasInFullCondition != IsFullCondition)
{
conditionUpdatePending = true;
isActive = true;
}
else if (!MathUtils.NearlyEqual(lastSentCondition, condition) && (condition <= 0.0f || condition >= MaxCondition))
bool needsConditionUpdate = false;
if (!MathUtils.NearlyEqual(lastSentCondition, condition) && (condition <= 0.0f || condition >= MaxCondition))
{
//send the update immediately if the condition changed to max or min
sendConditionUpdateTimer = 0.0f;
conditionUpdatePending = true;
isActive = true;
needsConditionUpdate = true;
}
else if (Math.Abs(lastSentCondition - condition) > 1.0f || wasInFullCondition != IsFullCondition)
{
needsConditionUpdate = true;
}
if (needsConditionUpdate && !itemsWithPendingConditionUpdates.Contains(this))
{
itemsWithPendingConditionUpdates.Add(this);
}
}
@@ -1967,12 +2062,16 @@ namespace Barotrauma
public void SendPendingNetworkUpdates()
{
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
if (!conditionUpdatePending) { return; }
if (!itemsWithPendingConditionUpdates.Contains(this)) { return; }
SendPendingNetworkUpdatesInternal();
itemsWithPendingConditionUpdates.Remove(this);
}
private void SendPendingNetworkUpdatesInternal()
{
CreateStatusEvent(loadingRound: false);
lastSentCondition = condition;
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
conditionUpdatePending = false;
}
public void CreateStatusEvent(bool loadingRound)
@@ -1980,21 +2079,32 @@ namespace Barotrauma
GameMain.NetworkMember.CreateEntityEvent(this, new ItemStatusEventData(loadingRound));
}
public static void UpdatePendingConditionUpdates(float deltaTime)
{
if (GameMain.NetworkMember is not { IsServer: true }) { return; }
for (int i = 0; i < itemsWithPendingConditionUpdates.Count; i++)
{
var item = itemsWithPendingConditionUpdates[i];
if (item == null || item.Removed)
{
itemsWithPendingConditionUpdates.RemoveAt(i--);
continue;
}
if (item.Submarine is { Loading: true }) { continue; }
item.sendConditionUpdateTimer -= deltaTime;
if (item.sendConditionUpdateTimer <= 0.0f)
{
item.SendPendingNetworkUpdatesInternal();
itemsWithPendingConditionUpdates.RemoveAt(i--);
}
}
}
private bool isActive = true;
public override void Update(float deltaTime, Camera cam)
{
#if SERVER
if (!(Submarine is { Loading: true }))
{
sendConditionUpdateTimer -= deltaTime;
if (conditionUpdatePending && sendConditionUpdateTimer <= 0.0f)
{
SendPendingNetworkUpdates();
}
}
#endif
if (!isActive) { return; }
if (impactQueue != null)
@@ -2004,14 +2114,27 @@ namespace Barotrauma
HandleCollision(impact);
}
}
if (isDroppedStackOwner && body != null)
{
foreach (var item in droppedStack)
{
if (item != this)
{
item.body.Enabled = false;
item.body.SetTransformIgnoreContacts(this.SimPosition, body.Rotation);
}
}
}
if (aiTarget != null && aiTarget.NeedsUpdate)
{
aiTarget.Update(deltaTime);
}
var containedEffectType = parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained;
ApplyStatusEffects(ActionType.Always, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
ApplyStatusEffects(parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
ApplyStatusEffects(containedEffectType, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
for (int i = 0; i < updateableComponents.Count; i++)
{
@@ -2019,7 +2142,7 @@ namespace Barotrauma
if (ic.IsActiveConditionals != null)
{
if (ic.IsActiveConditionalComparison == PropertyConditional.Comparison.And)
if (ic.IsActiveConditionalComparison == PropertyConditional.LogicalOperatorType.And)
{
bool shouldBeActive = true;
foreach (var conditional in ic.IsActiveConditionals)
@@ -2140,6 +2263,7 @@ namespace Barotrauma
updateableComponents.Count == 0 &&
(aiTarget == null || !aiTarget.NeedsUpdate) &&
!hasStatusEffectsOfType[(int)ActionType.Always] &&
!hasStatusEffectsOfType[(int)containedEffectType] &&
(body == null || !body.Enabled))
{
#if CLIENT
@@ -2651,9 +2775,20 @@ namespace Barotrauma
public bool TryInteract(Character user, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceUseKey = false)
{
if (CampaignMode.BlocksInteraction(CampaignInteractionType))
var campaignInteractionType = CampaignInteractionType;
#if SERVER
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == user);
if (ownerClient != null)
{
return false;
if (!campaignInteractionTypePerClient.TryGetValue(ownerClient, out campaignInteractionType))
{
campaignInteractionType = CampaignMode.InteractionType.None;
}
}
#endif
if (CampaignMode.BlocksInteraction(campaignInteractionType))
{
return false;
}
bool picked = false, selected = false;
@@ -2821,9 +2956,9 @@ namespace Barotrauma
return -1;
}
public void Use(float deltaTime, Character character = null, Limb targetLimb = null)
public void Use(float deltaTime, Character user = null, Limb targetLimb = null, Entity useTarget = null)
{
if (RequireAimToUse && (character == null || !character.IsKeyDown(InputType.Aim)))
if (RequireAimToUse && (user == null || !user.IsKeyDown(InputType.Aim)))
{
return;
}
@@ -2836,17 +2971,17 @@ namespace Barotrauma
{
bool isControlled = false;
#if CLIENT
isControlled = character == Character.Controlled;
isControlled = user == Character.Controlled;
#endif
if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; }
if (ic.Use(deltaTime, character))
if (!ic.HasRequiredContainedItems(user, isControlled)) { continue; }
if (ic.Use(deltaTime, user))
{
ic.WasUsed = true;
#if CLIENT
ic.PlaySound(ActionType.OnUse, character);
ic.PlaySound(ActionType.OnUse, user);
#endif
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb, useTarget: character, user: character);
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, user, targetLimb, useTarget: useTarget, user: user);
ic.OnUsed.Invoke(new ItemComponent.ItemUseInfo(this, user));
if (ic.DeleteOnUse) { remove = true; }
}
}
@@ -2876,7 +3011,7 @@ namespace Barotrauma
#if CLIENT
ic.PlaySound(ActionType.OnSecondaryUse, character);
#endif
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character: character, user: character);
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character: character, user: character, useTarget: character);
if (ic.DeleteOnUse) { remove = true; }
}
@@ -3009,10 +3144,10 @@ namespace Barotrauma
Container = null;
}
if (parentInventory != null)
if (ParentInventory != null)
{
parentInventory.RemoveItem(this);
parentInventory = null;
ParentInventory.RemoveItem(this);
ParentInventory = null;
}
SetContainedItemPositions();
@@ -3021,6 +3156,108 @@ namespace Barotrauma
#endif
}
private List<Item> droppedStack;
public IEnumerable<Item> DroppedStack => droppedStack ?? Enumerable.Empty<Item>();
private bool isDroppedStackOwner;
/// <summary>
/// "Merges" the set of items so they behave as one physical object and can be picked up by clicking once.
/// The items need to be instances of the same prefab and have a physics body.
/// </summary>
public void CreateDroppedStack(IEnumerable<Item> items, bool allowClientExecute)
{
if (!allowClientExecute && GameMain.NetworkMember is { IsClient: true }) { return; }
int itemCount = items.Count();
if (itemCount == 1) { return; }
if (items.DistinctBy(it => it.Prefab).Count() > 1)
{
DebugConsole.ThrowError($"Attempted to create a dropped stack of multiple different items ({string.Join(", ", items.DistinctBy(it => it.Prefab))})\n{Environment.StackTrace}");
return;
}
if (items.Any(it => it.body == null))
{
DebugConsole.ThrowError($"Attempted to create a dropped stack for an item with no body ({items.First().Prefab.Identifier})\n{Environment.StackTrace}");
return;
}
if (items.None())
{
DebugConsole.ThrowError($"Attempted to create a dropped stack of an empty list of items.\n{Environment.StackTrace}");
return;
}
int maxStackSize = items.First().Prefab.MaxStackSize;
if (itemCount > maxStackSize)
{
for (int i = 0; i < MathF.Ceiling(itemCount / maxStackSize); i++)
{
int startIndex = i * maxStackSize;
items.ElementAt(startIndex).CreateDroppedStack(items.Skip(startIndex).Take(maxStackSize), allowClientExecute);
}
}
else
{
droppedStack ??= new List<Item>();
foreach (Item item in items)
{
if (!droppedStack.Contains(item))
{
droppedStack.Add(item);
}
}
SetDroppedStackItemStates();
#if SERVER
if (GameMain.NetworkMember is { IsServer: true } server)
{
server.CreateEntityEvent(this, new DroppedStackEventData(droppedStack));
}
#endif
}
}
private void RemoveFromDroppedStack(bool allowClientExecute)
{
if (!allowClientExecute && GameMain.NetworkMember is { IsClient: true }) { return; }
if (droppedStack == null) { return; }
body.Enabled = ParentInventory == null;
isDroppedStackOwner = false;
droppedStack.Remove(this);
SetDroppedStackItemStates();
droppedStack = null;
#if SERVER
if (GameMain.NetworkMember is { IsServer: true } server)
{
server.CreateEntityEvent(this, new DroppedStackEventData(Enumerable.Empty<Item>()));
}
#endif
}
private void SetDroppedStackItemStates()
{
if (droppedStack == null) { return; }
bool isFirst = true;
foreach (Item item in droppedStack)
{
item.droppedStack = droppedStack;
item.isDroppedStackOwner = isFirst;
if (item.body != null)
{
item.body.Enabled = item.body.PhysEnabled = isFirst;
if (isFirst)
{
item.isActive = true;
item.body.ResetDynamics();
}
}
isFirst = false;
}
}
public void Equip(Character character)
{
if (Removed)
@@ -3072,7 +3309,7 @@ namespace Barotrauma
if (allProperties.Count > 1)
{
int propertyIndex = allProperties.FindIndex(p => p.property == property && p.obj == entity);
if (propertyIndex < -1)
if (propertyIndex < 0)
{
throw new Exception($"Could not find the property \"{property.Name}\" in \"{entity.Name ?? "null"}\"");
}
@@ -3187,6 +3424,11 @@ namespace Barotrauma
propertyIndex = (int)msg.ReadVariableUInt32();
}
if (propertyIndex >= allProperties.Count || propertyIndex < 0)
{
throw new Exception($"Error in ReadPropertyChange. Property index out of bounds (index: {propertyIndex}, property count: {allProperties.Count}, in-game editable only: {inGameEditableOnly})");
}
bool allowEditing = true;
object parentObject = allProperties[propertyIndex].obj;
SerializableProperty property = allProperties[propertyIndex].property;
@@ -3598,7 +3840,9 @@ namespace Barotrauma
if (component.Parent != null) { component.IsActive = component.Parent.IsActive; }
component.OnItemLoaded();
}
item.FullyInitialized = true;
return item;
}
@@ -3811,6 +4055,7 @@ namespace Barotrauma
repairableItems.Remove(this);
sonarVisibleItems.Remove(this);
cleanableItems.Remove(this);
RemoveFromDroppedStack(allowClientExecute: true);
}
partial void RemoveProjSpecific();