5 using Microsoft.Xna.Framework;
7 using System.Collections.Generic;
8 using System.Collections.Immutable;
10 using System.Xml.Linq;
19 public static float MinimumLoadingTime;
41 private readonly List<Mission> missions =
new List<Mission>();
42 public IEnumerable<Mission>
Missions {
get {
return missions; } }
44 private readonly HashSet<Character> casualties =
new HashSet<Character>();
45 public IEnumerable<Character>
Casualties {
get {
return casualties; } }
81 if (dummyLocations ==
null)
85 if (dummyLocations ==
null) {
throw new NullReferenceException(
"dummyLocations is null somehow!"); }
86 return dummyLocations[0];
95 if (dummyLocations ==
null)
99 if (dummyLocations ==
null) {
throw new NullReferenceException(
"dummyLocations is null somehow!"); }
100 return dummyLocations[1];
116 partial
void InitProjSpecific();
130 : this(submarineInfo)
134 GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, settings, missionType: missionType);
135 InitOwnedSubs(submarineInfo);
142 : this(submarineInfo)
145 GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, CampaignSettings.Empty, missionPrefabs: missionPrefabs);
146 InitOwnedSubs(submarineInfo);
152 public GameSession(
SubmarineInfo submarineInfo, List<SubmarineInfo> ownedSubmarines, XDocument doc,
string saveFile) : this(submarineInfo)
156 XElement rootElement = doc.Root ??
throw new NullReferenceException(
"Game session XML element is invalid: document is null.");
160 foreach (var subElement
in rootElement.Elements())
162 switch (subElement.Name.ToString().ToLowerInvariant())
165 case "singleplayercampaign":
171 InitOwnedSubs(submarineInfo, ownedSubmarines);
173 throw new Exception(
"The server cannot load a single player campaign.");
176 case "multiplayercampaign":
182 mpCampaign.LoadNewLevel();
183 InitOwnedSubs(submarineInfo, ownedSubmarines);
185 SaveUtil.SaveGame(saveFile);
192 private void InitOwnedSubs(
SubmarineInfo submarineInfo, List<SubmarineInfo>? ownedSubmarines =
null)
201 private GameMode InstantiateGameMode(GameModePreset gameModePreset,
string? seed,
SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable<MissionPrefab>? missionPrefabs =
null,
MissionType missionType =
MissionType.None)
203 if (gameModePreset.GameModeType == typeof(CoOpMode) || gameModePreset.GameModeType == typeof(PvPMode))
207 for (
int i = 0; i < missionTypes.Length; i++)
209 if (MissionPrefab.HiddenMissionClasses.Contains(missionTypes[i]))
211 missionType &= ~missionTypes[i];
215 if (gameModePreset.GameModeType == typeof(CoOpMode))
217 return missionPrefabs !=
null ?
218 new CoOpMode(gameModePreset, missionPrefabs) :
219 new CoOpMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
221 else if (gameModePreset.GameModeType == typeof(PvPMode))
223 return missionPrefabs !=
null ?
224 new PvPMode(gameModePreset, missionPrefabs) :
225 new PvPMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
227 else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
229 var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
230 if (selectedSub !=
null)
232 campaign.Bank.Deduct(selectedSub.Price);
233 campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0);
235 if (GameMain.Server?.ServerSettings?.NewCampaignDefaultSalary is { } salary)
237 campaign.Bank.SetRewardDistribution((
int)Math.Round(salary, digits: 0));
244 else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign))
246 var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
247 if (selectedSub !=
null)
249 campaign.Bank.TryDeduct(selectedSub.Price);
250 campaign.Bank.Balance = Math.Max(campaign.Bank.Balance, 0);
254 else if (gameModePreset.GameModeType == typeof(TutorialMode))
256 return new TutorialMode(gameModePreset);
258 else if (gameModePreset.GameModeType == typeof(TestGameMode))
260 return new TestGameMode(gameModePreset);
263 else if (gameModePreset.GameModeType == typeof(
GameMode))
265 return new GameMode(gameModePreset);
269 throw new Exception($
"Could not find a game mode of the type \"{gameModePreset.GameModeType}\"");
277 if (forceLocationType ==
null &&
278 forceParams !=
null && forceParams.AllowedLocationTypes.Any() && !forceParams.AllowedLocationTypes.Contains(
"Any".ToIdentifier()))
281 LocationType.
Prefabs.Where(lt => forceParams.AllowedLocationTypes.Contains(lt.Identifier)).GetRandom(rand);
284 List<Faction> factions =
new List<Faction>();
285 foreach (var factionPrefab
in FactionPrefab.Prefabs)
287 factions.Add(
new Faction(
new CampaignMetadata(), factionPrefab));
289 foreach (var location
in dummyLocations)
291 if (location.Type.HasOutpost)
297 return dummyLocations;
307 var dummyLocations =
new Location[2];
308 for (
int i = 0; i < 2; i++)
310 dummyLocations[i] =
Location.
CreateRandom(
new Vector2((
float)rand.NextDouble() * 10000.0f, (
float)rand.NextDouble() * 10000.0f),
null, rand, requireOutpost:
true, forceLocationType);
312 return dummyLocations;
348 if (
Campaign is
null) {
return false; }
349 int price = newSubmarine.
GetPrice();
353 GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.
Name);
371 if (
Map?.CurrentLocation ==
null ||
Campaign ==
null) {
return false; }
377 isRadiated |= endLocation.IsRadiated();
390 if (missionPrefab !=
null &&
402 tryCreateFaction(locationType.
Faction, dummyLocations, static (loc, fac) => loc.Faction = fac);
403 tryCreateFaction(locationType.
SecondaryFaction, dummyLocations, static (loc, fac) => loc.SecondaryFaction = fac);
405 static bool tryCreateFaction(Identifier factionIdentifier,
Location[] locations, Action<Location, Faction> setter)
407 if (factionIdentifier.IsEmpty) {
return false; }
408 if (!FactionPrefab.Prefabs.TryGet(factionIdentifier, out var prefab)) {
return false; }
409 if (locations.Length == 0) {
return false; }
411 var newFaction =
new Faction(metadata:
null, prefab);
412 for (
int i = 0; i < locations.Length; i++)
414 setter(locations[i], newFaction);
420 randomLevel =
LevelData.
CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost:
true);
431 DateTime startTime = DateTime.Now;
439 DebugConsole.ThrowError(
"Couldn't start game session, submarine not selected.");
444 DebugConsole.ThrowError(
"Couldn't start game session, submarine file corrupted.");
449 DebugConsole.ThrowError(
"Couldn't start game session, saved submarine is empty. The submarine file may be corrupted.");
482 if (enemySubmarineInfo !=
null)
490 List<Item> items =
new List<Item>();
496 foreach (
Item item
in items)
498 if (item.GetComponent<CircuitBox>() is { } cb)
503 Wire wire = item.GetComponent<
Wire>();
509 if (levelData !=
null)
514 InitializeLevel(level);
521 GameAnalyticsManager.AddProgressionEvent(
522 GameAnalyticsManager.ProgressionStatus.Start,
527 GameAnalyticsManager.AddDesignEvent(eventId +
"GameMode:" + (
GameMode?.Preset?.Identifier.Value ??
"none"));
528 GameAnalyticsManager.AddDesignEvent(eventId +
"CrewSize:" + (
CrewManager?.GetCharacterInfos()?.Count() ?? 0));
529 foreach (
Mission mission
in missions)
531 GameAnalyticsManager.AddDesignEvent(eventId +
"MissionType:" + (mission.
Prefab.
Type.ToString() ??
"none") +
":" + mission.
Prefab.
Identifier);
538 GameAnalyticsManager.AddDesignEvent(eventId +
"LevelType:" +
Level.
Loaded.
Type.ToString() +
":" + levelId);
544 GameAnalyticsManager.AddDesignEvent(eventId + tutorialMode.Tutorial.Identifier);
547 GameAnalyticsManager.AddDesignEvent(
"FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
550 GameAnalyticsManager.AddDesignEvent($
"{eventId}HintManager:{(HintManager.Enabled ? "Enabled
" : "Disabled")}");
553 if (campaignMode !=
null)
555 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:RadiationEnabled:" + campaignMode.Settings.RadiationEnabled);
556 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:WorldHostility:" + campaignMode.Settings.WorldHostility);
557 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:ShowHuskWarning:" + campaignMode.Settings.ShowHuskWarning);
558 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:StartItemSet:" + campaignMode.Settings.StartItemSet);
559 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:MaxMissionCount:" + campaignMode.Settings.MaxMissionCount);
561 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:RepairFailMultiplier:" + (
int)(campaignMode.Settings.RepairFailMultiplier * 100));
562 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:FuelMultiplier:" + (
int)(campaignMode.Settings.FuelMultiplier * 100));
563 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:MissionRewardMultiplier:" + (
int)(campaignMode.Settings.MissionRewardMultiplier * 100));
564 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:CrewVitalityMultiplier:" + (
int)(campaignMode.Settings.CrewVitalityMultiplier * 100));
565 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:NonCrewVitalityMultiplier:" + (
int)(campaignMode.Settings.NonCrewVitalityMultiplier * 100));
566 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:OxygenMultiplier:" + (
int)(campaignMode.Settings.OxygenMultiplier * 100));
567 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:RepairFailMultiplier:" + (
int)(campaignMode.Settings.RepairFailMultiplier * 100));
568 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:ShipyardPriceMultiplier:" + (
int)(campaignMode.Settings.ShipyardPriceMultiplier * 100));
569 GameAnalyticsManager.AddDesignEvent(
"CampaignSettings:ShopPriceMultiplier:" + (
int)(campaignMode.Settings.ShopPriceMultiplier * 100));
572 if (firstTimeInBiome)
579 GameAnalyticsManager.AddDesignEvent(
"ServerSettings:RespawnMode:" + serverSettings.RespawnMode);
580 GameAnalyticsManager.AddDesignEvent(
"ServerSettings:IronmanMode:" + serverSettings.IronmanMode);
581 GameAnalyticsManager.AddDesignEvent(
"ServerSettings:AllowBotTakeoverOnPermadeath:" + serverSettings.AllowBotTakeoverOnPermadeath);
586 double startDuration = (DateTime.Now - startTime).TotalSeconds;
587 if (startDuration < MinimumLoadingTime)
589 int sleepTime = (int)((MinimumLoadingTime - startDuration) * 1000);
590 DebugConsole.NewMessage($
"Stalling round start by {sleepTime / 1000.0f} s (minimum loading time set to {MinimumLoadingTime})...", Color.Magenta);
591 System.Threading.Thread.Sleep(sleepTime);
595 if (campaignMode !=
null && levelData !=
null) { AchievementManager.OnBiomeDiscovered(levelData.Biome); }
597 var existingRoundSummary = GUIMessageBox.MessageBoxes.Find(mb => mb.UserData is
RoundSummary)?.UserData as
RoundSummary;
598 if (existingRoundSummary?.ContinueButton !=
null)
609 GUI.AddMessage(
"", Color.Transparent, 3.0f, playSound:
false);
612 GUI.AddMessage(levelData.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, levelData.Difficulty / 100.0f), 5.0f, playSound:
false);
613 GUI.AddMessage(TextManager.AddPunctuation(
':', TextManager.Get(
"Destination"),
EndLocation.
DisplayName), Color.CadetBlue, playSound:
false);
614 var missionsToShow = missions.Where(m => m.Prefab.ShowStartMessage);
615 if (missionsToShow.Count() > 1)
617 string joinedMissionNames =
string.Join(
", ", missions.Select(m => m.Name));
618 GUI.AddMessage(TextManager.AddPunctuation(
':', TextManager.Get(
"Mission"), joinedMissionNames), Color.CadetBlue, playSound:
false);
622 var mission = missionsToShow.FirstOrDefault();
623 GUI.AddMessage(TextManager.AddPunctuation(
':', TextManager.Get(
"Mission"), mission?.Name ?? TextManager.Get(
"None")), Color.CadetBlue, playSound:
false);
628 GUI.AddMessage(TextManager.AddPunctuation(
':', TextManager.Get(
"Location"),
StartLocation.
DisplayName), Color.CadetBlue, playSound:
false);
632 ReadyCheck.ReadyCheckCooldown = DateTime.MinValue;
633 GUI.PreventPauseMenuToggle =
false;
634 HintManager.OnRoundStarted();
636 GameMain.LuaCs.Hook.Call(
"roundStart");
639 if (campaignMode is { ItemsRelocatedToMainSub:
true })
642 GameMain.Server.SendChatMessage(TextManager.Get(
"itemrelocated").Value,
ChatMessageType.ServerMessageBoxInGame);
644 if (campaignMode.IsSinglePlayer)
646 new GUIMessageBox(
string.Empty, TextManager.Get(
"itemrelocated"));
649 campaignMode.ItemsRelocatedToMainSub =
false;
653 if (campaignMode is { DivingSuitWarningShown:
false } &&
657 CoroutineManager.Invoke(() =>
new GUIMessageBox(TextManager.Get(
"warning"), TextManager.Get(
"hint.upgradedivingsuits")), delay: 5.0f);
659 campaignMode.DivingSuitWarningShown =
true;
663 private void InitializeLevel(Level? level)
667 StatusEffect.StopAll();
670 GameMain.LightManager.LosEnabled = (GameMain.Client ==
null || GameMain.Client.CharacterInfo !=
null) && !GameMain.DevMode;
671 if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
672 if (GameMain.Client ==
null) { GameMain.LightManager.LosMode = GameSettings.CurrentConfig.Graphics.LosMode; }
674 LevelData = level?.LevelData;
677 PlaceSubAtStart(Level);
682 if (sub.Info.IsOutpost || sub.Info.IsBeacon || sub.Info.IsWreck)
684 sub.DisableObstructedWayPoints();
688 Entity.Spawner =
new EntitySpawner();
690 if (GameMode !=
null && Submarine !=
null)
693 missions.AddRange(GameMode.
Missions);
695 foreach (Mission mission
in missions)
697 int prevEntityCount = Entity.GetEntities().Count;
698 mission.Start(Level.Loaded);
699 if (GameMain.NetworkMember !=
null && GameMain.NetworkMember.IsClient && Entity.GetEntities().Count != prevEntityCount)
701 DebugConsole.ThrowError(
702 $
"Entity count has changed after starting a mission ({mission.Prefab.Identifier}) as a client. " +
703 "The clients should not instantiate entities themselves when starting the mission," +
704 " but instead the server should inform the client of the spawned entities using Mission.ServerWriteInitial.");
709 ObjectiveManager.ResetObjectives();
711 EventManager?.StartRound(Level.Loaded);
712 AchievementManager.OnStartRound();
716 if (GameMain.NetworkMember ==
null)
723 Level.SpawnCorpses();
724 Level.PrepareBeaconStation();
726 AutoItemPlacer.SpawnItems(Campaign?.Settings.StartItemSet);
728 if (GameMode is MultiPlayerCampaign mpCampaign)
730 mpCampaign.UpgradeManager.ApplyUpgrades();
731 mpCampaign.UpgradeManager.SanityCheckUpgrades();
735 CreatureMetrics.RecentlyEncountered.Clear();
737 GameMain.GameScreen.Cam.Position =
Character.Controlled?.WorldPosition ??
Submarine.MainSub.WorldPosition;
738 RoundDuration = 0.0f;
739 GameMain.ResetFrameTime();
753 if (spawnPoint !=
null)
768 new Vector2(0.0f, outpostBorders.Height / 2 + subBorders.Height / 2));
771 float closestDistance = 0.0f;
790 if ((myPort ==
null || dist < closestDistance || port.
MainDockingPort) && !(myPort?.MainDockingPort ??
false))
793 closestDistance = dist;
797 if (myPort !=
null && outPostPort !=
null)
800 Vector2 spawnPos = (outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance;
809 myPort.Dock(outPostPort);
810 myPort.Lock(isNetworkMessage:
true, applyEffects:
false);
849 RoundDuration += deltaTime;
853 for (
int i = missions.Count - 1; i >= 0; i--)
855 missions[i].Update(deltaTime);
857 UpdateProjSpecific(deltaTime);
862 if (index < 0 || index >= missions.Count) {
return null; }
863 return missions[index];
868 return missions.IndexOf(mission);
873 List<Mission> sortedMissions =
new List<Mission>();
874 foreach (Identifier missionId
in missionIdentifiers)
876 var matchingMission = missions.Find(m => m.Prefab.Identifier == missionId);
877 if (matchingMission ==
null) {
continue; }
878 sortedMissions.Add(matchingMission);
879 missions.Remove(matchingMission);
881 missions.AddRange(sortedMissions);
884 partial
void UpdateProjSpecific(
float deltaTime);
897 if (result !=
null)
return ImmutableHashSet.
Create(result);
901 IEnumerable<Character> players;
902 IEnumerable<Character> bots;
903 HashSet<Character> characters =
new HashSet<Character>();
906 players = GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info !=
null && !c.IsDead);
907 bots = crewManager.GetCharacters().Where(c => !c.IsRemotePlayer);
909 players = crewManager.GetCharacters().Where(
static c => c.IsPlayer);
910 bots = crewManager.GetCharacters().Where(
static c => c.IsBot);
914 foreach (Character bot
in bots) { characters.Add(bot); }
919 foreach (Character player
in players) { characters.Add(player); }
922 return characters.ToImmutableHashSet();
926 private double LastEndRoundErrorMessageTime;
944 ImmutableHashSet<Character> crewCharacters = GetSessionCrewCharacters(
CharacterType.Both);
945 int prevMoney = GetAmountOfMoney(crewCharacters);
946 foreach (
Mission mission
in missions)
951 foreach (
Character character
in crewCharacters)
958 if (missions.Any(m => m.Completed))
960 foreach (
Character character
in crewCharacters)
965 if (missions.All(m => m.Completed))
967 foreach (
Character character
in crewCharacters)
977 if (GUI.PauseMenuOpen)
979 GUI.TogglePauseMenu();
985 DeathPrompt?.Close();
986 DeathPrompt.CloseBotPanel();
988 GUI.PreventPauseMenuToggle =
true;
1001 GUIMessageBox.
MessageBoxes.RemoveAll(mb => mb.UserData as
string ==
"ConversationAction" || ReadyCheck.IsReadyCheck(mb));
1002 ObjectiveManager.ResetUI();
1005 AchievementManager.OnRoundEnded(
this);
1008 GameMain.Server?.TraitorManager?.EndRound();
1021 GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character !=
null && !c.Character.IsDead);
1023 GameAnalyticsManager.AddProgressionEvent(
1024 success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail,
1028 LogEndRoundStats(eventId, traitorResults);
1031 GameAnalyticsManager.AddDesignEvent(eventId +
"MoneyEarned", GetAmountOfMoney(crewCharacters) - prevMoney);
1032 campaignMode.TotalPlayTime += RoundDuration;
1035 HintManager.OnRoundEnded();
1041 string errorMsg =
"Unknown error while ending the round.";
1042 DebugConsole.ThrowError(errorMsg, e);
1043 GameAnalyticsManager.AddErrorEventOnce(
"GameSession.EndRound:UnknownError", GameAnalyticsManager.ErrorSeverity.Error, errorMsg +
"\n" + e.StackTrace);
1045 if (Timing.TotalTime > LastEndRoundErrorMessageTime + 1.0)
1047 GameMain.Server?.SendChatMessage(errorMsg +
"\n" + e.StackTrace, Networking.ChatMessageType.Error);
1048 LastEndRoundErrorMessageTime = Timing.TotalTime;
1054 RoundEnding =
false;
1057 int GetAmountOfMoney(IEnumerable<Character> crew)
1063 null => campaign.Bank.Balance,
1064 _ => crew.Sum(c => c.Wallet.Balance) + campaign.Bank.Balance
1074 GameAnalyticsManager.AddDesignEvent(eventId +
"Submarine:" + (
Submarine.
MainSub?.
Info?.
Name ??
"none"), RoundDuration);
1076 GameAnalyticsManager.AddDesignEvent(eventId +
"GameMode:" + (
GameMode?.Name.Value ??
"none"), RoundDuration);
1077 GameAnalyticsManager.AddDesignEvent(eventId +
"CrewSize:" + (
CrewManager?.GetCharacterInfos()?.Count() ?? 0), RoundDuration);
1078 foreach (
Mission mission
in missions)
1080 GameAnalyticsManager.AddDesignEvent(eventId +
"MissionType:" + (mission.
Prefab.
Type.ToString() ??
"none") +
":" + mission.
Prefab.
Identifier +
":" + (mission.
Completed ?
"Completed" :
"Failed"), RoundDuration);
1082 if (!ContentPackageManager.ModsEnabled)
1089 GameAnalyticsManager.AddDesignEvent(eventId +
"LevelType:" + (
Level.
Loaded?.
Type.ToString() ??
"none" +
":" + levelId), RoundDuration);
1121 if (traitorResults.HasValue)
1123 GameAnalyticsManager.AddDesignEvent($
"TraitorEvent:{traitorResults.Value.TraitorEventIdentifier}:{traitorResults.Value.ObjectiveSuccessful}");
1124 GameAnalyticsManager.AddDesignEvent($
"TraitorEvent:{traitorResults.Value.TraitorEventIdentifier}:{(traitorResults.Value.VotedCorrectTraitor ? "TraitorIdentifier
" : "TraitorUnidentified
")}");
1131 string characterType =
"Unknown";
1134 characterType =
"Bot";
1138 characterType =
"Player";
1140 GameAnalyticsManager.AddDesignEvent(
"TimeSpentOnDevices:" + (
GameMode?.Preset?.Identifier.Value ??
"none") +
":" + characterType +
":" + (c.
Info?.
Job?.
Prefab.
Identifier.Value ??
"NoJob") +
":" + itemSelectedDuration.Key.Identifier, itemSelectedDuration.Value);
1146 GameAnalyticsManager.AddDesignEvent(eventId + tutorialMode.Tutorial.Identifier);
1149 GameAnalyticsManager.AddDesignEvent(
"FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
1152 GameAnalyticsManager.AddDesignEvent(eventId +
"TimeSpentCleaning", TimeSpentCleaning);
1153 GameAnalyticsManager.AddDesignEvent(eventId +
"TimeSpentPainting", TimeSpentPainting);
1154 TimeSpentCleaning = TimeSpentPainting = 0.0;
1162 casualties.Add(character);
1171 casualties.Remove(character);
1181 if (!contentPackageNames.Any()) {
return true; }
1183 List<string> missingPackages =
new List<string>();
1184 foreach (
string packageName
in contentPackageNames)
1186 if (!ContentPackageManager.EnabledPackages.All.Any(cp => cp.NameMatches(packageName)))
1188 missingPackages.Add(packageName);
1191 List<string> excessPackages =
new List<string>();
1192 foreach (
ContentPackage cp
in ContentPackageManager.EnabledPackages.All)
1195 if (!contentPackageNames.Any(p => cp.
NameMatches(p)))
1197 excessPackages.Add(cp.
Name);
1201 bool orderMismatch =
false;
1202 if (missingPackages.Count == 0 && missingPackages.Count == 0)
1205 for (
int i = 0; i < contentPackageNames.Count && i < enabledPackages.Length; i++)
1207 if (!enabledPackages[i].NameMatches(contentPackageNames[i]))
1209 orderMismatch =
true;
1215 if (!orderMismatch && missingPackages.Count == 0 && excessPackages.Count == 0) {
return true; }
1217 if (missingPackages.Count == 1)
1219 errorMsg = TextManager.GetWithVariable(
"campaignmode.missingcontentpackage",
"[missingcontentpackage]", missingPackages[0]);
1221 else if (missingPackages.Count > 1)
1223 errorMsg = TextManager.GetWithVariable(
"campaignmode.missingcontentpackages",
"[missingcontentpackages]",
string.Join(
", ", missingPackages));
1225 if (excessPackages.Count == 1)
1227 if (!errorMsg.IsNullOrEmpty()) { errorMsg +=
"\n"; }
1228 errorMsg += TextManager.GetWithVariable(
"campaignmode.incompatiblecontentpackage",
"[incompatiblecontentpackage]", excessPackages[0]);
1230 else if (excessPackages.Count > 1)
1232 if (!errorMsg.IsNullOrEmpty()) { errorMsg +=
"\n"; }
1233 errorMsg += TextManager.GetWithVariable(
"campaignmode.incompatiblecontentpackages",
"[incompatiblecontentpackages]",
string.Join(
", ", excessPackages));
1237 if (!errorMsg.IsNullOrEmpty()) { errorMsg +=
"\n"; }
1238 errorMsg += TextManager.GetWithVariable(
"campaignmode.contentpackageordermismatch",
"[loadorder]",
string.Join(
", ", contentPackageNames));
1248 throw new NotSupportedException(
"GameSessions can only be saved when playing in a campaign mode.");
1251 XDocument doc =
new XDocument(
new XElement(
"Gamesession"));
1252 XElement rootElement = doc.Root ??
throw new NullReferenceException(
"Game session XML element is invalid: document is null.");
1255 #warning TODO: after this gets on main, replace savetime with the commented line
1262 bool hasNewPendingSub = Campaign.PendingSubmarineSwitch !=
null &&
1264 if (hasNewPendingSub)
1266 Campaign.SwitchSubs();
1270 if (OwnedSubmarines !=
null)
1272 List<string> ownedSubmarineNames =
new List<string>();
1273 var ownedSubsElement =
new XElement(
"ownedsubmarines");
1274 rootElement.Add(ownedSubsElement);
1275 foreach (var ownedSub
in OwnedSubmarines)
1277 ownedSubsElement.Add(
new XElement(
"sub",
new XAttribute(
"name", ownedSub.Name)));
1280 if (
Map !=
null) { rootElement.Add(
new XAttribute(
"mapseed",
Map.
Seed)); }
1281 rootElement.Add(
new XAttribute(
"selectedcontentpackagenames",
1286 doc.SaveSafe(filePath, throwExceptions:
true);
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static void ClearAllEffects()
Removes all the effects of the prefab (including the sounds and other assets defined in them)....
static void LoadAllEffectsAndTreatmentSuitabilities()
Should be called before each round: loads all StatusEffects and refreshes treatment suitabilities.
virtual bool TryPurchase(Client client, int price)
Faction GetRandomFaction(Rand.RandSync randSync, bool allowEmpty=true)
Returns a random faction based on their ControlledOutpostPercentage
SubmarineInfo PendingSubmarineSwitch
bool TransferItemsOnSubSwitch
static void ClearBossProgressBars()
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
Dictionary< ItemPrefab, double > ItemSelectedDurations
bool NameMatches(Identifier name)
bool HasMultiplayerSyncedContent
Does the content package include some content that needs to match between all players in multiplayer.
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
IEnumerable< Character > GetCharacters()
void KillCharacter(Character killedCharacter, bool resetCrewListIndex=true)
void ReviveCharacter(Character revivedCharacter)
void TriggerOnEndRoundActions()
void Update(float deltaTime)
readonly EventLog EventLog
static readonly List< GUIComponent > MessageBoxes
static GameSession?? GameSession
static NetLobbyScreen NetLobbyScreen
static bool IsFirstLaunch
static readonly Version Version
static GameScreen GameScreen
static NetworkMember NetworkMember
virtual void ShowStartMessage()
virtual IEnumerable< Mission > Missions
virtual void AddExtraMissions(LevelData levelData)
virtual void End(CampaignMode.TransitionType transitionType=CampaignMode.TransitionType.None)
virtual void Update(float deltaTime)
readonly bool IsSinglePlayer
readonly Identifier Identifier
Mission? GetMission(int index)
void StartRound(LevelData? levelData, bool mirrorLevel=false, SubmarineInfo? startOutpost=null, SubmarineInfo? endOutpost=null)
List< SubmarineInfo > OwnedSubmarines
CharacterTeamType? WinningTeam
static Location[] CreateDummyLocations(string seed, LocationType? forceLocationType=null)
void PlaceSubAtStart(Level? level)
void LogEndRoundStats(string eventId, TraitorManager.TraitorResults? traitorResults=null)
GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, CampaignSettings settings, string? seed=null, MissionType missionType=MissionType.None)
Start a new GameSession. Will be saved to the specified save path (if playing a game mode that can be...
void Save(string filePath)
bool IsSubmarineOwned(SubmarineInfo query)
void StartRound(string levelSeed, float? difficulty=null, LevelGenerationParams? levelGenerationParams=null)
void EnforceMissionOrder(List< Identifier > missionIdentifiers)
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
IEnumerable< Mission > Missions
static bool IsCompatibleWithEnabledContentPackages(IList< string > contentPackageNames, out LocalizedString errorMsg)
RoundSummary RoundSummary
bool IsCurrentLocationRadiated()
bool TryPurchaseSubmarine(SubmarineInfo newSubmarine, Client? client=null)
SubmarineInfo SubmarineInfo
void ReviveCharacter(Character character)
void KillCharacter(Character character)
IEnumerable< Character > Casualties
GameSession(SubmarineInfo submarineInfo, GameModePreset gameModePreset, string? seed=null, IEnumerable< MissionPrefab >? missionPrefabs=null)
Start a new GameSession with a specific pre-selected mission.
static Location[] CreateDummyLocations(LevelData levelData, LocationType? forceLocationType=null)
readonly EventManager EventManager
int GetMissionIndex(Mission mission)
GameSession(SubmarineInfo submarineInfo, List< SubmarineInfo > ownedSubmarines, XDocument doc, string saveFile)
Load a game session from the specified XML document. The session will be saved to the specified path.
void EndRound(string endMessage, CampaignMode.TransitionType transitionType=CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults=null)
void Update(float deltaTime)
void EnableEventLogNotificationIcon(bool enabled)
void SwitchSubmarine(SubmarineInfo newSubmarine, bool transferItems, Client? client=null)
Switch to another submarine. The sub is loaded when the next round starts.
static readonly List< Item > ItemList
static IEnumerable< DockingPort > List
DockingPort DockingTarget
static readonly Dictionary< int, GridInfo > Grids
static readonly HashSet< Connection > ChangedConnections
static LevelData CreateRandom(string seed="", float? difficulty=null, LevelGenerationParams generationParams=null, bool requireOutpost=false)
OutpostGenerationParams ForceOutpostGenerationParams
LevelGenerationParams GenerationParams
readonly LevelData LevelData
float GetRealWorldDepth(float worldPositionY)
Calculate the "real" depth in meters from the surface of Europa (the value you see on the nav termina...
static Level Generate(LevelData levelData, bool mirror, Location startLocation, Location endLocation, SubmarineInfo startOutpost=null, SubmarineInfo endOutpost=null)
void SetPositionRelativeToMainSub()
ushort OriginalLinkedToID
static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, LocationType forceLocationType=null, IEnumerable< Location > existingLocations=null)
LocalizedString DisplayName
Identifier Faction
If set, forces the location to be assigned to this faction. Set to "None" if you don't want the locat...
Identifier SecondaryFaction
If set, forces the location to be assigned to this secondary faction. Set to "None" if you don't want...
static readonly PrefabCollection< LocationType > Prefabs
object Call(string name, params object[] args)
Mersenne Twister based random
static readonly List< MapEntity > MapEntityList
Map(CampaignSettings settings)
Location SelectedLocation
List< LocationConnection > Connections
readonly string StringRepresentation
readonly MissionPrefab Prefab
virtual void SetLevel(LevelData level)
void End()
End the mission and give a reward if it was completed successfully
readonly MissionType Type
readonly List<(Identifier from, Identifier to)> AllowedConnectionTypes
The mission can only be received when travelling from a location of the first type to a location of t...
readonly Identifier RequiredLocationFaction
The mission can only happen in locations owned by this faction. In the mission mode,...
readonly List< Identifier > AllowedLocationTypes
The mission can only be received in these location types
static MultiPlayerCampaign LoadNew(XElement element)
readonly Identifier Identifier
GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, CampaignMode.TransitionType transitionType=CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults=null)
static SinglePlayerCampaign Load(XElement element)
Load a previously saved single player campaign from xml
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
List< Item > GetItems(bool alsoFromConnectedSubs)
static readonly Submarine[] MainSubs
override Vector2? WorldPosition
void EnableMaintainPosition()
IEnumerable< Submarine > DockedTo
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
void SetPosition(Vector2 position, List< Submarine > checkd=null, bool forceUndockFromStaticSubmarines=true)
Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize=null, float subDockingPortOffset=0.0f, int verticalMoveDir=0)
Attempt to find a spawn position close to the specified position where the sub doesn't collide with w...
bool IsVanillaSubmarine()
XElement SubmarineElement
readonly List< ushort > LeftBehindDockingPortIDs
OutpostGenerationParams OutpostGenerationParams
int GetPrice(Location location=null, ImmutableHashSet< Character > characterList=null)
static List< WayPoint > WayPointList
DateTime wrapper that tries to offer a reliable string representation that's also human-friendly
static SerializableDateTime UtcNow