(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static class AutoItemPlacer
|
||||
{
|
||||
private static readonly List<Item> spawnedItems = new List<Item>();
|
||||
|
||||
public static bool OutputDebugInfo = false;
|
||||
|
||||
public static void PlaceIfNeeded(GameMode gameMode)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
CampaignMode campaign = gameMode as CampaignMode;
|
||||
if (campaign == null || !campaign.InitialSuppliesSpawned)
|
||||
{
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
if (Submarine.MainSubs[i] == null) { continue; }
|
||||
List<Submarine> subs = new List<Submarine>() { Submarine.MainSubs[i] };
|
||||
subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.IsOutpost));
|
||||
Place(subs);
|
||||
}
|
||||
if (campaign != null) { campaign.InitialSuppliesSpawned = true; }
|
||||
}
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
DebugConsole.ThrowError("Clients are not allowed to use AutoItemPlacer.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
int sizeApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(100);
|
||||
var prefabsWithContainer = new List<ItemPrefab>(sizeApprox / 3);
|
||||
var prefabsWithoutContainer = new List<ItemPrefab>(sizeApprox);
|
||||
var removals = new List<ItemPrefab>();
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!subs.Contains(item.Submarine)) { continue; }
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
}
|
||||
|
||||
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
|
||||
{
|
||||
if (!(prefab is ItemPrefab ip)) { continue; }
|
||||
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
prefabsWithContainer.Add(ip);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabsWithoutContainer.Add(ip);
|
||||
}
|
||||
}
|
||||
|
||||
spawnedItems.Clear();
|
||||
var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
|
||||
prefabsWithContainer.Shuffle();
|
||||
// Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
|
||||
for (int i = 0; i < prefabsWithContainer.Count; i++)
|
||||
{
|
||||
var itemPrefab = prefabsWithContainer[i];
|
||||
if (itemPrefab == null) { continue; }
|
||||
if (SpawnItems(itemPrefab))
|
||||
{
|
||||
removals.Add(itemPrefab);
|
||||
}
|
||||
}
|
||||
// Remove containers that we successfully spawned items into so that they are not counted in in the second pass.
|
||||
removals.ForEach(i => prefabsWithContainer.Remove(i));
|
||||
// Another pass for items with containers because also they can spawn inside other items (like smg magazine)
|
||||
prefabsWithContainer.ForEach(i => SpawnItems(i));
|
||||
// Spawn items that don't have containers last
|
||||
prefabsWithoutContainer.Shuffle();
|
||||
prefabsWithoutContainer.ForEach(i => SpawnItems(i));
|
||||
|
||||
if (OutputDebugInfo)
|
||||
{
|
||||
DebugConsole.NewMessage("Automatically placed items: ");
|
||||
foreach (string itemName in spawnedItems.Select(it => it.Name).Distinct())
|
||||
{
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + spawnedItems.Count(it => it.Name == itemName));
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnItems(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Error in AutoItemPlacer.SpawnItems - itemPrefab was null.\n"+Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AutoItemPlacer.SpawnItems:ItemNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
|
||||
{
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0) { continue; }
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
|
||||
if (validContainers.None())
|
||||
{
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: false);
|
||||
}
|
||||
foreach (var validContainer in validContainers)
|
||||
{
|
||||
if (SpawnItem(itemPrefab, containers, validContainer))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<ItemContainer, PreferredContainer> GetValidContainers(PreferredContainer preferredContainer, IEnumerable<ItemContainer> allContainers, Dictionary<ItemContainer, PreferredContainer> validContainers, bool primary)
|
||||
{
|
||||
validContainers.Clear();
|
||||
foreach (ItemContainer container in allContainers)
|
||||
{
|
||||
if (!container.AutoFill) { continue; }
|
||||
if (primary)
|
||||
{
|
||||
if (!ItemPrefab.IsContainerPreferred(preferredContainer.Primary, container)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ItemPrefab.IsContainerPreferred(preferredContainer.Secondary, container)) { continue; }
|
||||
}
|
||||
if (!validContainers.ContainsKey(container))
|
||||
{
|
||||
validContainers.Add(container, preferredContainer);
|
||||
}
|
||||
}
|
||||
return validContainers;
|
||||
}
|
||||
|
||||
private static bool SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
bool success = false;
|
||||
if (Rand.Value() > validContainer.Value.SpawnProbability) { return success; }
|
||||
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (validContainer.Key.Inventory.IsFull())
|
||||
{
|
||||
containers.Remove(validContainer.Key);
|
||||
break;
|
||||
}
|
||||
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine);
|
||||
spawnedItems.Add(item);
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(item, remove: false);
|
||||
#endif
|
||||
validContainer.Key.Inventory.TryPutItem(item, null);
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class PurchasedItem
|
||||
{
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
public int Quantity;
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity)
|
||||
{
|
||||
this.ItemPrefab = itemPrefab;
|
||||
this.Quantity = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
class CargoManager
|
||||
{
|
||||
private readonly List<PurchasedItem> purchasedItems;
|
||||
|
||||
private readonly CampaignMode campaign;
|
||||
|
||||
public Action OnItemsChanged;
|
||||
|
||||
public List<PurchasedItem> PurchasedItems
|
||||
{
|
||||
get { return purchasedItems; }
|
||||
}
|
||||
|
||||
public CargoManager(CampaignMode campaign)
|
||||
{
|
||||
purchasedItems = new List<PurchasedItem>();
|
||||
this.campaign = campaign;
|
||||
}
|
||||
|
||||
public void SetPurchasedItems(List<PurchasedItem> items)
|
||||
{
|
||||
purchasedItems.Clear();
|
||||
purchasedItems.AddRange(items);
|
||||
|
||||
OnItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void PurchaseItem(ItemPrefab item, int quantity = 1)
|
||||
{
|
||||
PurchasedItem purchasedItem = PurchasedItems.Find(pi => pi.ItemPrefab == item);
|
||||
|
||||
campaign.Money -= item.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity;
|
||||
if (purchasedItem != null)
|
||||
{
|
||||
purchasedItem.Quantity += quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
purchasedItem = new PurchasedItem(item, quantity);
|
||||
purchasedItems.Add(purchasedItem);
|
||||
}
|
||||
|
||||
OnItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void SellItem(PurchasedItem purchasedItem, int quantity = 1)
|
||||
{
|
||||
quantity = Math.Min(purchasedItem.Quantity, quantity);
|
||||
campaign.Money += purchasedItem.ItemPrefab.GetPrice(campaign.Map.CurrentLocation).BuyPrice * quantity;
|
||||
purchasedItem.Quantity -= quantity;
|
||||
if (purchasedItem != null && purchasedItem.Quantity <= 0)
|
||||
{
|
||||
PurchasedItems.Remove(purchasedItem);
|
||||
}
|
||||
|
||||
OnItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public int GetTotalItemCost()
|
||||
{
|
||||
if (purchasedItems == null) return 0;
|
||||
return purchasedItems.Sum(i => i.ItemPrefab.GetPrice(campaign.Map.CurrentLocation).BuyPrice * i.Quantity);
|
||||
}
|
||||
|
||||
public void CreateItems()
|
||||
{
|
||||
CreateItems(purchasedItems);
|
||||
OnItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public static void CreateItems(List<PurchasedItem> itemsToSpawn)
|
||||
{
|
||||
if (itemsToSpawn.Count == 0) { return; }
|
||||
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);
|
||||
if (wp == null)
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
|
||||
return;
|
||||
}
|
||||
|
||||
Hull cargoRoom = Hull.FindHull(wp.WorldPosition);
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true));
|
||||
#endif
|
||||
|
||||
Dictionary<ItemContainer, int> availableContainers = new Dictionary<ItemContainer, int>();
|
||||
ItemPrefab containerPrefab = null;
|
||||
foreach (PurchasedItem pi in itemsToSpawn)
|
||||
{
|
||||
Vector2 position = new Vector2(
|
||||
Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.ItemPrefab.Size.Y / 2);
|
||||
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
|
||||
{
|
||||
itemContainer = availableContainers.Keys.ToList().Find(ac =>
|
||||
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 \"" + containerPrefab.Name + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Item containerItem = new Item(containerPrefab, position, 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, itemContainer.Capacity);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < pi.Quantity; i++)
|
||||
{
|
||||
if (itemContainer == null)
|
||||
{
|
||||
//no container, place at the waypoint
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, position, wp.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
//if the intial container has been removed due to it running out of space, add a new container
|
||||
//of the same type and begin filling it
|
||||
if (!availableContainers.ContainsKey(itemContainer))
|
||||
{
|
||||
Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine);
|
||||
itemContainer = containerItemOverFlow.GetComponent<ItemContainer>();
|
||||
availableContainers.Add(itemContainer, itemContainer.Capacity);
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//place in the container
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(pi.ItemPrefab, itemContainer.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer.Inventory.TryPutItem(item, null);
|
||||
}
|
||||
|
||||
//reduce the number of available slots in the container
|
||||
//if there is a container
|
||||
if (availableContainers.ContainsKey(itemContainer))
|
||||
{
|
||||
availableContainers[itemContainer]--;
|
||||
}
|
||||
if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0)
|
||||
{
|
||||
availableContainers.Remove(itemContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsToSpawn.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CrewManager
|
||||
{
|
||||
const float ConversationIntervalMin = 100.0f;
|
||||
const float ConversationIntervalMax = 180.0f;
|
||||
private float conversationTimer, conversationLineTimer;
|
||||
private List<Pair<Character, string>> pendingConversationLines = new List<Pair<Character, string>>();
|
||||
|
||||
public List<Pair<Order, float>> ActiveOrders { get; } = new List<Pair<Order, float>>();
|
||||
public bool IsSinglePlayer { get; private set; }
|
||||
|
||||
public CrewManager(bool isSinglePlayer)
|
||||
{
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
conversationTimer = 5.0f;
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
public bool AddOrder(Order order, float fadeOutTime)
|
||||
{
|
||||
if (order.TargetEntity == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to add an order with no target entity to CrewManager!\n" + Environment.StackTrace);
|
||||
return false;
|
||||
}
|
||||
|
||||
Pair<Order, float> existingOrder = ActiveOrders.Find(o => o.First.Prefab == order.Prefab && o.First.TargetEntity == order.TargetEntity);
|
||||
if (existingOrder != null)
|
||||
{
|
||||
existingOrder.Second = fadeOutTime;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveOrders.Add(new Pair<Order, float>(order, fadeOutTime));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveOrder(Order order)
|
||||
{
|
||||
ActiveOrders.RemoveAll(o => o.First == order);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Pair<Order, float> order in ActiveOrders)
|
||||
{
|
||||
order.Second -= deltaTime;
|
||||
}
|
||||
ActiveOrders.RemoveAll(o => o.Second <= 0.0f);
|
||||
|
||||
UpdateConversations(deltaTime);
|
||||
UpdateProjectSpecific(deltaTime);
|
||||
}
|
||||
|
||||
#region Dialog
|
||||
|
||||
public void AddConversation(List<Pair<Character, string>> conversationLines)
|
||||
{
|
||||
if (conversationLines == null || conversationLines.Count == 0) { return; }
|
||||
pendingConversationLines.AddRange(conversationLines);
|
||||
}
|
||||
|
||||
partial void CreateRandomConversation();
|
||||
|
||||
private void UpdateConversations(float deltaTime)
|
||||
{
|
||||
conversationTimer -= deltaTime;
|
||||
if (conversationTimer <= 0.0f)
|
||||
{
|
||||
CreateRandomConversation();
|
||||
conversationTimer = Rand.Range(ConversationIntervalMin, ConversationIntervalMax);
|
||||
}
|
||||
|
||||
if (pendingConversationLines.Count > 0)
|
||||
{
|
||||
conversationLineTimer -= deltaTime;
|
||||
if (conversationLineTimer <= 0.0f)
|
||||
{
|
||||
//speaker of the next line can't speak, interrupt the conversation
|
||||
if (pendingConversationLines[0].First.SpeechImpediment >= 100.0f)
|
||||
{
|
||||
pendingConversationLines.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
pendingConversationLines[0].First.Speak(pendingConversationLines[0].Second, null);
|
||||
if (pendingConversationLines.Count > 1)
|
||||
{
|
||||
conversationLineTimer = MathHelper.Clamp(pendingConversationLines[0].Second.Length * 0.1f, 1.0f, 5.0f);
|
||||
}
|
||||
pendingConversationLines.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
partial void UpdateProjectSpecific(float deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
public readonly CargoManager CargoManager;
|
||||
|
||||
public bool CheatsEnabled;
|
||||
|
||||
const int InitialMoney = 8700;
|
||||
public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000;
|
||||
|
||||
protected bool watchmenSpawned;
|
||||
protected Character startWatchman, endWatchman;
|
||||
|
||||
//key = dialog flag, double = Timing.TotalTime when the line was last said
|
||||
private Dictionary<string, double> dialogLastSpoken = new Dictionary<string, double>();
|
||||
|
||||
public bool PurchasedHullRepairs, PurchasedLostShuttles, PurchasedItemRepairs;
|
||||
|
||||
public bool InitialSuppliesSpawned;
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
{
|
||||
get { return map; }
|
||||
}
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return Map.CurrentLocation?.SelectedMission;
|
||||
}
|
||||
}
|
||||
|
||||
private int money;
|
||||
public int Money
|
||||
{
|
||||
get { return money; }
|
||||
set { money = Math.Max(value, 0); }
|
||||
}
|
||||
|
||||
public CampaignMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
Money = InitialMoney;
|
||||
CargoManager = new CargoManager(this);
|
||||
}
|
||||
|
||||
public void GenerateMap(string seed)
|
||||
{
|
||||
map = new Map(seed);
|
||||
}
|
||||
|
||||
protected List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
|
||||
{
|
||||
//leave subs behind if they're not docked to the leaving sub and not at the same exit
|
||||
return Submarine.Loaded.FindAll(s =>
|
||||
s != leavingSub &&
|
||||
!leavingSub.DockedTo.Contains(s) &&
|
||||
s != Level.Loaded.StartOutpost && s != Level.Loaded.EndOutpost &&
|
||||
(s.AtEndPosition != leavingSub.AtEndPosition || s.AtStartPosition != leavingSub.AtStartPosition));
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
dialogLastSpoken.Clear();
|
||||
watchmenSpawned = false;
|
||||
startWatchman = null;
|
||||
endWatchman = null;
|
||||
|
||||
if (PurchasedHullRepairs)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine == null || wall.Submarine.IsOutpost) { continue; }
|
||||
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -wall.Prefab.Health);
|
||||
}
|
||||
}
|
||||
}
|
||||
PurchasedHullRepairs = false;
|
||||
}
|
||||
if (PurchasedItemRepairs)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || item.Submarine.IsOutpost) { continue; }
|
||||
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
|
||||
{
|
||||
if (item.GetComponent<Items.Components.Repairable>() != null)
|
||||
{
|
||||
item.Condition = item.Prefab.Health;
|
||||
}
|
||||
}
|
||||
}
|
||||
PurchasedItemRepairs = false;
|
||||
}
|
||||
PurchasedLostShuttles = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (!IsRunning) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
if (!watchmenSpawned)
|
||||
{
|
||||
if (Level.Loaded.StartOutpost != null) { startWatchman = SpawnWatchman(Level.Loaded.StartOutpost); }
|
||||
if (Level.Loaded.EndOutpost != null) { endWatchman = SpawnWatchman(Level.Loaded.EndOutpost); }
|
||||
watchmenSpawned = true;
|
||||
#if SERVER
|
||||
(this as MultiPlayerCampaign).LastUpdateID++;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
#if SERVER
|
||||
if (string.IsNullOrEmpty(character.OwnerClientEndPoint)) { continue; }
|
||||
#else
|
||||
if (!CrewManager.GetCharacters().Contains(character)) { continue; }
|
||||
#endif
|
||||
if (character.Submarine == Level.Loaded.StartOutpost &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, startWatchman.WorldPosition) < 500.0f * 500.0f)
|
||||
{
|
||||
CreateDialog(new List<Character> { startWatchman }, "EnterStartOutpost", 5 * 60.0f);
|
||||
}
|
||||
else if (character.Submarine == Level.Loaded.EndOutpost &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, endWatchman.WorldPosition) < 500.0f * 500.0f)
|
||||
{
|
||||
CreateDialog(new List<Character> { endWatchman }, "EnterEndOutpost", 5 * 60.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateDialog(List<Character> speakers, string conversationTag, float minInterval)
|
||||
{
|
||||
if (dialogLastSpoken.TryGetValue(conversationTag, out double lastTime))
|
||||
{
|
||||
if (Timing.TotalTime - lastTime < minInterval) { return; }
|
||||
}
|
||||
|
||||
CrewManager.AddConversation(
|
||||
NPCConversation.CreateRandom(speakers, new List<string>() { conversationTag }));
|
||||
dialogLastSpoken[conversationTag] = Timing.TotalTime;
|
||||
}
|
||||
|
||||
private Character SpawnWatchman(Submarine outpost)
|
||||
{
|
||||
WayPoint watchmanSpawnpoint = WayPoint.WayPointList.Find(wp => wp.Submarine == outpost);
|
||||
if (watchmanSpawnpoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to spawn a watchman at the outpost. No spawnpoints found inside the outpost.");
|
||||
return null;
|
||||
}
|
||||
|
||||
string seed = outpost == Level.Loaded.StartOutpost ? map.SelectedLocation.Name : map.CurrentLocation.Name;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
JobPrefab watchmanJob = JobPrefab.Get("watchman");
|
||||
var variant = Rand.Range(0, watchmanJob.Variants, Rand.RandSync.Server);
|
||||
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: watchmanJob, variant: variant);
|
||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||
InitializeWatchman(spawnedCharacter);
|
||||
var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;
|
||||
if (objectiveManager != null)
|
||||
{
|
||||
var moveOrder = new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false);
|
||||
moveOrder.Completed += () =>
|
||||
{
|
||||
// Turn towards the center of the sub. Doesn't work in all possible cases, but this is the simplest solution for now.
|
||||
spawnedCharacter.AnimController.TargetDir = spawnedCharacter.Submarine.WorldPosition.X > spawnedCharacter.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
};
|
||||
objectiveManager.SetOrder(moveOrder);
|
||||
}
|
||||
if (watchmanJob != null)
|
||||
{
|
||||
spawnedCharacter.GiveJobItems();
|
||||
}
|
||||
return spawnedCharacter;
|
||||
}
|
||||
|
||||
protected void InitializeWatchman(Character character)
|
||||
{
|
||||
character.CharacterHealth.UseHealthWindow = false;
|
||||
character.CharacterHealth.Unkillable = true;
|
||||
character.CanInventoryBeAccessed = false;
|
||||
character.CanBeDragged = false;
|
||||
character.TeamID = Character.TeamType.FriendlyNPC;
|
||||
character.SetCustomInteract(
|
||||
WatchmanInteract,
|
||||
#if CLIENT
|
||||
hudText: TextManager.GetWithVariable("TalkHint", "[key]", GameMain.Config.KeyBindText(InputType.Select)));
|
||||
#else
|
||||
hudText: TextManager.Get("TalkHint"));
|
||||
#endif
|
||||
}
|
||||
|
||||
protected abstract void WatchmanInteract(Character watchman, Character interactor);
|
||||
|
||||
public abstract void Save(XElement element);
|
||||
|
||||
public void LogState()
|
||||
{
|
||||
DebugConsole.NewMessage("********* CAMPAIGN STATUS *********", Color.White);
|
||||
DebugConsole.NewMessage(" Money: " + Money, Color.White);
|
||||
DebugConsole.NewMessage(" Current location: " + map.CurrentLocation.Name, Color.White);
|
||||
|
||||
DebugConsole.NewMessage(" Available destinations: ", Color.White);
|
||||
for (int i = 0; i < map.CurrentLocation.Connections.Count; i++)
|
||||
{
|
||||
Location destination = map.CurrentLocation.Connections[i].OtherLocation(map.CurrentLocation);
|
||||
if (destination == map.SelectedLocation)
|
||||
{
|
||||
DebugConsole.NewMessage(" " + i + ". " + destination.Name + " [SELECTED]", Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage(" " + i + ". " + destination.Name, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
if (map.CurrentLocation?.SelectedMission != null)
|
||||
{
|
||||
DebugConsole.NewMessage(" Selected mission: " + map.CurrentLocation.SelectedMission.Name, Color.White);
|
||||
DebugConsole.NewMessage("\n" + map.CurrentLocation.SelectedMission.Description, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
map?.Remove();
|
||||
map = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CharacterCampaignData
|
||||
{
|
||||
public CharacterInfo CharacterInfo
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public string ClientEndPoint
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public ulong SteamID
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private XElement itemData;
|
||||
|
||||
partial void InitProjSpecific(Client client);
|
||||
public CharacterCampaignData(Client client)
|
||||
{
|
||||
Name = client.Name;
|
||||
InitProjSpecific(client);
|
||||
|
||||
if (client.Character.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
client.Character.SaveInventory(client.Character.Inventory, itemData);
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterCampaignData(XElement element)
|
||||
{
|
||||
Name = element.GetAttributeString("name", "Unnamed");
|
||||
ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
|
||||
string steamID = element.GetAttributeString("steamid", "");
|
||||
if (!string.IsNullOrEmpty(steamID))
|
||||
{
|
||||
ulong.TryParse(steamID, out ulong parsedID);
|
||||
SteamID = parsedID;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "character":
|
||||
case "characterinfo":
|
||||
CharacterInfo = new CharacterInfo(subElement);
|
||||
break;
|
||||
case "inventory":
|
||||
itemData = subElement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("endpoint", ClientEndPoint),
|
||||
new XAttribute("steamid", SteamID));
|
||||
|
||||
CharacterInfo?.Save(element);
|
||||
|
||||
if (itemData != null)
|
||||
{
|
||||
element.Add(itemData);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameMode
|
||||
{
|
||||
public static List<GameModePreset> PresetList = new List<GameModePreset>();
|
||||
|
||||
protected DateTime startTime;
|
||||
|
||||
protected bool isRunning;
|
||||
|
||||
protected GameModePreset preset;
|
||||
|
||||
private string endMessage;
|
||||
|
||||
protected CrewManager CrewManager
|
||||
{
|
||||
get { return GameMain.GameSession?.CrewManager; }
|
||||
}
|
||||
|
||||
public virtual Mission Mission
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return isRunning; }
|
||||
}
|
||||
|
||||
public bool IsSinglePlayer
|
||||
{
|
||||
get { return preset.IsSinglePlayer; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return preset.Name; }
|
||||
}
|
||||
|
||||
public string EndMessage
|
||||
{
|
||||
get { return endMessage; }
|
||||
}
|
||||
|
||||
public GameModePreset Preset
|
||||
{
|
||||
get { return preset; }
|
||||
}
|
||||
|
||||
public GameMode(GameModePreset preset, object param)
|
||||
{
|
||||
this.preset = preset;
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
|
||||
endMessage = "The round has ended!";
|
||||
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
public virtual void ShowStartMessage() { }
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
#if CLIENT
|
||||
if (!isRunning) return;
|
||||
|
||||
GameMain.GameSession?.CrewManager.AddToGUIUpdateList();
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
CrewManager?.Update(deltaTime);
|
||||
}
|
||||
|
||||
public virtual void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
if (endMessage != "" || this.endMessage == null) this.endMessage = endMessage;
|
||||
|
||||
GameMain.GameSession.EndRound(endMessage);
|
||||
}
|
||||
|
||||
public virtual void Remove() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GameModePreset
|
||||
{
|
||||
public static List<GameModePreset> List = new List<GameModePreset>();
|
||||
|
||||
public readonly ConstructorInfo Constructor;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly bool IsSinglePlayer;
|
||||
|
||||
//are clients allowed to vote for this gamemode
|
||||
public readonly bool Votable;
|
||||
|
||||
public GameModePreset(string identifier, Type type, bool isSinglePlayer = false, bool votable = true)
|
||||
{
|
||||
Name = TextManager.Get("GameMode." + identifier);
|
||||
Description = TextManager.Get("GameModeDescription." + identifier, returnNull: true) ?? "";
|
||||
Identifier = identifier;
|
||||
|
||||
Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) });
|
||||
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
Votable = votable;
|
||||
|
||||
List.Add(this);
|
||||
}
|
||||
|
||||
public GameMode Instantiate(object param)
|
||||
{
|
||||
object[] lobject = new object[] { this, param };
|
||||
return (GameMode)Constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
#if CLIENT
|
||||
new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true);
|
||||
#endif
|
||||
new GameModePreset("sandbox", typeof(GameMode), false);
|
||||
new GameModePreset("mission", typeof(MissionMode), false);
|
||||
new GameModePreset("multiplayercampaign", typeof(MultiPlayerCampaign), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionMode : GameMode
|
||||
{
|
||||
private Mission mission;
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return mission;
|
||||
}
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
if (param is MissionType missionType)
|
||||
{
|
||||
mission = Mission.LoadRandom(locations, GameMain.NetLobbyScreen.LevelSeed, false, missionType);
|
||||
}
|
||||
else if (param is MissionPrefab missionPrefab)
|
||||
{
|
||||
mission = missionPrefab.Instantiate(locations);
|
||||
}
|
||||
else if (param is Mission)
|
||||
{
|
||||
mission = (Mission)param;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.ArgumentException("Unrecognized MissionMode parameter \"" + param + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MultiPlayerCampaign : CampaignMode
|
||||
{
|
||||
private UInt16 lastUpdateID;
|
||||
public UInt16 LastUpdateID
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastUpdateID < 1) lastUpdateID++;
|
||||
#endif
|
||||
return lastUpdateID;
|
||||
}
|
||||
set { lastUpdateID = value; }
|
||||
}
|
||||
|
||||
private UInt16 lastSaveID;
|
||||
public UInt16 LastSaveID
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && lastSaveID < 1) lastSaveID++;
|
||||
#endif
|
||||
return lastSaveID;
|
||||
}
|
||||
set { lastSaveID = value; }
|
||||
}
|
||||
|
||||
public UInt16 PendingSaveID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private static byte currentCampaignID;
|
||||
|
||||
public byte CampaignID
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public MultiPlayerCampaign(GameModePreset preset, object param) :
|
||||
base(preset, param)
|
||||
{
|
||||
currentCampaignID++;
|
||||
CampaignID = currentCampaignID;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
if (GameMain.NetworkMember.IsServer) lastUpdateID++;
|
||||
}
|
||||
|
||||
public override void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.GameSession.EndRound("");
|
||||
GameMain.GameSession.CrewManager.EndRound();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SERVER
|
||||
lastUpdateID++;
|
||||
|
||||
bool success =
|
||||
GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||
|
||||
success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead);
|
||||
|
||||
/*if (success)
|
||||
{
|
||||
if (subsToLeaveBehind == null || leavingSub == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one");
|
||||
|
||||
leavingSub = GetLeavingSub();
|
||||
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
}
|
||||
}*/
|
||||
|
||||
GameMain.GameSession.EndRound("");
|
||||
|
||||
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has an alive character)
|
||||
characterData.RemoveAll(cd => cd.HasSpawned);
|
||||
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.Character?.Info != null && !c.Character.IsDead)
|
||||
{
|
||||
c.CharacterInfo = c.Character.Info;
|
||||
characterData.Add(new CharacterCampaignData(c));
|
||||
}
|
||||
}
|
||||
|
||||
//remove all items that are in someone's inventory
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
c.Inventory?.DeleteAllItems();
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
bool atEndPosition = Submarine.MainSub.AtEndPosition;
|
||||
|
||||
/*if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (atEndPosition)
|
||||
{
|
||||
map.MoveToNextLocation();
|
||||
|
||||
//select a random location to make sure we've got some destination
|
||||
//to head towards even if the host/clients don't select anything
|
||||
map.SelectRandomLocation(true);
|
||||
}
|
||||
map.ProgressWorld();
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void SetDelegates();
|
||||
|
||||
public static MultiPlayerCampaign LoadNew(XElement element)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "multiplayercampaign"), null);
|
||||
campaign.Load(element);
|
||||
campaign.SetDelegates();
|
||||
|
||||
return campaign;
|
||||
}
|
||||
|
||||
public static string GetCharacterDataSavePath(string savePath)
|
||||
{
|
||||
return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
|
||||
}
|
||||
|
||||
public string GetCharacterDataSavePath()
|
||||
{
|
||||
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
{
|
||||
Money = element.GetAttributeInt("money", 0);
|
||||
InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false);
|
||||
CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
||||
if (CheatsEnabled)
|
||||
{
|
||||
DebugConsole.CheatsEnabled = true;
|
||||
#if USE_STEAM
|
||||
if (!SteamAchievementManager.CheatsEnabled)
|
||||
{
|
||||
SteamAchievementManager.CheatsEnabled = true;
|
||||
#if CLIENT
|
||||
new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the server. You will not receive Steam Achievements until you restart the game.");
|
||||
#else
|
||||
DebugConsole.NewMessage("Cheat commands have been enabled.", Color.Red);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "map":
|
||||
if (map == null)
|
||||
{
|
||||
//map not created yet, loading this campaign for the first time
|
||||
map = Map.LoadNew(subElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
//map already created, update it
|
||||
//if we're not downloading the initial save file (LastSaveID > 0),
|
||||
//show notifications about location type changes
|
||||
map.Load(subElement, LastSaveID > 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
|
||||
if (characterDataDoc?.Root == null) return;
|
||||
foreach (XElement subElement in characterDataDoc.Root.Elements())
|
||||
{
|
||||
characterData.Add(new CharacterCampaignData(subElement));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameSession
|
||||
{
|
||||
public enum InfoFrameTab { Crew, Mission, MyCharacter, ManagePlayers };
|
||||
|
||||
public readonly EventManager EventManager;
|
||||
|
||||
public GameMode GameMode;
|
||||
|
||||
//two locations used as the start and end in the MP mode
|
||||
private Location[] dummyLocations;
|
||||
public CrewManager CrewManager;
|
||||
|
||||
public double RoundStartTime;
|
||||
|
||||
public Mission Mission { get; private set; }
|
||||
|
||||
public Character.TeamType? WinningTeam;
|
||||
|
||||
public Level Level { get; private set; }
|
||||
|
||||
public Map Map
|
||||
{
|
||||
get
|
||||
{
|
||||
return (GameMode as CampaignMode)?.Map;
|
||||
}
|
||||
}
|
||||
|
||||
public Location StartLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map != null) return Map.CurrentLocation;
|
||||
|
||||
if (dummyLocations == null)
|
||||
{
|
||||
CreateDummyLocations();
|
||||
}
|
||||
|
||||
return dummyLocations[0];
|
||||
}
|
||||
}
|
||||
|
||||
public Location EndLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map != null) return Map.SelectedLocation;
|
||||
|
||||
if (dummyLocations == null)
|
||||
{
|
||||
CreateDummyLocations();
|
||||
}
|
||||
|
||||
return dummyLocations[1];
|
||||
}
|
||||
}
|
||||
|
||||
public Submarine Submarine { get; set; }
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public GameSession(Submarine submarine, string savePath, GameModePreset gameModePreset, MissionType missionType = MissionType.None)
|
||||
: this(submarine, savePath)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = gameModePreset.Instantiate(missionType);
|
||||
}
|
||||
|
||||
public GameSession(Submarine submarine, string savePath, GameModePreset gameModePreset, MissionPrefab missionPrefab)
|
||||
: this(submarine, savePath)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = gameModePreset.Instantiate(missionPrefab);
|
||||
}
|
||||
|
||||
private GameSession(Submarine submarine, string savePath)
|
||||
{
|
||||
InitProjSpecific();
|
||||
Submarine.MainSub = submarine;
|
||||
this.Submarine = submarine;
|
||||
GameMain.GameSession = this;
|
||||
EventManager = new EventManager();
|
||||
this.SavePath = savePath;
|
||||
}
|
||||
|
||||
|
||||
public GameSession(Submarine selectedSub, string saveFile, XDocument doc)
|
||||
: this(selectedSub, saveFile)
|
||||
{
|
||||
Submarine.MainSub = Submarine;
|
||||
|
||||
GameMain.GameSession = this;
|
||||
selectedSub.Name = doc.Root.GetAttributeString("submarine", selectedSub.Name);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
#if CLIENT
|
||||
case "gamemode": //legacy support
|
||||
case "singleplayercampaign":
|
||||
CrewManager = new CrewManager(true);
|
||||
GameMode = SinglePlayerCampaign.Load(subElement);
|
||||
break;
|
||||
#endif
|
||||
case "multiplayercampaign":
|
||||
CrewManager = new CrewManager(false);
|
||||
GameMode = MultiPlayerCampaign.LoadNew(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDummyLocations()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadPrevious()
|
||||
{
|
||||
Submarine.Unload();
|
||||
SaveUtil.LoadGame(SavePath);
|
||||
}
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null)
|
||||
{
|
||||
Level randomLevel = Level.CreateRandom(levelSeed, difficulty);
|
||||
|
||||
StartRound(randomLevel, true);
|
||||
}
|
||||
|
||||
public void StartRound(Level level, bool reloadSub = true, bool mirrorLevel = false)
|
||||
{
|
||||
//make sure no status effects have been carried on from the next round
|
||||
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
|
||||
StatusEffect.StopAll();
|
||||
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
|
||||
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
|
||||
#endif
|
||||
this.Level = level;
|
||||
|
||||
if (Submarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
|
||||
Submarine.MainSub = Submarine;
|
||||
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1)
|
||||
{
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
{
|
||||
Submarine.MainSubs[1] = new Submarine(Submarine.MainSub.FilePath, Submarine.MainSub.MD5Hash.Hash, true);
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
else if (reloadSub)
|
||||
{
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Submarine.IsFileCorrupted)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
level.Generate(mirrorLevel);
|
||||
if (level.StartOutpost != null)
|
||||
{
|
||||
//start by placing the sub below the outpost
|
||||
Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders();
|
||||
Rectangle subBorders = Submarine.GetDockedBorders();
|
||||
|
||||
Vector2 startOutpostSize = Vector2.Zero;
|
||||
if (Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
startOutpostSize = Level.Loaded.StartOutpost.Borders.Size.ToVector2();
|
||||
}
|
||||
Submarine.SetPosition(
|
||||
Level.Loaded.StartOutpost.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
|
||||
float closestDistance = 0.0f;
|
||||
DockingPort myPort = null, outPostPort = null;
|
||||
foreach (DockingPort port in DockingPort.List)
|
||||
{
|
||||
if (port.IsHorizontal || port.Docked) { continue; }
|
||||
if (port.Item.Submarine == level.StartOutpost)
|
||||
{
|
||||
outPostPort = port;
|
||||
continue;
|
||||
}
|
||||
if (port.Item.Submarine != Submarine) { continue; }
|
||||
|
||||
//the submarine port has to be at the top of the sub
|
||||
if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition);
|
||||
if (myPort == null || dist < closestDistance || (port.MainDockingPort && !myPort.MainDockingPort))
|
||||
{
|
||||
myPort = port;
|
||||
closestDistance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (myPort != null && outPostPort != null)
|
||||
{
|
||||
Vector2 portDiff = myPort.Item.WorldPosition - Submarine.WorldPosition;
|
||||
Submarine.SetPosition((outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance);
|
||||
myPort.Dock(outPostPort);
|
||||
myPort.Lock(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine.SetPosition(Submarine.FindSpawnPos(level.StartPosition));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.IsOutpost)
|
||||
{
|
||||
sub.DisableObstructedWayPoints();
|
||||
}
|
||||
}
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
if (GameMode.Mission != null) { Mission = GameMode.Mission; }
|
||||
if (GameMode != null) { GameMode.Start(); }
|
||||
if (GameMode.Mission != null)
|
||||
{
|
||||
int prevEntityCount = Entity.GetEntityList().Count;
|
||||
Mission.Start(Level.Loaded);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntityList().Count != prevEntityCount)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
"Entity count has changed after starting a mission as a client. " +
|
||||
"The clients should not instantiate entities themselves when starting the mission," +
|
||||
" but instead the server should inform the client of the spawned entities using Mission.ServerWriteInitial.");
|
||||
}
|
||||
}
|
||||
|
||||
EventManager.StartRound(level);
|
||||
SteamAchievementManager.OnStartRound();
|
||||
|
||||
if (GameMode != null)
|
||||
{
|
||||
GameMode.ShowStartMessage();
|
||||
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
//only autoplace items here in single player
|
||||
//the server does this after loading the respawn shuttle
|
||||
AutoItemPlacer.PlaceIfNeeded(GameMode);
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
mpCampaign.CargoManager.CreateItems();
|
||||
}
|
||||
}
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Name);
|
||||
GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level.Seed));
|
||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start,
|
||||
GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
|
||||
#if CLIENT
|
||||
if (GameMode is SinglePlayerCampaign) { SteamAchievementManager.OnBiomeDiscovered(level.Biome); }
|
||||
RoundSummary = new RoundSummary(this);
|
||||
|
||||
GameMain.GameScreen.ColorFade(Color.Black, Color.TransparentBlack, 5.0f);
|
||||
|
||||
if (!(GameMode is TutorialMode))
|
||||
{
|
||||
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
||||
GUI.AddMessage(level.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, level.Difficulty / 100.0f), 5.0f, playSound: false);
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false);
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), (Mission == null ? TextManager.Get("None") : Mission.Name)), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
#endif
|
||||
|
||||
RoundStartTime = Timing.TotalTime;
|
||||
GameMain.ResetFrameTime();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
EventManager.Update(deltaTime);
|
||||
GameMode?.Update(deltaTime);
|
||||
Mission?.Update(deltaTime);
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
public void EndRound(string endMessage)
|
||||
{
|
||||
if (Mission != null) Mission.End();
|
||||
GameAnalyticsManager.AddProgressionEvent(
|
||||
(Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail,
|
||||
GameMode.Preset.Identifier,
|
||||
(Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
|
||||
#if CLIENT
|
||||
if (RoundSummary != null)
|
||||
{
|
||||
GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(endMessage);
|
||||
GUIMessageBox.MessageBoxes.Add(summaryFrame);
|
||||
var okButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), summaryFrame.Children.First().Children.First().FindChild("buttonarea").RectTransform),
|
||||
TextManager.Get("OK"))
|
||||
{
|
||||
OnClicked = (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
EventManager.EndRound();
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
Mission = null;
|
||||
|
||||
StatusEffect.StopAll();
|
||||
}
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
CrewManager.KillCharacter(character);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ReviveCharacter(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
CrewManager.ReviveCharacter(character);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsCompatibleWithSelectedContentPackages(IList<string> contentPackagePaths, out string errorMsg)
|
||||
{
|
||||
errorMsg = "";
|
||||
//no known content packages, must be an older save file
|
||||
if (!contentPackagePaths.Any()) { return true; }
|
||||
|
||||
List<string> missingPackages = new List<string>();
|
||||
foreach (string packagePath in contentPackagePaths)
|
||||
{
|
||||
if (!GameMain.Config.SelectedContentPackages.Any(cp => cp.Path == packagePath))
|
||||
{
|
||||
missingPackages.Add(packagePath);
|
||||
}
|
||||
}
|
||||
List<string> excessPackages = new List<string>();
|
||||
foreach (ContentPackage cp in GameMain.Config.SelectedContentPackages)
|
||||
{
|
||||
if (!cp.HasMultiplayerIncompatibleContent) { continue; }
|
||||
if (!contentPackagePaths.Any(p => p == cp.Path))
|
||||
{
|
||||
excessPackages.Add(cp.Name);
|
||||
}
|
||||
}
|
||||
|
||||
bool orderMismatch = false;
|
||||
if (missingPackages.Count == 0 && missingPackages.Count == 0)
|
||||
{
|
||||
var selectedPackages = GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
|
||||
for (int i = 0; i < contentPackagePaths.Count && i < selectedPackages.Count; i++)
|
||||
{
|
||||
if (contentPackagePaths[i] != selectedPackages[i].Path)
|
||||
{
|
||||
orderMismatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!orderMismatch && missingPackages.Count == 0 && excessPackages.Count == 0) { return true; }
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
errorMsg = TextManager.GetWithVariable("campaignmode.missingcontentpackage", "[missingcontentpackage]", missingPackages[0]);
|
||||
}
|
||||
else if (missingPackages.Count > 1)
|
||||
{
|
||||
errorMsg = TextManager.GetWithVariable("campaignmode.missingcontentpackages", "[missingcontentpackages]", string.Join(", ", missingPackages));
|
||||
}
|
||||
if (excessPackages.Count == 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMsg)) { errorMsg += "\n"; }
|
||||
errorMsg += TextManager.GetWithVariable("campaignmode.incompatiblecontentpackage", "[incompatiblecontentpackage]", excessPackages[0]);
|
||||
}
|
||||
else if (excessPackages.Count > 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMsg)) { errorMsg += "\n"; }
|
||||
errorMsg += TextManager.GetWithVariable("campaignmode.incompatiblecontentpackages", "[incompatiblecontentpackages]", string.Join(", ", excessPackages));
|
||||
}
|
||||
if (orderMismatch)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(errorMsg)) { errorMsg += "\n"; }
|
||||
errorMsg += TextManager.GetWithVariable("campaignmode.contentpackageordermismatch", "[loadorder]", string.Join(", ", contentPackagePaths));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Save(string filePath)
|
||||
{
|
||||
if (!(GameMode is CampaignMode))
|
||||
{
|
||||
throw new NotSupportedException("GameSessions can only be saved when playing in a campaign mode.");
|
||||
}
|
||||
|
||||
XDocument doc = new XDocument(new XElement("Gamesession"));
|
||||
|
||||
doc.Root.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal));
|
||||
doc.Root.Add(new XAttribute("submarine", Submarine == null ? "" : Submarine.Name));
|
||||
doc.Root.Add(new XAttribute("mapseed", Map.Seed));
|
||||
doc.Root.Add(new XAttribute("selectedcontentpackages",
|
||||
string.Join("|", GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).Select(cp => cp.Path))));
|
||||
|
||||
((CampaignMode)GameMode).Save(doc.Root);
|
||||
|
||||
try
|
||||
{
|
||||
doc.Save(filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving gamesession to \"" + filePath + "\" failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(XElement saveElement)
|
||||
{
|
||||
foreach (XElement subElement in saveElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
#if CLIENT
|
||||
case "gamemode": //legacy support
|
||||
case "singleplayercampaign":
|
||||
GameMode = SinglePlayerCampaign.Load(subElement);
|
||||
break;
|
||||
#endif
|
||||
case "multiplayercampaign":
|
||||
if (!(GameMode is MultiPlayerCampaign mpCampaign))
|
||||
{
|
||||
DebugConsole.ThrowError("Error while loading a save file: the save file is for a multiplayer campaign but the current gamemode is " + GameMode.GetType().ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
mpCampaign.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HireManager
|
||||
{
|
||||
private List<CharacterInfo> availableCharacters;
|
||||
public IEnumerable<CharacterInfo> AvailableCharacters
|
||||
{
|
||||
get { return availableCharacters; }
|
||||
}
|
||||
|
||||
public const int MaxAvailableCharacters = 10;
|
||||
|
||||
public HireManager()
|
||||
{
|
||||
availableCharacters = new List<CharacterInfo>();
|
||||
}
|
||||
|
||||
public void RemoveCharacter(CharacterInfo character)
|
||||
{
|
||||
availableCharacters.Remove(character);
|
||||
}
|
||||
|
||||
public void GenerateCharacters(Location location, int amount)
|
||||
{
|
||||
availableCharacters.ForEach(c => c.Remove());
|
||||
availableCharacters.Clear();
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
JobPrefab job = location.Type.GetRandomHireable();
|
||||
if (job == null) { return; }
|
||||
|
||||
var variant = Rand.Range(0, job.Variants, Rand.RandSync.Server);
|
||||
availableCharacters.Add(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, variant: variant));
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
availableCharacters.ForEach(c => c.Remove());
|
||||
availableCharacters.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user