(965c31410a) Unstable v0.10.4.0
This commit is contained in:
+548
-74
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -10,36 +11,57 @@ namespace Barotrauma
|
||||
{
|
||||
partial class MultiPlayerCampaign : CampaignMode
|
||||
{
|
||||
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
|
||||
private readonly List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
|
||||
|
||||
private bool forceMapUI;
|
||||
public bool ForceMapUI
|
||||
{
|
||||
get { return forceMapUI; }
|
||||
set
|
||||
{
|
||||
if (forceMapUI == value) { return; }
|
||||
forceMapUI = value;
|
||||
LastUpdateID++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool GameOver { get; private set; }
|
||||
|
||||
public override bool Paused
|
||||
{
|
||||
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
|
||||
}
|
||||
|
||||
public static void StartNewCampaign(string savePath, string subPath, string seed)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(savePath)) return;
|
||||
|
||||
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath, ""), savePath,
|
||||
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
|
||||
var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
|
||||
campaign.GenerateMap(seed);
|
||||
campaign.SetDelegates();
|
||||
if (string.IsNullOrWhiteSpace(savePath)) { return; }
|
||||
|
||||
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, seed);
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
GameMain.GameSession.Map.SelectRandomLocation(true);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
campaign.LastSaveID++;
|
||||
|
||||
DebugConsole.NewMessage("Campaign started!", Color.Cyan);
|
||||
DebugConsole.NewMessage(GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan);
|
||||
DebugConsole.NewMessage("Current location: " + GameMain.GameSession.Map.CurrentLocation.Name, Color.Cyan);
|
||||
((MultiPlayerCampaign)GameMain.GameSession.GameMode).LoadInitialLevel();
|
||||
}
|
||||
|
||||
public static void LoadCampaign(string selectedSave)
|
||||
{
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
SaveUtil.LoadGame(selectedSave);
|
||||
((MultiPlayerCampaign)GameMain.GameSession.GameMode).LastSaveID++;
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
GameMain.GameSession.Map.SelectRandomLocation(true);
|
||||
|
||||
DebugConsole.NewMessage("Campaign loaded!", Color.Cyan);
|
||||
DebugConsole.NewMessage(GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan);
|
||||
DebugConsole.NewMessage(
|
||||
GameMain.GameSession.Map.SelectedLocation == null ?
|
||||
GameMain.GameSession.Map.CurrentLocation.Name :
|
||||
GameMain.GameSession.Map.CurrentLocation.Name + " -> " + GameMain.GameSession.Map.SelectedLocation.Name, Color.Cyan);
|
||||
}
|
||||
|
||||
protected override void LoadInitialLevel()
|
||||
{
|
||||
NextLevel = map.SelectedConnection?.LevelData ?? map.CurrentLocation.LevelData;
|
||||
MirrorLevel = false;
|
||||
GameMain.Server.StartGame();
|
||||
}
|
||||
|
||||
public static void StartCampaignSetup()
|
||||
@@ -57,7 +79,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer).ToArray();
|
||||
var saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false).ToArray();
|
||||
if (saveFiles.Length == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("No save files found.");
|
||||
@@ -86,53 +108,206 @@ namespace Barotrauma
|
||||
});
|
||||
}
|
||||
|
||||
public bool AllowedToEndRound(Character interactor)
|
||||
public override void Start()
|
||||
{
|
||||
if (interactor == null || Level.Loaded?.StartOutpost == null || Level.Loaded?.EndOutpost == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (interactor.Submarine == Level.Loaded.StartOutpost &&
|
||||
interactor.CanInteractWith(startWatchman))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (interactor.Submarine == Level.Loaded.EndOutpost &&
|
||||
interactor.CanInteractWith(endWatchman))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
base.Start();
|
||||
lastUpdateID++;
|
||||
}
|
||||
|
||||
protected override void WatchmanInteract(Character watchman, Character interactor)
|
||||
{
|
||||
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
|
||||
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition))
|
||||
{
|
||||
CreateDialog(new List<Character> { watchman }, "WatchmanInteractNoLeavingSub", 5.0f);
|
||||
return;
|
||||
}
|
||||
private static bool IsOwner(Client client) => client != null && client.Connection == GameMain.Server.OwnerConnection;
|
||||
|
||||
bool hasPermissions = true;
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor);
|
||||
hasPermissions = client != null;
|
||||
CreateDialog(new List<Character> { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f);
|
||||
}
|
||||
/// <summary>
|
||||
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
|
||||
/// </summary>
|
||||
public bool AllowedToEndRound(Client client)
|
||||
{
|
||||
//allow ending the round if the client has permissions, is the owner, the only client in the server,
|
||||
//or if no-one has permissions
|
||||
return
|
||||
client.HasPermission(ClientPermissions.ManageRound) ||
|
||||
client.HasPermission(ClientPermissions.ManageCampaign) ||
|
||||
GameMain.Server.ConnectedClients.Count == 1 ||
|
||||
IsOwner(client) ||
|
||||
GameMain.Server.ConnectedClients.None(c =>
|
||||
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
|
||||
}
|
||||
|
||||
partial void SetDelegates()
|
||||
/// <summary>
|
||||
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
|
||||
/// </summary>
|
||||
public bool AllowedToManageCampaign(Client client)
|
||||
{
|
||||
//allow ending the round if the client has permissions, is the owner, or the only client in the server,
|
||||
//or if no-one has management permissions
|
||||
return
|
||||
client.HasPermission(ClientPermissions.ManageCampaign) ||
|
||||
GameMain.Server.ConnectedClients.Count == 1 ||
|
||||
IsOwner(client) ||
|
||||
GameMain.Server.ConnectedClients.None(c =>
|
||||
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageCampaign)));
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults)
|
||||
{
|
||||
lastUpdateID++;
|
||||
|
||||
switch (transitionType)
|
||||
{
|
||||
case TransitionType.None:
|
||||
throw new InvalidOperationException("Level transition failed (no transitions available).");
|
||||
case TransitionType.ReturnToPreviousLocation:
|
||||
//deselect destination on map
|
||||
map.SelectLocation(-1);
|
||||
break;
|
||||
case TransitionType.ProgressToNextLocation:
|
||||
Map.MoveToNextLocation();
|
||||
Map.ProgressWorld();
|
||||
break;
|
||||
case TransitionType.End:
|
||||
EndCampaign();
|
||||
IsFirstRound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||
|
||||
GameMain.GameSession.EndRound("", traitorResults, transitionType);
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
if (success)
|
||||
{
|
||||
List<CharacterCampaignData> prevCharacterData = new List<CharacterCampaignData>(characterData);
|
||||
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character)
|
||||
characterData.RemoveAll(cd => cd.HasSpawned);
|
||||
|
||||
//refresh the character data of clients who are still in the server
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.Character?.Info == null) { continue; }
|
||||
if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { continue; }
|
||||
c.CharacterInfo = c.Character.Info;
|
||||
characterData.RemoveAll(cd => cd.MatchesClient(c));
|
||||
characterData.Add(new CharacterCampaignData(c));
|
||||
}
|
||||
|
||||
//refresh the character data of clients who aren't in the server anymore
|
||||
foreach (CharacterCampaignData data in prevCharacterData)
|
||||
{
|
||||
if (data.HasSpawned && !characterData.Any(cd => cd.IsDuplicate(data)))
|
||||
{
|
||||
var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo);
|
||||
if (character != null && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))
|
||||
{
|
||||
data.Refresh(character);
|
||||
characterData.Add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
characterData.ForEach(cd => cd.HasSpawned = false);
|
||||
|
||||
//remove all items that are in someone's inventory
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Inventory == null) { continue; }
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost && c.Submarine != Level.Loaded.StartOutpost)
|
||||
{
|
||||
Map.CurrentLocation.RegisterTakenItems(c.Inventory.Items.Where(it => it != null && it.SpawnedInOutpost && it.OriginalModuleIndex > 0).Distinct());
|
||||
}
|
||||
|
||||
if (c.Info != null && c.IsBot)
|
||||
{
|
||||
if (c.IsDead && c.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { CrewManager.RemoveCharacterInfo(c.Info); }
|
||||
c.Info.HealthData = new XElement("health");
|
||||
c.CharacterHealth.Save(c.Info.HealthData);
|
||||
c.Info.InventoryData = new XElement("inventory");
|
||||
c.SaveInventory(c.Inventory, c.Info.InventoryData);
|
||||
}
|
||||
|
||||
c.Inventory.DeleteAllItems();
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}
|
||||
NextLevel = newLevel;
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.EndGame(TransitionType.None);
|
||||
LoadCampaign(GameMain.GameSession.SavePath);
|
||||
LastSaveID++;
|
||||
LastUpdateID++;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
GameMain.Server.EndGame(transitionType);
|
||||
|
||||
ForceMapUI = false;
|
||||
|
||||
NextLevel = newLevel;
|
||||
MirrorLevel = mirror;
|
||||
if (PendingSubmarineSwitch != null)
|
||||
{
|
||||
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
|
||||
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
|
||||
PendingSubmarineSwitch = null;
|
||||
|
||||
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
|
||||
{
|
||||
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
|
||||
{
|
||||
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
|
||||
}
|
||||
}
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
LastSaveID++;
|
||||
}
|
||||
|
||||
//give clients time to play the end cinematic before starting the next round
|
||||
if (transitionType == TransitionType.End)
|
||||
{
|
||||
yield return new WaitForSeconds(EndCinematicDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new WaitForSeconds(EndTransitionDuration * 0.5f);
|
||||
}
|
||||
|
||||
GameMain.Server.StartGame();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CargoManager.OnItemsChanged += () => { LastUpdateID++; };
|
||||
CargoManager.OnItemsInBuyCrateChanged += () => { LastUpdateID++; };
|
||||
CargoManager.OnPurchasedItemsChanged += () => { LastUpdateID++; };
|
||||
CargoManager.OnSoldItemsChanged += () => { LastUpdateID++; };
|
||||
UpgradeManager.OnUpgradesChanged += () => { LastUpdateID++; };
|
||||
Map.OnLocationSelected += (loc, connection) => { LastUpdateID++; };
|
||||
Map.OnMissionSelected += (loc, mission) => { LastUpdateID++; };
|
||||
}
|
||||
//increment save ID so clients know they're lacking the most up-to-date save file
|
||||
LastSaveID++;
|
||||
}
|
||||
|
||||
public void DiscardClientCharacterData(Client client)
|
||||
@@ -144,14 +319,22 @@ namespace Barotrauma
|
||||
{
|
||||
return characterData.Find(cd => cd.MatchesClient(client));
|
||||
}
|
||||
|
||||
|
||||
public CharacterCampaignData SetClientCharacterData(Client client)
|
||||
{
|
||||
characterData.RemoveAll(cd => cd.MatchesClient(client));
|
||||
var data = new CharacterCampaignData(client);
|
||||
characterData.Add(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void AssignClientCharacterInfos(IEnumerable<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
if (client.SpectateOnly && GameMain.Server.ServerSettings.AllowSpectating) { continue; }
|
||||
var matchingData = GetClientCharacterData(client);
|
||||
if (matchingData != null) client.CharacterInfo = matchingData.CharacterInfo;
|
||||
if (matchingData != null) { client.CharacterInfo = matchingData.CharacterInfo; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,10 +349,49 @@ namespace Barotrauma
|
||||
return assignedJobs;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (CoroutineManager.IsCoroutineRunning("LevelTransition")) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
if (Level.Loaded != null)
|
||||
{
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
var transitionType = GetAvailableTransition(out _, out Submarine leavingSub);
|
||||
if (transitionType == TransitionType.End)
|
||||
{
|
||||
LoadNewLevel();
|
||||
}
|
||||
else if (transitionType == TransitionType.ProgressToNextLocation && Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
|
||||
{
|
||||
LoadNewLevel();
|
||||
}
|
||||
else if (transitionType == TransitionType.ReturnToPreviousLocation && Level.Loaded.StartOutpost != null && Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
|
||||
{
|
||||
LoadNewLevel();
|
||||
}
|
||||
}
|
||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
KeepCharactersCloseToOutpost(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void End(TransitionType transitionType = TransitionType.None)
|
||||
{
|
||||
GameOver = !GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||
base.End(transitionType);
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
|
||||
|
||||
Reputation reputation = Map?.CurrentLocation?.Reputation;
|
||||
|
||||
msg.Write(IsFirstRound);
|
||||
msg.Write(CampaignID);
|
||||
msg.Write(lastUpdateID);
|
||||
msg.Write(lastSaveID);
|
||||
@@ -177,15 +399,53 @@ namespace Barotrauma
|
||||
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
|
||||
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
|
||||
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
|
||||
msg.Write(reputation != null);
|
||||
if (reputation != null) { msg.Write(reputation.Value); }
|
||||
|
||||
msg.Write(isRunning && startWatchman != null ? startWatchman.ID : (UInt16)0);
|
||||
msg.Write(isRunning && endWatchman != null ? endWatchman.ID : (UInt16)0);
|
||||
// hopefully we'll never have more than 128 factions
|
||||
msg.Write((byte)Factions.Count);
|
||||
foreach (Faction faction in Factions)
|
||||
{
|
||||
msg.Write(faction.Prefab.Identifier);
|
||||
msg.Write(faction.Reputation.Value);
|
||||
}
|
||||
|
||||
msg.Write(ForceMapUI);
|
||||
|
||||
msg.Write(Money);
|
||||
msg.Write(PurchasedHullRepairs);
|
||||
msg.Write(PurchasedItemRepairs);
|
||||
msg.Write(PurchasedLostShuttles);
|
||||
|
||||
if (map.CurrentLocation != null)
|
||||
{
|
||||
msg.Write((byte)map.CurrentLocation?.AvailableMissions.Count());
|
||||
foreach (Mission mission in map.CurrentLocation.AvailableMissions)
|
||||
{
|
||||
msg.Write(mission.Prefab.Identifier);
|
||||
Location missionDestination = mission.Locations[0] == map.CurrentLocation ? mission.Locations[1] : mission.Locations[0];
|
||||
LocationConnection connection = map.CurrentLocation.Connections.Find(c => c.OtherLocation(map.CurrentLocation) == missionDestination);
|
||||
msg.Write((byte)map.CurrentLocation.Connections.IndexOf(connection));
|
||||
}
|
||||
|
||||
// Store balance
|
||||
msg.Write(true);
|
||||
msg.Write((UInt16)map.CurrentLocation.StoreCurrentBalance);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
// Store balance
|
||||
msg.Write(false);
|
||||
}
|
||||
|
||||
msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count);
|
||||
foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate)
|
||||
{
|
||||
msg.Write(pi.ItemPrefab.Identifier);
|
||||
msg.WriteRangedInteger(pi.Quantity, 0, 100);
|
||||
}
|
||||
|
||||
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
|
||||
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
|
||||
{
|
||||
@@ -193,6 +453,23 @@ namespace Barotrauma
|
||||
msg.WriteRangedInteger(pi.Quantity, 0, 100);
|
||||
}
|
||||
|
||||
msg.Write((UInt16)CargoManager.SoldItems.Count);
|
||||
foreach (SoldItem si in CargoManager.SoldItems)
|
||||
{
|
||||
msg.Write(si.ItemPrefab.Identifier);
|
||||
msg.Write((UInt16)si.ID);
|
||||
msg.Write(si.Removed);
|
||||
msg.Write(si.SellerID);
|
||||
}
|
||||
|
||||
msg.Write((ushort)UpgradeManager.PendingUpgrades.Count);
|
||||
foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades)
|
||||
{
|
||||
msg.Write(prefab.Identifier);
|
||||
msg.Write(category.Identifier);
|
||||
msg.Write((byte)level);
|
||||
}
|
||||
|
||||
var characterData = GetClientCharacterData(c);
|
||||
if (characterData?.CharacterInfo == null)
|
||||
{
|
||||
@@ -207,13 +484,23 @@ namespace Barotrauma
|
||||
|
||||
public void ServerRead(IReadMessage msg, Client sender)
|
||||
{
|
||||
UInt16 currentLocIndex = msg.ReadUInt16();
|
||||
UInt16 selectedLocIndex = msg.ReadUInt16();
|
||||
byte selectedMissionIndex = msg.ReadByte();
|
||||
bool purchasedHullRepairs = msg.ReadBoolean();
|
||||
bool purchasedItemRepairs = msg.ReadBoolean();
|
||||
bool purchasedLostShuttles = msg.ReadBoolean();
|
||||
UInt16 purchasedItemCount = msg.ReadUInt16();
|
||||
|
||||
UInt16 buyCrateItemCount = msg.ReadUInt16();
|
||||
List<PurchasedItem> buyCrateItems = new List<PurchasedItem>();
|
||||
for (int i = 0; i < buyCrateItemCount; i++)
|
||||
{
|
||||
string itemPrefabIdentifier = msg.ReadString();
|
||||
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
|
||||
buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
|
||||
}
|
||||
|
||||
UInt16 purchasedItemCount = msg.ReadUInt16();
|
||||
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
|
||||
for (int i = 0; i < purchasedItemCount; i++)
|
||||
{
|
||||
@@ -222,36 +509,68 @@ namespace Barotrauma
|
||||
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
|
||||
}
|
||||
|
||||
if (!sender.HasPermission(ClientPermissions.ManageCampaign))
|
||||
UInt16 soldItemCount = msg.ReadUInt16();
|
||||
List<SoldItem> soldItems = new List<SoldItem>();
|
||||
for (int i = 0; i < soldItemCount; i++)
|
||||
{
|
||||
string itemPrefabIdentifier = msg.ReadString();
|
||||
UInt16 id = msg.ReadUInt16();
|
||||
bool removed = msg.ReadBoolean();
|
||||
byte sellerId = msg.ReadByte();
|
||||
soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId));
|
||||
}
|
||||
|
||||
ushort purchasedUpgradeCount = msg.ReadUInt16();
|
||||
List<PurchasedUpgrade> purchasedUpgrades = new List<PurchasedUpgrade>();
|
||||
for (int i = 0; i < purchasedUpgradeCount; i++)
|
||||
{
|
||||
string upgradeIdentifier = msg.ReadString();
|
||||
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
|
||||
|
||||
string categoryIdentifier = msg.ReadString();
|
||||
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
|
||||
|
||||
int upgradeLevel = msg.ReadByte();
|
||||
|
||||
if (category == null || prefab == null) { continue; }
|
||||
purchasedUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
|
||||
}
|
||||
|
||||
if (!AllowedToManageCampaign(sender))
|
||||
{
|
||||
DebugConsole.ThrowError("Client \"" + sender.Name + "\" does not have a permission to manage the campaign");
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = Map.CurrentLocation;
|
||||
int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost;
|
||||
int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost;
|
||||
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost;
|
||||
|
||||
if (purchasedHullRepairs != this.PurchasedHullRepairs)
|
||||
{
|
||||
if (purchasedHullRepairs && Money >= HullRepairCost)
|
||||
if (purchasedHullRepairs && Money >= hullRepairCost)
|
||||
{
|
||||
this.PurchasedHullRepairs = true;
|
||||
Money -= HullRepairCost;
|
||||
Money -= hullRepairCost;
|
||||
}
|
||||
else if (!purchasedHullRepairs)
|
||||
{
|
||||
this.PurchasedHullRepairs = false;
|
||||
Money += HullRepairCost;
|
||||
Money += hullRepairCost;
|
||||
}
|
||||
}
|
||||
if (purchasedItemRepairs != this.PurchasedItemRepairs)
|
||||
{
|
||||
if (purchasedItemRepairs && Money >= ItemRepairCost)
|
||||
if (purchasedItemRepairs && Money >= itemRepairCost)
|
||||
{
|
||||
this.PurchasedItemRepairs = true;
|
||||
Money -= ItemRepairCost;
|
||||
Money -= itemRepairCost;
|
||||
}
|
||||
else if (!purchasedItemRepairs)
|
||||
{
|
||||
this.PurchasedItemRepairs = false;
|
||||
Money += ItemRepairCost;
|
||||
Money += itemRepairCost;
|
||||
}
|
||||
}
|
||||
if (purchasedLostShuttles != this.PurchasedLostShuttles)
|
||||
@@ -261,41 +580,196 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox);
|
||||
}
|
||||
else if (purchasedLostShuttles && Money >= ShuttleReplaceCost)
|
||||
else if (purchasedLostShuttles && Money >= shuttleRetrieveCost)
|
||||
{
|
||||
this.PurchasedLostShuttles = true;
|
||||
Money -= ShuttleReplaceCost;
|
||||
Money -= shuttleRetrieveCost;
|
||||
}
|
||||
else if (!purchasedItemRepairs)
|
||||
{
|
||||
this.PurchasedLostShuttles = false;
|
||||
Money += ShuttleReplaceCost;
|
||||
Money += shuttleRetrieveCost;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (currentLocIndex < Map.Locations.Count)
|
||||
{
|
||||
Map.SetLocation(currentLocIndex);
|
||||
}
|
||||
#endif
|
||||
|
||||
Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
|
||||
if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); }
|
||||
if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndex); }
|
||||
|
||||
List<PurchasedItem> currentItems = new List<PurchasedItem>(CargoManager.PurchasedItems);
|
||||
foreach (PurchasedItem pi in currentItems)
|
||||
List<PurchasedItem> currentBuyCrateItems = new List<PurchasedItem>(CargoManager.ItemsInBuyCrate);
|
||||
currentBuyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, -i.Quantity));
|
||||
buyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, i.Quantity));
|
||||
|
||||
CargoManager.SellBackPurchasedItems(new List<PurchasedItem>(CargoManager.PurchasedItems));
|
||||
CargoManager.PurchaseItems(purchasedItems, false);
|
||||
|
||||
// for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all
|
||||
// sold items that are removed so they should be discarded on the next message
|
||||
CargoManager.BuyBackSoldItems(new List<SoldItem>(CargoManager.SoldItems));
|
||||
CargoManager.SellItems(soldItems);
|
||||
|
||||
foreach (var (prefab, category, _) in purchasedUpgrades)
|
||||
{
|
||||
CargoManager.SellItem(pi, pi.Quantity);
|
||||
UpgradeManager.PurchaseUpgrade(prefab, category);
|
||||
|
||||
// unstable logging
|
||||
int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation);
|
||||
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
|
||||
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerReadCrew(IReadMessage msg, Client sender)
|
||||
{
|
||||
int[] pendingHires = null;
|
||||
|
||||
bool updatePending = msg.ReadBoolean();
|
||||
if (updatePending)
|
||||
{
|
||||
ushort pendingHireLength = msg.ReadUInt16();
|
||||
pendingHires = new int[pendingHireLength];
|
||||
for (int i = 0; i < pendingHireLength; i++)
|
||||
{
|
||||
pendingHires[i] = msg.ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PurchasedItem pi in purchasedItems)
|
||||
bool validateHires = msg.ReadBoolean();
|
||||
bool fireCharacter = msg.ReadBoolean();
|
||||
|
||||
int firedIdentifier = -1;
|
||||
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
|
||||
|
||||
Location location = map?.CurrentLocation;
|
||||
|
||||
CharacterInfo firedCharacter = null;
|
||||
|
||||
if (location != null && AllowedToManageCampaign(sender))
|
||||
{
|
||||
CargoManager.PurchaseItem(pi.ItemPrefab, pi.Quantity);
|
||||
if (fireCharacter)
|
||||
{
|
||||
firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
|
||||
if (firedCharacter != null && (firedCharacter.Character?.IsBot ?? true))
|
||||
{
|
||||
CrewManager.FireCharacter(firedCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to fire an invalid character ({firedIdentifier})");
|
||||
}
|
||||
}
|
||||
|
||||
if (location.HireManager != null)
|
||||
{
|
||||
if (validateHires)
|
||||
{
|
||||
foreach (CharacterInfo hireInfo in location.HireManager.PendingHires)
|
||||
{
|
||||
TryHireCharacter(location, hireInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (updatePending)
|
||||
{
|
||||
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();
|
||||
foreach (int identifier in pendingHires)
|
||||
{
|
||||
CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.GetIdentifier() == identifier);
|
||||
if (match == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to hire a character that doesn't exist ({identifier})");
|
||||
continue;
|
||||
}
|
||||
|
||||
pendingHireInfos.Add(match);
|
||||
}
|
||||
location.HireManager.PendingHires = pendingHireInfos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bounce back
|
||||
SendCrewState(validateHires, firedCharacter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the clients of the current bot situation like syncing pending and available hires
|
||||
/// available hires are also synced
|
||||
/// </summary>
|
||||
/// <param name="validateHires">When set to true notifies the clients that the hires have been validated.</param>
|
||||
/// <param name="firedCharacter">When not null will inform the clients that his character has been fired.</param>
|
||||
/// <remarks>
|
||||
/// It might be obsolete to sync available hires. I found that the available hires are always the same between
|
||||
/// the client and the server when there's only one person on the server but when a second person joins both of
|
||||
/// their available hires are different from the server.
|
||||
/// </remarks>
|
||||
public void SendCrewState(bool validateHires, CharacterInfo firedCharacter)
|
||||
{
|
||||
List<CharacterInfo> availableHires = new List<CharacterInfo>();
|
||||
List<CharacterInfo> pendingHires = new List<CharacterInfo>();
|
||||
|
||||
if (map.CurrentLocation != null && map.CurrentLocation.Type.HasHireableCharacters)
|
||||
{
|
||||
availableHires = map.CurrentLocation.GetHireableCharacters().ToList();
|
||||
pendingHires = map.CurrentLocation?.HireManager.PendingHires;
|
||||
}
|
||||
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.Write((byte)ServerPacketHeader.CREW);
|
||||
|
||||
msg.Write((ushort)availableHires.Count);
|
||||
foreach (CharacterInfo hire in availableHires)
|
||||
{
|
||||
hire.ServerWrite(msg);
|
||||
msg.Write(hire.Salary);
|
||||
}
|
||||
|
||||
msg.Write((ushort)pendingHires.Count);
|
||||
foreach (CharacterInfo pendingHire in pendingHires)
|
||||
{
|
||||
msg.Write(pendingHire.GetIdentifier());
|
||||
}
|
||||
|
||||
msg.Write(validateHires);
|
||||
|
||||
msg.Write(firedCharacter != null);
|
||||
if (firedCharacter != null) { msg.Write(firedCharacter.GetIdentifier()); }
|
||||
|
||||
GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save(XElement element)
|
||||
{
|
||||
element.Add(new XAttribute("campaignid", CampaignID));
|
||||
XElement modeElement = new XElement("MultiPlayerCampaign",
|
||||
new XAttribute("money", Money),
|
||||
new XAttribute("cheatsenabled", CheatsEnabled),
|
||||
new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned));
|
||||
new XAttribute("cheatsenabled", CheatsEnabled));
|
||||
CampaignMetadata?.Save(modeElement);
|
||||
Map.Save(modeElement);
|
||||
CargoManager?.SavePurchasedItems(modeElement);
|
||||
UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades);
|
||||
|
||||
// save bots
|
||||
CrewManager.SaveMultiplayer(modeElement);
|
||||
|
||||
// save available submarines
|
||||
XElement availableSubsElement = new XElement("AvailableSubs");
|
||||
for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++)
|
||||
{
|
||||
availableSubsElement.Add(new XElement("Sub", new XAttribute("name", GameMain.NetLobbyScreen.CampaignSubmarines[i].Name)));
|
||||
}
|
||||
modeElement.Add(availableSubsElement);
|
||||
|
||||
element.Add(modeElement);
|
||||
|
||||
//save character data to a separate file
|
||||
|
||||
Reference in New Issue
Block a user