Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests

This commit is contained in:
Evil Factory
2022-04-08 12:52:28 -03:00
990 changed files with 44338 additions and 38589 deletions
@@ -7,6 +7,8 @@ using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma;
using MoonSharp.Interpreter;
using Barotrauma.IO;
using Barotrauma.Networking;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Sounds;
@@ -66,26 +68,26 @@ namespace Barotrauma.Items.Components
}
}
public readonly XElement originalElement;
public readonly ContentXElement originalElement;
protected const float CorrectionDelay = 1.0f;
protected CoroutineHandle delayedCorrectionCoroutine;
[Editable, Serialize(0.0f, false, description: "How long it takes to pick up the item (in seconds).")]
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "How long it takes to pick up the item (in seconds).")]
public float PickingTime
{
get;
set;
}
[Serialize("", false, description: "What to display on the progress bar when this item is being picked.")]
[Serialize("", IsPropertySaveable.No, description: "What to display on the progress bar when this item is being picked.")]
public string PickingMsg
{
get;
set;
}
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
public Action<bool> OnActiveStateChanged;
@@ -137,42 +139,42 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(false, false, description: "Can the item be picked up (or interacted with, if the pick action does something else than picking up the item).")] //Editable for doors to do their magic
[Editable, Serialize(false, IsPropertySaveable.No, description: "Can the item be picked up (or interacted with, if the pick action does something else than picking up the item).")] //Editable for doors to do their magic
public bool CanBePicked
{
get { return canBePicked; }
set { canBePicked = value; }
}
[Serialize(false, false, description: "Should the interface of the item (if it has one) be drawn when the item is equipped.")]
[Serialize(false, IsPropertySaveable.No, description: "Should the interface of the item (if it has one) be drawn when the item is equipped.")]
public bool DrawHudWhenEquipped
{
get;
protected set;
}
[Serialize(false, false, description: "Can the item be selected by interacting with it.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the item be selected by interacting with it.")]
public bool CanBeSelected
{
get { return canBeSelected; }
set { canBeSelected = value; }
}
[Serialize(false, false, description: "Can the item be combined with other items of the same type.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the item be combined with other items of the same type.")]
public bool CanBeCombined
{
get { return canBeCombined; }
set { canBeCombined = value; }
}
[Serialize(false, false, description: "Should the item be removed if combining it with an other item causes the condition of this item to drop to 0.")]
[Serialize(false, IsPropertySaveable.No, description: "Should the item be removed if combining it with an other item causes the condition of this item to drop to 0.")]
public bool RemoveOnCombined
{
get { return removeOnCombined; }
set { removeOnCombined = value; }
}
[Serialize(false, false, description: "Can the \"Use\" action of the item be triggered by characters or just other items/StatusEffects.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the \"Use\" action of the item be triggered by characters or just other items/StatusEffects.")]
public bool CharacterUsable
{
get { return characterUsable; }
@@ -180,7 +182,7 @@ namespace Barotrauma.Items.Components
}
//Remove item if combination results in 0 condition
[Serialize(true, false, description: "Can the properties of the component be edited in-game (only applicable if the component has in-game editable properties)."), Editable()]
[Serialize(true, IsPropertySaveable.No, description: "Can the properties of the component be edited in-game (only applicable if the component has in-game editable properties)."), Editable()]
public bool AllowInGameEditing
{
get;
@@ -199,7 +201,7 @@ namespace Barotrauma.Items.Components
protected set;
}
[Serialize(false, false, description: "Should the item be deleted when it's used.")]
[Serialize(false, IsPropertySaveable.No, description: "Should the item be deleted when it's used.")]
public bool DeleteOnUse
{
get;
@@ -216,14 +218,14 @@ namespace Barotrauma.Items.Components
get { return name; }
}
[Editable, Serialize("", true, translationTextTag: "ItemMsg", description: "A text displayed next to the item when it's highlighted (generally instructs how to interact with the item, e.g. \"[Mouse1] Pick up\").")]
[Editable, Serialize("", IsPropertySaveable.Yes, translationTextTag: "ItemMsg", description: "A text displayed next to the item when it's highlighted (generally instructs how to interact with the item, e.g. \"[Mouse1] Pick up\").")]
public string Msg
{
get;
set;
}
public string DisplayMsg
public LocalizedString DisplayMsg
{
get;
set;
@@ -234,16 +236,16 @@ namespace Barotrauma.Items.Components
/// <summary>
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
/// </summary>
[Serialize(0f, false, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).")]
[Serialize(0f, IsPropertySaveable.No, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).")]
public float CombatPriority { get; private set; }
/// <summary>
/// Which sound should be played when manual sound selection type is selected? Not [Editable] because we don't want this visible in the editor for every component.
/// </summary>
[Serialize(0, true, alwaysUseInstanceValues: true)]
[Serialize(0, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
public int ManuallySelectedSound { get; private set; }
public ItemComponent(Item item, XElement element)
public ItemComponent(Item item, ContentXElement element)
{
this.item = item;
originalElement = element;
@@ -323,7 +325,7 @@ namespace Barotrauma.Items.Components
}
}
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -344,13 +346,13 @@ namespace Barotrauma.Items.Components
break;
case "requiredskill":
case "requiredskills":
if (subElement.Attribute("name") != null)
if (subElement.GetAttribute("name") != null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill.");
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill.");
continue;
}
string skillIdentifier = subElement.GetAttributeString("identifier", "");
Identifier skillIdentifier = subElement.GetAttributeIdentifier("identifier", "");
requiredSkills.Add(new Skill(skillIdentifier, subElement.GetAttributeInt("level", 0)));
break;
case "statuseffect":
@@ -359,7 +361,7 @@ namespace Barotrauma.Items.Components
break;
default:
if (LoadElemProjSpecific(subElement)) { break; }
ItemComponent ic = Load(subElement, item, item.ConfigFile, false);
ItemComponent ic = Load(subElement, item, false);
if (ic == null) { break; }
ic.Parent = this;
@@ -371,7 +373,7 @@ namespace Barotrauma.Items.Components
}
}
void LoadStatusEffect(XElement subElement)
void LoadStatusEffect(ContentXElement subElement)
{
var statusEffect = StatusEffect.Load(subElement, item.Name);
if (!statusEffectLists.TryGetValue(statusEffect.type, out List<StatusEffect> effectList))
@@ -388,7 +390,7 @@ namespace Barotrauma.Items.Components
IsActive = isActive;
}
public void SetRequiredItems(XElement element)
public void SetRequiredItems(ContentXElement element)
{
bool returnEmpty = false;
#if CLIENT
@@ -412,7 +414,7 @@ namespace Barotrauma.Items.Components
}
else
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
}
}
@@ -519,7 +521,7 @@ namespace Barotrauma.Items.Components
}
item.ParentInventory.RemoveItem(item);
}
Entity.Spawner.AddToRemoveQueue(item);
Entity.Spawner.AddItemToRemoveQueue(item);
}
else
{
@@ -535,7 +537,7 @@ namespace Barotrauma.Items.Components
}
this.Item.ParentInventory.RemoveItem(this.Item);
}
Entity.Spawner.AddToRemoveQueue(this.Item);
Entity.Spawner.AddItemToRemoveQueue(this.Item);
}
else
{
@@ -611,6 +613,9 @@ namespace Barotrauma.Items.Components
protected virtual void RemoveComponentSpecific()
{
}
protected string GetTextureDirectory(ContentXElement subElement)
=> subElement.DoesAttributeReferenceFileNameAlone("texture") ? Path.GetDirectoryName(item.Prefab.FilePath) : string.Empty;
public bool HasRequiredSkills(Character character)
{
@@ -678,7 +683,7 @@ namespace Barotrauma.Items.Components
HasRequiredContainedItems(user, addMessage: false) &&
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
public bool HasRequiredContainedItems(Character user, bool addMessage, string msg = null)
public bool HasRequiredContainedItems(Character user, bool addMessage, LocalizedString msg = null)
{
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return true; }
if (item.OwnInventory == null) { return false; }
@@ -688,8 +693,8 @@ namespace Barotrauma.Items.Components
if (!ri.CheckRequirements(user, item))
{
#if CLIENT
msg = msg ?? ri.Msg;
if (addMessage && !string.IsNullOrEmpty(msg))
msg ??= ri.Msg;
if (addMessage && !msg.IsNullOrEmpty())
{
GUI.AddMessage(msg, Color.Red);
}
@@ -722,7 +727,7 @@ namespace Barotrauma.Items.Components
return false;
}
public virtual bool HasRequiredItems(Character character, bool addMessage, string msg = null)
public virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
{
if (requiredItems.None()) { return true; }
if (character.Inventory == null) { return false; }
@@ -748,7 +753,7 @@ namespace Barotrauma.Items.Components
}
#if CLIENT
if (!hasRequiredItems && addMessage && !string.IsNullOrEmpty(msg))
if (!hasRequiredItems && addMessage && !msg.IsNullOrEmpty())
{
GUI.AddMessage(msg, Color.Red);
}
@@ -804,7 +809,7 @@ namespace Barotrauma.Items.Components
}
if (!hasRequiredItems)
{
if (msg == null && !string.IsNullOrEmpty(relatedItem.Msg))
if (msg == null && !relatedItem.Msg.IsNullOrEmpty())
{
msg = relatedItem.Msg;
}
@@ -851,14 +856,14 @@ namespace Barotrauma.Items.Components
HintManager.OnStatusEffectApplied(this, type, character);
#endif
}
[MoonSharpHidden]
public virtual void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
public virtual void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
if (componentElement != null)
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
if (!SerializableProperties.TryGetValue(attribute.NameAsIdentifier(), out SerializableProperty property)) { continue; }
if (property.OverridePrefabValues || !usePrefabValues)
{
property.TrySetValue(this, attribute.Value);
@@ -883,43 +888,48 @@ namespace Barotrauma.Items.Components
public virtual void OnScaleChanged() { }
// TODO: Consider using generics, interfaces, or inheritance instead of reflection -> would be easier to debug when something changes/goes wrong.
// For example, currently we can edit the constructors but they will fail in runtime because the parameters are not changed here.
// It's also painful to find where the constructors are used, because the references exist only at runtime.
public static ItemComponent Load(XElement element, Item item, string file, bool errorMessages = true)
public static ItemComponent Load(ContentXElement element, Item item, bool errorMessages = true)
{
Type t;
string type = element.Name.ToString().ToLowerInvariant();
Type type;
Identifier typeName = element.NameAsIdentifier();
try
{
// Get the type of a specified class.
t = Type.GetType("Barotrauma.Items.Components." + type + "", false, true);
if (t == null)
// Get the type of a specified class.
type = ReflectionUtils.GetDerivedNonAbstract<ItemComponent>().Append(typeof(ItemComponent)).FirstOrDefault(t => t.Name == typeName);
if (type == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")");
if (errorMessages)
{
DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})");
}
return null;
}
}
catch (Exception e)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")", e);
if (errorMessages)
{
DebugConsole.ThrowError($"Could not find the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e);
}
return null;
}
ConstructorInfo constructor;
try
{
if (t != typeof(ItemComponent) && !t.IsSubclassOf(typeof(ItemComponent))) return null;
constructor = t.GetConstructor(new Type[] { typeof(Item), typeof(XElement) });
if (type != typeof(ItemComponent) && !type.IsSubclassOf(typeof(ItemComponent))) { return null; }
constructor = type.GetConstructor(new Type[] { typeof(Item), typeof(ContentXElement) });
if (constructor == null)
{
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")");
DebugConsole.ThrowError(
$"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})");
return null;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")", e);
DebugConsole.ThrowError(
$"Could not find the constructor of the component \"{typeName}\" ({item.Prefab.ContentFile.Path})", e);
return null;
}
ItemComponent ic = null;
@@ -932,10 +942,11 @@ namespace Barotrauma.Items.Components
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException);
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.Load:TargetInvocationException" + item.Name + element.Name,
DebugConsole.ThrowError($"Error while loading component of the type {type}.", e.InnerException);
GameAnalyticsManager.AddErrorEventOnce(
$"ItemComponent.Load:TargetInvocationException{item.Name}{element.Name}",
GameAnalyticsManager.ErrorSeverity.Error,
"Error while loading entity of the type " + t + " (" + e.InnerException + ")\n" + Environment.StackTrace.CleanupStackTrace());
$"Error while loading entity of the type {type} ({e.InnerException})\n{Environment.StackTrace.CleanupStackTrace()}");
}
return ic;
@@ -976,7 +987,7 @@ namespace Barotrauma.Items.Components
OverrideRequiredItems(originalElement);
}
private void OverrideRequiredItems(XElement element)
private void OverrideRequiredItems(ContentXElement element)
{
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
requiredItems.Clear();
@@ -985,7 +996,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
returnEmptyRequirements = Screen.Selected == GameMain.SubEditorScreen;
#endif
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -1016,8 +1027,8 @@ namespace Barotrauma.Items.Components
public virtual void ParseMsg()
{
string msg = TextManager.Get(Msg, true);
if (msg != null)
LocalizedString msg = TextManager.Get(Msg);
if (msg.Loaded)
{
msg = TextManager.ParseInputTypes(msg);
DisplayMsg = msg;
@@ -1028,6 +1039,30 @@ namespace Barotrauma.Items.Components
}
}
public interface IEventData { }
public virtual bool ValidateEventData(NetEntityEvent.IData data)
=> true;
protected T ExtractEventData<T>(NetEntityEvent.IData data) where T : IEventData
=> TryExtractEventData(data, out T componentData)
? componentData
: throw new Exception($"Malformed item component state event for {item.Name} " +
$"(item ID {item.ID}, component type {GetType().Name}): " +
$"could not extract ComponentData of type {typeof(T).Name}");
protected bool TryExtractEventData<T>(NetEntityEvent.IData data, out T componentData)
{
componentData = default;
if (data is Item.ComponentStateEventData { ComponentData: T nestedData })
{
componentData = nestedData;
return true;
}
return false;
}
#region AI related
protected const float AIUpdateInterval = 0.2f;
protected float aiUpdateTimer;