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 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 targetNames; private List requiredItems; #if CLIENT private List particleEmitters; private Sound sound; private bool loopSound; #endif public string[] propertyNames; private object[] propertyEffects; List propertyConditionals; private bool setValue; private bool disableDeltaTime; private HashSet onContainingNames; private HashSet tags; private readonly float duration; public static List DurationList = new List(); 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 TargetNames { get { return targetNames; } } public HashSet 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(); tags = new HashSet(element.GetAttributeString("tags", "").Split(',')); #if CLIENT particleEmitters = new List(); #endif IEnumerable attributes = element.Attributes(); List propertyAttributes = new List(); propertyConditionals = new List(); 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(); 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(); 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 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 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 targets = new List(); targets.Add(target); if (!HasRequiredConditions(targets)) return; Apply(deltaTime, entity, targets); } public virtual void Apply(ActionType type, float deltaTime, Entity entity, List 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 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.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character); } } if (removeItem) { foreach (Item item in targets.FindAll(t => t is Item).Cast()) { 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())); } } }