Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user