Merge pull request #188 from Crystalwarrior/moStuff

Motion detectors, ducts, more chem effects and chems, fabricator/deconstructor overhaul, more footsteps, clown hitsounds...
This commit is contained in:
Joonas Rikkonen
2018-01-09 12:11:15 +02:00
committed by GitHub
69 changed files with 1411 additions and 539 deletions
@@ -1000,7 +1000,7 @@ namespace Barotrauma
{
target.AddDamage(CauseOfDeath.Bloodloss, 1.0f, character);
#if CLIENT
SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 25.0f, targetTorso.body);
SoundPlayer.PlayDamageSound("LimbBlunt", 25.0f, targetTorso.body);
for (int i = 0; i < 4; i++)
{
@@ -371,6 +371,7 @@ namespace Barotrauma
{
if (!MathUtils.IsValid(value)) return;
if (GameMain.Client != null) return;
if (!DoesBleed) return;
float newBleeding = MathHelper.Clamp(value, 0.0f, 5.0f);
//if (newBleeding == bleeding) return;
@@ -1618,6 +1619,15 @@ namespace Barotrauma
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
if (DoesBleed)
{
Health -= bleeding * deltaTime;
Bleeding -= BleedingDecreaseSpeed * deltaTime;
}
if (health <= minHealth) Kill(CauseOfDeath.Bloodloss);
if (!IsDead) LockHands = false;
//CPR stuff is handled in the UpdateCPR function in HumanoidAnimController
}
@@ -49,8 +49,8 @@ namespace Barotrauma
#if CLIENT
[Serialize(DamageSoundType.None, false)]
public DamageSoundType DamageSoundType
[Serialize("", false)]
public string DamageSound
{
get;
private set;
@@ -3,23 +3,19 @@ using System.Xml.Linq;
namespace Barotrauma
{
class DelayedListElement
{
public DelayedEffect Parent;
public Entity Entity;
public List<ISerializableEntity> Targets;
public float StartTimer;
}
class DelayedEffect : StatusEffect
{
public static List<DelayedEffect> List = new List<DelayedEffect>();
public static List<DelayedListElement> DelayList = new List<DelayedListElement>();
private float delay;
private float startTimer;
private Entity entity;
private List<ISerializableEntity> targets;
public float StartTimer
{
get { return startTimer; }
}
public DelayedEffect(XElement element)
: base(element)
{
@@ -28,25 +24,36 @@ namespace Barotrauma
public override void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets)
{
if (this.type != type) return;
startTimer = delay;
this.entity = entity;
if (this.type != type || !HasRequiredItems(entity)) return;
if (!base.Stackable && DelayList.Find(d => d.Parent == this && d.Entity == entity && d.Targets == targets) != null) return;
this.targets = targets;
DelayedListElement element = new DelayedListElement();
element.Parent = this;
element.StartTimer = delay;
element.Entity = entity;
element.Targets = targets;
List.Add(this);
DelayList.Add(element);
}
public void Update(float deltaTime)
public static void Update(float deltaTime)
{
startTimer -= deltaTime;
for (int i = DelayList.Count - 1; i >= 0; i--)
{
DelayedListElement element = DelayList[i];
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
{
DelayList.Remove(element);
continue;
}
if (startTimer > 0.0f) return;
element.StartTimer -= deltaTime;
base.Apply(1.0f, entity, targets);
List.Remove(this);
if (element.StartTimer > 0.0f) continue;
element.Parent.Apply(1.0f, element.Entity, element.Targets);
DelayList.Remove(element);
}
}
}
}
}
@@ -373,13 +373,13 @@ namespace Barotrauma
#if CLIENT
if (playSound)
{
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
string damageSoundType = (damageType == DamageType.Blunt) ? "LimbBlunt" : "LimbSlash";
foreach (DamageModifier damageModifier in appliedDamageModifiers)
{
if (damageModifier.DamageSoundType != DamageSoundType.None)
if (!string.IsNullOrWhiteSpace(damageModifier.DamageSound))
{
damageSoundType = damageModifier.DamageSoundType;
damageSoundType = damageModifier.DamageSound;
break;
}
}
@@ -594,12 +594,6 @@ namespace Barotrauma
}
#if CLIENT
if (hitSound != null)
{
hitSound.Remove();
hitSound = null;
}
if (LightSource != null)
{
LightSource.Remove();
@@ -9,6 +9,93 @@ using Barotrauma.Particles;
namespace Barotrauma
{
class DurationListElement
{
public StatusEffect Parent;
public Entity Entity;
public List<ISerializableEntity> Targets;
public float StartTimer;
}
partial class PropertyConditional
{
public string Attribute;
public string Operator;
public object Value;
public PropertyConditional(string Attribute, string Operator, object Value)
{
this.Attribute = Attribute;
this.Operator = Operator;
this.Value = Value;
}
public bool Matches(SerializableProperty property)
{
if (property.GetValue() == null)
{
DebugConsole.ThrowError("Couldn't compare " + Value.ToString() + " (" + Value.GetType() + ") to property \"" + property.Name + "- property.GetValue() returns null!!");
return false;
}
Type type = property.GetValue().GetType();
float? floatValue = null;
float? floatProperty = null;
if (type == typeof(float) || type == typeof(int))
{
floatValue = Convert.ToSingle(Value);
floatProperty = Convert.ToSingle(property.GetValue());
}
switch (Operator)
{
case "==":
if (property.GetValue().Equals(floatValue == null ? Value : floatValue))
return true;
break;
case "!=":
if (property.GetValue().Equals(floatValue == null ? Value : floatValue))
return true;
break;
case ">":
if (floatValue == null)
{
DebugConsole.ThrowError("Couldn't compare " + Value.ToString() + " (" + Value.GetType() + ") to property \"" + property.Name + "\" (" + type + ")! "
+ "Make sure the type of the value set in the config files matches the type of the property.");
}
else if (floatProperty > floatValue)
return true;
break;
case "<":
if (floatValue == null)
{
DebugConsole.ThrowError("Couldn't compare " + Value.ToString() + " (" + Value.GetType() + ") to property \"" + property.Name + "\" (" + type + ")! "
+ "Make sure the type of the value set in the config files matches the type of the property.");
}
else if (floatProperty < floatValue)
return true;
break;
case ">=":
if (floatValue == null)
{
DebugConsole.ThrowError("Couldn't compare " + Value.ToString() + " (" + Value.GetType() + ") to property \"" + property.Name + "\" (" + type + ")! "
+ "Make sure the type of the value set in the config files matches the type of the property.");
}
else if (floatProperty >= floatValue)
return true;
break;
case "<=":
if (floatValue == null)
{
DebugConsole.ThrowError("Couldn't compare " + Value.ToString() + " (" + Value.GetType() + ") to property \"" + property.Name + "\" (" + type + ")! "
+ "Make sure the type of the value set in the config files matches the type of the property.");
}
else if (floatProperty <= floatValue)
return true;
break;
}
return false;
}
}
partial class StatusEffect
{
[Flags]
@@ -32,15 +119,24 @@ namespace Barotrauma
public string[] propertyNames;
private object[] propertyEffects;
List<PropertyConditional> propertyConditionals;
private bool setValue;
private bool disableDeltaTime;
private HashSet<string> onContainingNames;
private HashSet<string> tags;
private readonly float duration;
public static List<DurationListElement> DurationList = new List<DurationListElement>();
private readonly bool useItem;
public bool CheckConditionalAlways; //Always do the conditional checks for the duration/delay. If false, only check conditional on apply.
public bool Stackable; //Can the same status effect be applied several times to the same targets?
private readonly int useItemCount;
private readonly int cancelStatusEffect;
public readonly ActionType type;
@@ -63,6 +159,24 @@ namespace Barotrauma
get { return onContainingNames; }
}
public string Tags
{
get { return string.Join(",", tags); }
set
{
tags.Clear();
if (value == null) return;
string[] newTags = value.Split(',');
foreach (string tag in newTags)
{
string newTag = tag.Trim();
if (!tags.Contains(newTag)) tags.Add(newTag);
}
}
}
public static StatusEffect Load(XElement element)
{
if (element.Attribute("delay")!=null)
@@ -76,14 +190,16 @@ namespace Barotrauma
protected StatusEffect(XElement element)
{
requiredItems = new List<RelatedItem>();
tags = new HashSet<string>(element.GetAttributeString("tags", "").Split(','));
#if CLIENT
particleEmitters = new List<ParticleEmitter>();
#endif
IEnumerable<XAttribute> attributes = element.Attributes();
IEnumerable<XAttribute> attributes = element.Attributes();
List<XAttribute> propertyAttributes = new List<XAttribute>();
propertyConditionals = new List<PropertyConditional>();
foreach (XAttribute attribute in attributes)
{
switch (attribute.Name.ToString())
@@ -133,6 +249,12 @@ namespace Barotrauma
case "duration":
duration = attribute.GetAttributeFloat(0.0f);
break;
case "stackable":
Stackable = attribute.GetAttributeBool(true);
break;
case "checkconditionalalways":
CheckConditionalAlways = attribute.GetAttributeBool(false);
break;
case "sound":
DebugConsole.ThrowError("Error in StatusEffect " + element.Parent.Name.ToString() +
" - sounds should be defined as child elements of the StatusEffect, not as attributes.");
@@ -167,7 +289,14 @@ namespace Barotrauma
break;
case "use":
case "useitem":
useItem = true;
useItemCount++;
break;
case "cancel":
case "cancelstatuseffect":
//This only works if there's a conditional checking for status effect tags. There is no way to cancel *all* status effects atm.
cancelStatusEffect = 1;
if (subElement.GetAttributeBool("all", false) == true)
cancelStatusEffect = 2;
break;
case "requireditem":
case "requireditems":
@@ -177,6 +306,67 @@ namespace Barotrauma
requiredItems.Add(newRequiredItem);
break;
case "conditional":
IEnumerable<XAttribute> conditionalAttributes = subElement.Attributes();
foreach(XAttribute attribute in conditionalAttributes)
{
string attributeString = XMLExtensions.GetAttributeObject(attribute).ToString();
string atStr = attributeString;
string[] splitString = atStr.Split(' ');
string op = splitString[0];
if (splitString.Length > 0)
{
for (int i=1; i<splitString.Length; i++)
{
atStr = splitString[i] + (i > 1 && i < splitString.Length ? " " : "");
}
}
//thanks xml for not letting me use < or > in attributes :(
switch (op)
{
case "e":
case "eq":
case "equals":
op = "==";
break;
case "ne":
case "neq":
case "notequals":
case "!":
case "!e":
case "!eq":
case "!equals":
op = "!=";
break;
case "gt":
case "greaterthan":
op = ">";
break;
case "lt":
case "lessthan":
op = "<";
break;
case "gte":
case "gteq":
case "greaterthanequals":
op = ">=";
break;
case "lte":
case "lteq":
case "lessthanequals":
op = "<=";
break;
default:
if (op != "==" && op != "!=" && op != ">" && op != "<" && op != ">=" && op != "<=") //Didn't use escape strings or anything
{
atStr = attributeString; //We probably don't even have an operator
op = "==";
}
break;
}
propertyConditionals.Add(new PropertyConditional(attribute.Name.ToString().ToLowerInvariant(), op, atStr));
}
break;
#if CLIENT
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
@@ -190,7 +380,7 @@ namespace Barotrauma
}
}
private bool HasRequiredItems(Entity entity)
public virtual bool HasRequiredItems(Entity entity)
{
if (requiredItems == null) return true;
foreach (RelatedItem requiredItem in requiredItems)
@@ -209,21 +399,97 @@ namespace Barotrauma
return true;
}
public virtual bool HasRequiredConditions(List<ISerializableEntity> targets)
{
if (!propertyConditionals.Any()) return true;
foreach (ISerializableEntity target in targets)
{
foreach (PropertyConditional pc in propertyConditionals)
{
if (target == null || target.SerializableProperties == null) continue;
if (!target.SerializableProperties.TryGetValue(pc.Attribute, out SerializableProperty property))
{
//Do special conditional checks
string valStr = pc.Value.ToString();
if (pc.Attribute == "name")
return pc.Operator == "==" ? target.Name == valStr : target.Name != valStr;
if (pc.Attribute == "speciesname" && target is Character)
return pc.Operator == "==" ? ((Character)target).SpeciesName == valStr : ((Character)target).SpeciesName != valStr;
if ((pc.Attribute == "hastag" || pc.Attribute == "hastags") && target is Item)
{
string[] readTags = valStr.Split(',');
int matches = 0;
foreach (string tag in readTags)
if (((Item)target).HasTag(tag)) matches++;
//If operator is == then it needs to match everything, otherwise if its != there must be zero matches.
return pc.Operator == "==" ? matches >= readTags.Length : matches <= 0;
}
List<DurationListElement> durations = DurationList.FindAll(d => d.Targets.Contains(target));
List<DelayedListElement> delays = DelayedEffect.DelayList.FindAll(d => d.Targets.Contains(target));
bool success = false;
if (pc.Attribute == "hasstatustag" || pc.Attribute == "hasstatustags" && (durations.Any() || delays.Any()))
{
string[] readTags = valStr.Split(',');
foreach (DurationListElement duration in durations)
{
int matches = 0;
foreach (string tag in readTags)
if (duration.Parent.HasTag(tag)) matches++;
success = pc.Operator == "==" ? matches >= readTags.Length : matches <= 0;
if (cancelStatusEffect > 0 && success)
DurationList.Remove(duration);
if (cancelStatusEffect != 2) //cancelStatusEffect 1 = only cancel once, cancelStatusEffect 2 = cancel all of matching tags
return success;
}
foreach (DelayedListElement delay in delays)
{
int matches = 0;
foreach (string tag in readTags)
if (delay.Parent.HasTag(tag)) matches++;
success = pc.Operator == "==" ? matches >= readTags.Length : matches <= 0;
if (cancelStatusEffect > 0 && success)
DelayedEffect.DelayList.Remove(delay);
if (cancelStatusEffect != 2) //ditto
return success;
}
}
return success;
}
else if (!pc.Matches(property))
return false;
}
}
return true;
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target)
{
if (this.type != type || !HasRequiredItems(entity)) return;
if (targetNames != null && !targetNames.Contains(target.Name)) return;
if (duration > 0.0f && !Stackable && DurationList.Find(d => d.Parent == this && d.Entity == entity && d.Targets.Contains(target)) != null) return;
List<ISerializableEntity> targets = new List<ISerializableEntity>();
targets.Add(target);
Apply(deltaTime, entity, targets);
if (!HasRequiredConditions(targets)) return;
Apply(type, deltaTime, entity, targets);
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets)
{
if (this.type != type || !HasRequiredItems(entity)) return;
if (this.type != type || !HasRequiredItems(entity) || !HasRequiredConditions(targets)) return;
Apply(deltaTime, entity, targets);
}
@@ -251,35 +517,43 @@ namespace Barotrauma
}
#endif
if (useItem)
if (useItemCount > 0)
{
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
for (int i=0; i<useItemCount; i++)
{
item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character);
}
}
foreach (ISerializableEntity target in targets)
{
for (int i = 0; i < propertyNames.Length; i++)
{
SerializableProperty property;
if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(propertyNames[i], out property)) continue;
if (duration > 0.0f)
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
{
CoroutineManager.StartCoroutine(
ApplyToPropertyOverDuration(duration, property, propertyEffects[i]), "statuseffect");
}
else
{
ApplyToProperty(property, propertyEffects[i], deltaTime);
item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character);
}
}
}
if (duration > 0.0f)
{
DurationListElement element = new DurationListElement();
element.Parent = this;
element.StartTimer = duration;
element.Entity = entity;
element.Targets = targets;
DurationList.Add(element);
}
else
{
foreach (ISerializableEntity target in targets)
{
for (int i = 0; i < propertyNames.Length; i++)
{
SerializableProperty property;
if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(propertyNames[i], out property)) continue;
ApplyToProperty(property, propertyEffects[i], deltaTime);
}
}
}
if (explosion != null) explosion.Explode(entity.WorldPosition);
@@ -308,21 +582,6 @@ namespace Barotrauma
#endif
}
private IEnumerable<object> ApplyToPropertyOverDuration(float duration, SerializableProperty property, object value)
{
float timer = duration;
while (timer > 0.0f)
{
ApplyToProperty(property, value, CoroutineManager.UnscaledDeltaTime);
timer -= CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
yield return CoroutineStatus.Success;
}
private void ApplyToProperty(SerializableProperty property, object value, float deltaTime)
{
if (disableDeltaTime || setValue) deltaTime = 1.0f;
@@ -359,9 +618,33 @@ namespace Barotrauma
public static void UpdateAll(float deltaTime)
{
for (int i = DelayedEffect.List.Count-1; i>= 0; i--)
DelayedEffect.Update(deltaTime);
for (int i = DurationList.Count - 1; i >= 0; i--)
{
DelayedEffect.List[i].Update(deltaTime);
DurationListElement element = DurationList[i];
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
{
DurationList.Remove(element);
continue;
}
foreach (ISerializableEntity target in element.Targets)
{
for (int n = 0; n < element.Parent.propertyNames.Length; n++)
{
SerializableProperty property;
if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(element.Parent.propertyNames[n], out property)) continue;
element.Parent.ApplyToProperty(property, element.Parent.propertyEffects[n], CoroutineManager.UnscaledDeltaTime);
}
}
element.StartTimer -= deltaTime;
if (element.StartTimer > 0.0f) continue;
DurationList.Remove(element);
}
}
@@ -369,5 +652,18 @@ namespace Barotrauma
{
CoroutineManager.StopCoroutines("statuseffect");
}
public void AddTag(string tag)
{
if (tags.Contains(tag)) return;
tags.Add(tag);
}
public bool HasTag(string tag)
{
if (tag == null) return true;
return (tags.Contains(tag) || tags.Contains(tag.ToLowerInvariant()));
}
}
}
@@ -203,8 +203,23 @@ namespace Barotrauma.Items.Components
#endif
}
public override bool HasRequiredItems(Character character, bool addMessage)
{
if (item.Condition <= 0.0f) return true; //For repairing
//this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing.
return requiredItems.Any() ? base.HasRequiredItems(character, addMessage) : canBePicked;
}
public override bool Pick(Character picker)
{
return item.Condition <= 0.0f ? true : base.Pick(picker);
}
public override bool OnPicked(Character picker)
{
if (item.Condition <= 0.0f) return true; //repairs
SetState(predictedState == null ? !isOpen : !predictedState.Value, false, true); //crowbar function
#if CLIENT
PlaySound(ActionType.OnPicked, item.WorldPosition);
@@ -369,7 +384,7 @@ namespace Barotrauma.Items.Components
if (Math.Sign(diff) != dir)
{
#if CLIENT
SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 1.0f, body);
SoundPlayer.PlayDamageSound("LimbBlunt", 1.0f, body);
#endif
if (isHorizontal)
@@ -215,6 +215,7 @@ namespace Barotrauma.Items.Components
{
Character targetCharacter = null;
Limb targetLimb = null;
Structure targetStructure = null;
if (f2.Body.UserData is Limb)
{
@@ -224,8 +225,12 @@ namespace Barotrauma.Items.Components
}
else if (f2.Body.UserData is Character)
{
targetCharacter = (Character)f2.Body.UserData;
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
}
else if (f2.Body.UserData is Structure)
{
targetStructure = (Structure)f2.Body.UserData;
}
else
{
@@ -236,14 +241,22 @@ namespace Barotrauma.Items.Components
if (attack != null)
{
if (targetLimb == null)
if (targetLimb != null)
{
attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
}
else
else if (targetCharacter != null)
{
attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
else if (targetStructure != null)
{
attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
}
else
{
return false;
}
}
RestoreCollision();
@@ -251,7 +264,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Client != null) return true;
if (GameMain.Server != null)
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
{
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, targetCharacter.ID });
@@ -263,8 +276,9 @@ namespace Barotrauma.Items.Components
logStr += " on " + targetCharacter.LogName + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetLimb.character);
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter != null ? targetCharacter : null);
return true;
}
@@ -30,6 +30,8 @@ namespace Barotrauma.Items.Components
protected bool canBePicked;
protected bool canBeSelected;
protected bool canBeCombined;
protected bool removeOnCombined;
public bool WasUsed;
@@ -47,7 +49,7 @@ namespace Barotrauma.Items.Components
private string msg;
[Serialize(0.0f, false)]
[Editable, Serialize(0.0f, false)]
public float PickingTime
{
get;
@@ -103,7 +105,7 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, false)]
[Editable, Serialize(false, false)] //Editable for doors to do their magic
public bool CanBePicked
{
get { return canBePicked; }
@@ -124,6 +126,22 @@ namespace Barotrauma.Items.Components
set { canBeSelected = value; }
}
//Transfer conditions between same prefab items
[Serialize(false, false)]
public bool CanBeCombined
{
get { return canBeCombined; }
set { canBeCombined = value; }
}
//Remove item if combination results in 0 condition
[Serialize(false, false)]
public bool RemoveOnCombined
{
get { return removeOnCombined; }
set { removeOnCombined = value; }
}
public InputType PickKey
{
get;
@@ -153,7 +171,7 @@ namespace Barotrauma.Items.Components
get { return name; }
}
[Serialize("", false)]
[Editable, Serialize("", false)]
public string Msg
{
get { return msg; }
@@ -196,7 +214,7 @@ namespace Barotrauma.Items.Components
try
{
string pickKeyStr = element.GetAttributeString("selectkey", "Select");
string pickKeyStr = element.GetAttributeString("pickkey", "Select");
pickKeyStr = ToolBox.ConvertInputType(pickKeyStr);
PickKey = (InputType)Enum.Parse(typeof(InputType),pickKeyStr, true);
}
@@ -323,6 +341,43 @@ namespace Barotrauma.Items.Components
public virtual bool Combine(Item item)
{
if (canBeCombined && this.item.Prefab == item.Prefab && item.Condition > 0.0f && this.item.Condition > 0.0f)
{
float transferAmount = 0.0f;
if (this.Item.Condition <= item.Condition)
transferAmount = Math.Min(item.Condition, this.item.Prefab.Health - this.item.Condition);
else
transferAmount = -Math.Min(this.item.Condition, item.Prefab.Health - item.Condition);
if (transferAmount == 0.0f)
return false;
this.Item.Condition += transferAmount;
item.Condition -= transferAmount;
if (removeOnCombined)
{
if (item.Condition <= 0.0f)
{
if (item.ParentInventory != null)
{
Character owner = (Character)item.ParentInventory.Owner;
if (owner != null && owner.HasSelectedItem(item)) item.Unequip(owner);
item.ParentInventory.RemoveItem(item);
}
Entity.Spawner.AddToRemoveQueue(item);
}
if (this.Item.Condition <= 0.0f)
{
if (this.Item.ParentInventory != null)
{
Character owner = (Character)this.Item.ParentInventory.Owner;
if (owner != null && owner.HasSelectedItem(this.Item)) this.Item.Unequip(owner);
this.Item.ParentInventory.RemoveItem(this.Item);
}
Entity.Spawner.AddToRemoveQueue(this.Item);
}
}
return true;
}
return false;
}
@@ -437,7 +492,7 @@ namespace Barotrauma.Items.Components
return true;
}
public bool HasRequiredItems(Character character, bool addMessage)
public virtual bool HasRequiredItems(Character character, bool addMessage)
{
if (!requiredItems.Any()) return true;
if (character.Inventory == null) return false;
@@ -170,7 +170,7 @@ namespace Barotrauma.Items.Components
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
if (effect.Targets.HasFlag(StatusEffect.TargetType.Contained))
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
}
}
@@ -192,7 +192,7 @@ namespace Barotrauma.Items.Components
return true;
}
return false;
return false;
}
public override void OnMapLoaded()
@@ -57,7 +57,8 @@ namespace Barotrauma.Items.Components
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
if (deconstructProduct.RequireFullCondition && targetItem.Condition < targetItem.Prefab.Health) continue;
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
var itemPrefab = MapEntityPrefab.Find(deconstructProduct.ItemPrefabName) as ItemPrefab;
if (itemPrefab == null)
@@ -69,11 +70,11 @@ namespace Barotrauma.Items.Components
//container full, drop the items outside the deconstructor
if (containers[1].Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine);
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, itemPrefab.Health * deconstructProduct.OutCondition);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory);
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory, itemPrefab.Health * deconstructProduct.OutCondition);
}
}
@@ -12,10 +12,12 @@ namespace Barotrauma.Items.Components
{
public readonly ItemPrefab TargetItem;
public readonly List<Tuple<ItemPrefab, int>> RequiredItems;
public readonly List<Tuple<ItemPrefab, int, float, bool>> RequiredItems;
public readonly float RequiredTime;
public readonly float OutCondition; //Percentage-based from 0 to 1
public readonly List<Skill> RequiredSkills;
public FabricableItem(XElement element)
@@ -31,8 +33,9 @@ namespace Barotrauma.Items.Components
RequiredSkills = new List<Skill>();
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
RequiredItems = new List<Tuple<ItemPrefab, int>>();
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
RequiredItems = new List<Tuple<ItemPrefab, int, float, bool>>();
//Backwards compatibility for string lists
string[] requiredItemNames = element.GetAttributeString("requireditems", "").Split(',');
foreach (string requiredItemName in requiredItemNames)
{
@@ -48,12 +51,12 @@ namespace Barotrauma.Items.Components
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, 1));
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, 1, 1.0f, false));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, existing.Item2 + 1));
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + 1, 1.0f, false));
}
}
@@ -66,6 +69,34 @@ namespace Barotrauma.Items.Components
subElement.GetAttributeString("name", ""),
subElement.GetAttributeInt("level", 0)));
break;
case "item": //New system allowing for setting minimal item condition
string requiredItemName = subElement.GetAttributeString("name", "");
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
//Substract mincondition from required item's condition or delete it regardless?
bool useCondition = subElement.GetAttributeBool("usecondition", true);
int count = subElement.GetAttributeInt("count", 1);
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, count, minCondition, useCondition));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + count, minCondition, useCondition));
}
break;
}
}
@@ -236,25 +267,32 @@ namespace Barotrauma.Items.Components
return;
}
foreach (Tuple<ItemPrefab, int> ip in fabricatedItem.RequiredItems)
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ip.Item2; i++)
{
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1);
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3);
if (requiredItem == null) continue;
//Item4 = use condition bool
if (ip.Item4 && requiredItem.Condition - ip.Item1.Health * ip.Item3 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
{
requiredItem.Condition -= ip.Item1.Health * ip.Item3;
continue;
}
Entity.Spawner.AddToRemoveQueue(requiredItem);
containers[0].Inventory.RemoveItem(requiredItem);
}
}
//TODO: apply OutCondition
if (containers[1].Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine);
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory);
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
}
CancelFabricating(null);
@@ -269,11 +307,10 @@ namespace Barotrauma.Items.Components
{
return false;
}
ItemContainer container = item.GetComponent<ItemContainer>();
foreach (Tuple<ItemPrefab, int> ip in fabricableItem.RequiredItems)
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricableItem.RequiredItems)
{
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1).Length < ip.Item2) return false;
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3).Length < ip.Item2) return false;
}
return true;
@@ -10,19 +10,23 @@ namespace Barotrauma.Items.Components
class WearableSprite
{
public readonly Sprite Sprite;
public readonly LimbType Limb;
public readonly bool HideLimb;
public readonly bool InheritLimbDepth;
public readonly LimbType DepthLimb;
public readonly Wearable WearableComponent;
public readonly string Sound;
public WearableSprite(Wearable item, Sprite sprite, bool hideLimb, bool inheritLimbDepth = true, LimbType depthLimb = LimbType.None)
public WearableSprite(Wearable item, Sprite sprite, LimbType limb, bool hideLimb, bool inheritLimbDepth = true, LimbType depthLimb = LimbType.None, string sound = null)
{
WearableComponent = item;
Sprite = sprite;
Limb = limb;
HideLimb = hideLimb;
InheritLimbDepth = inheritLimbDepth;
DepthLimb = depthLimb;
Sound = sound;
}
}
@@ -66,15 +70,16 @@ namespace Barotrauma.Items.Components
string spritePath = subElement.Attribute("texture").Value;
spritePath = Path.GetDirectoryName(item.Prefab.ConfigFile) + "/" + spritePath;
var sound = subElement.GetAttributeString("sound", "");
var sprite = new Sprite(subElement, "", spritePath);
wearableSprites[i] = new WearableSprite(this, sprite,
subElement.GetAttributeBool("hidelimb", false),
subElement.GetAttributeBool("inheritlimbdepth", true),
(LimbType)Enum.Parse(typeof(LimbType), subElement.GetAttributeString("depthlimb", "None"), true));
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
subElement.GetAttributeString("limb", "Head"), true);
wearableSprites[i] = new WearableSprite(this, sprite, limbType[i],
subElement.GetAttributeBool("hidelimb", false),
subElement.GetAttributeBool("inheritlimbdepth", true),
(LimbType)Enum.Parse(typeof(LimbType), subElement.GetAttributeString("depthlimb", "None"), true), sound);
i++;
break;
case "damagemodifier":
@@ -335,18 +335,18 @@ namespace Barotrauma
}
}
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine)
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, float? spawnCondition = null)
: this(new Rectangle(
(int)(position.X - itemPrefab.sprite.size.X / 2),
(int)(position.Y + itemPrefab.sprite.size.Y / 2),
(int)itemPrefab.sprite.size.X,
(int)itemPrefab.sprite.size.Y),
itemPrefab, submarine)
itemPrefab, submarine, spawnCondition)
{
}
public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine)
public Item(Rectangle newRect, ItemPrefab itemPrefab, Submarine submarine, float? spawnCondition = null)
: base(itemPrefab, submarine)
{
prefab = itemPrefab;
@@ -361,8 +361,8 @@ namespace Barotrauma
rect = newRect;
condition = prefab.Health;
lastSentCondition = prefab.Health;
condition = (float)(spawnCondition ?? prefab.Health);
lastSentCondition = condition;
XElement element = prefab.ConfigElement;
if (element == null) return;
@@ -781,7 +781,7 @@ namespace Barotrauma
float surfaceY = CurrentHull.Surface;
return Position.Y < surfaceY;
return CurrentHull.WaterVolume > 0.0f && Position.Y < surfaceY;
}
@@ -826,44 +826,46 @@ namespace Barotrauma
}
}
inWater = IsInWater();
if (inWater) ApplyStatusEffects(ActionType.InWater, deltaTime);
if (body == null || !body.Enabled) return;
System.Diagnostics.Debug.Assert(body.FarseerBody.FixtureList != null);
if (Math.Abs(body.LinearVelocity.X) > 0.01f || Math.Abs(body.LinearVelocity.Y) > 0.01f)
if (body != null && body.Enabled)
{
Submarine prevSub = Submarine;
System.Diagnostics.Debug.Assert(body.FarseerBody.FixtureList != null);
FindHull();
if (Math.Abs(body.LinearVelocity.X) > 0.01f || Math.Abs(body.LinearVelocity.Y) > 0.01f)
{
Submarine prevSub = Submarine;
if (Submarine == null && prevSub != null)
{
body.SetTransform(body.SimPosition + prevSub.SimPosition, body.Rotation);
}
else if (Submarine != null && prevSub == null)
{
body.SetTransform(body.SimPosition - Submarine.SimPosition, body.Rotation);
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
FindHull();
if (Math.Abs(body.LinearVelocity.X) > MaxVel || Math.Abs(body.LinearVelocity.Y) > MaxVel)
{
body.LinearVelocity = new Vector2(
MathHelper.Clamp(body.LinearVelocity.X, -MaxVel, MaxVel),
MathHelper.Clamp(body.LinearVelocity.Y, -MaxVel, MaxVel));
if (Submarine == null && prevSub != null)
{
body.SetTransform(body.SimPosition + prevSub.SimPosition, body.Rotation);
}
else if (Submarine != null && prevSub == null)
{
body.SetTransform(body.SimPosition - Submarine.SimPosition, body.Rotation);
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
rect.X = (int)(displayPos.X - rect.Width / 2.0f);
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
if (Math.Abs(body.LinearVelocity.X) > MaxVel || Math.Abs(body.LinearVelocity.Y) > MaxVel)
{
body.LinearVelocity = new Vector2(
MathHelper.Clamp(body.LinearVelocity.X, -MaxVel, MaxVel),
MathHelper.Clamp(body.LinearVelocity.Y, -MaxVel, MaxVel));
}
}
UpdateNetPosition();
}
UpdateNetPosition();
if (!inWater || ParentInventory != null) return;
inWater = IsInWater();
if (inWater) ApplyStatusEffects(ActionType.InWater, deltaTime);
if (body == null || !body.Enabled || !inWater || ParentInventory != null) return;
ApplyWaterForces();
CurrentHull?.ApplyFlowForces(deltaTime, this);
}
@@ -1104,8 +1106,6 @@ namespace Barotrauma
ic.ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
#if CLIENT
ic.PlaySound(ActionType.OnPicked, picker.WorldPosition);
if (picker == Character.Controlled) GUIComponent.ForceMouseOn(null);
#endif
@@ -10,12 +10,16 @@ namespace Barotrauma
struct DeconstructItem
{
public readonly string ItemPrefabName;
public readonly bool RequireFullCondition;
public readonly float MinCondition;
public readonly float MaxCondition;
public readonly float OutCondition;
public DeconstructItem(string itemPrefabName, bool requireFullCondition)
public DeconstructItem(string itemPrefabName, float minCondition, float maxCondition, float outCondition)
{
ItemPrefabName = itemPrefabName;
RequireFullCondition = requireFullCondition;
MinCondition = minCondition;
MaxCondition = maxCondition;
OutCondition = outCondition;
}
}
@@ -301,9 +305,14 @@ namespace Barotrauma
{
string deconstructItemName = deconstructItem.GetAttributeString("name", "not found");
bool requireFullCondition = deconstructItem.GetAttributeBool("requirefullcondition", false);
//minCondition does <= check, meaning that below or equeal to min condition will be skipped.
float minCondition = deconstructItem.GetAttributeFloat("mincondition", -0.1f);
//maxCondition does > check, meaning that above this max the deconstruct item will be skipped.
float maxCondition = deconstructItem.GetAttributeFloat("maxcondition", 1.0f);
//Condition of item on creation
float outCondition = deconstructItem.GetAttributeFloat("outcondition", 1.0f);
DeconstructItems.Add(new DeconstructItem(deconstructItemName, requireFullCondition));
DeconstructItems.Add(new DeconstructItem(deconstructItemName, minCondition, maxCondition, outCondition));
}
@@ -518,7 +518,7 @@ namespace Barotrauma
float impact = Vector2.Dot(f2.Body.LinearVelocity, -normal)*f2.Body.Mass*0.1f;
#if CLIENT
SoundPlayer.PlayDamageSound(DamageSoundType.StructureBlunt, impact,
SoundPlayer.PlayDamageSound("StructureBlunt", impact,
new Vector2(
sections[section].rect.X + sections[section].rect.Width / 2,
sections[section].rect.Y - sections[section].rect.Height / 2), tags: Tags);
@@ -679,7 +679,7 @@ namespace Barotrauma
#if CLIENT
if (playSound)// && !SectionBodyDisabled(i))
{
DamageSoundType damageSoundType = (attack.DamageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt : DamageSoundType.StructureSlash;
string damageSoundType = (attack.DamageType == DamageType.Blunt) ? "StructureBlunt" : "StructureSlash";
SoundPlayer.PlayDamageSound(damageSoundType, damageAmount, worldPosition, tags: Tags);
}
#endif
@@ -681,7 +681,7 @@ namespace Barotrauma
if (maxDamageStructure != null)
{
SoundPlayer.PlayDamageSound(
DamageSoundType.StructureBlunt,
"StructureBlunt",
impact * 10.0f,
ConvertUnits.ToDisplayUnits(lastContactPoint),
MathHelper.Clamp(maxDamage * 4.0f, 1000.0f, 4000.0f),
@@ -22,24 +22,28 @@ namespace Barotrauma
public readonly Vector2 Position;
public readonly Inventory Inventory;
public readonly Submarine Submarine;
public readonly float Condition;
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition)
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, float? condition = null)
{
Prefab = prefab;
Position = worldPosition;
Condition = (float)(condition ?? prefab.Health);
}
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub)
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, float? condition = null)
{
Prefab = prefab;
Position = position;
Submarine = sub;
Condition = (float)(condition ?? prefab.Health);
}
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory)
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, float? condition = null)
{
Prefab = prefab;
Inventory = inventory;
Condition = (float)(condition ?? prefab.Health);
}
public Entity Spawn()
@@ -48,12 +52,12 @@ namespace Barotrauma
if (Inventory != null)
{
spawnedItem = new Item(Prefab, Vector2.Zero, null);
spawnedItem = new Item(Prefab, Vector2.Zero, null, Condition);
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots);
}
else
{
spawnedItem = new Item(Prefab, Position, Submarine);
spawnedItem = new Item(Prefab, Position, Submarine, Condition);
}
return spawnedItem;
@@ -83,25 +87,25 @@ namespace Barotrauma
removeQueue = new Queue<Entity>();
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null)
{
if (GameMain.Client != null) return;
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition));
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, condition));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null)
{
if (GameMain.Client != null) return;
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub));
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, condition));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory)
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null)
{
if (GameMain.Client != null) return;
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory));
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, condition));
}
public void AddToRemoveQueue(Entity entity)
@@ -1509,7 +1509,9 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Running;
} while (cinematic.Running);//(secondsLeft > 0.0f);
#if CLIENT
SoundPlayer.OverrideMusicType = null;
#endif
Submarine.Unload();
entityEventManager.Clear();