4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
17 CONVERSATION_SELECTED_OPTION,
26 public readonly record
struct NetEventLogEntry(Identifier EventPrefabId, Identifier LogEntryId, string Text) : INetSerializableStruct;
29 public readonly record struct NetEventObjective(
30 EventObjectiveAction.SegmentActionType Type,
31 Identifier Identifier,
32 Identifier ObjectiveTag,
34 Identifier ParentObjectiveId,
35 bool CanBeCompleted) : INetSerializableStruct;
40 const float CalculateDistanceTraveledInterval = 5.0f;
42 const int MaxEventHistory = 20;
46 private readonly List<Sprite> preloadedSprites = new List<Sprite>();
50 private float currentIntensity;
52 private float targetIntensity;
54 private float musicIntensity;
60 private float eventThreshold = 0.2f;
63 private float eventCoolDown;
65 private float intensityUpdateTimer;
68 private float totalPathLength;
69 private float calculateDistanceTraveledTimer;
70 private float distanceTraveled;
72 private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger, monsterStrength;
78 private float roundDuration;
80 private bool isCrewAway;
82 const float CrewAwayResetDelay = 60.0f;
83 private float crewAwayResetTimer;
84 private float crewAwayDuration;
86 private readonly List<EventSet> pendingEventSets = new List<EventSet>();
88 private readonly Dictionary<EventSet, List<Event>> selectedEvents = new Dictionary<EventSet, List<Event>>();
90 private readonly List<Event> activeEvents = new List<Event>();
92 private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
93 private readonly HashSet<Identifier> nonRepeatableEvents = new HashSet<Identifier>();
97 private DateTime nextIntensityLogTime;
102 private readonly bool isClient;
106 get {
return currentIntensity; }
110 get {
return musicIntensity; }
115 get {
return activeEvents; }
122 private readonly
struct TimeStamp
124 public readonly
double Time;
127 public TimeStamp(
Event e)
130 Time = Timing.TotalTime;
134 private readonly List<TimeStamp> timeStamps =
new List<TimeStamp>();
153 if (isClient) {
return; }
156 pendingEventSets.Clear();
157 selectedEvents.Clear();
158 activeEvents.Clear();
163 totalPathLength = 0.0f;
167 totalPathLength = steeringPath.TotalLength;
177 RandomSeed ^= ToolBox.IdentifierToInt(previousEvent);
183 EventSet initialEventSet = SelectRandomEvents(
185 requireCampaignSet: playingCampaign,
188 if (initialEventSet !=
null && initialEventSet.
Additive)
190 additiveSet = initialEventSet;
191 initialEventSet = SelectRandomEvents(
193 requireCampaignSet: playingCampaign,
196 if (initialEventSet !=
null)
198 pendingEventSets.Add(initialEventSet);
199 CreateEvents(initialEventSet);
201 if (additiveSet !=
null)
203 pendingEventSets.Add(additiveSet);
204 CreateEvents(additiveSet);
215 if (unlockPathEventPrefab !=
null)
218 activeEvents.Add(newEvent);
227 RegisterNonRepeatableChildEvents(initialEventSet);
228 void RegisterNonRepeatableChildEvents(
EventSet eventSet)
230 if (eventSet ==
null) {
return; }
240 RegisterNonRepeatableChildEvents(childSet);
248 if (eventPrefab ==
null)
250 DebugConsole.ThrowError($
"Error in EventManager.StartRound - could not find an event with the identifier {id}.");
262 roundDuration = 0.0f;
263 eventsInitialized =
false;
265 crewAwayDuration = 0.0f;
266 crewAwayResetTimer = 0.0f;
267 intensityUpdateTimer = 0.0f;
268 CalculateCurrentIntensity(0.0f);
269 currentIntensity = musicIntensity = targetIntensity;
270 eventCoolDown = 0.0f;
275 distanceTraveled = 0;
280 activeEvents.Add(newEvent);
286 activeEvents.Clear();
289 private void SelectSettings()
293 throw new InvalidOperationException(
"Could not select EventManager settings (no settings loaded).");
295 var orderedByDifficulty = EventManagerSettings.OrderedByDifficulty.ToArray();
299 if (GameMain.GameSession.GameMode is TestGameMode)
301 settings = orderedByDifficulty.GetRandom(Rand.RandSync.ServerAndClient);
302 if (settings !=
null)
309 throw new InvalidOperationException(
"Could not select EventManager settings (level not set).");
312 float extraDifficulty = 0;
313 if (GameMain.GameSession.Campaign?.Settings !=
null)
315 extraDifficulty = GameMain.GameSession.Campaign.Settings.ExtraEventManagerDifficulty;
317 float modifiedDifficulty = Math.Clamp(level.
Difficulty + extraDifficulty, 0, 100);
318 var suitableSettings = EventManagerSettings.OrderedByDifficulty.Where(s =>
319 modifiedDifficulty >= s.MinLevelDifficulty &&
320 modifiedDifficulty <= s.MaxLevelDifficulty).ToArray();
322 if (suitableSettings.Length == 0)
324 DebugConsole.ThrowError(
"No suitable event manager settings found for the selected level (difficulty " + level.
Difficulty +
")");
325 settings = orderedByDifficulty.GetRandom(Rand.RandSync.ServerAndClient);
329 settings = suitableSettings.GetRandom(Rand.RandSync.ServerAndClient);
331 if (settings !=
null)
339 foreach (List<Event> eventList
in selectedEvents.Values)
341 foreach (
Event ev
in eventList)
345 yield
return contentFile;
353 var filesToPreload = contentFiles.ToList();
356 if (sub.
WreckAI ==
null) {
continue; }
361 if (prefab !=
null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
363 filesToPreload.Add(prefab.ContentFile);
369 foreach (Items.Components.ItemComponent component in item.
Components)
371 if (component.statusEffectLists ==
null) {
continue; }
372 foreach (var statusEffectList
in component.statusEffectLists.Values)
379 if (prefab !=
null && !filesToPreload.Contains(prefab.ContentFile))
381 filesToPreload.Add(prefab.ContentFile);
392 file.
Preload(preloadedSprites.Add);
398 foreach (var ev
in activeEvents)
406 pendingEventSets.Clear();
407 selectedEvents.Clear();
408 activeEvents.Clear();
410 finishedEvents.Clear();
411 nonRepeatableEvents.Clear();
413 preloadedSprites.ForEach(s => s.Remove());
414 preloadedSprites.Clear();
426 if (level?.
LevelData ==
null) {
return; }
432 if (registerFinishedOnly)
434 foreach (var finishedEvent
in finishedEvents)
436 EventSet parentSet = finishedEvent.ParentSet;
437 if (parentSet ==
null) {
continue; }
451 .Select(e => e.Prefab.Identifier)
461 if (!registerFinishedOnly)
466 bool Register(Identifier eventId) => !registerFinishedOnly || finishedEvents.Any(fe => fe.Prefab.Identifier == eventId);
471 eventCoolDown = 0.0f;
474 private float CalculateCommonness(
EventPrefab eventPrefab,
float baseCommonness)
477 float retVal = baseCommonness;
482 private void CreateEvents(EventSet eventSet)
484 selectedEvents.Remove(eventSet);
485 if (level ==
null) {
return; }
489 DebugConsole.NewMessage($
"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly:
true);
492 List<Func<Level.InterestingPosition,
bool>> spawnPosFilter =
new List<Func<Level.InterestingPosition,
bool>>();
493 if (eventSet.PerRuin)
495 applyCount = level.
Ruins.Count;
496 foreach (var ruin
in level.
Ruins)
498 spawnPosFilter.Add(pos => pos.Ruin == ruin);
501 else if (eventSet.PerCave)
503 applyCount = level.
Caves.Count;
504 foreach (var cave
in level.
Caves)
506 spawnPosFilter.Add(pos => pos.Cave == cave);
509 else if (eventSet.PerWreck)
511 var wrecks =
Submarine.Loaded.Where(s => s.Info.IsWreck && (s.WreckAI ==
null || !s.WreckAI.IsAlive));
512 applyCount = wrecks.Count();
513 foreach (var wreck
in wrecks)
515 spawnPosFilter.Add(pos => pos.Submarine == wreck);
519 foreach (var subEventPrefab
in eventSet.EventPrefabs)
521 foreach (Identifier missingId
in subEventPrefab.GetMissingIdentifiers())
523 DebugConsole.ThrowError($
"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".",
524 contentPackage: eventSet.ContentPackage);
528 var suitablePrefabSubsets = eventSet.EventPrefabs.Where(
529 e => IsFactionSuitable(e.Faction, level) && e.EventPrefabs.Any(ep =>
IsSuitable(ep, level))).ToArray();
531 for (
int i = 0; i < applyCount; i++)
533 if (eventSet.ChooseRandom)
535 if (suitablePrefabSubsets.Any())
537 var unusedEvents = suitablePrefabSubsets.ToList();
538 int eventCount = eventSet.GetEventCount(level);
539 for (
int j = 0; j < eventCount; j++)
541 if (unusedEvents.All(e => e.EventPrefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) {
break; }
542 EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), random);
543 (IEnumerable<EventPrefab> eventPrefabs,
float commonness,
float probability) = subEventPrefab;
544 if (eventPrefabs !=
null && random.
NextDouble() <= probability)
546 var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e =>
IsSuitable(e, level)), e => e.Commonness, random);
548 if (newEvent ==
null) {
continue; }
549 if (i < spawnPosFilter.Count) { newEvent.
SpawnPosFilter = spawnPosFilter[i]; }
550 DebugConsole.NewMessage($
"Initialized event {newEvent}", debugOnly:
true);
551 if (!selectedEvents.ContainsKey(eventSet))
553 selectedEvents.Add(eventSet,
new List<Event>());
555 selectedEvents[eventSet].Add(newEvent);
556 unusedEvents.Remove(subEventPrefab);
560 if (eventSet.ChildSets.Any())
562 int setCount = eventSet.SubSetCount;
565 var unusedSets = eventSet.ChildSets.ToList();
566 for (
int j = 0; j < setCount; j++)
568 var newEventSet = SelectRandomEvents(unusedSets, random: random);
569 if (newEventSet ==
null) {
break; }
570 unusedSets.Remove(newEventSet);
571 CreateEvents(newEventSet);
576 var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: random);
577 if (newEventSet !=
null)
579 CreateEvents(newEventSet);
586 foreach ((IEnumerable<EventPrefab> eventPrefabs,
float commonness,
float probability) in suitablePrefabSubsets)
588 if (random.
NextDouble() > probability) {
continue; }
590 var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e =>
IsSuitable(e, level)), e => e.Commonness, random);
591 var newEvent = eventPrefab.CreateInstance(
RandomSeed);
592 if (newEvent ==
null) {
continue; }
593 if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
594 if (!selectedEvents.ContainsKey(eventSet))
596 selectedEvents.Add(eventSet,
new List<Event>());
598 selectedEvents[eventSet].Add(newEvent);
601 var location = GetEventLocation();
602 foreach (EventSet childEventSet
in eventSet.ChildSets)
604 if (!IsValidForLevel(childEventSet, level)) {
continue; }
605 if (!IsValidForLocation(childEventSet, location)) {
continue; }
606 CreateEvents(childEventSet);
612 private EventSet SelectRandomEvents(IReadOnlyList<EventSet> eventSets,
bool? requireCampaignSet =
null, Random random =
null)
614 if (level ==
null) {
return null; }
615 Random rand = random ??
new MTRandom(ToolBox.StringToInt(level.
Seed));
617 var allowedEventSets =
618 eventSets.Where(
set => IsValidForLevel(
set, level));
620 if (requireCampaignSet.HasValue)
622 if (requireCampaignSet.Value)
624 if (allowedEventSets.Any(es => es.IsCampaignSet))
627 allowedEventSets.Where(es => es.IsCampaignSet);
631 DebugConsole.AddWarning(
"No campaign event sets available. Using a non-campaign-specific set instead.");
637 allowedEventSets.Where(es => !es.IsCampaignSet);
641 var location = GetEventLocation();
642 allowedEventSets = allowedEventSets.Where(
set => IsValidForLocation(
set, location));
644 allowedEventSets = allowedEventSets.Where(
set => !
set.CampaignTutorialOnly ||
645 (GameMain.IsSingleplayer && GameMain.GameSession?.Campaign?.Settings is { TutorialEnabled: true }));
647 int? discoveryIndex = GameMain.GameSession?.Map?.GetDiscoveryIndex(location);
648 int? visitIndex = GameMain.GameSession?.Map?.GetVisitIndex(location);
649 if (discoveryIndex is not
null && discoveryIndex >= 0 && allowedEventSets.Any(
set =>
set.ForceAtDiscoveredNr == discoveryIndex))
651 allowedEventSets = allowedEventSets.Where(
set =>
set.ForceAtDiscoveredNr == discoveryIndex);
653 else if (visitIndex is not
null && visitIndex >= 0 && allowedEventSets.Any(
set =>
set.ForceAtVisitedNr == visitIndex))
655 allowedEventSets = allowedEventSets.Where(
set =>
set.ForceAtVisitedNr == visitIndex);
660 allowedEventSets = allowedEventSets.Where(
set =>
set.ForceAtDiscoveredNr < 0 &&
set.ForceAtVisitedNr < 0);
663 if (allowedEventSets.Count() == 1)
666 return allowedEventSets.First();
669 float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
670 float randomNumber = (float)rand.NextDouble();
671 randomNumber *= totalCommonness;
672 foreach (EventSet eventSet
in allowedEventSets)
674 float commonness = eventSet.GetCommonness(level);
675 if (randomNumber <= commonness)
679 randomNumber -= commonness;
698 private static bool IsFactionSuitable(Identifier factionId,
Level level)
703 private static bool IsValidForLevel(EventSet eventSet, Level level)
706 level.IsAllowedDifficulty(eventSet.MinLevelDifficulty, eventSet.MaxLevelDifficulty) &&
707 level.LevelData.Type == eventSet.LevelType &&
708 (eventSet.RequiredLayer.IsEmpty || Submarine.LayerExistsInAnySub(eventSet.RequiredLayer)) &&
709 (eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
712 private bool IsValidForLocation(EventSet eventSet, Location location)
714 if (location is
null) {
return true; }
715 if (!eventSet.Faction.IsEmpty)
717 if (eventSet.Faction != location.Faction?.Prefab.Identifier && eventSet.Faction != location.SecondaryFaction?.Prefab.Identifier) {
return false; }
719 var locationType = location.GetLocationType();
720 bool includeGenericEvents = level.Type == LevelData.LevelType.LocationConnection || !locationType.IgnoreGenericEvents;
721 if (includeGenericEvents && eventSet.LocationTypeIdentifiers ==
null) {
return true; }
722 return eventSet.LocationTypeIdentifiers !=
null && eventSet.LocationTypeIdentifiers.Any(identifier => identifier == locationType.Identifier);
725 private Location GetEventLocation()
727 return GameMain.GameSession?.Campaign?.Map?.CurrentLocation ?? level?.StartLocation;
730 private bool CanStartEventSet(EventSet eventSet)
732 if (!eventSet.AllowAtStart)
735 float distFromStart = (float)Math.Sqrt(MathUtils.LineSegmentToPointDistanceSquared(level.StartExitPosition.ToPoint(), level.StartPosition.ToPoint(), refEntity.WorldPosition.ToPoint()));
736 float distFromEnd = (float)Math.Sqrt(MathUtils.LineSegmentToPointDistanceSquared(level.EndExitPosition.ToPoint(), level.EndPosition.ToPoint(), refEntity.WorldPosition.ToPoint()));
737 if (distFromStart * Physics.DisplayToRealWorldRatio < 50.0f || distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
743 if (eventSet.DelayWhenCrewAway)
751 if ((
Submarine.MainSub ==
null || distanceTraveled < eventSet.MinDistanceTraveled) &&
752 roundDuration < eventSet.MinMissionTime)
757 if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
765 private bool eventsInitialized;
772 if (!eventsInitialized)
774 foreach (var eventSet
in selectedEvents.Keys)
776 foreach (var ev
in selectedEvents[eventSet])
781 eventsInitialized =
true;
786 CalculateCurrentIntensity(deltaTime);
789 if (DateTime.Now > nextIntensityLogTime)
791 DebugConsole.NewMessage(
"EventManager intensity: " + (
int)Math.Round(currentIntensity * 100) +
" %");
792 nextIntensityLogTime = DateTime.Now +
new TimeSpan(0, minutes: 1, seconds: 0);
796 if (isClient) {
return; }
798 roundDuration += deltaTime;
800 if (settings ==
null)
802 DebugConsole.ThrowError(
"Event settings not set before updating EventManager. Attempting to select...");
804 if (settings ==
null)
806 DebugConsole.ThrowError(
"Could not select EventManager settings. Disabling EventManager for the round...");
808 GameMain.Server?.SendChatMessage(
"Could not select EventManager settings. Disabling EventManager for the round...", Networking.ChatMessageType.Error);
818 crewAwayResetTimer = CrewAwayResetDelay;
819 crewAwayDuration += deltaTime;
821 else if (crewAwayResetTimer > 0.0f)
824 crewAwayResetTimer -= deltaTime;
829 crewAwayDuration = 0.0f;
831 eventThreshold = Math.Min(eventThreshold, 1.0f);
832 eventCoolDown -= deltaTime;
835 calculateDistanceTraveledTimer -= deltaTime;
836 if (calculateDistanceTraveledTimer <= 0.0f)
838 distanceTraveled = CalculateDistanceTraveled();
839 calculateDistanceTraveledTimer = CalculateDistanceTraveledInterval;
842 bool recheck =
false;
847 for (
int i = pendingEventSets.Count - 1; i >= 0; i--)
849 var eventSet = pendingEventSets[i];
850 if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) {
continue; }
851 if (currentIntensity > eventThreshold && !eventSet.IgnoreIntensity) {
continue; }
852 if (!CanStartEventSet(eventSet)) {
continue; }
854 pendingEventSets.RemoveAt(i);
856 if (selectedEvents.ContainsKey(eventSet))
859 foreach (
Event ev
in selectedEvents[eventSet])
861 activeEvents.Add(ev);
863 if (eventSet.TriggerEventCooldown && selectedEvents[eventSet].Any(e => e.Prefab.TriggerEventCooldown))
867 if (eventSet.ResetTime > 0)
871 pendingEventSets.Add(eventSet);
872 CreateEvents(eventSet);
873 foreach (
Event newEvent
in selectedEvents[eventSet])
885 pendingEventSets.Add(childEventSet);
891 foreach (
Event ev
in activeEvents)
897 else if (ev.
Prefab !=
null && !finishedEvents.Any(e => e.Prefab == ev.
Prefab))
903 finishedEvents.Add(ev);
915 foreach (var ev
in activeEvents)
919 scriptedEvent.EntitySpawned(entity);
924 private void CalculateCurrentIntensity(
float deltaTime)
926 intensityUpdateTimer -= deltaTime;
927 if (intensityUpdateTimer > 0.0f) {
return; }
932 avgCrewHealth = 0.0f;
933 int characterCount = 0;
934 foreach (Character character
in Character.CharacterList)
936 if (character.IsDead || character.TeamID ==
CharacterTeamType.FriendlyNPC) {
continue; }
937 if (character.AIController is HumanAIController || character.IsRemotePlayer)
939 avgCrewHealth += character.Vitality / character.MaxVitality * (character.IsUnconscious ? 0.5f : 1.0f);
943 if (characterCount > 0)
945 avgCrewHealth /= characterCount;
949 avgCrewHealth = 0.5f;
956 foreach (Character character
in Character.CharacterList)
958 if (character.IsIncapacitated || character.IsHandcuffed || !character.Enabled || character.IsPet) {
continue; }
960 if (character.AIController is EnemyAIController enemyAI)
962 if (!enemyAI.AIParams.StayInAbyss)
965 monsterStrength += enemyAI.CombatStrength;
968 if (character.CurrentHull?.Submarine?.Info !=
null &&
969 (character.CurrentHull.Submarine ==
Submarine.MainSub ||
Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) &&
970 character.CurrentHull.Submarine.Info.Type ==
SubmarineType.Player)
973 enemyDanger += enemyAI.CombatStrength / 500.0f;
975 else if (enemyAI.SelectedAiTarget?.Entity?.Submarine !=
null)
979 enemyDanger += enemyAI.CombatStrength / 5000.0f;
982 else if (character.AIController is HumanAIController humanAi && !character.IsOnFriendlyTeam(
CharacterTeamType.Team1))
984 if (character.Submarine !=
null &&
985 character.Submarine.PhysicsBody is { BodyType: BodyType.Dynamic } &&
1004 enemyDanger += monsterStrength / 5000f;
1005 enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
1019 float holeCount = 0.0f;
1020 float waterAmount = 0.0f;
1021 float dryHullVolume = 0.0f;
1022 foreach (Hull hull
in Hull.HullList)
1024 if (hull.Submarine ==
null || hull.Submarine.Info.Type !=
SubmarineType.Player) {
continue; }
1025 if (GameMain.GameSession?.GameMode is PvPMode)
1033 fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
1034 if (hull.IsWetRoom) {
continue; }
1035 foreach (Gap gap
in hull.ConnectedGaps)
1037 if (!gap.IsRoomToRoom)
1039 holeCount += gap.Open;
1042 waterAmount += hull.WaterVolume;
1043 dryHullVolume += hull.Volume;
1045 if (dryHullVolume > 0)
1047 floodingAmount = waterAmount / dryHullVolume;
1051 avgHullIntegrity = MathHelper.Clamp(1.0f - holeCount / 10.0f, 0.0f, 1.0f);
1055 fireAmount = MathHelper.Clamp(fireAmount / 1000.0f, fireAmount > 0.0f ? 0.2f : 0.0f, 1.0f);
1059 if (floodingAmount < 0.1f)
1061 floodingAmount = 0.0f;
1065 floodingAmount *= 1.5f;
1071 ((1.0f - avgCrewHealth) + (1.0f - avgHullIntegrity) + floodingAmount) / 3.0f;
1072 targetIntensity += fireAmount * 0.5f;
1073 targetIntensity += enemyDanger;
1074 targetIntensity = MathHelper.Clamp(targetIntensity, 0.0f, 1.0f);
1076 if (targetIntensity > currentIntensity)
1092 private float CalculateDistanceTraveled()
1094 if (level ==
null || pathFinder ==
null) {
return 0.0f; }
1095 var refEntity = GetRefEntity();
1096 if (refEntity ==
null) {
return 0.0f; }
1097 Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
1098 var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
1099 if (steeringPath.Unreachable ||
float.IsPositiveInfinity(totalPathLength))
1102 return MathHelper.Clamp((refEntity.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X), 0.0f, 1.0f);
1106 return MathHelper.Clamp(1.0f - steeringPath.TotalLength / totalPathLength, 0.0f, 1.0f);
1133 if (client.Character ==
null) {
continue; }
1137 if (client.Character.Submarine !=
null &&
1138 client.Character.Submarine.Info.Type ==
SubmarineType.Player)
1140 if (client.Character.Submarine.WorldPosition.X > refEntity.
WorldPosition.X)
1150 private bool IsCrewAway()
1155 int playerCount = 0;
1156 int awayPlayerCount = 0;
1159 if (client.Character ==
null || client.Character.IsDead || client.Character.IsIncapacitated) {
continue; }
1162 if (IsCharacterAway(client.Character)) { awayPlayerCount++; }
1164 return playerCount > 0 && awayPlayerCount / (float)playerCount > 0.5f;
1168 private bool IsCharacterAway(Character character)
1170 if (character.Submarine !=
null)
1172 switch (character.Submarine.Info.Type)
1185 const int maxDist = 1000;
1187 if (level !=
null && !level.Removed)
1189 foreach (var ruin
in level.Ruins)
1192 area.Inflate(maxDist, maxDist);
1193 if (area.Contains(character.WorldPosition)) {
return true; }
1195 foreach (var cave
in level.Caves)
1198 area.Inflate(maxDist, maxDist);
1199 if (area.Contains(character.WorldPosition)) {
return true; }
1203 foreach (Submarine sub
in Submarine.Loaded)
1207 sub.Borders.X + (
int)sub.WorldPosition.X - maxDist,
1208 sub.Borders.Y + (
int)sub.WorldPosition.Y + maxDist,
1209 sub.Borders.Width + maxDist * 2,
1210 sub.Borders.Height + maxDist * 2);
1211 if (
Submarine.RectContains(worldBorders, character.WorldPosition))
1222 foreach (var
id in element.GetAttributeIdentifierArray(nameof(QueuedEventsForNextRound), Array.Empty<Identifier>()))
1224 QueuedEventsForNextRound.Enqueue(
id);
1230 return new XElement(
"eventmanager",
1231 new XAttribute(nameof(QueuedEventsForNextRound),
1232 string.Join(
',', QueuedEventsForNextRound)));
static Character? Controlled
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
Base class for content file types, which are loaded from filelist.xml via reflection....
virtual void Preload(Action< Sprite > addPreloadedSprite)
virtual void Update(float deltaTime)
Func< Level.InterestingPosition, bool > SpawnPosFilter
virtual IEnumerable< ContentFile > GetFilesToPreload()
void Init(EventSet parentSet=null)
Used to store logs of scripted events (a sort of "quest log")
void StartRound(Level level)
IEnumerable< ContentFile > GetFilesToPreload()
void Load(XElement element)
readonly Queue< Event > QueuedEvents
static bool IsLevelSuitable(EventPrefab e, Level level)
float CumulativeMonsterStrengthWrecks
void TriggerOnEndRoundActions()
void ActivateEvent(Event newEvent)
float CumulativeMonsterStrengthCaves
readonly Queue< Identifier > QueuedEventsForNextRound
static ISpatialEntity GetRefEntity()
Get the entity that should be used in determining how far the player has progressed in the level....
static bool IsSuitable(EventPrefab e, Level level)
void AddTimeStamp(Event e)
float CumulativeMonsterStrengthMain
void EntitySpawned(Entity entity)
IEnumerable< Event > ActiveEvents
void Update(float deltaTime)
void PreloadContent(IEnumerable< ContentFile > contentFiles)
readonly record struct NetEventLogEntry(Identifier EventPrefabId, Identifier LogEntryId, string Text)[NetworkSerialize] readonly record struct const NetEventObjective(EventObjectiveAction.SegmentActionType Type, Identifier Identifier, Identifier ObjectiveTag, Identifier TextTag, Identifier ParentObjectiveId, bool CanBeCompleted) float IntensityUpdateInterval
void RegisterEventHistory(bool registerFinishedOnly=false)
Registers the exhaustible events in the level as exhausted, and adds the current events to the event ...
readonly EventLog EventLog
float CumulativeMonsterStrengthRuins
readonly float EventThresholdIncrease
readonly float EventCooldown
readonly float FreezeDurationWhenCrewAway
readonly float DefaultEventThreshold
static readonly PrefabCollection< EventManagerSettings > Prefabs
readonly Identifier BiomeIdentifier
If set, the event set can only be chosen in this biome.
readonly Identifier RequiredLayer
If set, this layer must be present somewhere in the level.
Event CreateInstance(int seed)
static EventPrefab GetUnlockPathEvent(Identifier biomeIdentifier, Faction faction)
readonly Identifier Faction
If set, the event set can only be chosen in locations that belong to this faction.
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
static EventPrefab GetEventPrefab(Identifier identifier)
readonly ImmutableArray< SubEventPrefab > EventPrefabs
static readonly PrefabCollection< EventSet > Prefabs
readonly ImmutableArray< EventSet > ChildSets
readonly bool Additive
Additive sets are important to be aware of when creating custom event sets! If an additive set gets c...
readonly bool Exhaustible
'Exhaustible' sets won't appear in the same level until after one world step (~10 min,...
readonly bool OncePerLevel
If enabled, events from this set can only occur once in the level.
static GameSession?? GameSession
static NetworkMember NetworkMember
static readonly List< Item > ItemList
List< ItemComponent > Components
const float DefaultSonarRange
bool EventsExhausted
'Exhaustible' sets won't appear in the same level until after one world step (~10 min,...
readonly Dictionary< EventSet, int > FinishedEvents
readonly List< Identifier > EventHistory
Events that have previously triggered in this level. Used for making events the player hasn't seen ye...
LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
readonly List< Identifier > NonRepeatableEvents
Events that have already triggered in this level and can never trigger again. EventSet....
readonly LevelData LevelData
readonly List< LocationConnection > Connections
Mersenne Twister based random
override double NextDouble()
Returns random value larger or equal to 0.0 and less than 1.0
Unlocks a mission in a nearby level or location.
Marks fields and properties as to be serialized and deserialized by INetSerializableStruct....
Executes all the child actions when the round ends.
SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub=null, string errorMsgStr=null, float minGapSize=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisibility=true)
readonly Identifier Identifier
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
IEnumerable< CharacterSpawnInfo >?? SpawnCharacters
static bool LayerExistsInAnySub(Identifier layer)
static List< Submarine > Loaded
static List< WayPoint > WayPointList
Identifier DefensiveAgent