3 using System.Collections.Generic;
4 using System.Collections.Immutable;
32 internal class EventDebugStats
35 public readonly Dictionary<Identifier, int> MonsterCounts =
new Dictionary<Identifier, int>();
36 public float MonsterStrength;
38 public EventDebugStats(
EventSet rootSet)
48 if (
string.IsNullOrWhiteSpace(identifier)) {
return null; }
56 DebugConsole.ThrowError($
"Could not find the event sprite \"{identifier}\"");
58 DebugConsole.AddWarning($
"Could not find the event sprite \"{identifier}\"");
67 foreach (var eventSet
in Prefabs)
76 list.AddRange(
set.
EventPrefabs.SelectMany(ep => ep.EventPrefabs));
132 private readonly
int eventCount = 1;
134 private readonly Dictionary<Identifier, int> overrideEventCount =
new Dictionary<Identifier, int>();
277 public void Deconstruct(out IEnumerable<EventPrefab> eventPrefabs, out
float commonness, out
float probability)
288 foreach (var
id in ids)
312 throw new Exception($
"Error in {file.Path}: All root EventSets in a core package must have identifiers");
316 DebugConsole.AddWarning($
"{file.Path}: All root EventSets should have an identifier",
321 XElement currElement = element;
322 string siblingIndices =
"";
323 while (currElement.Parent !=
null)
325 int siblingIndex = currElement.ElementsBeforeSelf().Count();
326 siblingIndices = $
"-{siblingIndex}{siblingIndices}";
327 if (parent !=
null) {
break; }
328 currElement = currElement.Parent;
334 : $
"{file.ContentPackage.Name}-{file.Path}")
342 : base(file, DetermineIdentifier(parentSet, element, file))
344 var eventPrefabs =
new List<SubEventPrefab>();
345 var childSets =
new List<EventSet>();
346 var overrideCommonness =
new Dictionary<Identifier, float>();
355 if (!Enum.TryParse(levelTypeStr,
true, out
LevelType))
357 DebugConsole.ThrowError($
"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.",
364 if (locationTypeStr !=
null)
400 DebugConsole.ThrowError($
"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior",
405 foreach (var subElement
in element.Elements())
407 switch (subElement.Name.ToString().ToLowerInvariant())
411 foreach (XElement overrideElement
in subElement.Elements())
413 if (overrideElement.NameAsIdentifier() ==
"override")
415 Identifier levelType = overrideElement.GetAttributeIdentifier(
"leveltype",
"");
416 if (!overrideCommonness.ContainsKey(levelType))
418 overrideCommonness.Add(levelType, overrideElement.GetAttributeFloat(
"commonness", 0.0f));
424 childSets.Add(
new EventSet(subElement, file,
this));
426 case "overrideeventcount":
427 Identifier locationType = subElement.GetAttributeIdentifier(
"locationtype",
"");
428 if (!overrideEventCount.ContainsKey(locationType))
430 overrideEventCount.Add(locationType, subElement.GetAttributeInt(
"eventcount", eventCount));
435 if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals(
"identifier", StringComparison.OrdinalIgnoreCase))
437 Identifier[] identifiers = subElement.GetAttributeIdentifierArray(
"identifier", Array.Empty<
Identifier>());
438 float commonness = subElement.GetAttributeFloat(
"commonness", -1f);
439 float probability = subElement.GetAttributeFloat(
"probability", -1f);
443 commonness >= 0f ? commonness : (
float?)
null,
444 probability >= 0f ? probability : (
float?)
null,
449 var prefab =
new EventPrefab(subElement, file, $
"{Identifier}-{subElement.ElementsBeforeSelf().Count()}".ToIdentifier());
450 eventPrefabs.Add(
new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability, prefab.Faction));
457 ChildSets = childSets.ToImmutableArray();
462 DebugConsole.AddWarning($
"Error in event set \"{Identifier}\". Only one of the settings {nameof(PerRuin)}, {nameof(PerCave)} or {nameof(PerWreck)} can be enabled at the time.");
473 DebugConsole.ThrowError($
"Error in event set \"{Identifier}\". Location type \"{locationTypeId}\" not found.");
483 return generationParamsCommonness;
488 return startOutpostParamsCommonness;
493 return endOutpostParamsCommonness;
500 int finishedEventCount = 0;
501 if (level is not
null)
507 return eventCount - finishedEventCount;
509 return count - finishedEventCount;
512 public static List<string>
GetDebugStatistics(
int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter =
null,
bool fullLog =
false)
514 List<string> debugLines =
new List<string>();
516 foreach (var eventSet
in Prefabs)
518 List<EventDebugStats> stats =
new List<EventDebugStats>();
519 for (
int i = 0; i < simulatedRoundCount; i++)
521 var newStats =
new EventDebugStats(eventSet);
522 CheckEventSet(newStats, eventSet, filter);
525 debugLines.Add($
"Event stats ({eventSet.Identifier}): ");
526 LogEventStats(stats, debugLines, fullLog);
531 static void CheckEventSet(EventDebugStats stats,
EventSet thisSet, Func<MonsterEvent, bool> filter =
null)
536 if (unusedEvents.Any())
538 for (
int i = 0; i < thisSet.eventCount; i++)
540 var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
541 if (eventPrefab.EventPrefabs.Any(p => p !=
null))
543 AddEvents(stats, eventPrefab.EventPrefabs, filter);
544 unusedEvents.Remove(eventPrefab);
549 .SelectMany(s => s.DefaultCommonness.ToEnumerable().Concat(s.OverrideCommonness.Values))
551 EventSet childSet = ToolBox.SelectWeightedRandom(thisSet.
ChildSets, values, Rand.RandSync.Unsynced);
552 if (childSet !=
null)
554 CheckEventSet(stats, childSet, filter);
561 AddEvents(stats, eventPrefab.EventPrefabs, filter);
563 foreach (var childSet
in thisSet.
ChildSets)
565 CheckEventSet(stats, childSet, filter);
570 static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs, Func<MonsterEvent, bool> filter =
null)
571 => eventPrefabs.ForEach(p => AddEvent(stats, p, filter));
573 static void AddEvent(EventDebugStats stats,
EventPrefab eventPrefab, Func<MonsterEvent, bool> filter =
null)
578 if (filter !=
null && !filter(monsterEvent)) {
return; }
579 float spawnProbability = monsterEvent.Prefab?.Probability ?? 0.0f;
580 if (Rand.Value() > spawnProbability) {
return; }
581 int count = Rand.Range(monsterEvent.MinAmount, monsterEvent.MaxAmount + 1);
582 if (count <= 0) {
return; }
583 Identifier character = monsterEvent.SpeciesName;
584 if (stats.MonsterCounts.TryGetValue(character, out
int currentCount))
586 if (currentCount >= monsterEvent.MaxAmountPerLevel) {
return; }
590 stats.MonsterCounts[character] = 0;
592 stats.MonsterCounts[character] += count;
595 if (aiElement !=
null)
597 stats.MonsterStrength += aiElement.GetAttributeFloat(
"combatstrength", 0) * count;
602 static void LogEventStats(List<EventDebugStats> stats, List<string> debugLines,
bool fullLog)
604 if (stats.Count == 0 || stats.All(s => s.MonsterCounts.Values.Sum() == 0))
606 debugLines.Add(
" No monster spawns");
607 debugLines.Add($
" ");
611 var allMonsters =
new Dictionary<Identifier, int>();
612 foreach (var stat
in stats)
614 foreach (var monster
in stat.MonsterCounts)
616 if (!allMonsters.TryAdd(monster.Key, monster.Value))
618 allMonsters[monster.Key] += monster.Value;
622 allMonsters = allMonsters.OrderBy(m => m.Key).ToDictionary(m => m.Key, m => m.Value);
623 stats.Sort((s1, s2) => s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()));
624 debugLines.Add($
" Average monster count: {StringFormatter.FormatZeroDecimal((float)stats.Average(s => s.MonsterCounts.Values.Sum()))} (Min: {stats.First().MonsterCounts.Values.Sum()}, Max: {stats.Last().MonsterCounts.Values.Sum()})");
625 debugLines.Add($
" {LogMonsterCounts(allMonsters, divider: stats.Count)}");
628 debugLines.Add($
" All samples:");
629 stats.ForEach(s => debugLines.Add($
" {LogMonsterCounts(s.MonsterCounts)}"));
631 stats.Sort((s1, s2) => s1.MonsterStrength.CompareTo(s2.MonsterStrength));
632 debugLines.Add($
" Average monster strength: {StringFormatter.FormatZeroDecimal(stats.Average(s => s.MonsterStrength))} (Min: {StringFormatter.FormatZeroDecimal(stats.First().MonsterStrength)}, Max: {StringFormatter.FormatZeroDecimal(stats.Last().MonsterStrength)})");
633 debugLines.Add($
" ");
637 static string LogMonsterCounts(Dictionary<Identifier, int> stats,
float divider = 0)
641 return string.Join(
"\n ", stats.Select(mc => mc.Key +
" x " + (mc.Value / divider).FormatSingleDecimal()));
645 return string.Join(
", ", stats.Select(mc => mc.Key +
" x " + mc.Value));
652 return $
"{base.ToString()} ({Identifier.Value})";
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
ContentXElement ConfigElement
readonly ContentPackage ContentPackage
string? GetAttributeString(string key, string? def)
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
float GetAttributeFloat(string key, float def)
ContentPackage? ContentPackage
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
Identifier GetAttributeIdentifier(string key, string def)
static readonly PrefabCollection< EventPrefab > Prefabs
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
static EventPrefab GetEventPrefab(Identifier identifier)
static Sprite GetEventSprite(string identifier)
readonly bool PerWreck
The set is applied once per each wreck in the level. Can be used to ensure there's a consistent amoun...
readonly bool DisableInHuntingGrounds
If enabled, this event will not be applied if the level contains hunting grounds.
readonly bool ChooseRandom
If set, one event, or a sub event set, is chosen randomly from this set.
readonly bool AllowAtStart
If the event is not allowed at start, it won't become active until the submarine has moved at least 5...
EventSet(ContentXElement element, RandomEventsFile file, EventSet parentSet=null)
readonly bool TriggerEventCooldown
Should this event set trigger the event cooldown (during which no new events are created) when it bec...
readonly bool PerCave
The set is applied once per each cave in the level. Can be used to ensure there's a consistent amount...
readonly bool PerRuin
The set is applied once per each ruin in the level. Can be used to ensure there's a consistent amount...
readonly ImmutableArray< Identifier > LocationTypeIdentifiers
If set, the event set can only be chosen in locations of this type.
readonly bool IgnoreIntensity
Normally events can only trigger if the intensity of the situation is low enough (e....
readonly LevelData.LevelType LevelType
If set, the event set can only be chosen in this type of level (outpost level or a connection between...
readonly ImmutableArray< SubEventPrefab > EventPrefabs
readonly float ResetTime
If set, the event set can trigger again after this amount of seconds has passed since it last trigger...
readonly int ForceAtVisitedNr
Used to force an event set based on how many other outposts have been visited before this (used for c...
void CheckLocationTypeErrors()
int GetEventCount(Level level)
readonly float MinLevelDifficulty
The difficulty of the current level must be equal to or higher than this for this set to be chosen.
static readonly PrefabCollection< EventSet > Prefabs
readonly ImmutableArray< EventSet > ChildSets
readonly float MinIntensity
float GetCommonness(Level level)
readonly float MinMissionTime
The event set won't become active until the round has lasted at least this many seconds.
readonly float DefaultCommonness
The commonness of the event set (i.e. how likely it is for this specific set to be chosen).
readonly float MinDistanceTraveled
The event set won't become active until the submarine has travelled at least this far....
readonly bool Additive
Additive sets are important to be aware of when creating custom event sets! If an additive set gets c...
readonly int ForceAtDiscoveredNr
Used to force an event set based on how many other locations have been discovered before this (used f...
readonly ImmutableDictionary< Identifier, float > OverrideCommonness
readonly float MaxLevelDifficulty
The difficulty of the current level must be equal to or less than this for this set to be chosen.
readonly bool Exhaustible
'Exhaustible' sets won't appear in the same level until after one world step (~10 min,...
readonly Identifier RequiredLayer
If set, this layer must be present somewhere in the level.
readonly bool IsCampaignSet
If enabled, this set can only be chosen in the campaign mode.
static List< EventPrefab > GetAllEventPrefabs()
readonly bool CampaignTutorialOnly
If enabled, this set can only occur when the campaign tutorial is enabled (generally used for the tut...
static List< string > GetDebugStatistics(int simulatedRoundCount=100, Func< MonsterEvent, bool > filter=null, bool fullLog=false)
readonly bool OncePerLevel
If enabled, events from this set can only occur once in the level.
readonly bool DelayWhenCrewAway
Should the event set be delayed if at least half of the crew is away from the submarine?...
static void AddSetEventPrefabsToList(List< EventPrefab > list, EventSet set)
readonly bool IgnoreCoolDown
Normally an event (such as a monster spawn) triggers a cooldown during which no new events are create...
override string ToString()
readonly Identifier BiomeIdentifier
If set, the event set can only be chosen in this biome.
readonly Identifier Faction
If set, the event set can only be chosen in locations that belong to this faction.
EventSprite(ContentXElement element, RandomEventsFile file)
static readonly PrefabCollection< EventSprite > Prefabs
static GameSession?? GameSession
readonly EventManager EventManager
readonly Dictionary< EventSet, int > FinishedEvents
LevelGenerationParams GenerationParams
readonly LevelData LevelData
static readonly PrefabCollection< LocationType > Prefabs
readonly Identifier Identifier
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
OutpostGenerationParams OutpostGenerationParams
IEnumerable< Identifier > GetMissingIdentifiers()
readonly Identifier Faction
SubEventPrefab(Either< Identifier[], EventPrefab > prefabOrIdentifiers, float? commonness, float? probability, Identifier factionId)
readonly? float SelfCommonness
IEnumerable< EventPrefab > EventPrefabs
void Deconstruct(out IEnumerable< EventPrefab > eventPrefabs, out float commonness, out float probability)
readonly? float SelfProbability
readonly Either< Identifier[], EventPrefab > PrefabOrIdentifier