v0.14.6.0

This commit is contained in:
Joonas Rikkonen
2021-06-17 17:54:52 +03:00
parent 3f324b14e8
commit c27e2ea5ab
348 changed files with 13156 additions and 4266 deletions
@@ -6,7 +6,6 @@ using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
@@ -19,7 +18,6 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerSerializable, IClientSerializable
{
public static List<Item> ItemList = new List<Item>();
@@ -41,7 +39,24 @@ namespace Barotrauma
ParentRuin = currentHull?.ParentRuin;
}
}
private CampaignMode.InteractionType campaignInteractionType = CampaignMode.InteractionType.None;
public CampaignMode.InteractionType CampaignInteractionType
{
get { return campaignInteractionType; }
set
{
if (campaignInteractionType != value)
{
campaignInteractionType = value;
AssignCampaignInteractionTypeProjSpecific(campaignInteractionType);
}
}
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType);
public bool Visible = true;
#if CLIENT
@@ -109,7 +124,8 @@ namespace Barotrauma
foreach (ItemComponent component in components)
{
if (!component.AllowInGameEditing) { continue; }
if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any())
|| component.SerializableProperties.Values.Any(p => p.Attributes.OfType<ConditionallyEditable>().Any(a => a.IsEditable(this))))
{
hasInGameEditableProperties = true;
break;
@@ -181,6 +197,20 @@ namespace Barotrauma
set;
}
[ConditionallyEditable(ConditionallyEditable.ConditionType.IsSwappableItem), Serialize(true, true, alwaysUseInstanceValues: true)]
public bool AllowSwapping
{
get;
set;
}
[Serialize(false, true)]
public bool PurchasedNewSwap
{
get;
set;
}
/// <summary>
/// Checks both <see cref="NonInteractable"/> and <see cref="NonPlayerTeamInteractable"/>
/// </summary>
@@ -609,12 +639,12 @@ namespace Barotrauma
}
//which type of inventory slots (head, torso, any, etc) the item can be placed in
private readonly HashSet<InvSlotType> allowedSlots = new HashSet<InvSlotType>();
public IEnumerable<InvSlotType> AllowedSlots
{
get
{
Pickable p = GetComponent<Pickable>();
return (p == null) ? InvSlotType.Any.ToEnumerable() : p.AllowedSlots;
return allowedSlots;
}
}
@@ -661,8 +691,35 @@ namespace Barotrauma
get { return Prefab.Linkable; }
}
/// <summary>
/// Can be used to move the item from XML (e.g. to correct the positions of items whose sprite origin has been changed)
/// </summary>
public float PositionX
{
get { return Position.X; }
private set
{
Move(new Vector2((value - Position.X) * Scale, 0.0f));
}
}
/// <summary>
/// Can be used to move the item from XML (e.g. to correct the positions of items whose sprite origin has been changed)
/// </summary>
public float PositionY
{
get { return Position.Y; }
private set
{
Move(new Vector2(0.0f, (value - Position.Y) * Scale));
}
}
public BallastFloraBranch Infector { get; set; }
public ItemPrefab PendingItemSwap { get; set; }
public readonly HashSet<ItemPrefab> AvailableSwaps = new HashSet<ItemPrefab>();
public override string ToString()
{
#if CLIENT
@@ -678,7 +735,7 @@ namespace Barotrauma
get { return allPropertyObjects; }
}
public bool IgnoreByAI => OrderedToBeIgnored || HasTag("ignorebyai");
public bool IgnoreByAI(Character character) => HasTag("ignorebyai") || OrderedToBeIgnored && character.IsOnPlayerTeam;
public bool OrderedToBeIgnored { get; set; }
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID)
@@ -765,6 +822,7 @@ namespace Barotrauma
case "deconstruct":
case "brokensprite":
case "decorativesprite":
case "upgradepreviewsprite":
case "price":
case "levelcommonness":
case "suitabletreatment":
@@ -779,6 +837,7 @@ namespace Barotrauma
case "minimapicon":
case "infectedsprite":
case "damagedinfectedsprite":
case "swappableitem":
break;
case "staticbody":
StaticBodyConfig = subElement;
@@ -805,7 +864,15 @@ namespace Barotrauma
hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
foreach (ItemComponent ic in components)
{
if (ic.statusEffectLists == null) continue;
if (ic is Pickable pickable)
{
foreach (var allowedSlot in pickable.AllowedSlots)
{
allowedSlots.Add(allowedSlot);
}
}
if (ic.statusEffectLists == null) { continue; }
if (statusEffectLists == null)
{
@@ -1034,23 +1101,26 @@ namespace Barotrauma
{
return (T)component;
}
if (typeof(T) == typeof(ItemComponent))
{
return (T)components.FirstOrDefault();
}
return default;
}
public IEnumerable<T> GetComponents<T>()
{
if (typeof(T) == typeof(ItemComponent))
{
return components.Cast<T>();
}
if (!componentsByType.ContainsKey(typeof(T))) { return Enumerable.Empty<T>(); }
return components.Where(c => c is T).Cast<T>();
}
public void RemoveContained(Item contained)
{
if (ownInventory != null)
{
ownInventory.RemoveItem(contained);
}
ownInventory?.RemoveItem(contained);
contained.Container = null;
}
@@ -1240,16 +1310,16 @@ namespace Barotrauma
/// <summary>
/// Should this item or any of its containers be ignored by the AI?
/// </summary>
public bool IsThisOrAnyContainerIgnoredByAI()
public bool IsThisOrAnyContainerIgnoredByAI(Character character)
{
if (IgnoreByAI) { return true; }
if (IgnoreByAI(character)) { return true; }
if (Container == null) { return false; }
if (Container.IgnoreByAI) { return true; }
if (Container.IgnoreByAI(character)) { return true; }
var container = Container;
while (container.Container != null)
{
container = container.Container;
if (container.IgnoreByAI) { return true; }
if (container.IgnoreByAI(character)) { return true; }
}
return false;
}
@@ -1382,8 +1452,11 @@ namespace Barotrauma
if (effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters) || effect.HasTargetType(StatusEffect.TargetType.NearbyItems))
{
effect.GetNearbyTargets(WorldPosition, targets);
if (targets.Count > 0) { hasTargets = true; }
targets.AddRange(effect.GetNearbyTargets(WorldPosition, targets));
if (targets.Count > 0)
{
hasTargets = true;
}
}
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget) && useTarget is ISerializableEntity serializableTarget)
@@ -1932,26 +2005,36 @@ namespace Barotrauma
SendSignal(signal, connection);
}
private readonly HashSet<(Signal Signal, Connection Connection)> delayedSignals = new HashSet<(Signal Signal, Connection Connection)>();
public void SendSignal(Signal signal, Connection connection)
{
LastSentSignalRecipients.Clear();
if (connections == null || connection == null) { return; }
signal.stepsTaken++;
//if the signal has been passed through this item multiple times already, interrupt it to prevent infinite loops
if (signal.stepsTaken > 5 && signal.source != null)
{
if (signal.source.LastSentSignalRecipients.AtLeast(3, recipient => recipient == connection))
{
return;
}
}
//use a coroutine to prevent infinite loops by creating a one
//frame delay if the "signal chain" gets too long
if (signal.stepsTaken > 10)
{
//if the signal has been passed through this item multiple times already, interrupt it to prevent infinite loops
if (signal.source != null)
//if there's an equal signal waiting to be sent
//to the same connection, don't add a new one
signal.stepsTaken = 0;
if (!delayedSignals.Contains((signal, connection)))
{
if (signal.source.LastSentSignalRecipients.Count(recipient => recipient == connection) > 2)
{
return;
}
delayedSignals.Add((signal, connection));
CoroutineManager.StartCoroutine(DelaySignal(signal, connection));
}
//use a coroutine to prevent infinite loops by creating a one
//frame delay if the "signal chain" gets too long
CoroutineManager.StartCoroutine(DelaySignal(signal, connection));
}
else
{
@@ -1972,7 +2055,8 @@ namespace Barotrauma
//wait one frame
yield return CoroutineStatus.Running;
signal.stepsTaken = 0;
delayedSignals.Remove((signal, connection));
signal.source = this;
connection.SendSignal(signal);
@@ -2003,6 +2087,8 @@ namespace Barotrauma
public bool TryInteract(Character picker, bool ignoreRequiredItems = false, bool forceSelectKey = false, bool forceActionKey = false)
{
if (CampaignInteractionType != CampaignMode.InteractionType.None) { return false; }
bool picked = false, selected = false;
#if CLIENT
bool hasRequiredSkills = true;
@@ -2064,7 +2150,7 @@ namespace Barotrauma
if (!ic.HasRequiredSkills(picker, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); }
showUiMsg = picker == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen;
#endif
if (!ignoreRequiredItems && !ic.HasRequiredItems(picker, showUiMsg)) continue;
if (!ignoreRequiredItems && !ic.HasRequiredItems(picker, showUiMsg)) { continue; }
if ((ic.CanBePicked && pickHit && ic.Pick(picker)) ||
(ic.CanBeSelected && selectHit && ic.Select(picker)))
{
@@ -2074,11 +2160,11 @@ namespace Barotrauma
if (picker == Character.Controlled) { GUI.ForceMouseOn(null); }
if (tempRequiredSkill != null) { requiredSkill = tempRequiredSkill; }
#endif
if (ic.CanBeSelected) selected = true;
if (ic.CanBeSelected) { selected = true; }
}
}
if (!picked) return false;
if (!picked) { return false; }
if (picker != null)
{
@@ -2345,7 +2431,7 @@ namespace Barotrauma
private void WritePropertyChange(IWriteMessage msg, object[] extraData, bool inGameEditableOnly)
{
var allProperties = inGameEditableOnly ? GetProperties<InGameEditable>() : GetProperties<Editable>();
var allProperties = inGameEditableOnly ? GetInGameEditableProperties() : GetProperties<Editable>();
SerializableProperty property = extraData[1] as SerializableProperty;
if (property != null)
{
@@ -2424,11 +2510,16 @@ namespace Barotrauma
}
}
private CoroutineHandle logPropertyChangeCoroutine;
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties()
{
return GetProperties<ConditionallyEditable>()
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
.Union(GetProperties<InGameEditable>()).ToList();
}
private void ReadPropertyChange(IReadMessage msg, bool inGameEditableOnly, Client sender = null)
{
var allProperties = inGameEditableOnly ? GetProperties<InGameEditable>() : GetProperties<Editable>();
var allProperties = inGameEditableOnly ? GetInGameEditableProperties() : GetProperties<Editable>();
if (allProperties.Count == 0) { return; }
int propertyIndex = 0;
@@ -2579,18 +2670,31 @@ namespace Barotrauma
/// <returns></returns>
public static Item Load(XElement element, Submarine submarine, bool createNetworkEvent, IdRemap idRemap)
{
string name = element.Attribute("name").Value;
string name = element.Attribute("name").Value;
string identifier = element.GetAttributeString("identifier", "");
ItemPrefab prefab = ItemPrefab.Find(name, identifier);
if (prefab == null)
string pendingSwap = element.GetAttributeString("pendingswap", "");
ItemPrefab appliedSwap = null;
ItemPrefab oldPrefab = null;
if (!string.IsNullOrEmpty(pendingSwap) && Level.Loaded?.Type != LevelData.LevelType.Outpost)
{
return null;
oldPrefab = ItemPrefab.Find(name, identifier);
appliedSwap = ItemPrefab.Find(string.Empty, pendingSwap);
identifier = pendingSwap;
pendingSwap = null;
}
ItemPrefab prefab = ItemPrefab.Find(name, identifier);
if (prefab == null) { return null; }
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
if (rect.Width == 0 && rect.Height == 0)
Vector2 centerPos = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2);
if (appliedSwap != null)
{
rect.Width = (int)(prefab.sprite.size.X * prefab.Scale);
rect.Height = (int)(prefab.sprite.size.Y * prefab.Scale);
}
else if (rect.Width == 0 && rect.Height == 0)
{
rect.Width = (int)(prefab.Size.X * prefab.Scale);
rect.Height = (int)(prefab.Size.Y * prefab.Scale);
@@ -2599,7 +2703,8 @@ namespace Barotrauma
Item item = new Item(rect, prefab, submarine, callOnItemLoaded: false, id: idRemap.GetOffsetId(element))
{
Submarine = submarine,
linkedToID = new List<ushort>()
linkedToID = new List<ushort>(),
PendingItemSwap = string.IsNullOrEmpty(pendingSwap) ? null : MapEntityPrefab.Find(pendingSwap) as ItemPrefab
};
#if SERVER
@@ -2609,7 +2714,7 @@ namespace Barotrauma
}
#endif
foreach (XAttribute attribute in element.Attributes())
foreach (XAttribute attribute in (appliedSwap?.ConfigElement ?? element).Attributes())
{
if (!item.SerializableProperties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) { continue; }
bool shouldBeLoaded = false;
@@ -2622,14 +2727,14 @@ namespace Barotrauma
}
}
if (shouldBeLoaded)
if (shouldBeLoaded)
{
object prevValue = property.GetValue(item);
property.TrySetValue(item, attribute.Value);
//create network events for properties that differ from the prefab values
//(e.g. if a character has an item with modified colors in their inventory)
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && property.Attributes.OfType<Editable>().Any() &&
(submarine == null || !submarine.Loading ))
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && property.Attributes.OfType<Editable>().Any() &&
(submarine == null || !submarine.Loading))
{
switch (property.Name)
{
@@ -2655,48 +2760,111 @@ namespace Barotrauma
//if we're overriding a non-overridden item in a sub/assembly xml or vice versa,
//use the values from the prefab instead of loading them from the sub/assembly xml
bool usePrefabValues = thisIsOverride != prefab.IsOverride;
bool usePrefabValues = thisIsOverride != prefab.IsOverride || appliedSwap != null;
List<ItemComponent> unloadedComponents = new List<ItemComponent>(item.components);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "upgrade":
{
var upgradeIdentifier = subElement.GetAttributeString("identifier", string.Empty);
UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier);
int level = subElement.GetAttributeInt("level", 1);
if (upgradePrefab != null)
{
item.AddUpgrade(new Upgrade(item, upgradePrefab, level, subElement));
var upgradeIdentifier = subElement.GetAttributeString("identifier", string.Empty);
UpgradePrefab upgradePrefab = UpgradePrefab.Find(upgradeIdentifier);
int level = subElement.GetAttributeInt("level", 1);
if (upgradePrefab != null)
{
item.AddUpgrade(new Upgrade(item, upgradePrefab, level, subElement));
}
else
{
DebugConsole.AddWarning($"An upgrade with identifier \"{upgradeIdentifier}\" on {item.Name} was not found. " +
"It's effect will not be applied and won't be saved after the round ends.");
}
break;
}
else
default:
{
DebugConsole.AddWarning($"An upgrade with identifier \"{upgradeIdentifier}\" on {item.Name} was not found. " +
"It's effect will not be applied and won't be saved after the round ends.");
ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString());
if (component == null) { continue; }
component.Load(subElement, usePrefabValues, idRemap);
unloadedComponents.Remove(component);
break;
}
break;
}
default:
{
ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString());
if (component == null) { continue; }
component.Load(subElement, usePrefabValues, idRemap);
unloadedComponents.Remove(component);
break;
}
}
}
if (usePrefabValues)
if (usePrefabValues && appliedSwap == null)
{
//use prefab scale when overriding a non-overridden item or vice versa
item.Scale = prefab.ConfigElement.GetAttributeFloat(item.scale, "scale", "Scale");
}
item.Upgrades.ForEach(upgrade => upgrade.ApplyUpgrade());
var availableSwapIds = element.GetAttributeStringArray("availableswaps", new string[0]);
foreach (string swapId in availableSwapIds)
{
ItemPrefab swapPrefab = ItemPrefab.Find(string.Empty, swapId);
if (swapPrefab != null)
{
item.AvailableSwaps.Add(swapPrefab);
}
}
float prevRotation = item.Rotation;
if (element.GetAttributeBool("flippedx", false)) { item.FlipX(false); }
if (element.GetAttributeBool("flippedy", false)) { item.FlipY(false); }
item.Rotation = prevRotation;
if (appliedSwap != null)
{
item.SpriteDepth = element.GetAttributeFloat("spritedepth", item.SpriteDepth);
item.SpriteColor = element.GetAttributeColor("spritecolor", item.SpriteColor);
item.Rotation = element.GetAttributeFloat("rotation", item.Rotation);
item.PurchasedNewSwap = element.GetAttributeBool("purchasednewswap", false);
float scaleRelativeToPrefab = element.GetAttributeFloat(item.scale, "scale", "Scale") / oldPrefab.Scale;
item.Scale *= scaleRelativeToPrefab;
if (oldPrefab.SwappableItem != null && prefab.SwappableItem != null)
{
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.rotationRad);
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
relativeOrigin.Y = -relativeOrigin.Y;
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.rotationRad);
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
item.rect.Location -= (origin - oldOrigin).ToPoint();
}
if (item.PurchasedNewSwap && !string.IsNullOrEmpty(appliedSwap.SwappableItem?.SpawnWithId))
{
var container = item.GetComponent<ItemContainer>();
if (container != null)
{
container.SpawnWithId = appliedSwap.SwappableItem.SpawnWithId;
}
/*string[] splitIdentifier = appliedSwap.SwappableItem.SpawnWithId.Split(',');
foreach (string id in splitIdentifier)
{
ItemPrefab itemToSpawn = ItemPrefab.Find(name: null, identifier: id.Trim());
if (itemToSpawn == null)
{
DebugConsole.ThrowError($"Failed to spawn an item inside the purchased {item.Name} (could not find an item with the identifier \"{id}\").");
}
else
{
var spawnedItem = new Item(itemToSpawn, Vector2.Zero, null);
item.OwnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots, createNetworkEvent: false);
Spawner?.AddToSpawnQueue(itemToSpawn, item.OwnInventory, spawnIfInventoryFull: false);
}
}*/
}
item.PurchasedNewSwap = false;
}
float condition = element.GetAttributeFloat("condition", item.MaxCondition);
item.condition = MathHelper.Clamp(condition, 0, item.MaxCondition);
@@ -2726,12 +2894,22 @@ namespace Barotrauma
new XAttribute("identifier", Prefab.Identifier),
new XAttribute("ID", ID));
if (PendingItemSwap != null)
{
element.Add(new XAttribute("pendingswap", PendingItemSwap.Identifier));
}
if (Rotation != 0f) { element.Add(new XAttribute("rotation", Rotation)); }
if (Prefab.IsOverride) { element.Add(new XAttribute("isoverride", "true")); }
if (FlippedX) { element.Add(new XAttribute("flippedx", true)); }
if (FlippedY) { element.Add(new XAttribute("flippedy", true)); }
if (AvailableSwaps.Any())
{
element.Add(new XAttribute("availableswaps", string.Join(',', AvailableSwaps.Select(s => s.Identifier))));
}
if (condition < MaxCondition)
{
element.Add(new XAttribute("condition", condition.ToString("G", CultureInfo.InvariantCulture)));