Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git
This commit is contained in:
@@ -1,74 +1,141 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#warning TODO: This class needs some changes:
|
||||
// - We shouldn't be iterating over MapEntityPrefab.List. It has no guarantee of any sort of order and becomes entirely unpredictable once you start adding mods.
|
||||
// - Note: iterating over ItemPrefab.Prefabs would also be incorrect. Sorting by UintIdentifier is necessary for determinism.
|
||||
// - SpawnItems and SpawnItem are named incorrectly.
|
||||
static class AutoItemPlacer
|
||||
{
|
||||
public static bool OutputDebugInfo = false;
|
||||
|
||||
/// <summary>
|
||||
/// If we are spawning in an area where difficulty should not be a factor, assume difficulty is at the exact "middle"
|
||||
/// </summary>
|
||||
public const float DefaultDifficultyModifier = 0f;
|
||||
|
||||
public static void PlaceIfNeeded()
|
||||
public static void SpawnItems(Identifier? startItemSet = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
//player has more than one sub = we must have given the start items already
|
||||
bool startItemsGiven = GameMain.GameSession?.OwnedSubmarines != null && GameMain.GameSession.OwnedSubmarines.Count > 1;
|
||||
if (!startItemsGiven)
|
||||
{
|
||||
if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; }
|
||||
List<Submarine> subs = new List<Submarine>() { Submarine.MainSubs[i] };
|
||||
subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost));
|
||||
Place(subs);
|
||||
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
var sub = Submarine.MainSubs[i];
|
||||
if (sub == null || sub.Info.InitialSuppliesSpawned || !sub.Info.IsPlayer) { continue; }
|
||||
//1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
|
||||
SpawnStartItems(sub, startItemSet);
|
||||
//2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
|
||||
var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
|
||||
CreateAndPlace(subs);
|
||||
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
|
||||
}
|
||||
}
|
||||
|
||||
float difficultyModifier = GetLevelDifficultyModifier();
|
||||
//spawn items in wrecks, beacon stations and pirate subs
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type == SubmarineType.Player ||
|
||||
sub.Info.Type == SubmarineType.Outpost ||
|
||||
sub.Info.Type == SubmarineType.OutpostModule ||
|
||||
sub.Info.Type == SubmarineType.EnemySubmarine)
|
||||
sub.Info.Type == SubmarineType.OutpostModule)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier);
|
||||
if (sub.Info.InitialSuppliesSpawned) { continue; }
|
||||
CreateAndPlace(sub.ToEnumerable());
|
||||
sub.Info.InitialSuppliesSpawned = true;
|
||||
}
|
||||
|
||||
if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name));
|
||||
Place(Level.Loaded.StartOutpost.ToEnumerable());
|
||||
var sub = Level.Loaded.StartOutpost;
|
||||
if (!sub.Info.InitialSuppliesSpawned)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(sub.Info.Name));
|
||||
CreateAndPlace(sub.ToEnumerable());
|
||||
sub.Info.InitialSuppliesSpawned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const float MaxDifficultyModifier = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty.
|
||||
/// </summary>
|
||||
private static float GetLevelDifficultyModifier()
|
||||
{
|
||||
return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier);
|
||||
}
|
||||
|
||||
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer)
|
||||
{
|
||||
// Level difficulty currently doesn't affect regenerated loot for the sake of simplicity
|
||||
Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
|
||||
CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier)
|
||||
public static Identifier DefaultStartItemSet = new Identifier("normal");
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the items defined in the start item set in the specified sub.
|
||||
/// </summary>
|
||||
private static void SpawnStartItems(Submarine sub, Identifier? startItemSet)
|
||||
{
|
||||
Identifier setIdentifier = startItemSet ?? DefaultStartItemSet;
|
||||
if (!StartItemSet.Sets.TryGet(setIdentifier, out StartItemSet itemSet))
|
||||
{
|
||||
DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{setIdentifier}\"!");
|
||||
if (!StartItemSet.Sets.TryGet(DefaultStartItemSet, out StartItemSet defaultSet))
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't find the default start item set \"{DefaultStartItemSet}\"!");
|
||||
return;
|
||||
}
|
||||
itemSet = defaultSet;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
|
||||
ISpatialEntity initialSpawnPos;
|
||||
if (wp?.CurrentHull == null)
|
||||
{
|
||||
var spawnHull = Hull.HullList.Where(h => h.Submarine == sub && !h.IsWetRoom).GetRandomUnsynced();
|
||||
if (spawnHull == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to spawn start items in the sub. No cargo waypoint or dry hulls found to spawn the items in.");
|
||||
return;
|
||||
}
|
||||
initialSpawnPos = spawnHull;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialSpawnPos = wp;
|
||||
}
|
||||
var newItems = new List<Item>();
|
||||
foreach (var startItem in itemSet.Items)
|
||||
{
|
||||
if (!ItemPrefab.Prefabs.TryGet(startItem.Item, out ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.AddWarning($"Cannot find a start item with with the identifier \"{startItem.Item}\"");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < startItem.Amount; i++)
|
||||
{
|
||||
var item = new Item(itemPrefab, initialSpawnPos.Position, sub, callOnItemLoaded: false);
|
||||
// Is this necessary?
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
wifiComponent.TeamID = sub.TeamID;
|
||||
}
|
||||
newItems.Add(item);
|
||||
}
|
||||
}
|
||||
var cargoContainers = new List<ItemContainer>();
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.OnItemLoaded();
|
||||
}
|
||||
var container = sub.FindContainerFor(item, onlyPrimary: true);
|
||||
if (container == null)
|
||||
{
|
||||
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, initialSpawnPos, ref cargoContainers);
|
||||
container = cargoContainer?.Item;
|
||||
}
|
||||
container?.OwnInventory.TryPutItem(item, user: null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAndPlace(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -76,7 +143,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
List<Item> spawnedItems = new List<Item>(100);
|
||||
List<Item> itemsToSpawn = new List<Item>(100);
|
||||
|
||||
int itemCountApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(70 + 30 * subs.Count());
|
||||
@@ -100,11 +167,11 @@ namespace Barotrauma
|
||||
containers.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
foreach (ItemPrefab ip in ItemPrefab.Prefabs)
|
||||
var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier);
|
||||
foreach (ItemPrefab ip in itemPrefabs)
|
||||
{
|
||||
if (!ip.PreferredContainers.Any()) { continue; }
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) &&
|
||||
ItemPrefab.Prefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
|
||||
if (ip.PreferredContainers.None()) { continue; }
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && itemPrefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
|
||||
{
|
||||
prefabsItemsCanSpawnIn.Add(ip);
|
||||
}
|
||||
@@ -141,9 +208,9 @@ namespace Barotrauma
|
||||
{
|
||||
var subNames = subs.Select(s => s.Info.Name).ToList();
|
||||
DebugConsole.NewMessage($"Automatically placed items in { string.Join(", ", subNames) }:");
|
||||
foreach (string itemName in spawnedItems.Select(it => it.Name).Distinct())
|
||||
foreach (string itemName in itemsToSpawn.Select(it => it.Name).Distinct())
|
||||
{
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + spawnedItems.Count(it => it.Name == itemName));
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + itemsToSpawn.Count(it => it.Name == itemName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,24 +220,28 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems)
|
||||
{
|
||||
var matchingItem = spawnedItems.Find(it => takenItem.Matches(it));
|
||||
var matchingItem = itemsToSpawn.Find(it => takenItem.Matches(it));
|
||||
if (matchingItem == null) { continue; }
|
||||
var containedItems = spawnedItems.FindAll(it => it.ParentInventory?.Owner == matchingItem);
|
||||
if (OutputDebugInfo)
|
||||
{
|
||||
DebugConsole.NewMessage($"Removing the stolen item: {matchingItem.Prefab.Identifier} ({matchingItem.ID})");
|
||||
}
|
||||
var containedItems = itemsToSpawn.FindAll(it => it.ParentInventory?.Owner == matchingItem);
|
||||
matchingItem.Remove();
|
||||
spawnedItems.Remove(matchingItem);
|
||||
itemsToSpawn.Remove(matchingItem);
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
containedItem.Remove();
|
||||
spawnedItems.Remove(containedItem);
|
||||
itemsToSpawn.Remove(containedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item spawnedItem in spawnedItems)
|
||||
foreach (Item item in itemsToSpawn)
|
||||
{
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(spawnedItem));
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
foreach (ItemComponent ic in spawnedItem.Components)
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.OnItemLoaded();
|
||||
}
|
||||
@@ -186,9 +257,12 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
|
||||
{
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0) { continue; }
|
||||
if (preferredContainer.CampaignOnly && !isCampaign) { continue; }
|
||||
if (preferredContainer.NotCampaign && isCampaign) { continue; }
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; }
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
|
||||
if (validContainers.None())
|
||||
{
|
||||
@@ -196,10 +270,10 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (var validContainer in validContainers)
|
||||
{
|
||||
var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier);
|
||||
var newItems = CreateItems(itemPrefab, containers, validContainer);
|
||||
if (newItems.Any())
|
||||
{
|
||||
spawnedItems.AddRange(newItems);
|
||||
itemsToSpawn.AddRange(newItems);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -238,16 +312,20 @@ namespace Barotrauma
|
||||
(3, 0.0f),
|
||||
};
|
||||
|
||||
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
|
||||
private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
List<Item> spawnedItems = new List<Item>();
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; }
|
||||
List<Item> newItems = new List<Item>();
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) { return newItems; }
|
||||
// Don't add dangerously reactive materials in thalamus wrecks
|
||||
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
|
||||
{
|
||||
return spawnedItems;
|
||||
return newItems;
|
||||
}
|
||||
int amount = validContainer.Value.Amount;
|
||||
if (amount == 0)
|
||||
{
|
||||
amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true))
|
||||
@@ -255,14 +333,12 @@ namespace Barotrauma
|
||||
containers.Remove(validContainer.Key);
|
||||
break;
|
||||
}
|
||||
|
||||
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
|
||||
int quality =
|
||||
existingItem?.Quality ??
|
||||
ToolBox.SelectWeightedRandom(
|
||||
qualityCommonnesses.Select(q => q.quality).ToList(),
|
||||
qualityCommonnesses.Select(q => q.commonness).ToList(),
|
||||
Rand.RandSync.ServerAndClient);
|
||||
qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
|
||||
{
|
||||
@@ -277,11 +353,11 @@ namespace Barotrauma
|
||||
{
|
||||
wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID;
|
||||
}
|
||||
spawnedItems.Add(item);
|
||||
newItems.Add(item);
|
||||
validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
}
|
||||
return spawnedItems;
|
||||
return newItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ namespace Barotrauma
|
||||
public int Quantity { get; set; }
|
||||
public bool? IsStoreComponentEnabled { get; set; }
|
||||
|
||||
public readonly int BuyerCharacterInfoId;
|
||||
public readonly int BuyerCharacterInfoIdentifier;
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId)
|
||||
{
|
||||
ItemPrefabIdentifier = itemPrefab.Identifier;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyerCharacterInfoId;
|
||||
BuyerCharacterInfoIdentifier = buyerCharacterInfoId;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
@@ -44,7 +44,7 @@ namespace Barotrauma
|
||||
ItemPrefabIdentifier = itemPrefabId;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? Character.Controlled?.Info?.ID ?? 0;
|
||||
BuyerCharacterInfoIdentifier = buyer?.Character?.Info?.GetIdentifier() ?? Character.Controlled?.Info?.GetIdentifier() ?? 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -284,11 +284,10 @@ namespace Barotrauma
|
||||
foreach (PurchasedItem item in newItems)
|
||||
{
|
||||
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}");
|
||||
price += itemValue;
|
||||
|
||||
}
|
||||
|
||||
GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money);
|
||||
}
|
||||
#endif
|
||||
@@ -317,7 +316,10 @@ namespace Barotrauma
|
||||
// Exchange money
|
||||
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||
campaign.TryPurchase(client, itemValue);
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
}
|
||||
store.Balance += itemValue;
|
||||
if (removeFromCrate)
|
||||
{
|
||||
@@ -368,12 +370,13 @@ namespace Barotrauma
|
||||
|
||||
public void CreatePurchasedItems()
|
||||
{
|
||||
purchasedIDCards.Clear();
|
||||
var items = new List<PurchasedItem>();
|
||||
foreach (var storeSpecificItems in PurchasedItems)
|
||||
{
|
||||
items.AddRange(storeSpecificItems.Value);
|
||||
}
|
||||
CreateItems(items, Submarine.MainSub);
|
||||
CreateItems(items, Submarine.MainSub, this);
|
||||
PurchasedItems.Clear();
|
||||
OnPurchasedItemsChanged?.Invoke();
|
||||
}
|
||||
@@ -407,7 +410,7 @@ namespace Barotrauma
|
||||
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
|
||||
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
|
||||
if (!ItemAndAllContainersInteractable(item)) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("donttakeitems")) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
return true;
|
||||
}).Distinct();
|
||||
|
||||
@@ -428,7 +431,7 @@ namespace Barotrauma
|
||||
if (!item.Prefab.CanBeSold) { return false; }
|
||||
if (item.SpawnedInCurrentOutpost) { return false; }
|
||||
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
|
||||
if (confirmedItems.Any(ci => ci.Item == item)) { return false; }
|
||||
if (confirmedItems != null && confirmedItems.Any(ci => ci.Item == item)) { return false; }
|
||||
if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count))
|
||||
{
|
||||
int newCount = count - 1;
|
||||
@@ -448,13 +451,58 @@ namespace Barotrauma
|
||||
if (containedItems.None()) { return true; }
|
||||
// Allow selling the item if contained items are unsellable and set to be removed on deconstruct
|
||||
if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; }
|
||||
// Otherwise there must be no contained items or the contained items must be confirmed as sold
|
||||
if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
|
||||
if (confirmedItems != null)
|
||||
{
|
||||
// Otherwise there must be no contained items or the contained items must be confirmed as sold
|
||||
if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub)
|
||||
public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List<ItemContainer> availableContainers)
|
||||
{
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(item.CargoContainerIdentifier))
|
||||
{
|
||||
itemContainer = availableContainers.Find(ac =>
|
||||
ac.Inventory.CanBePut(item) &&
|
||||
(ac.Item.Prefab.Identifier == item.CargoContainerIdentifier ||
|
||||
ac.Item.Prefab.Tags.Contains(item.CargoContainerIdentifier)));
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
ItemPrefab containerPrefab = ItemPrefab.Prefabs.Find(ep =>
|
||||
ep.Identifier == item.CargoContainerIdentifier ||
|
||||
(ep.Tags != null && ep.Tags.Contains(item.CargoContainerIdentifier)));
|
||||
|
||||
if (containerPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
Vector2 containerPosition = cargoRoomOrSpawnPoint is Hull cargoRoom ? GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.Position;
|
||||
Item containerItem = new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.Submarine);
|
||||
itemContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!");
|
||||
return null;
|
||||
}
|
||||
availableContainers.Add(itemContainer);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager)
|
||||
{
|
||||
if (itemsToSpawn.Count == 0) { return; }
|
||||
|
||||
@@ -496,60 +544,26 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<ItemContainer> availableContainers = new List<ItemContainer>();
|
||||
ItemPrefab containerPrefab = null;
|
||||
foreach (PurchasedItem pi in itemsToSpawn)
|
||||
{
|
||||
Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab);
|
||||
|
||||
for (int i = 0; i < pi.Quantity; i++)
|
||||
{
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
|
||||
{
|
||||
itemContainer = availableContainers.Find(ac =>
|
||||
ac.Inventory.CanBePut(pi.ItemPrefab) &&
|
||||
(ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
|
||||
ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
containerPrefab = ItemPrefab.Prefabs.Find(ep =>
|
||||
ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
|
||||
(ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
|
||||
|
||||
if (containerPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 containerPosition = GetCargoPos(cargoRoom, containerPrefab);
|
||||
Item containerItem = new Item(containerPrefab, containerPosition, wp.Submarine);
|
||||
itemContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
|
||||
continue;
|
||||
}
|
||||
availableContainers.Add(itemContainer);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
|
||||
itemSpawned(item);
|
||||
var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
var idCard = item.GetComponent<IdCard>();
|
||||
if (cargoManager != null && idCard != null && pi.BuyerCharacterInfoIdentifier != 0)
|
||||
{
|
||||
cargoManager.purchasedIDCards.Add((pi, idCard));
|
||||
}
|
||||
itemSpawned(pi, item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(Item item)
|
||||
static void itemSpawned(PurchasedItem purchased, Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
if (sub != null)
|
||||
@@ -565,6 +579,23 @@ namespace Barotrauma
|
||||
itemsToSpawn.Clear();
|
||||
}
|
||||
|
||||
private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>();
|
||||
public void InitPurchasedIDCards()
|
||||
{
|
||||
foreach ((PurchasedItem purchased, IdCard idCard) in purchasedIDCards)
|
||||
{
|
||||
if (idCard != null && purchased.BuyerCharacterInfoIdentifier != 0)
|
||||
{
|
||||
var owner = Character.CharacterList.Find(c => c.Info?.GetIdentifier() == purchased.BuyerCharacterInfoIdentifier);
|
||||
if (owner?.Info != null)
|
||||
{
|
||||
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(new List<CharacterInfo>() { owner.Info }, Submarine.MainSub);
|
||||
idCard.Initialize(mainSubSpawnPoints.FirstOrDefault(), owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab)
|
||||
{
|
||||
float floorPos = hull.Rect.Y - hull.Rect.Height;
|
||||
@@ -603,7 +634,7 @@ namespace Barotrauma
|
||||
new XAttribute("id", item.ItemPrefab.Identifier),
|
||||
new XAttribute("qty", item.Quantity),
|
||||
new XAttribute("storeid", storeSpecificItems.Key),
|
||||
new XAttribute("buyer", item.BuyerCharacterInfoId)));
|
||||
new XAttribute("buyer", item.BuyerCharacterInfoIdentifier)));
|
||||
}
|
||||
}
|
||||
parentElement.Add(itemsElement);
|
||||
|
||||
@@ -51,8 +51,6 @@ namespace Barotrauma
|
||||
|
||||
public ReadyCheck ActiveReadyCheck;
|
||||
|
||||
public XElement ActiveOrdersElement { get; set; }
|
||||
|
||||
public CrewManager(bool isSinglePlayer)
|
||||
{
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
@@ -493,9 +491,8 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateProjectSpecific(float deltaTime);
|
||||
|
||||
private void SaveActiveOrders(XElement parentElement)
|
||||
public void SaveActiveOrders(XElement element)
|
||||
{
|
||||
ActiveOrdersElement = new XElement("activeorders");
|
||||
// Only save orders with no fade out time (e.g. ignore orders)
|
||||
var ordersToSave = new List<Order>();
|
||||
foreach (var activeOrder in ActiveOrders)
|
||||
@@ -504,14 +501,13 @@ namespace Barotrauma
|
||||
if (order == null || activeOrder.FadeOutTime.HasValue) { continue; }
|
||||
ordersToSave.Add(order.WithManualPriority(CharacterInfo.HighestManualOrderPriority));
|
||||
}
|
||||
CharacterInfo.SaveOrders(ActiveOrdersElement, ordersToSave.ToArray());
|
||||
parentElement?.Add(ActiveOrdersElement);
|
||||
CharacterInfo.SaveOrders(element, ordersToSave.ToArray());
|
||||
}
|
||||
|
||||
public void LoadActiveOrders()
|
||||
public void LoadActiveOrders(XElement element)
|
||||
{
|
||||
if (ActiveOrdersElement == null) { return; }
|
||||
foreach (var orderInfo in CharacterInfo.LoadOrders(ActiveOrdersElement))
|
||||
if (element == null) { return; }
|
||||
foreach (var orderInfo in CharacterInfo.LoadOrders(element))
|
||||
{
|
||||
IIgnorable ignoreTarget = null;
|
||||
if (orderInfo.IsIgnoreOrder)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -9,63 +10,6 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal struct CampaignSettings
|
||||
{
|
||||
public static CampaignSettings Empty => new CampaignSettings();
|
||||
|
||||
// Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down
|
||||
public static CampaignSettings Unsure => Empty;
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount();
|
||||
|
||||
private int maxMissionCount;
|
||||
public int MaxMissionCount
|
||||
{
|
||||
get { return maxMissionCount; }
|
||||
set { maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit); }
|
||||
}
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
public const int MinMissionCountLimit = 1;
|
||||
|
||||
public CampaignSettings(IReadMessage inc)
|
||||
{
|
||||
maxMissionCount = DefaultMaxMissionCount;
|
||||
RadiationEnabled = inc.ReadBoolean();
|
||||
MaxMissionCount = inc.ReadRangedInteger(MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public CampaignSettings(XElement element)
|
||||
{
|
||||
maxMissionCount = DefaultMaxMissionCount;
|
||||
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true);
|
||||
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount);
|
||||
}
|
||||
|
||||
public void Serialize(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(RadiationEnabled);
|
||||
msg.WriteRangedInteger(MaxMissionCount, MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public int GetAddedMissionCount()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount));
|
||||
}
|
||||
}
|
||||
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
[NetworkSerialize]
|
||||
@@ -107,6 +51,8 @@ namespace Barotrauma
|
||||
|
||||
protected XElement petsElement;
|
||||
|
||||
protected XElement ActiveOrdersElement { get; set; }
|
||||
|
||||
public CampaignSettings Settings;
|
||||
|
||||
private readonly List<Mission> extraMissions = new List<Mission>();
|
||||
@@ -146,9 +92,8 @@ namespace Barotrauma
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private readonly Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
|
||||
public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs;
|
||||
|
||||
public SubmarineInfo PendingSubmarineSwitch;
|
||||
public bool TransferItemsOnSubSwitch { get; set; }
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
@@ -186,12 +131,16 @@ namespace Barotrauma
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected CampaignMode(GameModePreset preset)
|
||||
public virtual bool PurchasedHullRepairs { get; set; }
|
||||
public virtual bool PurchasedLostShuttles { get; set; }
|
||||
public virtual bool PurchasedItemRepairs { get; set; }
|
||||
|
||||
protected CampaignMode(GameModePreset preset, CampaignSettings settings)
|
||||
: base(preset)
|
||||
{
|
||||
Bank = new Wallet(Option<Character>.None())
|
||||
{
|
||||
Balance = InitialMoney
|
||||
Balance = settings.InitialMoney
|
||||
};
|
||||
|
||||
CargoManager = new CargoManager(this);
|
||||
@@ -558,6 +507,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public TransitionType GetAvailableTransition() => GetAvailableTransition(out _, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Which submarine is at a position where it can leave the level and enter another one (if any).
|
||||
/// </summary>
|
||||
@@ -593,6 +544,7 @@ namespace Barotrauma
|
||||
if (Level.Loaded.StartOutpost == null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
if (closestSub == null) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
else
|
||||
@@ -726,7 +678,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void EndCampaign()
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
@@ -738,13 +689,16 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (LocationConnection connection in Map.Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f);
|
||||
connection.LevelData.Difficulty = connection.Difficulty;
|
||||
connection.LevelData.IsBeaconActive = false;
|
||||
connection.Difficulty = connection.Biome.MaxDifficulty;
|
||||
connection.LevelData = new LevelData(connection)
|
||||
{
|
||||
IsBeaconActive = false
|
||||
};
|
||||
connection.LevelData.HasHuntingGrounds = connection.LevelData.OriginallyHadHuntingGrounds;
|
||||
}
|
||||
foreach (Location location in Map.Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, location.Biome.MaxDifficulty);
|
||||
location.Reset();
|
||||
}
|
||||
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
|
||||
@@ -868,7 +822,7 @@ namespace Barotrauma
|
||||
const float MaxDist = 3000.0f;
|
||||
const float MinDist = 2500.0f;
|
||||
|
||||
if (!Level.IsLoadedOutpost) { return; }
|
||||
if (!Level.IsLoadedFriendlyOutpost) { return; }
|
||||
|
||||
Rectangle worldBorders = Submarine.MainSub.GetDockedBorders();
|
||||
worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint();
|
||||
@@ -1032,5 +986,190 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected void LeaveUnconnectedSubs(Submarine leavingSub)
|
||||
{
|
||||
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
|
||||
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
|
||||
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubmarineInfo SwitchSubs()
|
||||
{
|
||||
if (TransferItemsOnSubSwitch)
|
||||
{
|
||||
TransferItemsBetweenSubs();
|
||||
}
|
||||
RefreshOwnedSubmarines();
|
||||
PendingSubmarineSwitch = null;
|
||||
return GameMain.GameSession.SubmarineInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Also serializes the current sub.
|
||||
/// </summary>
|
||||
protected void TransferItemsBetweenSubs()
|
||||
{
|
||||
Submarine currentSub = GameMain.GameSession.Submarine;
|
||||
if (currentSub == null || currentSub.Removed)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot transfer items between subs, because the current sub is null or removed!");
|
||||
return;
|
||||
}
|
||||
var itemsToTransfer = new List<(Item item, Item container)>();
|
||||
if (PendingSubmarineSwitch != null)
|
||||
{
|
||||
var connectedSubs = currentSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player).ToHashSet();
|
||||
// Remove items from the old sub
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Removed) { continue; }
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (item.HiddenInGame) { continue; }
|
||||
if (!connectedSubs.Contains(item.Submarine)) { continue; }
|
||||
if (item.Prefab.DontTransferBetweenSubs) { continue; }
|
||||
if (item.GetRootInventoryOwner() is Character) { continue; }
|
||||
if (item.GetComponent<Holdable>() == null && item.GetComponent<Wearable>() == null && item.GetComponent<Projectile>() == null) { continue; }
|
||||
if (item.Components.Any(c => c is Holdable h && h.Attached)) { continue; }
|
||||
if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; }
|
||||
itemsToTransfer.Add((item, item.Container));
|
||||
item.Submarine = null;
|
||||
}
|
||||
foreach (var (item, container) in itemsToTransfer)
|
||||
{
|
||||
if (container?.Submarine != null)
|
||||
{
|
||||
// Drop the item if it's not inside another item set to be transferred.
|
||||
item.Drop(null, createNetworkEvent: false, setTransform: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Serialize the current sub
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(currentSub);
|
||||
if (PendingSubmarineSwitch != null && itemsToTransfer.Any())
|
||||
{
|
||||
// Load the new sub
|
||||
var newSub = new Submarine(PendingSubmarineSwitch);
|
||||
var connectedSubs = newSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player).ToHashSet();
|
||||
// Move the transferred items
|
||||
List<ItemContainer> availableContainers = Item.ItemList
|
||||
.Where(it => connectedSubs.Contains(it.Submarine) && it.HasTag("crate") && !it.NonInteractable && !it.HiddenInGame && !it.Removed)
|
||||
.Select(it => it.GetComponent<ItemContainer>())
|
||||
.Where(c => c != null)
|
||||
.ToList();
|
||||
foreach (var (item, oldContainer) in itemsToTransfer)
|
||||
{
|
||||
Item newContainer = null;
|
||||
item.Submarine = newSub;
|
||||
if (item.Container == null)
|
||||
{
|
||||
newContainer = newSub.FindContainerFor(item, onlyPrimary: true, checkTransferConditions: true, allowConnectedSubs: true);
|
||||
}
|
||||
if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false)))
|
||||
{
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, newSub);
|
||||
Hull spawnHull = wp?.CurrentHull ?? Hull.HullList.Where(h => h.Submarine == newSub && !h.IsWetRoom).GetRandomUnsynced();
|
||||
if (spawnHull == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to transfer items between subs. No cargo waypoint or dry hulls found in the new sub.");
|
||||
return;
|
||||
}
|
||||
if (spawnHull != null)
|
||||
{
|
||||
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers);
|
||||
if (cargoContainer == null || !cargoContainer.Inventory.TryPutItem(item, user: null, createNetworkEvent: false))
|
||||
{
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab));
|
||||
item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to transfer item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!");
|
||||
}
|
||||
}
|
||||
string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})";
|
||||
string msg = "Item transfer log error.";
|
||||
if (oldContainer != null)
|
||||
{
|
||||
if (newContainer == null && oldContainer == item.Container)
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) contained inside {oldContainer.Prefab.Identifier} ({oldContainer.ID})";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) from {oldContainer.Prefab.Identifier} ({oldContainer.Tags}) to {newContainerName}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) to {newContainerName}";
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage(msg);
|
||||
#else
|
||||
DebugConsole.Log(msg);
|
||||
#endif
|
||||
}
|
||||
// Serialize the new sub
|
||||
PendingSubmarineSwitch = new SubmarineInfo(newSub);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RefreshOwnedSubmarines()
|
||||
{
|
||||
if (PendingSubmarineSwitch != null)
|
||||
{
|
||||
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
|
||||
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
|
||||
|
||||
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
|
||||
{
|
||||
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
|
||||
{
|
||||
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SavePets(XElement parentElement = null)
|
||||
{
|
||||
petsElement = new XElement("pets");
|
||||
PetBehavior.SavePets(petsElement);
|
||||
parentElement?.Add(petsElement);
|
||||
}
|
||||
|
||||
public void LoadPets()
|
||||
{
|
||||
if (petsElement != null)
|
||||
{
|
||||
PetBehavior.LoadPets(petsElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveActiveOrders(XElement parentElement = null)
|
||||
{
|
||||
ActiveOrdersElement = new XElement("activeorders");
|
||||
CrewManager?.SaveActiveOrders(ActiveOrdersElement);
|
||||
parentElement?.Add(ActiveOrdersElement);
|
||||
}
|
||||
|
||||
public void LoadActiveOrders()
|
||||
{
|
||||
CrewManager?.LoadActiveOrders(ActiveOrdersElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal static class CampaignModePresets
|
||||
{
|
||||
public static readonly ImmutableArray<CampaignSettings> List;
|
||||
public static readonly ImmutableDictionary<Identifier, CampaignSettingDefinitions> Definitions;
|
||||
|
||||
private static readonly string fileListPath = Path.Combine("Data", "campaignsettings.xml");
|
||||
|
||||
static CampaignModePresets()
|
||||
{
|
||||
if (!File.Exists(fileListPath) || !(XMLExtensions.TryLoadXml(fileListPath)?.Root is { } docRoot))
|
||||
{
|
||||
List = ImmutableArray<CampaignSettings>.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
List<CampaignSettings> list = new List<CampaignSettings>();
|
||||
Dictionary<Identifier, CampaignSettingDefinitions> definitions = new Dictionary<Identifier, CampaignSettingDefinitions>();
|
||||
|
||||
foreach (XElement element in docRoot.Elements())
|
||||
{
|
||||
Identifier name = element.NameAsIdentifier();
|
||||
|
||||
if (name == CampaignSettings.LowerCaseSaveElementName)
|
||||
{
|
||||
list.Add(new CampaignSettings(element));
|
||||
}
|
||||
else if (name == nameof(CampaignSettingDefinitions))
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
definitions.Add(subElement.NameAsIdentifier(), new CampaignSettingDefinitions(subElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List = list.ToImmutableArray();
|
||||
Definitions = definitions.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct CampaignSettingDefinitions
|
||||
{
|
||||
// Definitely not the best way to do this
|
||||
private readonly ImmutableDictionary<Identifier, Either<int, float>> values;
|
||||
|
||||
public CampaignSettingDefinitions(XElement element)
|
||||
{
|
||||
var definitions = new Dictionary<Identifier, Either<int, float>>();
|
||||
foreach (XAttribute attribute in element.Attributes())
|
||||
{
|
||||
Identifier name = attribute.NameAsIdentifier();
|
||||
if (attribute.Value.Contains('.'))
|
||||
{
|
||||
definitions.Add(name, element.GetAttributeFloat(name.Value, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
definitions.Add(name, element.GetAttributeInt(name.Value, 0));
|
||||
}
|
||||
}
|
||||
|
||||
values = definitions.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public float GetFloat(Identifier identifier)
|
||||
{
|
||||
float range = 0;
|
||||
if (!values.TryGetValue(identifier, out Either<int, float> value) || !value.TryGet(out range))
|
||||
{
|
||||
DebugConsole.ThrowError($"CampaignSettings: Can't find value for {identifier}");
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
public int GetInt(Identifier identifier)
|
||||
{
|
||||
int integer = 0;
|
||||
if (!values.TryGetValue(identifier, out Either<int, float> value) || !value.TryGet(out integer))
|
||||
{
|
||||
DebugConsole.ThrowError($"CampaignSettings: Can't find value for {identifier}");
|
||||
}
|
||||
return integer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal class CampaignSettings : INetSerializableStruct, ISerializableEntity
|
||||
{
|
||||
public static CampaignSettings Empty => new CampaignSettings(element: null);
|
||||
|
||||
public string Name => "CampaignSettings";
|
||||
|
||||
public const string LowerCaseSaveElementName = "campaignsettings";
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string PresetName { get; set; } = string.Empty;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
private int maxMissionCount;
|
||||
|
||||
[Serialize(DefaultMaxMissionCount, IsPropertySaveable.Yes), NetworkSerialize(MinValueInt = MinMissionCountLimit, MaxValueInt = MaxMissionCountLimit)]
|
||||
public int MaxMissionCount
|
||||
{
|
||||
get => maxMissionCount;
|
||||
set => maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit);
|
||||
}
|
||||
|
||||
public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount();
|
||||
|
||||
[Serialize(StartingBalanceAmount.Medium, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public StartingBalanceAmount StartingBalanceAmount { get; set; }
|
||||
|
||||
[Serialize(GameDifficulty.Medium, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public GameDifficulty Difficulty { get; set; }
|
||||
|
||||
[Serialize("normal", IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public Identifier StartItemSet { get; set; }
|
||||
|
||||
public int InitialMoney
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(StartingBalanceAmount).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetInt(StartingBalanceAmount.ToIdentifier());
|
||||
}
|
||||
return 8000;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public float ExtraEventManagerDifficulty
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(ExtraEventManagerDifficulty).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetFloat(Difficulty.ToIdentifier());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public float LevelDifficultyMultiplier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CampaignModePresets.Definitions.TryGetValue(nameof(LevelDifficultyMultiplier).ToIdentifier(), out var definition))
|
||||
{
|
||||
return definition.GetFloat(Difficulty.ToIdentifier());
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public const int DefaultMaxMissionCount = 2;
|
||||
public const int MaxMissionCountLimit = 10;
|
||||
public const int MinMissionCountLimit = 1;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
// required for INetSerializableStruct
|
||||
public CampaignSettings()
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
}
|
||||
|
||||
public CampaignSettings(XElement? element = null)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement saveElement = new XElement(LowerCaseSaveElementName);
|
||||
SerializableProperty.SerializeProperties(this, saveElement, saveIfDefault: true);
|
||||
return saveElement;
|
||||
}
|
||||
|
||||
private static int GetAddedMissionCount()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,11 @@ namespace Barotrauma
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
missions.Add(Mission.LoadRandom(locations, seed, false, missionType));
|
||||
var mission = Mission.LoadRandom(locations, seed, false, missionType);
|
||||
if (mission != null)
|
||||
{
|
||||
missions.Add(mission);
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<MissionType, Type> missionClasses)
|
||||
|
||||
+72
-22
@@ -12,19 +12,60 @@ namespace Barotrauma
|
||||
{
|
||||
public const int MinimumInitialMoney = 500;
|
||||
|
||||
private UInt16 lastUpdateID;
|
||||
public UInt16 LastUpdateID
|
||||
[Flags]
|
||||
public enum NetFlags : UInt16
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastUpdateID < 1) { lastUpdateID++; }
|
||||
#endif
|
||||
return lastUpdateID;
|
||||
}
|
||||
set { lastUpdateID = value; }
|
||||
Misc = 0x1,
|
||||
MapAndMissions = 0x2,
|
||||
UpgradeManager = 0x4,
|
||||
SubList = 0x8,
|
||||
ItemsInBuyCrate = 0x10,
|
||||
ItemsInSellFromSubCrate = 0x20,
|
||||
PurchasedItems = 0x80,
|
||||
SoldItems = 0x100,
|
||||
Reputation = 0x200,
|
||||
CharacterInfo = 0x800
|
||||
}
|
||||
|
||||
private readonly Dictionary<NetFlags, UInt16> lastUpdateID;
|
||||
|
||||
public UInt16 GetLastUpdateIdForFlag(NetFlags flag)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return 0; }
|
||||
return lastUpdateID[flag];
|
||||
}
|
||||
public void SetLastUpdateIdForFlag(NetFlags flag, UInt16 id)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return; }
|
||||
lastUpdateID[flag] = id;
|
||||
}
|
||||
|
||||
public void IncrementLastUpdateIdForFlag(NetFlags flag)
|
||||
{
|
||||
if (!ValidateFlag(flag)) { return; }
|
||||
if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
|
||||
lastUpdateID[flag]++;
|
||||
}
|
||||
public void IncrementAllLastUpdateIds()
|
||||
{
|
||||
foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
|
||||
{
|
||||
if (!lastUpdateID.ContainsKey(flag)) { lastUpdateID[flag] = 0; }
|
||||
lastUpdateID[flag]++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateFlag(NetFlags flag)
|
||||
{
|
||||
if (MathHelper.IsPowerOfTwo((int)flag)) { return true; }
|
||||
#if DEBUG
|
||||
throw new InvalidOperationException($"\"{flag}\" is not a valid campaign update flag.");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private UInt16 lastSaveID;
|
||||
public UInt16 LastSaveID
|
||||
{
|
||||
@@ -35,11 +76,11 @@ namespace Barotrauma
|
||||
#endif
|
||||
return lastSaveID;
|
||||
}
|
||||
set
|
||||
set
|
||||
{
|
||||
#if SERVER
|
||||
//trigger a campaign update to notify the clients of the changed save ID
|
||||
lastUpdateID++;
|
||||
IncrementLastUpdateIdForFlag(NetFlags.Misc);
|
||||
#endif
|
||||
lastSaveID = value;
|
||||
}
|
||||
@@ -52,23 +93,33 @@ namespace Barotrauma
|
||||
get; set;
|
||||
}
|
||||
|
||||
private MultiPlayerCampaign() : base(GameModePreset.MultiPlayerCampaign)
|
||||
private MultiPlayerCampaign(CampaignSettings settings) : base(GameModePreset.MultiPlayerCampaign, settings)
|
||||
{
|
||||
currentCampaignID++;
|
||||
lastUpdateID = new Dictionary<NetFlags, ushort>();
|
||||
foreach (NetFlags flag in Enum.GetValues(typeof(NetFlags)))
|
||||
{
|
||||
#if SERVER
|
||||
//server starts from a higher ID to ensure we send the initial state
|
||||
lastUpdateID[flag] = 1;
|
||||
#else
|
||||
lastUpdateID[flag] = 0;
|
||||
#endif
|
||||
}
|
||||
CampaignID = currentCampaignID;
|
||||
CampaignMetadata = new CampaignMetadata(this);
|
||||
UpgradeManager = new UpgradeManager(this);
|
||||
InitCampaignData();
|
||||
}
|
||||
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings)
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(settings);
|
||||
//only the server generates the map, the clients load it from a save file
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
campaign.map = new Map(campaign, mapSeed, settings);
|
||||
campaign.Settings = settings;
|
||||
campaign.map = new Map(campaign, mapSeed);
|
||||
}
|
||||
campaign.InitProjSpecific();
|
||||
return campaign;
|
||||
@@ -76,7 +127,7 @@ namespace Barotrauma
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(CampaignSettings.Empty);
|
||||
campaign.Load(element);
|
||||
campaign.InitProjSpecific();
|
||||
campaign.IsFirstRound = false;
|
||||
@@ -124,18 +175,17 @@ namespace Barotrauma
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "campaignsettings":
|
||||
case CampaignSettings.LowerCaseSaveElementName:
|
||||
Settings = new CampaignSettings(subElement);
|
||||
#if CLIENT
|
||||
GameMain.NetworkMember.ServerSettings.MaxMissionCount = Settings.MaxMissionCount;
|
||||
GameMain.NetworkMember.ServerSettings.RadiationEnabled = Settings.RadiationEnabled;
|
||||
GameMain.NetworkMember.ServerSettings.CampaignSettings = Settings;
|
||||
#endif
|
||||
break;
|
||||
case "map":
|
||||
if (map == null)
|
||||
{
|
||||
//map not created yet, loading this campaign for the first time
|
||||
map = Map.Load(this, subElement, Settings);
|
||||
map = Map.Load(this, subElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -155,7 +205,7 @@ namespace Barotrauma
|
||||
case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer:
|
||||
CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false);
|
||||
CrewManager.AddCharacterElements(subElement);
|
||||
CrewManager.ActiveOrdersElement = subElement.GetChildElement("activeorders");
|
||||
ActiveOrdersElement = subElement.GetChildElement("activeorders");
|
||||
break;
|
||||
case "cargo":
|
||||
CargoManager?.LoadPurchasedItems(subElement);
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.CurrentLocation; }
|
||||
if (dummyLocations == null) { CreateDummyLocations(); }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[0];
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.SelectedLocation; }
|
||||
if (dummyLocations == null) { CreateDummyLocations(); }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[1];
|
||||
}
|
||||
@@ -207,7 +207,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
|
||||
{
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Bank.Deduct(selectedSub.Price);
|
||||
@@ -218,7 +218,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign))
|
||||
{
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), settings);
|
||||
if (selectedSub != null)
|
||||
{
|
||||
campaign.Bank.TryDeduct(selectedSub.Price);
|
||||
@@ -245,25 +245,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDummyLocations(LocationType? forceLocationType = null)
|
||||
public static Location[] CreateDummyLocations(string seed, LocationType? forceLocationType = null)
|
||||
{
|
||||
dummyLocations = new Location[2];
|
||||
|
||||
string seed = "";
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.Level != null)
|
||||
{
|
||||
seed = GameMain.GameSession.Level.Seed;
|
||||
}
|
||||
else if (GameMain.NetLobbyScreen != null)
|
||||
{
|
||||
seed = GameMain.NetLobbyScreen.LevelSeed;
|
||||
}
|
||||
|
||||
var dummyLocations = new Location[2];
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(seed));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true, forceLocationType: forceLocationType);
|
||||
}
|
||||
return dummyLocations;
|
||||
}
|
||||
|
||||
public void LoadPreviousSave()
|
||||
@@ -275,7 +265,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Switch to another submarine. The sub is loaded when the next round starts.
|
||||
/// </summary>
|
||||
public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null)
|
||||
public void SwitchSubmarine(SubmarineInfo newSubmarine, bool transferItems, int cost, Client? client = null)
|
||||
{
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
@@ -293,15 +283,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0)
|
||||
{
|
||||
Campaign!.TryPurchase(client, cost);
|
||||
}
|
||||
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
|
||||
Campaign!.PendingSubmarineSwitch = newSubmarine;
|
||||
|
||||
return newSubmarine;
|
||||
Campaign!.TransferItemsOnSubSwitch = transferItems;
|
||||
}
|
||||
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
@@ -312,6 +300,9 @@ namespace Barotrauma
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
OwnedSubmarines.Add(newSubmarine);
|
||||
#if SERVER
|
||||
(Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.SubList);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +339,7 @@ namespace Barotrauma
|
||||
!missionPrefab.AllowedConnectionTypes.Any())
|
||||
{
|
||||
LocationType? locationType = LocationType.Prefabs.FirstOrDefault(lt => missionPrefab.AllowedLocationTypes.Any(m => m == lt.Identifier));
|
||||
CreateDummyLocations(locationType);
|
||||
dummyLocations = CreateDummyLocations(levelSeed, locationType);
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost: true);
|
||||
break;
|
||||
}
|
||||
@@ -433,7 +424,7 @@ namespace Barotrauma
|
||||
Level? level = null;
|
||||
if (levelData != null)
|
||||
{
|
||||
level = Level.Generate(levelData, mirrorLevel, startOutpost, endOutpost);
|
||||
level = Level.Generate(levelData, mirrorLevel, StartLocation, EndLocation, startOutpost, endOutpost);
|
||||
}
|
||||
|
||||
InitializeLevel(level);
|
||||
@@ -602,10 +593,13 @@ namespace Barotrauma
|
||||
{
|
||||
//only place items and corpses here in single player
|
||||
//the server does this after loading the respawn shuttle
|
||||
Level?.SpawnNPCs();
|
||||
Level?.SpawnCorpses();
|
||||
Level?.PrepareBeaconStation();
|
||||
AutoItemPlacer.PlaceIfNeeded();
|
||||
if (Level != null)
|
||||
{
|
||||
Level.SpawnNPCs();
|
||||
Level.SpawnCorpses();
|
||||
Level.PrepareBeaconStation();
|
||||
}
|
||||
AutoItemPlacer.SpawnItems(Campaign?.Settings.StartItemSet);
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
@@ -630,8 +624,6 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
var originalSubPos = Submarine.WorldPosition;
|
||||
|
||||
if (level.StartOutpost != null)
|
||||
{
|
||||
//start by placing the sub below the outpost
|
||||
@@ -714,7 +706,7 @@ namespace Barotrauma
|
||||
if (!ls.LoadSub || ls.Sub.DockedTo.Contains(Submarine)) { continue; }
|
||||
if (Submarine.Info.LeftBehindDockingPortIDs.Contains(ls.OriginalLinkedToID)) { continue; }
|
||||
if (ls.Sub.Info.SubmarineElement.Attribute("location") != null) { continue; }
|
||||
ls.Sub.SetPosition(ls.Sub.WorldPosition + (Submarine.WorldPosition - originalSubPos));
|
||||
ls.SetPositionRelativeToMainSub();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,6 +838,11 @@ namespace Barotrauma
|
||||
{
|
||||
GUI.TogglePauseMenu();
|
||||
}
|
||||
if (IsTabMenuOpen)
|
||||
{
|
||||
ToggleTabMenu();
|
||||
}
|
||||
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
|
||||
@@ -1082,8 +1079,16 @@ namespace Barotrauma
|
||||
|
||||
rootElement.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal));
|
||||
rootElement.Add(new XAttribute("version", GameMain.Version));
|
||||
var submarineInfo = Campaign?.PendingSubmarineSwitch ?? SubmarineInfo;
|
||||
rootElement.Add(new XAttribute("submarine", submarineInfo == null ? "" : submarineInfo.Name));
|
||||
if (Submarine?.Info != null && !Submarine.Removed && Campaign != null)
|
||||
{
|
||||
bool hasNewPendingSub = Campaign.PendingSubmarineSwitch != null &&
|
||||
Campaign.PendingSubmarineSwitch.MD5Hash.StringRepresentation != Submarine.Info.MD5Hash.StringRepresentation;
|
||||
if (hasNewPendingSub)
|
||||
{
|
||||
Campaign.SwitchSubs();
|
||||
}
|
||||
}
|
||||
rootElement.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name));
|
||||
if (OwnedSubmarines != null)
|
||||
{
|
||||
List<string> ownedSubmarineNames = new List<string>();
|
||||
|
||||
@@ -13,12 +13,26 @@ namespace Barotrauma
|
||||
|
||||
internal partial class ReadyCheck
|
||||
{
|
||||
private readonly float endTime;
|
||||
private float time;
|
||||
private readonly DateTime endTime;
|
||||
private readonly DateTime startTime;
|
||||
public readonly Dictionary<byte, ReadyStatus> Clients;
|
||||
public bool IsFinished = false;
|
||||
|
||||
public ReadyCheck(List<byte> clients, float duration = 30)
|
||||
public ReadyCheck(List<byte> clients, DateTime startTime, DateTime endTime)
|
||||
: this(clients)
|
||||
{
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public ReadyCheck(List<byte> clients, float duration)
|
||||
: this(clients)
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
endTime = startTime + new TimeSpan(0, 0, 0, 0, (int)(duration * 1000));
|
||||
}
|
||||
|
||||
private ReadyCheck(List<byte> clients)
|
||||
{
|
||||
Clients = new Dictionary<byte, ReadyStatus>();
|
||||
foreach (byte client in clients)
|
||||
@@ -27,24 +41,17 @@ namespace Barotrauma
|
||||
|
||||
Clients.Add(client, ReadyStatus.Unanswered);
|
||||
}
|
||||
|
||||
time = duration;
|
||||
endTime = duration;
|
||||
#if CLIENT
|
||||
lastSecond = (int) Math.Ceiling(duration);
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void EndReadyCheck();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (time > 0)
|
||||
if (DateTime.Now < endTime)
|
||||
{
|
||||
#if CLIENT
|
||||
UpdateBar();
|
||||
#endif
|
||||
time -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user