using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma { class ScriptedEvent : Event { private readonly Dictionary>> targetPredicates = new Dictionary>>(); private readonly Dictionary> cachedTargets = new Dictionary>(); private int prevEntityCount; private int prevPlayerCount, prevBotCount; private Character prevControlled; private readonly string[] requiredDestinationTypes; public readonly bool RequireBeaconStation; public int CurrentActionIndex { get; private set; } public List Actions { get; } = new List(); public Dictionary> Targets { get; } = new Dictionary>(); public override string ToString() { return $"ScriptedEvent ({prefab.Identifier})"; } public ScriptedEvent(EventPrefab prefab) : base(prefab) { foreach (var element in prefab.ConfigElement.Elements()) { if (element.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction."); continue; } var action = EventAction.Instantiate(this, element); if (action != null) { Actions.Add(action); } } if (!Actions.Any()) { DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing."); } requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null); RequireBeaconStation = prefab.ConfigElement.GetAttributeBool("requirebeaconstation", false); GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Start"); } public void AddTarget(Identifier tag, Entity target) { if (target == null) { throw new System.ArgumentException("Target was null"); } if (target.Removed) { throw new System.ArgumentException("Target has been removed"); } if (!Targets.ContainsKey(tag)) { Targets.Add(tag, new List()); } Targets[tag].Add(target); if (cachedTargets.ContainsKey(tag)) { cachedTargets[tag].Add(target); } else { cachedTargets.Add(tag, new List { target }); } } public void AddTargetPredicate(Identifier tag, Predicate predicate) { if (!targetPredicates.ContainsKey(tag)) { targetPredicates.Add(tag, new List>()); } targetPredicates[tag].Add(predicate); // force re-search for this tag if (cachedTargets.ContainsKey(tag)) { cachedTargets.Remove(tag); } } public IEnumerable GetTargets(Identifier tag) { if (cachedTargets.ContainsKey(tag)) { if (cachedTargets[tag].Any(t => t.Removed)) { cachedTargets.Clear(); } else { return cachedTargets[tag]; } } List targetsToReturn = new List(); if (Targets.ContainsKey(tag)) { foreach (Entity e in Targets[tag]) { if (e.Removed) { continue; } targetsToReturn.Add(e); } } if (targetPredicates.ContainsKey(tag)) { foreach (Entity entity in Entity.GetEntities()) { if (targetPredicates[tag].Any(p => p(entity))) { targetsToReturn.Add(entity); } } } foreach (WayPoint wayPoint in WayPoint.WayPointList) { if (wayPoint.Tags.Contains(tag)) { targetsToReturn.Add(wayPoint); } } if (Level.Loaded?.StartOutpost != null && Level.Loaded.StartOutpost.Info.OutpostNPCs.TryGetValue(tag, out List outpostNPCs)) { foreach (Character npc in outpostNPCs) { if (npc.Removed) { continue; } targetsToReturn.Add(npc); } } cachedTargets.Add(tag, targetsToReturn); return targetsToReturn; } public void RemoveTag(Identifier tag) { if (tag.IsEmpty) { return; } if (Targets.ContainsKey(tag)) { Targets.Remove(tag); } if (cachedTargets.ContainsKey(tag)) { cachedTargets.Remove(tag); } if (targetPredicates.ContainsKey(tag)) { targetPredicates.Remove(tag); } } public override void Update(float deltaTime) { int botCount = 0; int playerCount = 0; foreach (Character c in Character.CharacterList) { if (c.IsPlayer) { playerCount++; } else if (c.IsBot) { botCount++; } } if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled != Character.Controlled) { cachedTargets.Clear(); prevEntityCount = Entity.EntityCount; prevBotCount = botCount; prevPlayerCount = playerCount; prevControlled = Character.Controlled; } if (!Actions.Any()) { Finish(); return; } var currentAction = Actions[CurrentActionIndex]; if (!currentAction.CanBeFinished()) { Finish(); return; } string goTo = null; if (currentAction.IsFinished(ref goTo)) { if (string.IsNullOrEmpty(goTo)) { CurrentActionIndex++; } else { CurrentActionIndex = -1; Actions.ForEach(a => a.Reset()); for (int i = 0; i < Actions.Count; i++) { if (Actions[i].SetGoToTarget(goTo)) { CurrentActionIndex = i; break; } } } if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0) { Finish(); } } else { currentAction.Update(deltaTime); } } public override bool LevelMeetsRequirements() { if (requiredDestinationTypes == null) { return true; } var currLocation = GameMain.GameSession?.Campaign?.Map.CurrentLocation; if (currLocation?.Connections == null) { return true; } foreach (LocationConnection c in currLocation.Connections) { if (RequireBeaconStation && !c.LevelData.HasBeaconStation) { continue; } if (requiredDestinationTypes.Any(t => c.OtherLocation(currLocation).Type.Identifier == t)) { return true; } } return false; } public override void Finish() { base.Finish(); GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}"); } } }