v1.6.17.0 (Unto the Breach update)
This commit is contained in:
@@ -57,9 +57,9 @@ namespace Barotrauma
|
||||
/// Spawns loot in the specified container.
|
||||
/// </summary>
|
||||
/// <param name="skipItemProbability">Probability for an individual loot item to be skipped. I.e. a value of 1.0 means nothing spawns, 0.5 means there's about 50% of the normal amount of loot.</param>
|
||||
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer, float skipItemProbability = 0.0f)
|
||||
public static IEnumerable<Item> RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer, float skipItemProbability = 0.0f)
|
||||
{
|
||||
CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer, skipItemProbability);
|
||||
return CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer, skipItemProbability);
|
||||
}
|
||||
|
||||
public static Identifier DefaultStartItemSet = new Identifier("normal");
|
||||
@@ -136,12 +136,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAndPlace(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float skipItemProbability = 0.0f)
|
||||
private static IEnumerable<Item> CreateAndPlace(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float skipItemProbability = 0.0f)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
DebugConsole.ThrowError("Clients are not allowed to use AutoItemPlacer.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
return Enumerable.Empty<Item>();
|
||||
}
|
||||
|
||||
List<Item> itemsToSpawn = new List<Item>(100);
|
||||
@@ -249,6 +249,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
return itemsToSpawn;
|
||||
|
||||
void SpawnItems(ItemPrefab itemPrefab, float skipItemProbability = 0.0f)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient) < skipItemProbability) { return; }
|
||||
@@ -260,11 +262,13 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
bool isPvP = GameMain.GameSession?.GameMode is PvPMode;
|
||||
float levelDifficulty = Level.Loaded?.Difficulty ?? 0.0f;
|
||||
foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
|
||||
{
|
||||
if (preferredContainer.CampaignOnly && !isCampaign) { continue; }
|
||||
if (preferredContainer.NotCampaign && isCampaign) { continue; }
|
||||
if (preferredContainer.NotPvP && isPvP) { continue; }
|
||||
if (levelDifficulty < preferredContainer.MinLevelDifficulty || levelDifficulty > preferredContainer.MaxLevelDifficulty) { continue; }
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; }
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
|
||||
|
||||
@@ -588,7 +588,7 @@ namespace Barotrauma
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
public static void DeliverItemsToSub(IEnumerable<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager)
|
||||
public static void DeliverItemsToSub(IEnumerable<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager, bool showNotification = true)
|
||||
{
|
||||
if (!itemsToSpawn.Any()) { return; }
|
||||
|
||||
@@ -606,7 +606,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub == Submarine.MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0))
|
||||
if (sub == Submarine.MainSub && itemsToSpawn.Any(it => !it.Delivered && it.Quantity > 0) && showNotification)
|
||||
{
|
||||
#if CLIENT
|
||||
new GUIMessageBox("",
|
||||
@@ -620,11 +620,14 @@ namespace Barotrauma
|
||||
#else
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
ChatMessage msg = ChatMessage.Create("",
|
||||
TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}",
|
||||
ChatMessageType.ServerMessageBoxInGame, null);
|
||||
msg.IconStyle = "StoreShoppingCrateIcon";
|
||||
GameMain.Server.SendDirectChatMessage(msg, client);
|
||||
if (client.TeamID == CharacterTeamType.None || client.TeamID == sub.TeamID)
|
||||
{
|
||||
ChatMessage msg = ChatMessage.Create("",
|
||||
TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}",
|
||||
ChatMessageType.ServerMessageBoxInGame, null);
|
||||
msg.IconStyle = "StoreShoppingCrateIcon";
|
||||
GameMain.Server.SendDirectChatMessage(msg, client);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -638,7 +641,7 @@ namespace Barotrauma
|
||||
{
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
itemContainer?.Inventory.TryPutItem(item, user: null);
|
||||
ItemSpawned(pi, item, cargoManager);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
@@ -695,6 +698,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
ItemSpawned(item);
|
||||
}
|
||||
|
||||
public static void ItemSpawned(Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.RootContainer?.Submarine;
|
||||
if (sub != null)
|
||||
{
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace Barotrauma
|
||||
characterInfos.Remove(characterInfo);
|
||||
}
|
||||
|
||||
public void AddCharacter(Character character, bool sortCrewList = true)
|
||||
public void AddCharacter(Character character)
|
||||
{
|
||||
if (character.Removed)
|
||||
{
|
||||
@@ -216,10 +216,6 @@ namespace Barotrauma
|
||||
}
|
||||
#if CLIENT
|
||||
var characterComponent = AddCharacterToCrewList(character);
|
||||
if (sortCrewList)
|
||||
{
|
||||
SortCrewList();
|
||||
}
|
||||
if (character.CurrentOrders != null)
|
||||
{
|
||||
foreach (var order in character.CurrentOrders)
|
||||
@@ -324,7 +320,7 @@ namespace Barotrauma
|
||||
Character character = Character.Create(info, spawnWaypoints[i].WorldPosition, info.Name);
|
||||
InitializeCharacter(character, mainSubWaypoints[i], spawnWaypoints[i]);
|
||||
|
||||
AddCharacter(character, sortCrewList: false);
|
||||
AddCharacter(character);
|
||||
#if CLIENT
|
||||
if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; }
|
||||
#endif
|
||||
@@ -364,7 +360,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!character.Info.StartItemsGiven)
|
||||
{
|
||||
character.GiveJobItems(mainSubWaypoint);
|
||||
character.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, mainSubWaypoint);
|
||||
foreach (Item item in character.Inventory.AllItems)
|
||||
{
|
||||
//if the character is loaded from a human prefab with preconfigured items, its ID card gets assigned to the sub it spawns in
|
||||
|
||||
@@ -103,6 +103,9 @@ namespace Barotrauma
|
||||
get { return map; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Which missions have been selected for the current round?
|
||||
/// </summary>
|
||||
public override IEnumerable<Mission> Missions
|
||||
{
|
||||
get
|
||||
@@ -112,10 +115,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (Map.CurrentLocation != null)
|
||||
{
|
||||
var currentLevelData = Level.Loaded?.LevelData ?? GameMain.GameSession?.LevelData;
|
||||
foreach (Mission mission in map.CurrentLocation.SelectedMissions)
|
||||
{
|
||||
if (mission.Locations[0] == mission.Locations[1] ||
|
||||
mission.Locations.Contains(Map.SelectedLocation))
|
||||
if (//mission takes place in current location
|
||||
mission.Locations[0] == mission.Locations[1] ||
|
||||
//mission takes place between two locations, and we're in the level between those locations
|
||||
mission.Locations.Contains(Map.SelectedLocation) && currentLevelData is { Type: LevelData.LevelType.LocationConnection })
|
||||
{
|
||||
yield return mission;
|
||||
}
|
||||
@@ -170,6 +176,7 @@ namespace Barotrauma
|
||||
protected CampaignMode(GameModePreset preset, CampaignSettings settings)
|
||||
: base(preset)
|
||||
{
|
||||
Settings = settings;
|
||||
Bank = new Wallet(Option<Character>.None())
|
||||
{
|
||||
Balance = settings.InitialMoney
|
||||
@@ -246,7 +253,7 @@ namespace Barotrauma
|
||||
sub != leavingSub &&
|
||||
!leavingSub.DockedTo.Contains(sub) &&
|
||||
sub.Info.Type == SubmarineType.Player && sub.TeamID == CharacterTeamType.Team1 && // pirate subs are currently tagged as player subs as well
|
||||
sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
|
||||
!sub.IsRespawnShuttle &&
|
||||
(sub.AtEndExit != leavingSub.AtEndExit || sub.AtStartExit != leavingSub.AtStartExit));
|
||||
}
|
||||
|
||||
@@ -401,9 +408,9 @@ namespace Barotrauma
|
||||
currentLocation.DeselectMission(mission);
|
||||
}
|
||||
}
|
||||
if (levelData.HasBeaconStation && !levelData.IsBeaconActive && Missions.None(m => m.Prefab.Type == MissionType.Beacon))
|
||||
if (levelData.HasBeaconStation && !levelData.IsBeaconActive && Missions.None(m => m.Prefab.Type == Tags.MissionTypeBeacon))
|
||||
{
|
||||
var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.IsSideObjective && m.Type == MissionType.Beacon);
|
||||
var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.IsSideObjective && m.Type == Tags.MissionTypeBeacon);
|
||||
if (beaconMissionPrefabs.Any())
|
||||
{
|
||||
var filteredMissions = beaconMissionPrefabs.Where(m => levelData.Difficulty >= m.MinLevelDifficulty && levelData.Difficulty <= m.MaxLevelDifficulty);
|
||||
@@ -490,7 +497,7 @@ namespace Barotrauma
|
||||
if (missionPrefabs.Any())
|
||||
{
|
||||
var missionPrefab = ToolBox.SelectWeightedRandom(missionPrefabs, p => p.Commonness, rand);
|
||||
if (missionPrefab.Type == MissionType.Pirate && Missions.Any(m => m.Prefab.Type == MissionType.Pirate))
|
||||
if (missionPrefab.Type == Tags.MissionTypePirate && Missions.Any(m => m.Prefab.Type == Tags.MissionTypePirate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -769,7 +776,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var dockedSub in Level.Loaded.StartOutpost.DockedTo)
|
||||
{
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
if (dockedSub.IsRespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
}
|
||||
@@ -809,7 +816,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var dockedSub in Level.Loaded.EndOutpost.DockedTo)
|
||||
{
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
if (dockedSub.IsRespawnShuttle || dockedSub.TeamID != submarineTeam) { continue; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
}
|
||||
@@ -1287,7 +1294,7 @@ namespace Barotrauma
|
||||
return Faction.GetPlayerAffiliationStatus(faction);
|
||||
}
|
||||
|
||||
public abstract void Save(XElement element);
|
||||
public abstract void Save(XElement element, bool isSavingOnLoading);
|
||||
|
||||
protected void LoadStats(XElement element)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -7,6 +6,6 @@ namespace Barotrauma
|
||||
{
|
||||
public CoOpMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs) : base(preset, ValidateMissionPrefabs(missionPrefabs, MissionPrefab.CoOpMissionClasses)) { }
|
||||
|
||||
public CoOpMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.CoOpMissionClasses), seed) { }
|
||||
public CoOpMode(GameModePreset preset, IEnumerable<Identifier> missionTypes, string seed) : base(preset, ValidateMissionTypes(missionTypes, MissionPrefab.CoOpMissionClasses), seed) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -25,42 +26,37 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, MissionType missionType, string seed)
|
||||
public MissionMode(GameModePreset preset, IEnumerable<Identifier> missionTypes, string seed)
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionType, difficultyLevel: GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty);
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes, difficultyLevel: GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty);
|
||||
if (mission != null)
|
||||
{
|
||||
missions.Add(mission);
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<MissionType, Type> missionClasses)
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<Identifier, Type> missionClasses)
|
||||
{
|
||||
foreach (MissionPrefab missionPrefab in missionPrefabs)
|
||||
{
|
||||
if (ValidateMissionType(missionPrefab.Type, missionClasses) != missionPrefab.Type)
|
||||
if (!missionClasses.ContainsValue(missionPrefab.MissionClass))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot start gamemode with mission type " + missionPrefab.Type);
|
||||
throw new InvalidOperationException($"Cannot start gamemode with a {missionPrefab.MissionClass} mission.");
|
||||
}
|
||||
}
|
||||
return missionPrefabs;
|
||||
}
|
||||
|
||||
protected static MissionType ValidateMissionType(MissionType missionType, Dictionary<MissionType, Type> missionClasses)
|
||||
/// <summary>
|
||||
/// Returns the mission types that are valid for the given mission classes (e.g. all mission types suitable for the PvP mission classes).
|
||||
/// </summary>
|
||||
public static IEnumerable<Identifier> ValidateMissionTypes(IEnumerable<Identifier> missionTypes, Dictionary<Identifier, Type> missionClasses)
|
||||
{
|
||||
var missionTypes = (MissionType[])Enum.GetValues(typeof(MissionType));
|
||||
for (int i = 0; i < missionTypes.Length; i++)
|
||||
{
|
||||
var type = missionTypes[i];
|
||||
if (type == MissionType.None || type == MissionType.All) { continue; }
|
||||
if (!missionClasses.ContainsKey(type))
|
||||
{
|
||||
missionType &= ~(type);
|
||||
}
|
||||
}
|
||||
return missionType;
|
||||
return missionTypes.Where(type =>
|
||||
MissionPrefab.Prefabs.OrderBy(missionPrefab => missionPrefab.UintIdentifier)
|
||||
.Any(missionPrefab => missionPrefab.Type == type && missionClasses.ContainsValue(missionPrefab.MissionClass)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-9
@@ -116,7 +116,6 @@ namespace Barotrauma
|
||||
//only the server generates the map, the clients load it from a save file
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
campaign.Settings = settings;
|
||||
campaign.map = new Map(campaign, mapSeed);
|
||||
}
|
||||
campaign.InitProjSpecific();
|
||||
@@ -133,16 +132,24 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public static string GetCharacterDataSavePath(string savePath)
|
||||
|
||||
public static string GetCharacterDataSavePath(string loadPath)
|
||||
{
|
||||
return Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
|
||||
string directory = Path.GetDirectoryName(loadPath);
|
||||
string fileName = Path.GetFileNameWithoutExtension(loadPath);
|
||||
|
||||
if (CampaignDataPath.IsBackupPath(loadPath, out uint backupIndex))
|
||||
{
|
||||
string trimmedFileName = Path.GetFileNameWithoutExtension(fileName);
|
||||
return Path.Combine(directory, $"{trimmedFileName}_CharacterData{SaveUtil.BackupCharacterDataExtensionStart}{backupIndex}");
|
||||
}
|
||||
return Path.Combine(directory, $"{fileName}_CharacterData.xml");
|
||||
}
|
||||
|
||||
public static string GetCharacterDataSavePath()
|
||||
{
|
||||
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
|
||||
}
|
||||
public static string GetCharacterDataPathForLoading()
|
||||
=> GetCharacterDataSavePath(GameMain.GameSession.DataPath.LoadPath);
|
||||
public static string GetCharacterDataPathForSaving()
|
||||
=> GetCharacterDataSavePath(GameMain.GameSession.DataPath.SavePath);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the campaign from an XML element. Creates the map if it hasn't been created yet, otherwise updates the state of the map.
|
||||
@@ -254,7 +261,7 @@ namespace Barotrauma
|
||||
|
||||
#if SERVER
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
string characterDataPath = GetCharacterDataPathForLoading();
|
||||
if (!File.Exists(characterDataPath))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to load the character data for the campaign. Could not find the file \"{characterDataPath}\".");
|
||||
|
||||
@@ -1,50 +1,27 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class PvPMode : MissionMode
|
||||
partial class PvPMode : MissionMode
|
||||
{
|
||||
public PvPMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs) : base(preset, ValidateMissionPrefabs(missionPrefabs, MissionPrefab.PvPMissionClasses)) { }
|
||||
|
||||
public PvPMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.PvPMissionClasses), seed) { }
|
||||
|
||||
public void AssignTeamIDs(IEnumerable<Client> clients)
|
||||
public PvPMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs) :
|
||||
base(preset, ValidateMissionPrefabs(missionPrefabs, MissionPrefab.PvPMissionClasses))
|
||||
{
|
||||
int team1Count = 0, team2Count = 0;
|
||||
//if a client has a preference, assign them to that team
|
||||
List<Client> unassignedClients = new List<Client>(clients);
|
||||
for (int i = 0; i < unassignedClients.Count; i++)
|
||||
if (Missions.None())
|
||||
{
|
||||
if (unassignedClients[i].PreferredTeam == CharacterTeamType.Team1 ||
|
||||
unassignedClients[i].PreferredTeam == CharacterTeamType.Team2)
|
||||
{
|
||||
assignTeam(unassignedClients[i], unassignedClients[i].PreferredTeam);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
//assign the rest of the clients to the team that has the least players
|
||||
while (unassignedClients.Any())
|
||||
{
|
||||
var randomClient = unassignedClients.GetRandom(Rand.RandSync.Unsynced);
|
||||
assignTeam(randomClient, team1Count < team2Count ? CharacterTeamType.Team1 : CharacterTeamType.Team2);
|
||||
throw new System.Exception($"Attempted to start {nameof(PvPMode)} without a mission.");
|
||||
}
|
||||
}
|
||||
|
||||
void assignTeam(Client client, CharacterTeamType team)
|
||||
public PvPMode(GameModePreset preset, IEnumerable<Identifier> missionTypes, string seed) :
|
||||
base(preset, ValidateMissionTypes(missionTypes, MissionPrefab.PvPMissionClasses), seed)
|
||||
{
|
||||
if (Missions.None())
|
||||
{
|
||||
client.TeamID = team;
|
||||
unassignedClients.Remove(client);
|
||||
if (team == CharacterTeamType.Team1)
|
||||
{
|
||||
team1Count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
team2Count++;
|
||||
}
|
||||
throw new System.Exception($"Attempted to start {nameof(PvPMode)} without a mission.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TestGameMode : GameMode
|
||||
{
|
||||
public TestGameMode(GameModePreset preset) : base(preset)
|
||||
{
|
||||
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs.OrderBy(p => p.Identifier))
|
||||
{
|
||||
for (int i = 0; i < jobPrefab.InitialCount; i++)
|
||||
{
|
||||
var variant = Rand.Range(0, jobPrefab.Variants);
|
||||
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,52 @@ using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.PerkBehaviors;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal readonly record struct PerkCollection(
|
||||
ImmutableArray<DisembarkPerkPrefab> Team1Perks,
|
||||
ImmutableArray<DisembarkPerkPrefab> Team2Perks)
|
||||
{
|
||||
public static readonly PerkCollection Empty = new PerkCollection(ImmutableArray<DisembarkPerkPrefab>.Empty, ImmutableArray<DisembarkPerkPrefab>.Empty);
|
||||
|
||||
public void ApplyAll(IReadOnlyCollection<Character> team1Characters, IReadOnlyCollection<Character> team2Characters)
|
||||
{
|
||||
// Usually there should only be 1 mission active on pvp and mission modes
|
||||
bool anyMissionDoesNotLoadSubs = GameMain.GameSession.Missions.Any(static m => !m.Prefab.LoadSubmarines);
|
||||
|
||||
foreach (var team1Perk in Team1Perks)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("DisembarkPerk:" + team1Perk.Identifier);
|
||||
foreach (PerkBase behavior in team1Perk.PerkBehaviors)
|
||||
{
|
||||
if (anyMissionDoesNotLoadSubs && !behavior.CanApplyWithoutSubmarine()) { continue; }
|
||||
#if CLIENT
|
||||
if (behavior.Simulation == PerkSimulation.ServerOnly) { continue; }
|
||||
#endif
|
||||
behavior.ApplyOnRoundStart(team1Characters, Submarine.MainSubs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (Submarine.MainSubs[1] is not null)
|
||||
{
|
||||
foreach (var team2Perk in Team2Perks)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("DisembarkPerk:" + team2Perk.Identifier);
|
||||
foreach (PerkBase behavior in team2Perk.PerkBehaviors)
|
||||
{
|
||||
if (anyMissionDoesNotLoadSubs && !behavior.CanApplyWithoutSubmarine()) { continue; }
|
||||
#if CLIENT
|
||||
if (behavior.Simulation == PerkSimulation.ServerOnly) { continue; }
|
||||
#endif
|
||||
behavior.ApplyOnRoundStart(team2Characters, Submarine.MainSubs[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class GameSession
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -43,10 +86,27 @@ namespace Barotrauma
|
||||
|
||||
private readonly HashSet<Character> casualties = new HashSet<Character>();
|
||||
public IEnumerable<Character> Casualties { get { return casualties; } }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Permadeaths per MP account are stored currently just for an achievement ("getoutalive").
|
||||
/// The dictionary stores Option<AccountId> directly just to keep the code using it simpler and leaner, but if
|
||||
/// this is ever used for something else too, feel free to refactor it to use actual AccountIds.
|
||||
/// </summary>
|
||||
private Dictionary<Option<AccountId>, int> permadeathsPerAccount = new Dictionary<Option<AccountId>, int>();
|
||||
public void IncrementPermadeath(Option<AccountId> accountId)
|
||||
{
|
||||
permadeathsPerAccount[accountId] = permadeathsPerAccount.GetValueOrDefault(accountId, 0) + 1;
|
||||
}
|
||||
public int PermadeathCountForAccount(Option<AccountId> accountId)
|
||||
{
|
||||
return permadeathsPerAccount.GetValueOrDefault(accountId, 0);
|
||||
}
|
||||
|
||||
public CharacterTeamType? WinningTeam;
|
||||
|
||||
/// <summary>
|
||||
/// Is a round currently running?
|
||||
/// </summary>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public bool RoundEnding { get; private set; }
|
||||
@@ -102,12 +162,15 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public SubmarineInfo SubmarineInfo { get; set; }
|
||||
public SubmarineInfo EnemySubmarineInfo { get; set; }
|
||||
|
||||
public SubmarineInfo? ForceOutpostModule;
|
||||
|
||||
public List<SubmarineInfo> OwnedSubmarines = new List<SubmarineInfo>();
|
||||
|
||||
public Submarine? Submarine { get; set; }
|
||||
|
||||
public string? SavePath { get; set; }
|
||||
public CampaignDataPath DataPath { get; set; }
|
||||
|
||||
public bool TraitorsEnabled =>
|
||||
GameMain.NetworkMember?.ServerSettings != null &&
|
||||
@@ -119,39 +182,48 @@ namespace Barotrauma
|
||||
{
|
||||
InitProjSpecific();
|
||||
SubmarineInfo = submarineInfo;
|
||||
EnemySubmarineInfo = SubmarineInfo;
|
||||
GameMain.GameSession = this;
|
||||
EventManager = new EventManager();
|
||||
}
|
||||
|
||||
private GameSession(SubmarineInfo submarineInfo, SubmarineInfo enemySubmarineInfo)
|
||||
: this(submarineInfo)
|
||||
{
|
||||
EnemySubmarineInfo = enemySubmarineInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a new GameSession. Will be saved to the specified save path (if playing a game mode that can be saved).
|
||||
/// </summary>
|
||||
public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, CampaignSettings settings, string? seed = null, MissionType missionType = MissionType.None)
|
||||
public GameSession(SubmarineInfo submarineInfo, Option<SubmarineInfo> enemySub, CampaignDataPath dataPath, GameModePreset gameModePreset, CampaignSettings settings, string? seed = null, IEnumerable<Identifier>? missionTypes = null)
|
||||
: this(submarineInfo)
|
||||
{
|
||||
this.SavePath = savePath;
|
||||
DataPath = dataPath;
|
||||
CrewManager = new CrewManager(gameModePreset.IsSinglePlayer);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, settings, missionType: missionType);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, settings, missionTypes: missionTypes);
|
||||
EnemySubmarineInfo = enemySub.TryUnwrap(out var enemySubmarine) ? enemySubmarine : submarineInfo;
|
||||
InitOwnedSubs(submarineInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a new GameSession with a specific pre-selected mission.
|
||||
/// </summary>
|
||||
public GameSession(SubmarineInfo submarineInfo, GameModePreset gameModePreset, string? seed = null, IEnumerable<MissionPrefab>? missionPrefabs = null)
|
||||
public GameSession(SubmarineInfo submarineInfo, Option<SubmarineInfo> enemySub, GameModePreset gameModePreset, string? seed = null, IEnumerable<MissionPrefab>? missionPrefabs = null)
|
||||
: this(submarineInfo)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset.IsSinglePlayer);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, CampaignSettings.Empty, missionPrefabs: missionPrefabs);
|
||||
EnemySubmarineInfo = enemySub.TryUnwrap(out var enemySubmarine) ? enemySubmarine : submarineInfo;
|
||||
InitOwnedSubs(submarineInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a game session from the specified XML document. The session will be saved to the specified path.
|
||||
/// </summary>
|
||||
public GameSession(SubmarineInfo submarineInfo, List<SubmarineInfo> ownedSubmarines, XDocument doc, string saveFile) : this(submarineInfo)
|
||||
public GameSession(SubmarineInfo submarineInfo, List<SubmarineInfo> ownedSubmarines, XDocument doc, CampaignDataPath campaignData) : this(submarineInfo)
|
||||
{
|
||||
this.SavePath = saveFile;
|
||||
DataPath = campaignData;
|
||||
GameMain.GameSession = this;
|
||||
XElement rootElement = doc.Root ?? throw new NullReferenceException("Game session XML element is invalid: document is null.");
|
||||
|
||||
@@ -182,7 +254,25 @@ namespace Barotrauma
|
||||
mpCampaign.LoadNewLevel();
|
||||
InitOwnedSubs(submarineInfo, ownedSubmarines);
|
||||
//save to ensure the campaign ID in the save file matches the one that got assigned to this campaign instance
|
||||
SaveUtil.SaveGame(saveFile);
|
||||
SaveUtil.SaveGame(campaignData, isSavingOnLoading: true);
|
||||
}
|
||||
break;
|
||||
case "permadeaths":
|
||||
permadeathsPerAccount = new Dictionary<Option<AccountId>, int>();
|
||||
foreach (XElement accountElement in subElement.Elements("account"))
|
||||
{
|
||||
if (accountElement.Attribute("id") is XAttribute accountIdAttr &&
|
||||
accountElement.Attribute("permadeathcount") is XAttribute permadeathCountAttr)
|
||||
{
|
||||
try
|
||||
{
|
||||
permadeathsPerAccount[AccountId.Parse(accountIdAttr.Value)] = int.Parse(permadeathCountAttr.Value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.AddWarning($"Exception while trying to load permadeath counts!\n{e}\n id: {accountIdAttr}\n permadeathcount: {permadeathCountAttr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -198,31 +288,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string? seed, SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable<MissionPrefab>? missionPrefabs = null, MissionType missionType = MissionType.None)
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string? seed, SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable<MissionPrefab>? missionPrefabs = null, IEnumerable<Identifier>? missionTypes = null)
|
||||
{
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode) || gameModePreset.GameModeType == typeof(PvPMode))
|
||||
{
|
||||
//don't allow hidden mission types (e.g. GoTo) in single mission modes
|
||||
var missionTypes = (MissionType[])Enum.GetValues(typeof(MissionType));
|
||||
for (int i = 0; i < missionTypes.Length; i++)
|
||||
{
|
||||
if (MissionPrefab.HiddenMissionClasses.Contains(missionTypes[i]))
|
||||
{
|
||||
missionType &= ~missionTypes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode))
|
||||
{
|
||||
return missionPrefabs != null ?
|
||||
new CoOpMode(gameModePreset, missionPrefabs) :
|
||||
new CoOpMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
|
||||
new CoOpMode(gameModePreset, missionTypes, seed ?? ToolBox.RandomSeed(8));
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(PvPMode))
|
||||
{
|
||||
return missionPrefabs != null ?
|
||||
new PvPMode(gameModePreset, missionPrefabs) :
|
||||
new PvPMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
|
||||
new PvPMode(gameModePreset, missionTypes, seed ?? ToolBox.RandomSeed(8));
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
|
||||
{
|
||||
@@ -312,10 +390,19 @@ namespace Barotrauma
|
||||
return dummyLocations;
|
||||
}
|
||||
|
||||
public static bool ShouldApplyDisembarkPoints(GameModePreset? preset)
|
||||
{
|
||||
if (preset is null) { return true; } // sure I guess?
|
||||
|
||||
return preset == GameModePreset.Sandbox ||
|
||||
preset == GameModePreset.Mission ||
|
||||
preset == GameModePreset.PvP;
|
||||
}
|
||||
|
||||
public void LoadPreviousSave()
|
||||
{
|
||||
Submarine.Unload();
|
||||
SaveUtil.LoadGame(SavePath ?? "");
|
||||
SaveUtil.LoadGame(DataPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,7 +449,7 @@ namespace Barotrauma
|
||||
public bool IsSubmarineOwned(SubmarineInfo query)
|
||||
{
|
||||
return
|
||||
Submarine.MainSub.Info.Name == query.Name ||
|
||||
Submarine.MainSub?.Info.Name == query.Name ||
|
||||
(OwnedSubmarines != null && OwnedSubmarines.Any(os => os.Name == query.Name));
|
||||
}
|
||||
|
||||
@@ -379,11 +466,13 @@ namespace Barotrauma
|
||||
|
||||
return isRadiated;
|
||||
}
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null, LevelGenerationParams? levelGenerationParams = null)
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null, LevelGenerationParams? levelGenerationParams = null, Identifier forceBiome = default)
|
||||
{
|
||||
if (GameMode == null) { return; }
|
||||
|
||||
LevelData? randomLevel = null;
|
||||
bool pvpOnly = GameMode is PvPMode;
|
||||
foreach (Mission mission in Missions.Union(GameMode.Missions))
|
||||
{
|
||||
MissionPrefab missionPrefab = mission.Prefab;
|
||||
@@ -392,9 +481,9 @@ namespace Barotrauma
|
||||
!missionPrefab.AllowedConnectionTypes.Any())
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelSeed));
|
||||
LocationType? locationType = LocationType.Prefabs
|
||||
LocationType locationType = LocationType.Prefabs
|
||||
.Where(lt => missionPrefab.AllowedLocationTypes.Any(m => m == lt.Identifier))
|
||||
.GetRandom(rand);
|
||||
.GetRandom(rand)!;
|
||||
dummyLocations = CreateDummyLocations(levelSeed, locationType);
|
||||
|
||||
if (!tryCreateFaction(mission.Prefab.RequiredLocationFaction, dummyLocations, static (loc, fac) => loc.Faction = fac))
|
||||
@@ -417,14 +506,90 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost: true);
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost: true, biomeId: forceBiome, pvpOnly: pvpOnly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
randomLevel ??= LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams);
|
||||
randomLevel ??= LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, biomeId: forceBiome, pvpOnly: pvpOnly);
|
||||
StartRound(randomLevel);
|
||||
}
|
||||
|
||||
private bool TryGenerateStationAroundModule(SubmarineInfo? moduleInfo, out Submarine? outpostSub)
|
||||
{
|
||||
outpostSub = null;
|
||||
if (moduleInfo == null) { return false; }
|
||||
|
||||
var allSuitableOutpostParams = OutpostGenerationParams.OutpostParams
|
||||
.Where(outpostParam => IsOutpostParamsSuitable(outpostParam));
|
||||
|
||||
// allow for fallback when there are no options with allowed location types defined
|
||||
var suitableOutpostParams =
|
||||
allSuitableOutpostParams.Where(p => p.AllowedLocationTypes.Any()).GetRandomUnsynced() ??
|
||||
allSuitableOutpostParams.GetRandomUnsynced();
|
||||
|
||||
bool IsOutpostParamsSuitable(OutpostGenerationParams outpostParams)
|
||||
{
|
||||
bool moduleWorksWithOutpostParams = outpostParams.ModuleCounts.Any(moduleCount => moduleInfo.OutpostModuleInfo.ModuleFlags.Contains(moduleCount.Identifier));
|
||||
if (!moduleWorksWithOutpostParams) { return false; }
|
||||
|
||||
// is there a location that these outpostParams are suitable for, and which this module is suitable for
|
||||
return LocationType.Prefabs.Any(locationType => IsSuitableLocationType(moduleInfo.OutpostModuleInfo.AllowedLocationTypes, locationType.Identifier)
|
||||
&& IsSuitableLocationType(outpostParams.AllowedLocationTypes, locationType.Identifier));
|
||||
|
||||
bool IsSuitableLocationType(IEnumerable<Identifier> allowedLocationTypes, Identifier locationType)
|
||||
{
|
||||
return allowedLocationTypes.None() || allowedLocationTypes.Contains("Any".ToIdentifier()) || allowedLocationTypes.Contains(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
if (suitableOutpostParams == null)
|
||||
{
|
||||
DebugConsole.AddWarning("No suitable generation parameters found for ForceOutpostModule, skipping outpost generation!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var suitableLocationType = LocationType.Prefabs.Where(locationType =>
|
||||
suitableOutpostParams.AllowedLocationTypes.Contains(locationType.Identifier)).GetRandomUnsynced();
|
||||
|
||||
if (suitableLocationType == null)
|
||||
{
|
||||
DebugConsole.AddWarning("No suitable location type found for ForceOutpostModule, skipping outpost generation!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to find a required faction id matching our module
|
||||
var requiredFactionModuleCount = suitableOutpostParams.ModuleCounts.FirstOrDefault(mc => !mc.RequiredFaction.IsEmpty && moduleInfo.OutpostModuleInfo.ModuleFlags.Contains(mc.Identifier));
|
||||
Identifier requiredFactionId = requiredFactionModuleCount?.RequiredFaction ?? Identifier.Empty;
|
||||
|
||||
if (requiredFactionId.IsEmpty)
|
||||
{
|
||||
// no matching faction requirements, generate normally from location type
|
||||
outpostSub = OutpostGenerator.Generate(suitableOutpostParams, suitableLocationType);
|
||||
return outpostSub != null;
|
||||
}
|
||||
|
||||
// if there is a faction requirement for the module, create a dummy location and augment its factions to match
|
||||
var dummyLocations = CreateDummyLocations("1337", suitableLocationType);
|
||||
var dummyLocation = dummyLocations[0];
|
||||
|
||||
if (FactionPrefab.Prefabs.TryGet(requiredFactionId, out FactionPrefab? factionPrefab))
|
||||
{
|
||||
if (factionPrefab.ControlledOutpostPercentage > factionPrefab.SecondaryControlledOutpostPercentage)
|
||||
{
|
||||
dummyLocation.Faction = new Faction(null, factionPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
dummyLocation.SecondaryFaction = new Faction(null, factionPrefab);
|
||||
}
|
||||
|
||||
outpostSub = OutpostGenerator.Generate(suitableOutpostParams, dummyLocation);
|
||||
return outpostSub != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void StartRound(LevelData? levelData, bool mirrorLevel = false, SubmarineInfo? startOutpost = null, SubmarineInfo? endOutpost = null)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -455,19 +620,37 @@ namespace Barotrauma
|
||||
LevelData = levelData;
|
||||
|
||||
Submarine.Unload();
|
||||
Submarine = Submarine.MainSub = new Submarine(SubmarineInfo);
|
||||
foreach (Submarine sub in Submarine.GetConnectedSubs())
|
||||
|
||||
bool loadSubmarine = GameMode!.Missions.None(m => !m.Prefab.LoadSubmarines);
|
||||
|
||||
// attempt to generate an outpost for the main sub, with the forced module inside it
|
||||
if (loadSubmarine)
|
||||
{
|
||||
sub.TeamID = CharacterTeamType.Team1;
|
||||
foreach (Item item in Item.ItemList)
|
||||
if (TryGenerateStationAroundModule(ForceOutpostModule, out Submarine? outpostSub))
|
||||
{
|
||||
if (item.Submarine != sub) { continue; }
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
Submarine = Submarine.MainSub = outpostSub ?? new Submarine(SubmarineInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine = Submarine.MainSub = new Submarine(SubmarineInfo);
|
||||
}
|
||||
foreach (Submarine sub in Submarine.GetConnectedSubs())
|
||||
{
|
||||
sub.TeamID = CharacterTeamType.Team1;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
wifiComponent.TeamID = sub.TeamID;
|
||||
if (item.Submarine != sub) { continue; }
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
wifiComponent.TeamID = sub.TeamID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine = Submarine.MainSub = null;
|
||||
}
|
||||
|
||||
GameMode!.AddExtraMissions(LevelData);
|
||||
foreach (Mission mission in GameMode!.Missions)
|
||||
@@ -476,16 +659,17 @@ namespace Barotrauma
|
||||
mission.SetLevel(levelData);
|
||||
}
|
||||
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
if (Submarine.MainSubs[1] == null && loadSubmarine)
|
||||
{
|
||||
var enemySubmarineInfo = GameMode is PvPMode ? SubmarineInfo : GameMode.Missions.FirstOrDefault(m => m.EnemySubmarineInfo != null)?.EnemySubmarineInfo;
|
||||
var enemySubmarineInfo = GameMode is PvPMode ? EnemySubmarineInfo : GameMode.Missions.FirstOrDefault(m => m.EnemySubmarineInfo != null)?.EnemySubmarineInfo;
|
||||
if (enemySubmarineInfo != null)
|
||||
{
|
||||
Submarine.MainSubs[1] = new Submarine(enemySubmarineInfo, true);
|
||||
Submarine.MainSubs[1] = new Submarine(enemySubmarineInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember?.ServerSettings?.LockAllDefaultWires ?? false)
|
||||
if (GameMain.NetworkMember?.ServerSettings is { LockAllDefaultWires: true } &&
|
||||
Submarine.MainSubs[0] != null)
|
||||
{
|
||||
List<Item> items = new List<Item>();
|
||||
items.AddRange(Submarine.MainSubs[0].GetItems(alsoFromConnectedSubs: true));
|
||||
@@ -497,7 +681,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.GetComponent<CircuitBox>() is { } cb)
|
||||
{
|
||||
cb.Locked = true;
|
||||
cb.TemporarilyLocked = true;
|
||||
}
|
||||
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
@@ -577,7 +761,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember?.ServerSettings is { } serverSettings)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ServerSettings:RespawnMode:" + serverSettings.RespawnMode);
|
||||
GameAnalyticsManager.AddDesignEvent("ServerSettings:IronmanMode:" + serverSettings.IronmanMode);
|
||||
GameAnalyticsManager.AddDesignEvent("ServerSettings:IronmanMode:" + serverSettings.IronmanModeActive);
|
||||
GameAnalyticsManager.AddDesignEvent("ServerSettings:AllowBotTakeoverOnPermadeath:" + serverSettings.AllowBotTakeoverOnPermadeath);
|
||||
}
|
||||
}
|
||||
@@ -592,8 +776,6 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
#if CLIENT
|
||||
if (campaignMode != null && levelData != null) { AchievementManager.OnBiomeDiscovered(levelData.Biome); }
|
||||
|
||||
var existingRoundSummary = GUIMessageBox.MessageBoxes.Find(mb => mb.UserData is RoundSummary)?.UserData as RoundSummary;
|
||||
if (existingRoundSummary?.ContinueButton != null)
|
||||
{
|
||||
@@ -664,15 +846,17 @@ namespace Barotrauma
|
||||
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
|
||||
StatusEffect.StopAll();
|
||||
|
||||
bool forceDocking = false;
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = (GameMain.Client == null || GameMain.Client.CharacterInfo != null) && !GameMain.DevMode;
|
||||
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
|
||||
if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameSettings.CurrentConfig.Graphics.LosMode; }
|
||||
forceDocking = GameMode is TutorialMode;
|
||||
#endif
|
||||
LevelData = level?.LevelData;
|
||||
Level = level;
|
||||
|
||||
PlaceSubAtStart(Level);
|
||||
PlaceSubAtInitialPosition(Submarine, Level, placeAtStart: true, forceDocking: forceDocking);
|
||||
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
@@ -685,7 +869,7 @@ namespace Barotrauma
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
if (GameMode != null && Submarine != null)
|
||||
if (GameMode != null)
|
||||
{
|
||||
missions.Clear();
|
||||
missions.AddRange(GameMode.Missions);
|
||||
@@ -707,7 +891,7 @@ namespace Barotrauma
|
||||
ObjectiveManager.ResetObjectives();
|
||||
#endif
|
||||
EventManager?.StartRound(Level.Loaded);
|
||||
AchievementManager.OnStartRound();
|
||||
AchievementManager.OnStartRound(Level?.LevelData.Biome);
|
||||
|
||||
GameMode.ShowStartMessage();
|
||||
|
||||
@@ -717,10 +901,24 @@ namespace Barotrauma
|
||||
//the server does this after loading the respawn shuttle
|
||||
if (Level != null)
|
||||
{
|
||||
Level.SpawnNPCs();
|
||||
if (GameMain.GameSession.Missions.None(m => !m.Prefab.AllowOutpostNPCs))
|
||||
{
|
||||
Level.SpawnNPCs();
|
||||
}
|
||||
Level.SpawnCorpses();
|
||||
Level.PrepareBeaconStation();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn npcs in the sub editor test mode.
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub?.Info?.OutpostGenerationParams != null)
|
||||
{
|
||||
OutpostGenerator.SpawnNPCs(StartLocation, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
AutoItemPlacer.SpawnItems(Campaign?.Settings.StartItemSet);
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign)
|
||||
@@ -732,37 +930,39 @@ namespace Barotrauma
|
||||
|
||||
CreatureMetrics.RecentlyEncountered.Clear();
|
||||
|
||||
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub?.WorldPosition ?? Submarine.Loaded.First().WorldPosition;
|
||||
RoundDuration = 0.0f;
|
||||
GameMain.ResetFrameTime();
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
public void PlaceSubAtStart(Level? level)
|
||||
public static void PlaceSubAtInitialPosition(Submarine? sub, Level? level, bool placeAtStart = true, bool forceDocking = false)
|
||||
{
|
||||
if (level == null || Submarine == null)
|
||||
if (level == null || sub == null)
|
||||
{
|
||||
Submarine?.SetPosition(Vector2.Zero);
|
||||
sub?.SetPosition(Vector2.Zero);
|
||||
return;
|
||||
}
|
||||
|
||||
var originalSubPos = Submarine.WorldPosition;
|
||||
var spawnPoint = WayPoint.WayPointList.Find(wp => wp.SpawnType.HasFlag(SpawnType.Submarine) && wp.Submarine == level.StartOutpost);
|
||||
Submarine outpost = placeAtStart ? level.StartOutpost : level.EndOutpost;
|
||||
|
||||
var originalSubPos = sub.WorldPosition;
|
||||
var spawnPoint = WayPoint.WayPointList.Find(wp => wp.SpawnType.HasFlag(SpawnType.Submarine) && wp.Submarine == outpost);
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
//pre-determine spawnpoint, just use it directly
|
||||
Submarine.SetPosition(spawnPoint.WorldPosition);
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
sub.SetPosition(spawnPoint.WorldPosition);
|
||||
sub.NeutralizeBallast();
|
||||
sub.EnableMaintainPosition();
|
||||
}
|
||||
else if (level.StartOutpost != null)
|
||||
else if (outpost != null)
|
||||
{
|
||||
//start by placing the sub below the outpost
|
||||
Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders();
|
||||
Rectangle subBorders = Submarine.GetDockedBorders();
|
||||
Rectangle outpostBorders = outpost.GetDockedBorders();
|
||||
Rectangle subBorders = sub.GetDockedBorders();
|
||||
|
||||
Submarine.SetPosition(
|
||||
Level.Loaded.StartOutpost.WorldPosition -
|
||||
sub.SetPosition(
|
||||
outpost.WorldPosition -
|
||||
new Vector2(0.0f, outpostBorders.Height / 2 + subBorders.Height / 2));
|
||||
|
||||
//find the port that's the nearest to the outpost and dock if one is found
|
||||
@@ -771,7 +971,7 @@ namespace Barotrauma
|
||||
foreach (DockingPort port in DockingPort.List)
|
||||
{
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine == level.StartOutpost)
|
||||
if (port.Item.Submarine == outpost)
|
||||
{
|
||||
if (port.DockingTarget == null || (outPostPort != null && !outPostPort.MainDockingPort && port.MainDockingPort))
|
||||
{
|
||||
@@ -779,12 +979,12 @@ namespace Barotrauma
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (port.Item.Submarine != Submarine) { continue; }
|
||||
if (port.Item.Submarine != sub) { continue; }
|
||||
|
||||
//the submarine port has to be at the top of the sub
|
||||
if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; }
|
||||
if (port.Item.WorldPosition.Y < sub.WorldPosition.Y) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition);
|
||||
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, outpost.WorldPosition);
|
||||
if ((myPort == null || dist < closestDistance || port.MainDockingPort) && !(myPort?.MainDockingPort ?? false))
|
||||
{
|
||||
myPort = port;
|
||||
@@ -794,38 +994,35 @@ namespace Barotrauma
|
||||
|
||||
if (myPort != null && outPostPort != null)
|
||||
{
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - Submarine.WorldPosition;
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - sub.WorldPosition;
|
||||
Vector2 spawnPos = (outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance;
|
||||
|
||||
bool startDocked = level.Type == LevelData.LevelType.Outpost;
|
||||
#if CLIENT
|
||||
startDocked |= GameMode is TutorialMode;
|
||||
#endif
|
||||
bool startDocked = level.Type == LevelData.LevelType.Outpost || forceDocking;
|
||||
if (startDocked)
|
||||
{
|
||||
Submarine.SetPosition(spawnPos);
|
||||
sub.SetPosition(spawnPos);
|
||||
myPort.Dock(outPostPort);
|
||||
myPort.Lock(isNetworkMessage: true, applyEffects: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine.SetPosition(spawnPos - Vector2.UnitY * 100.0f);
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
sub.SetPosition(spawnPos - Vector2.UnitY * 100.0f);
|
||||
sub.NeutralizeBallast();
|
||||
sub.EnableMaintainPosition();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
sub.NeutralizeBallast();
|
||||
sub.EnableMaintainPosition();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine.SetPosition(Submarine.FindSpawnPos(level.StartPosition));
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
sub.SetPosition(sub.FindSpawnPos(placeAtStart ? level.StartPosition : level.EndPosition));
|
||||
sub.NeutralizeBallast();
|
||||
sub.EnableMaintainPosition();
|
||||
}
|
||||
|
||||
// Make sure that linked subs which are NOT docked to the main sub
|
||||
@@ -834,9 +1031,9 @@ namespace Barotrauma
|
||||
var linkedSubs = MapEntity.MapEntityList.FindAll(me => me is LinkedSubmarine);
|
||||
foreach (LinkedSubmarine ls in linkedSubs)
|
||||
{
|
||||
if (ls.Sub == null || ls.Submarine != Submarine) { continue; }
|
||||
if (!ls.LoadSub || ls.Sub.DockedTo.Contains(Submarine)) { continue; }
|
||||
if (Submarine.Info.LeftBehindDockingPortIDs.Contains(ls.OriginalLinkedToID)) { continue; }
|
||||
if (ls.Sub == null || ls.Submarine != sub) { continue; }
|
||||
if (!ls.LoadSub || ls.Sub.DockedTo.Contains(sub)) { continue; }
|
||||
if (sub.Info.LeftBehindDockingPortIDs.Contains(ls.OriginalLinkedToID)) { continue; }
|
||||
if (ls.Sub.Info.SubmarineElement.Attribute("location") != null) { continue; }
|
||||
ls.SetPositionRelativeToMainSub();
|
||||
}
|
||||
@@ -954,6 +1151,7 @@ namespace Barotrauma
|
||||
character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
if (missions.All(m => m.Completed))
|
||||
{
|
||||
foreach (Character character in crewCharacters)
|
||||
@@ -1051,11 +1249,93 @@ namespace Barotrauma
|
||||
return GameMain.NetworkMember switch
|
||||
{
|
||||
null => campaign.Bank.Balance,
|
||||
_ => crew.Sum(c => c.Wallet.Balance) + campaign.Bank.Balance
|
||||
_ => crew.Sum(c => c.Wallet.Balance) + campaign.Bank.Balance
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static PerkCollection GetPerks()
|
||||
{
|
||||
if (GameMain.NetworkMember?.ServerSettings is not { } serverSettings)
|
||||
{
|
||||
return PerkCollection.Empty;
|
||||
}
|
||||
|
||||
var team1Builder = ImmutableArray.CreateBuilder<DisembarkPerkPrefab>();
|
||||
var team2Builder = ImmutableArray.CreateBuilder<DisembarkPerkPrefab>();
|
||||
|
||||
foreach (Identifier coalitionPerk in serverSettings.SelectedCoalitionPerks)
|
||||
{
|
||||
if (!DisembarkPerkPrefab.Prefabs.TryGet(coalitionPerk, out DisembarkPerkPrefab? disembarkPerk)) { continue; }
|
||||
team1Builder.Add(disembarkPerk);
|
||||
}
|
||||
|
||||
foreach (Identifier separatistsPerk in serverSettings.SelectedSeparatistsPerks)
|
||||
{
|
||||
if (!DisembarkPerkPrefab.Prefabs.TryGet(separatistsPerk, out DisembarkPerkPrefab? disembarkPerk)) { continue; }
|
||||
team2Builder.Add(disembarkPerk);
|
||||
}
|
||||
|
||||
return new PerkCollection(team1Builder.ToImmutable(), team2Builder.ToImmutable());
|
||||
}
|
||||
|
||||
public static bool ValidatedDisembarkPoints(GameModePreset preset, IEnumerable<Identifier> missionTypes)
|
||||
{
|
||||
if (GameMain.NetworkMember?.ServerSettings is not { } settings) { return false; }
|
||||
|
||||
bool checkBothTeams = preset == GameModePreset.PvP;
|
||||
|
||||
PerkCollection perks = GetPerks();
|
||||
|
||||
int team1TotalCost = GetTotalCost(perks.Team1Perks);
|
||||
if (team1TotalCost > settings.DisembarkPointAllowance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkBothTeams)
|
||||
{
|
||||
int team2TotalCost = GetTotalCost(perks.Team2Perks);
|
||||
if (team2TotalCost > settings.DisembarkPointAllowance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
int GetTotalCost(ImmutableArray<DisembarkPerkPrefab> perksToCheck)
|
||||
{
|
||||
if (preset == GameModePreset.Mission || preset == GameModePreset.PvP)
|
||||
{
|
||||
if (ShouldIgnorePerksThatCanNotApplyWithoutSubmarine(preset, missionTypes))
|
||||
{
|
||||
perksToCheck = perksToCheck.Where(static p => p.PerkBehaviors.All(static b => b.CanApplyWithoutSubmarine())).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
return perksToCheck.Sum(static p => p.Cost);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShouldIgnorePerksThatCanNotApplyWithoutSubmarine(GameModePreset preset, IEnumerable<Identifier> missionTypes)
|
||||
{
|
||||
if (preset == GameModePreset.Mission || preset == GameModePreset.PvP)
|
||||
{
|
||||
var missionTypesToCheck = MissionMode.ValidateMissionTypes(missionTypes, preset == GameModePreset.PvP ? MissionPrefab.PvPMissionClasses : MissionPrefab.CoOpMissionClasses);
|
||||
foreach (var missionType in missionTypesToCheck)
|
||||
{
|
||||
foreach (var missionPrefab in MissionPrefab.Prefabs.Where(mp => mp.Type == missionType))
|
||||
{
|
||||
if (missionPrefab.LoadSubmarines)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void LogEndRoundStats(string eventId, TraitorManager.TraitorResults? traitorResults = null)
|
||||
{
|
||||
if (Submarine.MainSub?.Info?.IsVanillaSubmarine() ?? false)
|
||||
@@ -1231,9 +1511,9 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Save(string filePath)
|
||||
public void Save(string filePath, bool isSavingOnLoading)
|
||||
{
|
||||
if (!(GameMode is CampaignMode))
|
||||
if (GameMode is not CampaignMode campaign)
|
||||
{
|
||||
throw new NotSupportedException("GameSessions can only be saved when playing in a campaign mode.");
|
||||
}
|
||||
@@ -1245,6 +1525,12 @@ namespace Barotrauma
|
||||
#warning TODO: after this gets on main, replace savetime with the commented line
|
||||
//rootElement.Add(new XAttribute("savetime", SerializableDateTime.LocalNow));
|
||||
|
||||
rootElement.Add(new XAttribute("currentlocation", Map?.CurrentLocation?.NameIdentifier.Value ?? string.Empty));
|
||||
rootElement.Add(new XAttribute("currentlocationnameformatindex", Map?.CurrentLocation?.NameFormatIndex ?? -1));
|
||||
rootElement.Add(new XAttribute("locationtype", Map?.CurrentLocation?.Type?.Identifier ?? Identifier.Empty));
|
||||
|
||||
rootElement.Add(new XAttribute("nextleveltype", campaign.NextLevel?.Type ?? LevelData?.Type ?? LevelData.LevelType.Outpost));
|
||||
|
||||
LastSaveVersion = GameMain.Version;
|
||||
rootElement.Add(new XAttribute("version", GameMain.Version));
|
||||
if (Submarine?.Info != null && !Submarine.Removed && Campaign != null)
|
||||
@@ -1270,8 +1556,21 @@ namespace Barotrauma
|
||||
if (Map != null) { rootElement.Add(new XAttribute("mapseed", Map.Seed)); }
|
||||
rootElement.Add(new XAttribute("selectedcontentpackagenames",
|
||||
string.Join("|", ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).Select(cp => cp.Name.Replace("|", @"\|")))));
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root);
|
||||
|
||||
XElement permadeathsElement = new XElement("permadeaths");
|
||||
foreach (var kvp in permadeathsPerAccount)
|
||||
{
|
||||
if (kvp.Key.TryUnwrap(out AccountId? accountId))
|
||||
{
|
||||
permadeathsElement.Add(
|
||||
new XElement("account"),
|
||||
new XAttribute("id", accountId.StringRepresentation),
|
||||
new XAttribute("permadeathcount", kvp.Value));
|
||||
}
|
||||
}
|
||||
rootElement.Add(permadeathsElement);
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root, isSavingOnLoading);
|
||||
|
||||
doc.SaveSafe(filePath, throwExceptions: true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user