3 using System.Collections.Generic;
12 Predicate<Entity> Predicate)
14 public enum EntityType
24 private readonly Dictionary<Identifier, List<TargetPredicate>> targetPredicates =
new Dictionary<Identifier, List<TargetPredicate>>();
26 private readonly Dictionary<Identifier, List<Entity>> cachedTargets =
new Dictionary<Identifier, List<Entity>>();
32 private readonly Dictionary<Identifier, int> initialAmounts =
new Dictionary<Identifier, int>();
34 private bool newEntitySpawned;
35 private int prevPlayerCount, prevBotCount;
40 private readonly Identifier[] requiredDestinationTypes;
46 public List<EventAction>
Actions {
get; } =
new List<EventAction>();
47 public Dictionary<Identifier, List<Entity>>
Targets {
get; } =
new Dictionary<Identifier, List<Entity>>();
53 return $
"{nameof(ScriptedEvent)} ({prefab.Identifier})";
60 Identifier elementId = element.Name.ToIdentifier();
67 if (elementId ==
"statuseffect")
69 DebugConsole.ThrowError($
"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction.",
74 if (action !=
null) {
Actions.Add(action); }
79 DebugConsole.ThrowError($
"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.",
88 var allActions = allActionsWithIndent.Select(a => a.action);
91 foreach (var action
in allActions)
95 int thisActionIndex = allActionsWithIndent.FindIndex(a => a.action == action);
96 int thisIndentationLevel = allActionsWithIndent[thisActionIndex].indent;
100 foreach (var actionWithIndent
in allActionsWithIndent.Skip(thisActionIndex + 1))
103 if (actionWithIndent.action is
ConversationAction && actionWithIndent.indent == thisIndentationLevel)
109 if (actionWithIndent.indent < thisIndentationLevel) {
break; }
113 foreach (var option
in conversationAction.Options)
115 if (!conversationAction.GetEndingOptions().Contains(conversationAction.Options.IndexOf(option)) &&
116 option.Actions.None(a =>
119 a is
GoTo { EndConversation:
false }))
121 DebugConsole.AddWarning($
"Potential error in event \"{prefab.Identifier}\": {nameof(ConversationAction)} ({conversationAction.Text}) has an option ({option.Text}) that doesn't end the conversation, but could not find any follow-ups to the conversation.");
127 static bool HasConversationSubAction(
EventAction action)
132 if (HasConversationSubAction(subAction)) {
return true; }
138 foreach (var label
in allActions.OfType<
Label>())
140 if (allActions.None(a => a is
GoTo gotoAction && label.
Name == gotoAction.Name))
143 DebugConsole.AddWarning($
"Error in event \"{prefab.Identifier}\". Could not find a GoTo matching the Label \"{label.Name}\".",
148 foreach (var gotoAction
in allActions.OfType<
GoTo>())
150 int labelCount = allActions.Count(a => a is
Label label && label.
Name == gotoAction.Name);
153 DebugConsole.ThrowError($
"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\".",
156 else if (labelCount > 1)
158 DebugConsole.ThrowError($
"Error in event \"{prefab.Identifier}\". Multiple labels with the name \"{gotoAction.Name}\".",
163 GameAnalyticsManager.AddDesignEvent($
"ScriptedEvent:{prefab.Identifier}:Start");
170 string text = $
"Finished: {IsFinished.ColorizeObject()}\n" +
171 $
"Action index: {CurrentActionIndex.ColorizeObject()}\n" +
172 $
"Current action: {currentAction?.ToDebugString() ?? ToolBox.ColorizeObject(null)}\n";
174 text +=
"All actions:\n";
175 text +=
GetAllActions().Aggregate(
string.Empty, (current, action) => current + $
"{new string(' ', action.indent * 6)}{action.action.ToDebugString()}\n");
177 text +=
"Targets:\n";
178 foreach (var (key, value) in
Targets)
180 text += $
" {key.ColorizeObject()}: {value.Aggregate(string.Empty, (current, entity) => current + $"{entity.ColorizeObject()}
")}\n";
187 if (tag.StartsWith(
"eventtag:"))
189 string targetTag = tag[
"eventtag:".Length..];
193 if (target is
Item item) {
return item.Name; }
194 if (target is
Character character) {
return character.Name; }
195 if (target is
Hull hull) {
return hull.DisplayName.Value; }
196 if (target is
Submarine sub) {
return sub.Info.DisplayName.Value; }
197 DebugConsole.AddWarning($
"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text.",
199 return target.ToString();
203 return $
"[target \"{targetTag}\" not found]";
220 var list =
new List<(int indent, EventAction action)>();
223 list.AddRange(FindActionsRecursive(eventAction));
227 static List<(
int indent,
EventAction action)> FindActionsRecursive(
EventAction eventAction,
int indent = 1)
229 var eventActions =
new List<(
int indent,
EventAction action)> { (indent, eventAction) };
233 eventActions.AddRange(FindActionsRecursive(action, indent));
243 throw new ArgumentException($
"Target was null (tag: {tag})");
247 throw new ArgumentException($
"Target has been removed (tag: {tag})");
251 if (!
Targets[tag].Contains(target))
258 Targets.Add(tag,
new List<Entity>() { target });
260 if (cachedTargets.ContainsKey(tag))
262 if (!cachedTargets[tag].Contains(target))
264 cachedTargets[tag].Add(target);
269 cachedTargets.Add(tag,
Targets[tag].ToList());
271 if (!initialAmounts.ContainsKey(tag))
273 initialAmounts.Add(tag, cachedTargets[tag].Count);
279 if (!targetPredicates.ContainsKey(tag))
281 targetPredicates.Add(tag,
new List<TargetPredicate>());
285 if (cachedTargets.ContainsKey(tag))
287 cachedTargets.Remove(tag);
293 if (initialAmounts.TryGetValue(tag, out
int count))
302 if (cachedTargets.ContainsKey(tag))
304 if (cachedTargets[tag].
Any(t => t.Removed))
306 cachedTargets.Clear();
310 return cachedTargets[tag];
314 List<Entity> targetsToReturn =
new List<Entity>();
320 targetsToReturn.Add(e);
323 if (targetPredicates.ContainsKey(tag))
325 foreach (var targetPredicate
in targetPredicates[tag])
327 IEnumerable<Entity> entityList = targetPredicate.Type
switch
336 foreach (
Entity entity
in entityList)
338 if (targetsToReturn.Contains(entity)) {
continue; }
339 if (targetPredicate.Predicate(entity))
341 targetsToReturn.Add(entity);
348 if (wayPoint.
Tags.Contains(tag)) { targetsToReturn.Add(wayPoint); }
355 if (npc.
Removed || targetsToReturn.Contains(npc)) {
continue; }
356 targetsToReturn.Add(npc);
360 cachedTargets.Add(tag, targetsToReturn);
361 if (!initialAmounts.ContainsKey(tag))
363 initialAmounts.Add(tag, targetsToReturn.Count);
365 return targetsToReturn;
372 if (kvp.Value.Contains(originalEntity))
374 kvp.Value.Add(newEntity);
381 if (tag.IsEmpty) {
return; }
383 if (cachedTargets.ContainsKey(tag)) { cachedTargets.Remove(tag); }
384 if (targetPredicates.ContainsKey(tag)) { targetPredicates.Remove(tag); }
387 public override void Update(
float deltaTime)
403 if (botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled !=
Character.
Controlled || NeedsToRefreshCachedTargets())
405 cachedTargets.Clear();
406 newEntitySpawned =
false;
407 prevBotCount = botCount;
408 prevPlayerCount = playerCount;
419 if (!currentAction.CanBeFinished())
426 if (currentAction.IsFinished(ref goTo))
428 if (
string.IsNullOrEmpty(goTo))
435 Actions.ForEach(a => a.Reset());
436 for (
int i = 0; i <
Actions.Count; i++)
438 if (
Actions[i].SetGoToTarget(goTo))
446 DebugConsole.AddWarning($
"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event.",
458 currentAction.Update(deltaTime);
462 private bool NeedsToRefreshCachedTargets()
464 if (newEntitySpawned) {
return true; }
465 foreach (var cachedTargetList
in cachedTargets.Values)
467 foreach (var target
in cachedTargetList)
481 if (newEntitySpawned) {
return; }
486 newEntitySpawned =
true;
490 foreach (var targetPredicateList
in targetPredicates.Values)
492 foreach (var targetPredicate
in targetPredicateList)
494 if (targetPredicate.Predicate(entity))
496 newEntitySpawned =
true;
506 if (currLocation?.Connections ==
null) {
return true; }
514 if (requiredDestinationTypes.Contains(Tags.AnyOutpost) && otherLocation.HasOutpost() && otherLocation.Type.IsAnyOutpost) {
return true; }
515 if (requiredDestinationTypes.Any(t => otherLocation.Type.Identifier == t))
527 GameAnalyticsManager.AddDesignEvent($
"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}");
static readonly List< Character > CharacterList
static Character? Controlled
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
Identifier GetAttributeIdentifier(string key, string def)
Triggers a "conversation popup" with text and support for different branching options.
List< SubactionGroup > Options
static IReadOnlyCollection< Entity > GetEntities()
virtual IEnumerable< EventAction > GetSubActions()
static EventAction Instantiate(ScriptedEvent scriptedEvent, ContentXElement element)
readonly EventPrefab prefab
readonly ContentXElement ConfigElement
static GameSession?? GameSession
Makes the event jump to a Label somewhere else in the event.
static readonly List< Hull > HullList
static readonly List< Item > ItemList
Defines a point in the event that GoTo actions can jump to.
Location OtherLocation(Location location)
static readonly List< MapEntity > MapEntityList
Executes all the child actions when the round ends.
ContentPackage? ContentPackage
virtual IEnumerable< Identifier > NonActionChildElementNames
readonly Identifier RequiredDestinationFaction
override string ToString()
override string GetDebugInfo()
int GetInitialTargetCount(Identifier tag)
ScriptedEvent(EventPrefab prefab, int seed)
sealed record TargetPredicate(TargetPredicate.EntityType Type, Predicate< Entity > Predicate)
readonly OnRoundEndAction OnRoundEndAction
void InheritTags(Entity originalEntity, Entity newEntity)
override void Update(float deltaTime)
List<(int indent, EventAction action)> GetAllActions()
Finds all actions in the ScriptedEvent using a depth-first search (recursively going through the suba...
virtual LocalizedString ReplaceVariablesInEventText(LocalizedString str)
readonly bool RequireBeaconStation
void RemoveTag(Identifier tag)
void AddTargetPredicate(Identifier tag, TargetPredicate.EntityType entityType, Predicate< Entity > predicate)
Dictionary< Identifier, List< Entity > > Targets
void EntitySpawned(Entity entity)
void AddTarget(Identifier tag, Entity target)
override bool LevelMeetsRequirements()
List< EventAction > Actions
IEnumerable< Entity > GetTargets(Identifier tag)
virtual string GetTextForReplacementElement(string tag)
static List< Submarine > Loaded
readonly Dictionary< Identifier, List< Character > > OutpostNPCs
static List< WayPoint > WayPointList
IEnumerable< Identifier > Tags