Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/StatusEffects/StatusEffect.cs

524 lines
18 KiB
C#

using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Particles;
#endif
namespace Barotrauma
{
class DurationListElement
{
public StatusEffect Parent;
public Entity Entity;
public List<ISerializableEntity> Targets;
public float Timer;
}
partial class StatusEffect
{
[Flags]
public enum TargetType
{
This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget = 32, Hull = 64
}
private TargetType targetTypes;
protected HashSet<string> targetNames;
private List<RelatedItem> requiredItems;
#if CLIENT
private List<ParticleEmitter> particleEmitters;
private Sound sound;
private bool loopSound;
#endif
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>();
public bool CheckConditionalAlways; //Always do the conditional checks for the duration/delay. If false, only check conditional on apply.
public bool Stackable = true; //Can the same status effect be applied several times to the same targets?
private readonly int useItemCount;
private readonly bool removeItem;
public readonly ActionType type;
private Explosion explosion;
public readonly float FireSize;
public TargetType Targets
{
get { return targetTypes; }
}
public HashSet<string> TargetNames
{
get { return targetNames; }
}
public HashSet<string> OnContainingNames
{
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)
{
return new DelayedEffect(element);
}
return new StatusEffect(element);
}
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();
List<XAttribute> propertyAttributes = new List<XAttribute>();
propertyConditionals = new List<PropertyConditional>();
foreach (XAttribute attribute in attributes)
{
switch (attribute.Name.ToString())
{
case "type":
try
{
type = (ActionType)Enum.Parse(typeof(ActionType), attribute.Value, true);
}
catch
{
string[] split = attribute.Value.Split('=');
type = (ActionType)Enum.Parse(typeof(ActionType), split[0], true);
string[] containingNames = split[1].Split(',');
onContainingNames = new HashSet<string>();
for (int i = 0; i < containingNames.Length; i++)
{
onContainingNames.Add(containingNames[i].Trim());
}
}
break;
case "target":
string[] Flags = attribute.Value.Split(',');
foreach (string s in Flags)
{
targetTypes |= (TargetType)Enum.Parse(typeof(TargetType), s, true);
}
break;
case "disabledeltatime":
disableDeltaTime = attribute.GetAttributeBool(false);
break;
case "setvalue":
setValue = attribute.GetAttributeBool(false);
break;
case "targetnames":
string[] names = attribute.Value.Split(',');
targetNames = new HashSet<string>();
for (int i=0; i < names.Length; i++ )
{
targetNames.Add(names[i].Trim());
}
break;
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.");
break;
default:
propertyAttributes.Add(attribute);
break;
}
}
int count = propertyAttributes.Count;
propertyNames = new string[count];
propertyEffects = new object[count];
int n = 0;
foreach (XAttribute attribute in propertyAttributes)
{
propertyNames[n] = attribute.Name.ToString().ToLowerInvariant();
propertyEffects[n] = XMLExtensions.GetAttributeObject(attribute);
n++;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "explosion":
explosion = new Explosion(subElement);
break;
case "fire":
FireSize = subElement.GetAttributeFloat("size",10.0f);
break;
case "use":
case "useitem":
useItemCount++;
break;
case "remove":
case "removeitem":
removeItem = true;
break;
case "requireditem":
case "requireditems":
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
if (newRequiredItem == null) continue;
requiredItems.Add(newRequiredItem);
break;
case "conditional":
IEnumerable<XAttribute> conditionalAttributes = subElement.Attributes();
foreach (XAttribute attribute in conditionalAttributes)
{
propertyConditionals.Add(new PropertyConditional(attribute));
}
break;
#if CLIENT
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
case "sound":
sound = Sound.Load(subElement);
loopSound = subElement.GetAttributeBool("loop", false);
break;
#endif
}
}
}
public virtual bool HasRequiredItems(Entity entity)
{
if (requiredItems == null) return true;
foreach (RelatedItem requiredItem in requiredItems)
{
Item item = entity as Item;
if (item != null)
{
if (!requiredItem.CheckRequirements(null, item)) return false;
}
Character character = entity as Character;
if (character != null)
{
if (!requiredItem.CheckRequirements(character, null)) return false;
}
}
return true;
}
public virtual bool HasRequiredConditions(List<ISerializableEntity> targets)
{
if (!propertyConditionals.Any()) return true;
foreach (ISerializableEntity target in targets)
{
if (target == null || target.SerializableProperties == null) continue;
foreach (PropertyConditional pc in propertyConditionals)
{
if (pc.Matches(target)) return true;
}
}
return false;
}
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)
{
//ignore if not stackable and there's already an identical statuseffect
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.Count == 1 && d.Targets[0] == target);
if (existingEffect != null)
{
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
return;
}
}
List<ISerializableEntity> targets = new List<ISerializableEntity>();
targets.Add(target);
if (!HasRequiredConditions(targets)) return;
Apply(deltaTime, entity, targets);
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets)
{
if (this.type != type) return;
//remove invalid targets
if (targetNames != null)
{
targets.RemoveAll(t =>
{
Item item = t as Item;
if (item == null)
{
return !targetNames.Contains(t.Name);
}
else
{
if (item.HasTag(targetNames)) return false;
if (item.Prefab.NameMatches(targetNames)) return false;
}
return true;
});
if (targets.Count == 0) return;
}
if (!HasRequiredItems(entity) || !HasRequiredConditions(targets)) return;
if (duration > 0.0f && !Stackable)
{
//ignore if not stackable and there's already an identical statuseffect
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(targets));
if (existingEffect != null)
{
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
return;
}
}
Apply(deltaTime, entity, targets);
}
protected void Apply(float deltaTime, Entity entity, List<ISerializableEntity> targets)
{
#if CLIENT
if (sound != null)
{
if (loopSound)
{
if (!Sounds.SoundManager.IsPlaying(sound))
{
sound.Play(entity.WorldPosition);
}
else
{
sound.UpdatePosition(entity.WorldPosition);
}
}
else
{
sound.Play(entity.WorldPosition);
}
}
#endif
for (int i = 0; i < useItemCount; i++)
{
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
{
item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character);
}
}
if (removeItem)
{
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
{
Entity.Spawner?.AddToRemoveQueue(item);
}
}
if (duration > 0.0f)
{
DurationListElement element = new DurationListElement();
element.Parent = this;
element.Timer = 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);
Hull hull = null;
if (entity is Character)
{
hull = ((Character)entity).AnimController.CurrentHull;
}
else if (entity is Item)
{
hull = ((Item)entity).CurrentHull;
}
if (FireSize > 0.0f)
{
var fire = new FireSource(entity.WorldPosition, hull);
fire.Size = new Vector2(FireSize, fire.Size.Y);
}
#if CLIENT
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(deltaTime, entity.WorldPosition, hull);
}
#endif
}
private void ApplyToProperty(SerializableProperty property, object value, float deltaTime)
{
if (disableDeltaTime || setValue) deltaTime = 1.0f;
Type type = value.GetType();
if (type == typeof(float) ||
(type == typeof(int) && property.GetValue() is float))
{
float floatValue = Convert.ToSingle(value) * deltaTime;
if (!setValue) floatValue += (float)property.GetValue();
property.TrySetValue(floatValue);
}
else if (type == typeof(int) && value is int)
{
int intValue = (int)((int)value * deltaTime);
if (!setValue) intValue += (int)property.GetValue();
property.TrySetValue(intValue);
}
else if (type == typeof(bool) && value is bool)
{
property.TrySetValue((bool)value);
}
else if (type == typeof(string))
{
property.TrySetValue((string)value);
}
else
{
DebugConsole.ThrowError("Couldn't apply value " + value.ToString() + " (" + type + ") to property \"" + property.Name + "\" (" + property.GetValue().GetType() + ")! "
+ "Make sure the type of the value set in the config files matches the type of the property.");
}
}
public static void UpdateAll(float deltaTime)
{
DelayedEffect.Update(deltaTime);
for (int i = DurationList.Count - 1; i >= 0; i--)
{
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.Timer -= deltaTime;
if (element.Timer > 0.0f) continue;
DurationList.Remove(element);
}
}
public static void StopAll()
{
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()));
}
}
}