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).Where(ep => !list.Contains(ep)));
137 private readonly
int eventCount = 1;
139 private readonly Dictionary<Identifier, int> overrideEventCount =
new Dictionary<Identifier, int>();
289 public void Deconstruct(out IEnumerable<EventPrefab> eventPrefabs, out
float commonness, out
float probability)
300 foreach (var
id in ids)
324 throw new Exception($
"Error in {file.Path}: All root EventSets in a core package must have identifiers");
328 DebugConsole.AddWarning($
"{file.Path}: All root EventSets should have an identifier",
333 XElement currElement = element;
334 string siblingIndices =
"";
335 while (currElement.Parent !=
null)
337 int siblingIndex = currElement.ElementsBeforeSelf().Count();
338 siblingIndices = $
"-{siblingIndex}{siblingIndices}";
339 if (parent !=
null) {
break; }
340 currElement = currElement.Parent;
346 : $
"{file.ContentPackage.Name}-{file.Path}")
354 : base(file, DetermineIdentifier(parentSet, element, file))
356 var eventPrefabs =
new List<SubEventPrefab>();
357 var childSets =
new List<EventSet>();
358 var overrideCommonness =
new Dictionary<Identifier, float>();
368 if (!Enum.TryParse(levelTypeStr,
true, out
LevelType))
370 DebugConsole.ThrowError($
"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.",
377 if (locationTypeStr !=
null)
414 DebugConsole.ThrowError($
"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior",
419 foreach (var subElement
in element.Elements())
421 switch (subElement.Name.ToString().ToLowerInvariant())
425 foreach (XElement overrideElement
in subElement.Elements())
427 if (overrideElement.NameAsIdentifier() ==
"override")
429 Identifier levelType = overrideElement.GetAttributeIdentifier(
"leveltype",
"");
430 if (!overrideCommonness.ContainsKey(levelType))
432 overrideCommonness.Add(levelType, overrideElement.GetAttributeFloat(
"commonness", 0.0f));
438 childSets.Add(
new EventSet(subElement, file,
this));
440 case "overrideeventcount":
441 Identifier locationType = subElement.GetAttributeIdentifier(
"locationtype",
"");
442 if (!overrideEventCount.ContainsKey(locationType))
444 overrideEventCount.Add(locationType, subElement.GetAttributeInt(
"eventcount", eventCount));
449 if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals(
"identifier", StringComparison.OrdinalIgnoreCase))
451 Identifier[] identifiers = subElement.GetAttributeIdentifierArray(
"identifier", Array.Empty<
Identifier>());
452 float commonness = subElement.GetAttributeFloat(
"commonness", -1f);
453 float probability = subElement.GetAttributeFloat(
"probability", -1f);
457 commonness >= 0f ? commonness : (
float?)
null,
458 probability >= 0f ? probability : (
float?)
null,
463 var prefab =
new EventPrefab(subElement, file, $
"{Identifier}-{subElement.ElementsBeforeSelf().Count()}".ToIdentifier());
464 eventPrefabs.Add(
new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability, prefab.Faction));
471 ChildSets = childSets.ToImmutableArray();
476 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.");
487 DebugConsole.ThrowError($
"Error in event set \"{Identifier}\". Location type \"{locationTypeId}\" not found.");
497 return generationParamsCommonness;
502 return startOutpostParamsCommonness;
507 return endOutpostParamsCommonness;
514 int finishedEventCount = 0;
515 if (level is not
null)
521 return eventCount - finishedEventCount;
523 return count - finishedEventCount;
526 public static List<string>
GetDebugStatistics(
int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter =
null,
bool fullLog =
false)
528 List<string> debugLines =
new List<string>();
530 foreach (var eventSet
in Prefabs)
532 List<EventDebugStats> stats =
new List<EventDebugStats>();
533 for (
int i = 0; i < simulatedRoundCount; i++)
535 var newStats =
new EventDebugStats(eventSet);
536 CheckEventSet(newStats, eventSet, filter);
539 debugLines.Add($
"Event stats ({eventSet.Identifier}): ");
540 LogEventStats(stats, debugLines, fullLog);
545 static void CheckEventSet(EventDebugStats stats,
EventSet thisSet, Func<MonsterEvent, bool> filter =
null)
550 if (unusedEvents.Any())
552 for (
int i = 0; i < thisSet.eventCount; i++)
554 var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
555 if (eventPrefab.EventPrefabs.Any(p => p !=
null))
557 AddEvents(stats, eventPrefab.EventPrefabs, filter);
558 unusedEvents.Remove(eventPrefab);
563 .SelectMany(s => s.DefaultCommonness.ToEnumerable().Concat(s.OverrideCommonness.Values))
565 EventSet childSet = ToolBox.SelectWeightedRandom(thisSet.
ChildSets, values, Rand.RandSync.Unsynced);
566 if (childSet !=
null)
568 CheckEventSet(stats, childSet, filter);
575 AddEvents(stats, eventPrefab.EventPrefabs, filter);
577 foreach (var childSet
in thisSet.
ChildSets)
579 CheckEventSet(stats, childSet, filter);
584 static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs, Func<MonsterEvent, bool> filter =
null)
585 => eventPrefabs.ForEach(p => AddEvent(stats, p, filter));
587 static void AddEvent(EventDebugStats stats,
EventPrefab eventPrefab, Func<MonsterEvent, bool> filter =
null)
592 if (filter !=
null && !filter(monsterEvent)) {
return; }
593 float spawnProbability = monsterEvent.Prefab?.Probability ?? 0.0f;
594 if (Rand.Value() > spawnProbability) {
return; }
595 int count = Rand.Range(monsterEvent.MinAmount, monsterEvent.MaxAmount + 1);
596 if (count <= 0) {
return; }
597 Identifier character = monsterEvent.SpeciesName;
598 if (stats.MonsterCounts.TryGetValue(character, out
int currentCount))
600 if (currentCount >= monsterEvent.MaxAmountPerLevel) {
return; }
604 stats.MonsterCounts[character] = 0;
606 stats.MonsterCounts[character] += count;
609 if (aiElement !=
null)
611 stats.MonsterStrength += aiElement.GetAttributeFloat(
"combatstrength", 0) * count;
616 static void LogEventStats(List<EventDebugStats> stats, List<string> debugLines,
bool fullLog)
618 if (stats.Count == 0 || stats.All(s => s.MonsterCounts.Values.Sum() == 0))
620 debugLines.Add(
" No monster spawns");
621 debugLines.Add($
" ");
625 var allMonsters =
new Dictionary<Identifier, int>();
626 foreach (var stat
in stats)
628 foreach (var monster
in stat.MonsterCounts)
630 if (!allMonsters.TryAdd(monster.Key, monster.Value))
632 allMonsters[monster.Key] += monster.Value;
636 allMonsters = allMonsters.OrderBy(m => m.Key).ToDictionary(m => m.Key, m => m.Value);
637 stats.Sort((s1, s2) => s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()));
638 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()})");
639 debugLines.Add($
" {LogMonsterCounts(allMonsters, divider: stats.Count)}");
642 debugLines.Add($
" All samples:");
643 stats.ForEach(s => debugLines.Add($
" {LogMonsterCounts(s.MonsterCounts)}"));
645 stats.Sort((s1, s2) => s1.MonsterStrength.CompareTo(s2.MonsterStrength));
646 debugLines.Add($
" Average monster strength: {StringFormatter.FormatZeroDecimal(stats.Average(s => s.MonsterStrength))} (Min: {StringFormatter.FormatZeroDecimal(stats.First().MonsterStrength)}, Max: {StringFormatter.FormatZeroDecimal(stats.Last().MonsterStrength)})");
647 debugLines.Add($
" ");
651 static string LogMonsterCounts(Dictionary<Identifier, int> stats,
float divider = 0)
655 return string.Join(
"\n ", stats.Select(mc => mc.Key +
" x " + (mc.Value / divider).FormatSingleDecimal()));
659 return string.Join(
", ", stats.Select(mc => mc.Key +
" x " + mc.Value));
666 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 Identifier RequiredSpawnPointTag
If set, this spawn point tag must be present somewhere in the level.
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?...
readonly bool SelectAlways
This will force the game to always choose this event set if it's suitable for the current level....
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