(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -91,19 +91,19 @@ namespace Barotrauma
|
||||
FocusedCharacter = null;
|
||||
}
|
||||
var closestEntity = FindEntityByID(memInput[memInput.Count - 1].interact);
|
||||
if (closestEntity is Item)
|
||||
if (closestEntity is Item item)
|
||||
{
|
||||
if (CanInteractWith((Item)closestEntity))
|
||||
if (CanInteractWith(item))
|
||||
{
|
||||
focusedItem = (Item)closestEntity;
|
||||
focusedItem = item;
|
||||
FocusedCharacter = null;
|
||||
}
|
||||
}
|
||||
else if (closestEntity is Character)
|
||||
else if (closestEntity is Character character)
|
||||
{
|
||||
if (CanInteractWith((Character)closestEntity))
|
||||
if (CanInteractWith(character, maxDist: 250.0f))
|
||||
{
|
||||
FocusedCharacter = (Character)closestEntity;
|
||||
FocusedCharacter = character;
|
||||
focusedItem = null;
|
||||
}
|
||||
}
|
||||
@@ -207,6 +207,13 @@ namespace Barotrauma
|
||||
{
|
||||
LastNetworkUpdateID = networkUpdateID;
|
||||
}
|
||||
else if (NetIdUtils.Difference(networkUpdateID, LastNetworkUpdateID) > 500)
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.AddWarning($"Large disrepancy between a client character's network update ID server-side and client-side (client: {networkUpdateID}, server: {LastNetworkUpdateID}). Resetting the ID.");
|
||||
#endif
|
||||
LastNetworkUpdateID = networkUpdateID;
|
||||
}
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
|
||||
@@ -264,21 +271,21 @@ namespace Barotrauma
|
||||
switch ((NetEntityEvent.Type)extraData[0])
|
||||
{
|
||||
case NetEntityEvent.Type.InventoryState:
|
||||
msg.WriteRangedInteger(0, 0, 4);
|
||||
msg.WriteRangedInteger(0, 0, 5);
|
||||
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
|
||||
Inventory.ServerWrite(msg, c);
|
||||
break;
|
||||
case NetEntityEvent.Type.Control:
|
||||
msg.WriteRangedInteger(1, 0, 4);
|
||||
msg.WriteRangedInteger(1, 0, 5);
|
||||
Client owner = (Client)extraData[1];
|
||||
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.WriteRangedInteger(2, 0, 4);
|
||||
msg.WriteRangedInteger(2, 0, 5);
|
||||
WriteStatus(msg);
|
||||
break;
|
||||
case NetEntityEvent.Type.UpdateSkills:
|
||||
msg.WriteRangedInteger(3, 0, 4);
|
||||
msg.WriteRangedInteger(3, 0, 5);
|
||||
if (Info?.Job == null)
|
||||
{
|
||||
msg.Write((byte)0);
|
||||
@@ -297,11 +304,15 @@ namespace Barotrauma
|
||||
Limb attackLimb = extraData[1] as Limb;
|
||||
UInt16 targetEntityID = (UInt16)extraData[2];
|
||||
int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0;
|
||||
msg.WriteRangedInteger(4, 0, 4);
|
||||
msg.WriteRangedInteger(4, 0, 5);
|
||||
msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb)));
|
||||
msg.Write(targetEntityID);
|
||||
msg.Write((byte)targetLimbIndex);
|
||||
break;
|
||||
case NetEntityEvent.Type.AssignCampaignInteraction:
|
||||
msg.WriteRangedInteger(5, 0, 5);
|
||||
msg.Write((byte)CampaignInteractionType);
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
|
||||
break;
|
||||
@@ -507,6 +518,8 @@ namespace Barotrauma
|
||||
msg.Write(info.SpeciesName);
|
||||
info.ServerWrite(msg);
|
||||
|
||||
msg.Write((byte)CampaignInteractionType);
|
||||
|
||||
// Current order
|
||||
if (info.CurrentOrder != null)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading;
|
||||
using Barotrauma.IO;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1213,7 +1214,7 @@ namespace Barotrauma
|
||||
if (int.TryParse(string.Join(" ", args), out index))
|
||||
{
|
||||
if (index > 0 && index < GameMain.NetLobbyScreen.GameModes.Length &&
|
||||
GameMain.NetLobbyScreen.GameModes[index].Identifier == "multiplayercampaign")
|
||||
GameMain.NetLobbyScreen.GameModes[index] == GameModePreset.MultiPlayerCampaign)
|
||||
{
|
||||
MultiPlayerCampaign.StartCampaignSetup();
|
||||
}
|
||||
@@ -1541,6 +1542,11 @@ namespace Barotrauma
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
if (Submarine.MainSub == null || Level.Loaded == null) return;
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage("The teleportsub command is unavailable in outpost levels!", client);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -2056,6 +2062,54 @@ namespace Barotrauma
|
||||
NewMessage(tag, Color.Yellow);
|
||||
}
|
||||
}));
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"setskill",
|
||||
(senderClient, cursorWorldPos, args) =>
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage($"Missing arguments. Expected at least 2 but got {args.Length} (skill, level, name)", senderClient);
|
||||
return;
|
||||
}
|
||||
|
||||
string skillIdentifier = args[0];
|
||||
string levelString = args[1];
|
||||
Character character = args.Length >= 3 ? FindMatchingCharacter(args.Skip(2).ToArray(), false) : senderClient.Character;
|
||||
|
||||
if (character?.Info?.Job == null)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage("Character is not valid.", senderClient);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMax = levelString.Equals("max", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (float.TryParse(levelString, NumberStyles.Number, CultureInfo.InvariantCulture, out float level) || isMax)
|
||||
{
|
||||
if (isMax) { level = 100; }
|
||||
if (skillIdentifier.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (Skill skill in character.Info.Job.Skills)
|
||||
{
|
||||
character.Info.SetSkillLevel(skill.Identifier, level, character.WorldPosition);
|
||||
}
|
||||
GameMain.Server.SendConsoleMessage($"Set all {character.Name}'s skills to {level}", senderClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Info.SetSkillLevel(skillIdentifier, level, character.WorldPosition);
|
||||
GameMain.Server.SendConsoleMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", senderClient);
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.UpdateSkills });
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage($"{levelString} is not a valid level. Expected number or \"max\".", senderClient);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class ConversationAction : EventAction
|
||||
{
|
||||
public int SelectedOption
|
||||
{
|
||||
get { return selectedOption; }
|
||||
set { selectedOption = value; }
|
||||
}
|
||||
|
||||
|
||||
private static readonly Dictionary<Client, ConversationAction> lastActiveAction = new Dictionary<Client, ConversationAction>();
|
||||
|
||||
private readonly HashSet<Client> targetClients = new HashSet<Client>();
|
||||
public IEnumerable<Client> TargetClients
|
||||
{
|
||||
get { return targetClients; }
|
||||
}
|
||||
|
||||
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets)
|
||||
{
|
||||
foreach (Entity e in targets)
|
||||
{
|
||||
if (!(e is Character character) || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (targetClient != null)
|
||||
{
|
||||
if (lastActiveAction.ContainsKey(targetClient) &&
|
||||
lastActiveAction[targetClient].ParentEvent != ParentEvent &&
|
||||
Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + BlockOtherConversationsDuration)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
partial void ShowDialog(Character speaker, Character targetCharacter)
|
||||
{
|
||||
targetClients.Clear();
|
||||
if (!string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
|
||||
foreach (Entity e in entities)
|
||||
{
|
||||
if (!(e is Character character) || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (targetClient != null)
|
||||
{
|
||||
targetClients.Add(targetClient);
|
||||
lastActiveAction[targetClient] = this;
|
||||
ServerWrite(speaker, targetClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null)
|
||||
{
|
||||
if (targetCharacter == null || targetCharacter == c.Character)
|
||||
{
|
||||
targetClients.Add(c);
|
||||
lastActiveAction[c] = this;
|
||||
ServerWrite(speaker, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerWrite(Character speaker, Client client)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
|
||||
outmsg.Write((byte)EventManager.NetworkEventType.CONVERSATION);
|
||||
outmsg.Write(Identifier);
|
||||
outmsg.Write(EventSprite);
|
||||
outmsg.Write((byte)DialogType);
|
||||
outmsg.Write(ContinueConversation);
|
||||
if (interrupt)
|
||||
{
|
||||
outmsg.Write(speaker?.ID ?? Entity.NullEntityID);
|
||||
outmsg.Write(string.Empty);
|
||||
outmsg.Write(false);
|
||||
outmsg.Write((byte)0);
|
||||
outmsg.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
outmsg.Write(speaker?.ID ?? Entity.NullEntityID);
|
||||
outmsg.Write(Text ?? string.Empty);
|
||||
outmsg.Write(FadeToBlack);
|
||||
outmsg.Write((byte)Options.Count);
|
||||
for (int i = 0; i < Options.Count; i++)
|
||||
{
|
||||
outmsg.Write(Options[i].Text);
|
||||
}
|
||||
|
||||
int[] endings = GetEndingOptions();
|
||||
outmsg.Write((byte)endings.Length);
|
||||
foreach (var end in endings)
|
||||
{
|
||||
outmsg.Write((byte)end);
|
||||
}
|
||||
}
|
||||
GameMain.Server?.ServerPeer?.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class StatusEffectAction : EventAction
|
||||
{
|
||||
private void ServerWrite(IEnumerable<Entity> targets)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
|
||||
outmsg.Write((byte)EventManager.NetworkEventType.STATUSEFFECT);
|
||||
outmsg.Write(ParentEvent.Prefab.Identifier);
|
||||
outmsg.Write((UInt16)actionIndex);
|
||||
outmsg.Write((UInt16)targets.Count());
|
||||
foreach (Entity target in targets)
|
||||
{
|
||||
outmsg.Write(target.ID);
|
||||
}
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
GameMain.Server.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EventManager
|
||||
{
|
||||
public void ServerRead(IReadMessage inc, Client sender)
|
||||
{
|
||||
UInt16 actionId = inc.ReadUInt16();
|
||||
byte selectedOption = inc.ReadByte();
|
||||
|
||||
foreach (Event ev in activeEvents)
|
||||
{
|
||||
if (!(ev is ScriptedEvent scriptedEvent)) { continue; }
|
||||
|
||||
var actions = FindActions(scriptedEvent);
|
||||
foreach (EventAction action in actions.Select(a => a.Item2))
|
||||
{
|
||||
if (!(action is ConversationAction convAction) || convAction.Identifier != actionId) { continue; }
|
||||
if (!convAction.TargetClients.Contains(sender))
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.ThrowError($"Client \"{sender.Name}\" tried to respond to a ConversationAction that was not targeted to them.");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
convAction.SelectedOption = selectedOption;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,9 @@ namespace Barotrauma
|
||||
foreach (Item item in items)
|
||||
{
|
||||
item.WriteSpawnData(msg,
|
||||
itemIDs[item],
|
||||
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID);
|
||||
item.OriginalID,
|
||||
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID,
|
||||
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||
{
|
||||
if (monsters.Count == 0 && monsterFiles.Count > 0)
|
||||
if (monsters.Count == 0 && monsterPrefabs.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Server attempted to write monster mission data when no monsters had been spawned.");
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Barotrauma
|
||||
{
|
||||
private bool usedExistingItem;
|
||||
|
||||
private UInt16 originalItemID;
|
||||
private UInt16 originalInventoryID;
|
||||
private byte originalItemContainerIndex;
|
||||
|
||||
private readonly List<Pair<int, int>> executedEffectIndices = new List<Pair<int, int>>();
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Barotrauma
|
||||
msg.Write(usedExistingItem);
|
||||
if (usedExistingItem)
|
||||
{
|
||||
msg.Write(originalItemID);
|
||||
msg.Write(item.OriginalID);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.WriteSpawnData(msg, originalItemID, originalInventoryID);
|
||||
item.WriteSpawnData(msg, item.OriginalID, originalInventoryID, originalItemContainerIndex);
|
||||
}
|
||||
|
||||
msg.Write((byte)executedEffectIndices.Count);
|
||||
|
||||
@@ -98,20 +98,23 @@ namespace Barotrauma
|
||||
|
||||
public void Init()
|
||||
{
|
||||
NPCSet.LoadSets();
|
||||
FactionPrefab.LoadFactions();
|
||||
CharacterPrefab.LoadAll();
|
||||
MissionPrefab.Init();
|
||||
TraitorMissionPrefab.Init();
|
||||
MapEntityPrefab.Init();
|
||||
MapGenerationParams.Init();
|
||||
LevelGenerationParams.LoadPresets();
|
||||
ScriptedEventSet.LoadPrefabs();
|
||||
OutpostGenerationParams.LoadPresets();
|
||||
EventSet.LoadPrefabs();
|
||||
Order.Init();
|
||||
EventManagerSettings.Init();
|
||||
|
||||
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
|
||||
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
|
||||
StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure));
|
||||
ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item));
|
||||
UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules));
|
||||
JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs));
|
||||
CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses));
|
||||
NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations));
|
||||
@@ -344,7 +347,10 @@ namespace Barotrauma
|
||||
{
|
||||
Timing.TotalTime += Timing.Step;
|
||||
DebugConsole.Update();
|
||||
Screen.Selected?.Update((float)Timing.Step);
|
||||
if (GameSession?.GameMode == null || !GameSession.GameMode.Paused)
|
||||
{
|
||||
Screen.Selected?.Update((float)Timing.Step);
|
||||
}
|
||||
Server.Update((float)Timing.Step);
|
||||
if (Server == null) { break; }
|
||||
SteamManager.Update((float)Timing.Step);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CargoManager
|
||||
{
|
||||
public void SellBackPurchasedItems(List<PurchasedItem> itemsToSell)
|
||||
{
|
||||
foreach (PurchasedItem item in itemsToSell)
|
||||
{
|
||||
var itemValue = GetBuyValueAtCurrentLocation(item);
|
||||
campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue;
|
||||
campaign.Money += itemValue;
|
||||
PurchasedItems.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void BuyBackSoldItems(List<SoldItem> itemsToBuy)
|
||||
{
|
||||
foreach (SoldItem item in itemsToBuy)
|
||||
{
|
||||
var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab);
|
||||
if (location.StoreCurrentBalance < itemValue || item.Removed) { continue; }
|
||||
location.StoreCurrentBalance += itemValue;
|
||||
campaign.Money -= itemValue;
|
||||
SoldItems.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void SellItems(List<SoldItem> itemsToSell)
|
||||
{
|
||||
var canAddToRemoveQueue = (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) && Entity.Spawner != null;
|
||||
foreach (SoldItem item in itemsToSell)
|
||||
{
|
||||
var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab);
|
||||
|
||||
// check if the store can afford the item and if the item hasn't been removed already
|
||||
if (location.StoreCurrentBalance < itemValue || item.Removed) { continue; }
|
||||
|
||||
if (!item.Removed && canAddToRemoveQueue && Entity.FindEntityByID(item.ID) is Item entity)
|
||||
{
|
||||
item.Removed = true;
|
||||
Entity.Spawner.AddToRemoveQueue(entity);
|
||||
}
|
||||
SoldItems.Add(item);
|
||||
location.StoreCurrentBalance -= itemValue;
|
||||
campaign.Money += itemValue;
|
||||
}
|
||||
OnSoldItemsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void ClearSoldItemsProjSpecific()
|
||||
{
|
||||
SoldItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,5 +22,23 @@ namespace Barotrauma
|
||||
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves bots in multiplayer
|
||||
/// </summary>
|
||||
/// <param name="root"></param>
|
||||
public void SaveMultiplayer(XElement root)
|
||||
{
|
||||
XElement saveElement = new XElement("bots", new XAttribute("hasbots", HasBots));
|
||||
foreach (CharacterInfo info in characterInfos)
|
||||
{
|
||||
if (info?.Character == null || info.Character.IsDead) { continue; }
|
||||
|
||||
XElement characterElement = info.Save(saveElement);
|
||||
if (info.InventoryData != null) { characterElement.Add(info.InventoryData); }
|
||||
if (info.HealthData != null) { characterElement.Add(info.HealthData); }
|
||||
}
|
||||
root.Add(saveElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
public bool MirrorLevel
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public override void ShowStartMessage()
|
||||
{
|
||||
if (Mission == null) return;
|
||||
|
||||
Networking.GameServer.Log(TextManager.Get("Mission") + ": " + Mission.Name, Networking.ServerLog.MessageType.ServerMessage);
|
||||
Networking.GameServer.Log(Mission.Description, Networking.ServerLog.MessageType.ServerMessage);
|
||||
GameServer.Log(TextManager.Get("Mission") + ": " + Mission.Name, Networking.ServerLog.MessageType.ServerMessage);
|
||||
GameServer.Log(Mission.Description, Networking.ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -25,9 +25,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDuplicate(CharacterCampaignData other)
|
||||
{
|
||||
return other.SteamID == SteamID && other.ClientEndPoint == ClientEndPoint;
|
||||
}
|
||||
|
||||
public void SpawnInventoryItems(CharacterInfo characterInfo, Inventory inventory)
|
||||
{
|
||||
characterInfo.SpawnInventoryItems(inventory, itemData);
|
||||
}
|
||||
|
||||
public void ApplyHealthData(CharacterInfo characterInfo, Character character)
|
||||
{
|
||||
characterInfo.ApplyHealthData(character, healthData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class UpgradeManager
|
||||
{
|
||||
partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character)
|
||||
{
|
||||
if (Level.Loaded?.StartOutpost?.Info?.OutpostNPCs == null) { return; }
|
||||
|
||||
foreach (Character npc in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value))
|
||||
{
|
||||
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade)
|
||||
{
|
||||
npc.Speak(text, ChatMessageType.Default);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to all clients telling them that all upgrades on the submarine were reset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <param name="newUpgrades"/> is supposed to have a list of reloaded metadata but seeing as
|
||||
/// this method is currently only used when switching submarines and that disables the repair NPC
|
||||
/// until the next round so currently there's no need for it as we get the new values from the save
|
||||
/// file anyways.
|
||||
/// </remarks>
|
||||
/// <see cref="UpgradeManager.ClientRead"/>
|
||||
private void SendUpgradeResetMessage(Dictionary<string, int> newUpgrades)
|
||||
{
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte)ServerPacketHeader.RESET_UPGRADES);
|
||||
outmsg.Write(true);
|
||||
outmsg.Write(Campaign.Money);
|
||||
// outmsg.Write((uint)newUpgrades.Count);
|
||||
// foreach (var (key, value) in newUpgrades)
|
||||
// {
|
||||
// outmsg.Write(key);
|
||||
// outmsg.Write((byte)value);
|
||||
// }
|
||||
GameMain.Server?.ServerPeer?.Send(outmsg, c.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,21 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent
|
||||
{
|
||||
[Serialize("", true, description: "The text to display on the label."), Editable(100)]
|
||||
[Serialize("", true, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(100)]
|
||||
public string Text
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label.")]
|
||||
[Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label.", alwaysUseInstanceValues: true)]
|
||||
public Color TextColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, true, description: "The scale of the text displayed on the label.")]
|
||||
[Editable, Serialize(1.0f, true, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)]
|
||||
public float TextScale
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class OutpostTerminal : ItemComponent, IClientSerializable, IServerSerializable
|
||||
{
|
||||
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Owner == c.Character)
|
||||
{
|
||||
HumanAIController.ItemTaken(item, c.Character);
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " picked up " + item.Name, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -138,6 +138,32 @@ namespace Barotrauma
|
||||
errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")";
|
||||
}
|
||||
break;
|
||||
case NetEntityEvent.Type.Upgrade:
|
||||
{
|
||||
if (extraData.Length > 0 && extraData[1] is Upgrade upgrade)
|
||||
{
|
||||
var upgradeTargets = upgrade.TargetComponents;
|
||||
msg.Write(upgrade.Identifier);
|
||||
msg.Write((byte)upgrade.Level);
|
||||
msg.Write((byte)upgradeTargets.Count);
|
||||
foreach (var (_, value) in upgrade.TargetComponents)
|
||||
{
|
||||
msg.Write((byte)value.Length);
|
||||
foreach (var propertyReference in value)
|
||||
{
|
||||
object originalValue = propertyReference.OriginalValue;
|
||||
msg.Write((float)(originalValue ?? -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMsg = extraData.Length > 0
|
||||
? $"Failed to write a network event for the item \"{Name}\" - \"{extraData[1].GetType()}\" is not a valid upgrade."
|
||||
: $"Failed to write a network event for the item \"{Name}\". No upgrade specified.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items.";
|
||||
break;
|
||||
@@ -152,7 +178,6 @@ namespace Barotrauma
|
||||
DebugConsole.Log(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
|
||||
@@ -213,7 +238,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID)
|
||||
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID, byte originalItemContainerIndex)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
@@ -238,29 +263,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
msg.Write(originalInventoryID);
|
||||
|
||||
//find the index of the ItemContainer this item is inside to get the item to
|
||||
//spawn in the correct inventory in multi-inventory items like fabricators
|
||||
byte containerIndex = 0;
|
||||
if (Container != null)
|
||||
{
|
||||
for (int i = 0; i < Container.components.Count; i++)
|
||||
{
|
||||
if (Container.components[i] is ItemContainer container &&
|
||||
container.Inventory == ParentInventory)
|
||||
{
|
||||
containerIndex = (byte)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.Write(containerIndex);
|
||||
msg.Write(originalItemContainerIndex);
|
||||
|
||||
int slotIndex = ParentInventory.FindIndex(this);
|
||||
msg.Write(slotIndex < 0 ? (byte)255 : (byte)slotIndex);
|
||||
}
|
||||
|
||||
msg.Write(body == null ? (byte)0 : (byte)body.BodyType);
|
||||
msg.Write(SpawnedInOutpost);
|
||||
|
||||
byte teamID = 0;
|
||||
foreach (WifiComponent wifiComponent in GetComponents<WifiComponent>())
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
msg.Write((byte)Sections.Length);
|
||||
for (int i = 0; i < Sections.Length; i++)
|
||||
{
|
||||
msg.WriteRangedSingle(Sections[i].damage / Health, 0.0f, 1.0f, 8);
|
||||
msg.WriteRangedSingle(Sections[i].damage / MaxHealth, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid order message from client \"" + c.Name + "\" - order index out of bounds.");
|
||||
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderIndex}, {orderOptionIndex}).");
|
||||
if (NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { c.LastSentChatMsgID = ID; }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ namespace Barotrauma.Networking
|
||||
txt = msg.ReadString() ?? "";
|
||||
}
|
||||
|
||||
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) return;
|
||||
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; }
|
||||
|
||||
c.LastSentChatMsgID = ID;
|
||||
|
||||
@@ -168,6 +169,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
msg.Write(Sender.ID);
|
||||
}
|
||||
if (Type == ChatMessageType.ServerMessageBoxInGame)
|
||||
{
|
||||
msg.Write(IconStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma
|
||||
{
|
||||
message.Write((byte)SpawnableType.Item);
|
||||
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
|
||||
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID);
|
||||
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID, entities.OriginalItemContainerIndex);
|
||||
}
|
||||
else if (entities.Entity is Character)
|
||||
{
|
||||
|
||||
@@ -11,12 +11,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public class FileTransferOut
|
||||
{
|
||||
private readonly byte[] data;
|
||||
|
||||
private readonly DateTime startingTime;
|
||||
|
||||
private readonly NetworkConnection connection;
|
||||
|
||||
public FileTransferStatus Status;
|
||||
|
||||
public string FileName
|
||||
@@ -48,10 +42,7 @@ namespace Barotrauma.Networking
|
||||
set;
|
||||
}
|
||||
|
||||
public byte[] Data
|
||||
{
|
||||
get { return data; }
|
||||
}
|
||||
public byte[] Data { get; }
|
||||
|
||||
public bool Acknowledged;
|
||||
|
||||
@@ -63,16 +54,15 @@ namespace Barotrauma.Networking
|
||||
|
||||
public int KnownReceivedOffset;
|
||||
|
||||
public NetworkConnection Connection
|
||||
{
|
||||
get { return connection; }
|
||||
}
|
||||
public NetworkConnection Connection { get; }
|
||||
|
||||
public DateTime StartingTime { get; }
|
||||
|
||||
public int ID;
|
||||
|
||||
public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
connection = recipient;
|
||||
Connection = recipient;
|
||||
|
||||
FileType = fileType;
|
||||
FilePath = filePath;
|
||||
@@ -84,14 +74,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
Status = FileTransferStatus.NotStarted;
|
||||
|
||||
startingTime = DateTime.Now;
|
||||
StartingTime = DateTime.Now;
|
||||
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
data = File.ReadAllBytes(filePath);
|
||||
Data = File.ReadAllBytes(filePath);
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
@@ -192,97 +182,104 @@ namespace Barotrauma.Networking
|
||||
foreach (FileTransferOut transfer in activeTransfers)
|
||||
{
|
||||
transfer.WaitTimer -= deltaTime;
|
||||
if (transfer.WaitTimer > 0.0f) continue;
|
||||
|
||||
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
|
||||
|
||||
// send another part of the file
|
||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
||||
|
||||
IWriteMessage message;
|
||||
|
||||
try
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
//first message; send length, file name etc
|
||||
//wait for acknowledgement before sending data
|
||||
if (!transfer.Acknowledged)
|
||||
{
|
||||
message = new WriteOnlyMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
if (transfer.WaitTimer > 0.0f) { break; }
|
||||
Send(transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if the recipient is the owner of the server (= a client running the server from the main exe)
|
||||
//we don't need to send anything, the client can just read the file directly
|
||||
if (transfer.Connection == GameMain.Server.OwnerConnection)
|
||||
{
|
||||
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
message.Write(transfer.FilePath);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
transfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Write((byte)FileTransferMessageType.Initiate);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
//message.Write((ushort)chunkLen);
|
||||
message.Write(transfer.Data.Length);
|
||||
message.Write(transfer.FileName);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
private void Send(FileTransferOut transfer)
|
||||
{
|
||||
// send another part of the file
|
||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
||||
|
||||
transfer.Status = FileTransferStatus.Sending;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending file transfer initiation message: ");
|
||||
DebugConsole.Log(" File: " + transfer.FileName);
|
||||
DebugConsole.Log(" Size: " + transfer.Data.Length);
|
||||
DebugConsole.Log(" ID: " + transfer.ID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
IWriteMessage message;
|
||||
|
||||
try
|
||||
{
|
||||
//first message; send length, file name etc
|
||||
//wait for acknowledgement before sending data
|
||||
if (!transfer.Acknowledged)
|
||||
{
|
||||
message = new WriteOnlyMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Data);
|
||||
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write(transfer.SentOffset);
|
||||
|
||||
byte[] sendBytes = new byte[sendByteCount];
|
||||
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
|
||||
|
||||
message.Write((ushort)sendByteCount);
|
||||
message.Write(sendBytes, 0, sendByteCount);
|
||||
|
||||
transfer.SentOffset += sendByteCount;
|
||||
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 ||
|
||||
transfer.SentOffset >= transfer.Data.Length)
|
||||
//if the recipient is the owner of the server (= a client running the server from the main exe)
|
||||
//we don't need to send anything, the client can just read the file directly
|
||||
if (transfer.Connection == GameMain.Server.OwnerConnection)
|
||||
{
|
||||
transfer.SentOffset = transfer.KnownReceivedOffset;
|
||||
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
message.Write(transfer.FilePath);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
transfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Write((byte)FileTransferMessageType.Initiate);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
//message.Write((ushort)chunkLen);
|
||||
message.Write(transfer.Data.Length);
|
||||
message.Write(transfer.FileName);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
transfer.Status = FileTransferStatus.Sending;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending file transfer initiation message: ");
|
||||
DebugConsole.Log(" File: " + transfer.FileName);
|
||||
DebugConsole.Log(" Size: " + transfer.Data.Length);
|
||||
DebugConsole.Log(" ID: " + transfer.ID);
|
||||
}
|
||||
}
|
||||
transfer.WaitTimer = 0.1f;
|
||||
return;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
message = new WriteOnlyMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Data);
|
||||
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write(transfer.SentOffset);
|
||||
|
||||
byte[] sendBytes = new byte[sendByteCount];
|
||||
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
|
||||
|
||||
message.Write((ushort)sendByteCount);
|
||||
message.Write(sendBytes, 0, sendByteCount);
|
||||
|
||||
transfer.SentOffset += sendByteCount;
|
||||
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 10 ||
|
||||
transfer.SentOffset >= transfer.Data.Length)
|
||||
{
|
||||
DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"FileSender.Update:Exception",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
|
||||
transfer.Status = FileTransferStatus.Error;
|
||||
break;
|
||||
transfer.SentOffset = transfer.KnownReceivedOffset;
|
||||
transfer.WaitTimer = 0.5f;
|
||||
}
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
|
||||
}
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"FileSender.Update:Exception",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
|
||||
transfer.Status = FileTransferStatus.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +314,11 @@ namespace Barotrauma.Networking
|
||||
matchingTransfer.Acknowledged = true;
|
||||
int offset = inc.ReadInt32();
|
||||
matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset;
|
||||
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) { matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; }
|
||||
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset)
|
||||
{
|
||||
matchingTransfer.WaitTimer = 0.0f;
|
||||
matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
|
||||
}
|
||||
|
||||
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -255,8 +255,8 @@ namespace Barotrauma.Networking
|
||||
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + " due to excessive desync (expected old event "
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
@@ -273,8 +273,8 @@ namespace Barotrauma.Networking
|
||||
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1)));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncRemovedEvent + "/ServerMessage.ExcessiveDesyncRemovedEvent");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Barotrauma.Networking
|
||||
partial class ServerSettings
|
||||
{
|
||||
public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml";
|
||||
public static readonly char SubmarineSeparatorChar = '|';
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
|
||||
@@ -16,6 +16,51 @@ namespace Barotrauma
|
||||
{
|
||||
get { return allowModeVoting; }
|
||||
set { allowModeVoting = value; }
|
||||
}
|
||||
|
||||
public struct SubmarineVote
|
||||
{
|
||||
public Client VoteStarter;
|
||||
public SubmarineInfo Sub;
|
||||
public VoteType VoteType;
|
||||
public float Timer;
|
||||
public int DeliveryFee;
|
||||
public VoteState State;
|
||||
}
|
||||
|
||||
public static SubmarineVote SubVote;
|
||||
|
||||
private void StartSubmarineVote(IReadMessage inc, VoteType voteType, Client sender)
|
||||
{
|
||||
string subName = inc.ReadString();
|
||||
SubVote.Sub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
|
||||
SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0;
|
||||
SubVote.VoteType = voteType;
|
||||
SubVote.State = VoteState.Started;
|
||||
SubVote.VoteStarter = sender;
|
||||
VoteRunning = true;
|
||||
sender.SetVote(voteType, 2);
|
||||
}
|
||||
|
||||
public void StopSubmarineVote(bool passed)
|
||||
{
|
||||
VoteRunning = false;
|
||||
SubVote.State = passed ? VoteState.Passed : VoteState.Failed;
|
||||
|
||||
GameMain.Server.UpdateVoteStatus();
|
||||
|
||||
GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0;
|
||||
for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++)
|
||||
{
|
||||
GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0);
|
||||
}
|
||||
|
||||
SubVote.Sub = null;
|
||||
SubVote.DeliveryFee = 0;
|
||||
SubVote.VoteType = VoteType.Unknown;
|
||||
SubVote.Timer = 0.0f;
|
||||
SubVote.State = VoteState.None;
|
||||
SubVote.VoteStarter = null;
|
||||
}
|
||||
|
||||
public void ServerRead(IReadMessage inc, Client sender)
|
||||
@@ -76,7 +121,24 @@ namespace Barotrauma
|
||||
sender.SetVote(VoteType.StartRound, ready);
|
||||
GameServer.Log(GameServer.ClientLogName(sender) + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
case VoteType.PurchaseSub:
|
||||
case VoteType.SwitchSub:
|
||||
bool startVote = inc.ReadBoolean();
|
||||
if (startVote)
|
||||
{
|
||||
StartSubmarineVote(inc, voteType, sender);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.SetVote(voteType, (int)inc.ReadByte());
|
||||
}
|
||||
|
||||
GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 2);
|
||||
GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 1);
|
||||
GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -120,6 +182,52 @@ namespace Barotrauma
|
||||
|
||||
msg.Write(AllowVoteKick);
|
||||
|
||||
msg.Write((byte)SubVote.State);
|
||||
if (SubVote.State != VoteState.None)
|
||||
{
|
||||
msg.Write((byte)SubVote.VoteType);
|
||||
|
||||
if (SubVote.VoteType != VoteType.Unknown)
|
||||
{
|
||||
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 2);
|
||||
msg.Write((byte)yesClients.Count);
|
||||
foreach (Client c in yesClients)
|
||||
{
|
||||
msg.Write(c.ID);
|
||||
}
|
||||
|
||||
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 1);
|
||||
msg.Write((byte)noClients.Count);
|
||||
foreach (Client c in noClients)
|
||||
{
|
||||
msg.Write(c.ID);
|
||||
}
|
||||
|
||||
msg.Write((byte)GameMain.Server.SubmarineVoteMax);
|
||||
|
||||
switch (SubVote.State)
|
||||
{
|
||||
case VoteState.Started:
|
||||
msg.Write(SubVote.Sub.Name);
|
||||
msg.Write(SubVote.VoteStarter.ID);
|
||||
msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout);
|
||||
break;
|
||||
case VoteState.Running:
|
||||
// Nothing specific
|
||||
break;
|
||||
case VoteState.Passed:
|
||||
case VoteState.Failed:
|
||||
msg.Write(SubVote.State == VoteState.Passed);
|
||||
msg.Write(SubVote.Sub.Name);
|
||||
if (SubVote.State == VoteState.Passed)
|
||||
{
|
||||
msg.Write((short)SubVote.DeliveryFee);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
|
||||
msg.Write((byte)readyClients.Count);
|
||||
foreach (Client c in readyClients)
|
||||
|
||||
@@ -69,12 +69,19 @@ namespace Barotrauma
|
||||
|
||||
static void CrashDump(string filePath, Exception exception)
|
||||
{
|
||||
GameMain.Server?.ServerSettings?.SaveSettings();
|
||||
GameMain.Server?.ServerSettings?.BanList.Save();
|
||||
if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
|
||||
try
|
||||
{
|
||||
GameMain.Server?.KarmaManager?.SaveCustomPreset();
|
||||
GameMain.Server?.KarmaManager?.Save();
|
||||
GameMain.Server?.ServerSettings?.SaveSettings();
|
||||
GameMain.Server?.ServerSettings?.BanList.Save();
|
||||
if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom")
|
||||
{
|
||||
GameMain.Server?.KarmaManager?.SaveCustomPreset();
|
||||
GameMain.Server?.KarmaManager?.Save();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//couldn't save, whatever
|
||||
}
|
||||
|
||||
int existingFiles = 0;
|
||||
@@ -146,11 +153,11 @@ namespace Barotrauma
|
||||
{
|
||||
GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport);
|
||||
GameAnalytics.OnQuit();
|
||||
Console.Write("A crash report (\"crashreport.log\") was saved in the root folder of the game and sent to the developers.");
|
||||
Console.Write("A crash report (\"servercrashreport.log\") was saved in the root folder of the game and sent to the developers.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write("A crash report(\"crashreport.log\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
|
||||
Console.Write("A crash report(\"servercrashreport.log\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
|
||||
" if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/");
|
||||
}
|
||||
SteamManager.ShutDown();
|
||||
|
||||
@@ -30,6 +30,61 @@ namespace Barotrauma
|
||||
set { selectedShuttle = value; lastUpdateID++; }
|
||||
}
|
||||
|
||||
public List<SubmarineInfo> CampaignSubmarines
|
||||
{
|
||||
get
|
||||
{
|
||||
return campaignSubmarines;
|
||||
}
|
||||
set
|
||||
{
|
||||
campaignSubmarines = value;
|
||||
lastUpdateID++;
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SubmarineInfo> campaignSubmarines;
|
||||
|
||||
public void AddCampaignSubmarine(SubmarineInfo sub)
|
||||
{
|
||||
if (!campaignSubmarines.Contains(sub))
|
||||
{
|
||||
campaignSubmarines.Add(sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastUpdateID++;
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCampaignSubmarine(SubmarineInfo sub)
|
||||
{
|
||||
if (campaignSubmarines.Contains(sub))
|
||||
{
|
||||
campaignSubmarines.Remove(sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lastUpdateID++;
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public GameModePreset[] GameModes { get; }
|
||||
|
||||
private int selectedModeIndex;
|
||||
@@ -40,6 +95,10 @@ namespace Barotrauma
|
||||
{
|
||||
lastUpdateID++;
|
||||
selectedModeIndex = MathHelper.Clamp(value, 0, GameModes.Length - 1);
|
||||
if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this)
|
||||
{
|
||||
GameMain.GameSession = null;
|
||||
}
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.GameModeIdentifier = SelectedModeIdentifier;
|
||||
@@ -121,7 +180,7 @@ namespace Barotrauma
|
||||
{
|
||||
LevelSeed = ToolBox.RandomSeed(8);
|
||||
|
||||
subs = SubmarineInfo.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)).ToList();
|
||||
subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.HideInMenus)).ToList();
|
||||
|
||||
if (subs == null || subs.Count() == 0)
|
||||
{
|
||||
@@ -176,7 +235,7 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < GameModes.Length; i++)
|
||||
{
|
||||
if ((GameModes[i].Identifier == "multiplayercampaign") == enabled)
|
||||
if ((GameModes[i] == GameModePreset.MultiPlayerCampaign) == enabled)
|
||||
{
|
||||
selectedModeIndex = i;
|
||||
break;
|
||||
@@ -190,6 +249,10 @@ namespace Barotrauma
|
||||
{
|
||||
base.Select();
|
||||
GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients);
|
||||
if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this)
|
||||
{
|
||||
GameMain.GameSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RandomizeSettings()
|
||||
@@ -203,7 +266,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (GameMain.Server.ServerSettings.ModeSelectionMode == SelectionMode.Random)
|
||||
{
|
||||
var allowedGameModes = Array.FindAll(GameModes, m => !m.IsSinglePlayer && m.Identifier != "multiplayercampaign");
|
||||
var allowedGameModes = Array.FindAll(GameModes, m => !m.IsSinglePlayer && m != GameModePreset.MultiPlayerCampaign);
|
||||
SelectedModeIdentifier = allowedGameModes[Rand.Range(0, allowedGameModes.Length)].Identifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TraitorManager
|
||||
partial class TraitorManager
|
||||
{
|
||||
public static readonly Random Random = new Random((int)DateTime.UtcNow.Ticks);
|
||||
|
||||
@@ -182,14 +182,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public string GetEndMessage()
|
||||
public List<TraitorMissionResult> GetEndResults()
|
||||
{
|
||||
#if DISABLE_MISSIONS
|
||||
return "";
|
||||
#endif
|
||||
if (GameMain.Server == null || !Missions.Any()) return "";
|
||||
List<TraitorMissionResult> results = new List<TraitorMissionResult>();
|
||||
|
||||
return TextManager.JoinServerMessages("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage).ToArray());
|
||||
#if DISABLE_MISSIONS
|
||||
return results;
|
||||
#endif
|
||||
if (GameMain.Server == null || !Missions.Any()) { return results; }
|
||||
|
||||
foreach (var mission in Missions)
|
||||
{
|
||||
results.Add(new TraitorMissionResult(mission.Value));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TraitorMissionResult
|
||||
{
|
||||
public TraitorMissionResult(Traitor.TraitorMission mission)
|
||||
{
|
||||
MissionIdentifier = mission.Identifier;
|
||||
EndMessage = mission.GlobalEndMessage;
|
||||
Success = mission.IsCompleted;
|
||||
foreach (Traitor traitor in mission.Traitors.Values)
|
||||
{
|
||||
Characters.Add(traitor.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(MissionIdentifier);
|
||||
msg.Write(EndMessage);
|
||||
msg.Write(Success);
|
||||
msg.Write((byte)Characters.Count);
|
||||
foreach (Character character in Characters)
|
||||
{
|
||||
msg.Write(character.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user