Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -0,0 +1,12 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class CharacterInventory : Inventory
{
public void ServerEventWrite(IWriteMessage msg, Client c, Character.InventoryStateEventData inventoryData)
{
SharedWrite(msg, inventoryData.SlotRange);
}
}
}
@@ -32,6 +32,7 @@ namespace Barotrauma.Items.Components
Drop(false, null);
item.SetTransform(simPosition, 0.0f, findNewHull: false);
AttachToWall();
OnUsed.Invoke(new ItemUseInfo(item, c.Character));
item.CreateServerEvent(this);
c.Character.Inventory?.CreateNetworkEvent();
@@ -49,21 +49,35 @@ namespace Barotrauma.Items.Components
msg.WriteSingle(jointAxis.Y);
if (StickTarget.UserData is Structure structure)
{
msg.WriteByte((byte)StickTargetType.Structure);
msg.WriteUInt16(structure.ID);
int bodyIndex = structure.Bodies.IndexOf(StickTarget);
msg.WriteByte((byte)(bodyIndex == -1 ? 0 : bodyIndex));
}
else if (StickTarget.UserData is Entity entity)
else if (StickTarget.UserData is Item item)
{
msg.WriteUInt16(entity.ID);
msg.WriteByte((byte)StickTargetType.Item);
msg.WriteUInt16(item.ID);
}
else if (StickTarget.UserData is Submarine sub)
{
msg.WriteByte((byte)StickTargetType.Submarine);
msg.WriteUInt16(sub.ID);
}
else if (StickTarget.UserData is Limb limb)
{
msg.WriteByte((byte)StickTargetType.Limb);
msg.WriteUInt16(limb.character.ID);
msg.WriteByte((byte)Array.IndexOf(limb.character.AnimController.Limbs, limb));
}
else if (StickTarget.UserData is Voronoi2.VoronoiCell cell)
{
msg.WriteByte((byte)StickTargetType.LevelWall);
msg.WriteInt32(Level.Loaded.GetAllCells().IndexOf(cell));
}
else
{
msg.WriteByte((byte)StickTargetType.Unknown);
throw new NotImplementedException(StickTarget.UserData?.ToString() ?? "null" + " is not a valid projectile stick target.");
}
}
@@ -45,8 +45,7 @@ namespace Barotrauma.Items.Components
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteSingle(deteriorationTimer);
msg.WriteSingle(deteriorateAlwaysResetTimer);
msg.WriteBoolean(DeteriorateAlways);
msg.WriteSingle(ForceDeteriorationTimer);
msg.WriteSingle(tinkeringDuration);
msg.WriteSingle(tinkeringStrength);
msg.WriteBoolean(tinkeringPowersDevices);
@@ -0,0 +1,320 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
internal sealed partial class CircuitBox
{
/// <summary>
/// If the server needs to initialize the circuit box to the clients
/// instead of the clients loading it from the save file.
/// </summary>
private bool needsServerInitialization;
/// <summary>
/// When in multiplayer and the circuit box is loaded from the players inventory,
/// We only load the components from XML on server side since only the server has access to CharacterCampaignData
/// and then send a network event syncing the loaded properties. But circuit box properties are too complex to
/// sync using the existing syncing logic so we instead send the state using <see cref="CircuitBoxInitializeStateFromServerEvent"/>.
/// </summary>
public void MarkServerRequiredInitialization()
=> needsServerInitialization = true;
public partial void OnDeselected(Character c)
{
ClearAllSelectionsInternal(c.ID);
BroadcastSelectionStatus();
}
public void ServerRead(INetSerializableStruct data, Client c)
{
switch (data)
{
case NetCircuitBoxCursorInfo { RecordedPositions.Length: 10 } cursorInfo:
{
RelayCursorState(cursorInfo, c);
break;
}
}
}
private void RelayCursorState(NetCircuitBoxCursorInfo data, Client sender)
{
if (GameMain.Server is null || !IsRoundRunning()) { return; }
SendToAll(CircuitBoxOpcode.Cursor, data with { CharacterID = sender.CharacterID }, FilterClients);
bool FilterClients(Client client)
{
// ReSharper disable once RedundantAssignment
bool isSender = client == sender;
#if DEBUG
// Shown own cursor in debug builds
isSender = false;
#endif
return !isSender && client.Character is not null && client.Character.SelectedItem == item;
}
}
public void SendToClient(CircuitBoxOpcode opcode, INetSerializableStruct data, Client targetClient)
{
var (msg, deliveryMethod) = PrepareToSend(opcode, data);
GameMain.Server?.ServerPeer?.Send(msg, targetClient.Connection, deliveryMethod);
}
public void SendToAll(CircuitBoxOpcode opcode, INetSerializableStruct data, Func<Client, bool>? predicate = null)
{
var (msg, deliveryMethod) = PrepareToSend(opcode, data);
foreach (Client client in GameMain.Server.ConnectedClients)
{
if (predicate is not null && !predicate(client)) { continue; }
GameMain.Server?.ServerPeer?.Send(msg, client.Connection, deliveryMethod);
}
}
private (IWriteMessage Message, DeliveryMethod DeliveryMethod) PrepareToSend(CircuitBoxOpcode opcode, INetSerializableStruct data)
{
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ServerPacketHeader.CIRCUITBOX);
msg.WriteNetSerializableStruct(new NetCircuitBoxHeader(
Opcode: opcode,
ItemID: item.ID,
ComponentIndex: (byte)item.GetComponentIndex(this)));
msg.WriteNetSerializableStruct(data);
DeliveryMethod deliveryMethod =
UnrealiableOpcodes.Contains(opcode)
? DeliveryMethod.Unreliable
: DeliveryMethod.Reliable;
return (msg, deliveryMethod);
}
public void CreateServerEvent(INetSerializableStruct data)
=> item.CreateServerEvent(this, new CircuitBoxEventData(data));
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData? extraData = null)
{
if (extraData is null) { return; }
var eventData = ExtractEventData<CircuitBoxEventData>(extraData);
msg.WriteByte((byte)eventData.Opcode);
msg.WriteNetSerializableStruct(eventData.Data);
}
public void ServerEventRead(IReadMessage msg, Client c)
{
var header = (CircuitBoxOpcode)msg.ReadByte();
switch (header)
{
case CircuitBoxOpcode.AddComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxAddComponentEvent>(msg);
if (!item.CanClientAccess(c)) { break; }
var prefab = ItemPrefab.Prefabs.Find(p => p.UintIdentifier == data.PrefabIdentifier);
if (prefab is null)
{
ThrowError("Unable to add component because the prefab was not found.", c);
return;
}
if (IsFull || !GetApplicableResourcePlayerHas(prefab, c.Character).TryUnwrap(out var resource)) { return; }
ushort id = ICircuitBoxIdentifiable.FindFreeID(Components);
if (id is ICircuitBoxIdentifiable.NullComponentID)
{
ThrowError("Unable to add component because there are no available IDs left.", c);
return;
}
bool result = AddComponentInternal(id, prefab, resource.Prefab, data.Position, it =>
{
CreateServerEvent(new CircuitBoxServerCreateComponentEvent(it.ID, resource.Prefab.UintIdentifier, id, data.Position));
});
if (!result)
{
ThrowError("Unable to add component because the component could not be created.", c);
return;
}
GameServer.Log($"{NetworkMember.ClientLogName(c)} added a {prefab.Name} into a circuit box.", ServerLog.MessageType.Wiring);
RemoveItem(resource);
break;
}
case CircuitBoxOpcode.MoveComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxMoveComponentEvent>(msg);
if (!item.CanClientAccess(c)) { break; }
MoveNodesInternal(data.TargetIDs, data.IOs, data.MoveAmount);
CreateServerEvent(data);
break;
}
case CircuitBoxOpcode.DeleteComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxRemoveComponentEvent>(msg);
if (!data.TargetIDs.Any() || !item.CanClientAccess(c)) { break; }
CreateRefundItemsForUsedResources(data.TargetIDs, c.Character);
GameServer.Log($"{NetworkMember.ClientLogName(c)} removed {GetLogComponentName(data.TargetIDs)} from circuit box.", ServerLog.MessageType.Wiring);
RemoveComponentInternal(data.TargetIDs);
CreateServerEvent(data);
break;
}
case CircuitBoxOpcode.SelectComponents:
{
var data = INetSerializableStruct.Read<CircuitBoxSelectNodesEvent>(msg);
if (!item.CanClientAccess(c)) { break; }
SelectComponentsInternal(data.TargetIDs, c.CharacterID, data.Overwrite);
SelectInputOutputInternal(data.IOs, c.CharacterID, data.Overwrite);
BroadcastSelectionStatus();
break;
}
case CircuitBoxOpcode.SelectWires:
{
var data = INetSerializableStruct.Read<CircuitBoxSelectWiresEvent>(msg);
if (!item.CanClientAccess(c)) { break; }
SelectWiresInternal(data.TargetIDs, c.CharacterID, data.Overwrite);
BroadcastSelectionStatus();
break;
}
case CircuitBoxOpcode.AddWire:
{
var data = INetSerializableStruct.Read<CircuitBoxClientAddWireEvent>(msg);
if (!item.CanClientAccess(c)) { break; }
var prefab = ItemPrefab.Prefabs.Find(p => p.UintIdentifier == data.SelectedWirePrefabIdentifier);
if (prefab is null)
{
ThrowError($"Unable to connect wire because wire by identifier \"{data.SelectedWirePrefabIdentifier}\" was not found.", c);
break;
}
if (data.Start.FindConnection(this).TryUnwrap(out var start) &&
data.End.FindConnection(this).TryUnwrap(out var end))
{
bool result = Connect(start, end, wire =>
{
CreateServerEvent(new CircuitBoxServerCreateWireEvent(data with { Start = wire.Start, End = wire.End }, wire.ID, wire.Item.Select(static i => i.ID)));
}, prefab);
if (!result)
{
ThrowError("Unable to connect wire because the circuit box rejected it.", c);
}
GameServer.Log($"{NetworkMember.ClientLogName(c)} connected a wire from {start.Name} to {end.Name} in a circuit box.", ServerLog.MessageType.Wiring);
}
else
{
ThrowError($"Unable to connect wire because the start or end connection was not found. (start: {data.Start}, end: {data.End})", c);
}
break;
}
case CircuitBoxOpcode.RemoveWire:
{
var data = INetSerializableStruct.Read<CircuitBoxRemoveWireEvent>(msg);
if (!data.TargetIDs.Any() || !item.CanClientAccess(c)) { break; }
GameServer.Log($"{NetworkMember.ClientLogName(c)} removed {GetLogWireName(data.TargetIDs)} from circuit box.", ServerLog.MessageType.Wiring);
RemoveWireInternal(data.TargetIDs);
CreateServerEvent(data);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
}
string GetLogComponentName(IReadOnlyList<ushort> ids)
{
if (ids.Count > 1) { return $"{ids.Count} components"; }
return Components.FirstOrDefault(comp => ids.Contains(comp.ID))?.Item.Name ?? "[UNKNOWN]";
}
string GetLogWireName(IReadOnlyList<ushort> ids)
{
if (ids.Count > 1) { return $"{ids.Count} wires"; }
if (Wires.FirstOrDefault(w => ids.Contains(w.ID)) is not { } wire) { return "[UNKNOWN]"; }
return wire.BackingWire.TryUnwrap(out var backingWire) ? backingWire.Name : "a wire";
}
}
/// <summary>
/// Creates an event that overrides the state of the circuit box for all clients.
/// This is only required if the circuit box is loaded from the players inventory in multiplayer.
/// </summary>
public void CreateInitializationEvent()
{
Vector2 inputPos = Vector2.Zero,
outputPos = Vector2.Zero;
foreach (var ioNode in InputOutputNodes)
{
switch (ioNode.NodeType)
{
case CircuitBoxInputOutputNode.Type.Input:
inputPos = ioNode.Position;
break;
case CircuitBoxInputOutputNode.Type.Output:
outputPos = ioNode.Position;
break;
}
}
CircuitBoxInitializeStateFromServerEvent data = new(
Components: Components.Select(EventFromComponent).ToImmutableArray(),
Wires: Wires.Select(EventFromWire).ToImmutableArray(),
InputPos: inputPos,
OutputPos: outputPos);
CreateServerEvent(data);
static CircuitBoxServerCreateComponentEvent EventFromComponent(CircuitBoxComponent component)
=> new(component.Item.ID, component.UsedResource.UintIdentifier, component.ID, component.Position);
static CircuitBoxServerCreateWireEvent EventFromWire(CircuitBoxWire wire)
{
var backingWire = wire.BackingWire.Select(static i => i.ID);
var from = CircuitBoxConnectorIdentifier.FromConnection(wire.From);
var to = CircuitBoxConnectorIdentifier.FromConnection(wire.To);
var request = new CircuitBoxClientAddWireEvent(wire.Color, from, to, wire.UsedItemPrefab.UintIdentifier);
return new CircuitBoxServerCreateWireEvent(request, wire.ID, backingWire);
}
}
// we don't care about updating the view on server
public partial void OnViewUpdateProjSpecific() { }
private void ThrowError(string message, Client c)
{
DebugConsole.ThrowError(message);
SendToClient(CircuitBoxOpcode.Error, new CircuitBoxErrorEvent(message), c);
}
private void BroadcastSelectionStatus()
{
var nodes = Components.Select(static c => new CircuitBoxIdSelectionPair(c.ID, c.IsSelected ? Option.Some(c.SelectedBy) : Option.None)).ToImmutableArray();
var wires = Wires.Select(static w => new CircuitBoxIdSelectionPair(w.ID, w.IsSelected ? Option.Some(w.SelectedBy) : Option.None)).ToImmutableArray();
var ios = InputOutputNodes.Select(static n => new CircuitBoxTypeSelectionPair(n.NodeType, n.IsSelected ? Option.Some(n.SelectedBy) : Option.None)).ToImmutableArray();
CreateServerEvent(new CircuitBoxServerUpdateSelection(nodes, wires, ios));
}
}
}
@@ -12,7 +12,8 @@ namespace Barotrauma.Items.Components
List<Wire>[] wires = new List<Wire>[Connections.Count];
//read wire IDs for each connection
for (int i = 0; i < Connections.Count; i++)
byte connectionCount = msg.ReadByte();
for (int i = 0; i < Connections.Count && i < connectionCount; i++)
{
wires[i] = new List<Wire>();
uint wireCount = msg.ReadVariableUInt32();
@@ -32,17 +32,17 @@ namespace Barotrauma.Items.Components
GameServer.Log(GameServer.CharacterLogName(c.Character) + " entered \"" + newOutputValue + "\" on " + item.Name,
ServerLog.MessageType.ItemInteraction);
OutputValue = newOutputValue;
ShowOnDisplay(newOutputValue, addToHistory: true, TextColor);
ShowOnDisplay(newOutputValue, addToHistory: true, TextColor, isWelcomeMessage: false);
item.SendSignal(newOutputValue, "signal_out");
item.CreateServerEvent(this);
}
}
partial void ShowOnDisplay(string input, bool addToHistory, Color color)
partial void ShowOnDisplay(string input, bool addToHistory, Color color, bool isWelcomeMessage)
{
if (addToHistory)
{
messageHistory.Add(new TerminalMessage(input, color));
messageHistory.Add(new TerminalMessage(input, color, isWelcomeMessage));
while (messageHistory.Count > MaxMessages)
{
messageHistory.RemoveAt(0);
@@ -54,9 +54,11 @@ namespace Barotrauma.Items.Components
{
//split too long messages to multiple parts
int msgIndex = 0;
foreach (var (str, _) in messageHistory)
foreach (var msg in messageHistory)
{
string msgToSend = str;
//the clients create the welcome message themselves, no need to sync it
if (msg.IsWelcomeMessage) { continue; }
string msgToSend = msg.Text;
if (string.IsNullOrEmpty(msgToSend))
{
item.CreateServerEvent(this, new ServerEventData(msgIndex, msgToSend));
@@ -4,17 +4,25 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Inventory : IServerSerializable, IClientSerializable
partial class Inventory : IClientSerializable
{
private readonly Dictionary<Client, List<ushort>[]> receivedItemIds = new Dictionary<Client, List<ushort>[]>();
public void ServerEventRead(IReadMessage msg, Client c)
{
List<Item> prevItems = new List<Item>(AllItems.Distinct());
SharedRead(msg, out var newItemIDs);
if (!receivedItemIds.TryGetValue(c, out List<ushort>[] receivedItemIdsFromClient))
{
receivedItemIdsFromClient = new List<ushort>[capacity];
receivedItemIds.Add(c, receivedItemIdsFromClient);
}
SharedRead(msg, receivedItemIdsFromClient, out bool readyToApply);
if (!readyToApply) { return; }
if (c == null || c.Character == null) { return; }
@@ -39,7 +47,7 @@ namespace Barotrauma
CreateNetworkEvent();
for (int i = 0; i < capacity; i++)
{
foreach (ushort id in newItemIDs[i])
foreach (ushort id in receivedItemIdsFromClient[i])
{
if (Entity.FindEntityByID(id) is not Item item) { continue; }
item.PositionUpdateInterval = 0.0f;
@@ -58,7 +66,7 @@ namespace Barotrauma
{
foreach (Item item in slots[i].Items.ToList())
{
if (!newItemIDs[i].Contains(item.ID))
if (!receivedItemIdsFromClient[i].Contains(item.ID))
{
Item droppedItem = item;
Entity prevOwner = Owner;
@@ -85,7 +93,7 @@ namespace Barotrauma
}
}
foreach (ushort id in newItemIDs[i])
foreach (ushort id in receivedItemIdsFromClient[i])
{
Item newItem = id == 0 ? null : Entity.FindEntityByID(id) as Item;
prevItemInventories.Add(newItem?.ParentInventory);
@@ -94,7 +102,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
foreach (ushort id in newItemIDs[i])
foreach (ushort id in receivedItemIdsFromClient[i])
{
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
@@ -128,7 +136,7 @@ namespace Barotrauma
TryPutItem(item, i, true, true, c.Character, false);
for (int j = 0; j < capacity; j++)
{
if (slots[j].Contains(item) && !newItemIDs[j].Contains(item.ID))
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
{
slots[j].RemoveItem(item);
}
@@ -138,42 +146,48 @@ namespace Barotrauma
EnsureItemsInBothHands(c.Character);
receivedItemIds.Remove(c);
CreateNetworkEvent();
foreach (Inventory prevInventory in prevItemInventories.Distinct())
{
if (prevInventory != this) { prevInventory?.CreateNetworkEvent(); }
}
foreach (Item item in AllItems.Distinct())
foreach (Item item in AllItems.DistinctBy(it => it.Prefab))
{
if (item == null) { continue; }
if (!prevItems.Contains(item))
{
int amount = AllItems.Count(it => it.Prefab == item.Prefab && !prevItems.Contains(it));
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
if (Owner == c.Character)
{
HumanAIController.ItemTaken(item, c.Character);
GameServer.Log(GameServer.CharacterLogName(c.Character) + " picked up " + item.Name, ServerLog.MessageType.Inventory);
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} picked up {amountText}{item.Name}", ServerLog.MessageType.Inventory);
}
else
{
GameServer.Log(GameServer.CharacterLogName(c.Character) + " placed " + item.Name + " in " + Owner, ServerLog.MessageType.Inventory);
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} placed {amountText}{item.Name} in {Owner}", ServerLog.MessageType.Inventory);
}
}
}
foreach (Item item in prevItems.Distinct())
var droppedItems = prevItems.Where(it => it != null && !AllItems.Contains(it));
foreach (Item item in droppedItems.DistinctBy(it => it.Prefab))
{
if (item == null) { continue; }
if (!AllItems.Contains(item))
var matchingItems = prevItems.Where(it => it.Prefab == item.Prefab && !AllItems.Contains(it));
int amount = matchingItems.Count();
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
if (Owner == c.Character)
{
if (Owner == c.Character)
{
GameServer.Log(GameServer.CharacterLogName(c.Character) + " dropped " + item.Name, ServerLog.MessageType.Inventory);
}
else
{
GameServer.Log(GameServer.CharacterLogName(c.Character) + " removed " + item.Name + " from " + Owner, ServerLog.MessageType.Inventory);
}
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} dropped {amountText}{item.Name}", ServerLog.MessageType.Inventory);
}
else
{
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} removed {amountText}{item.Name} from {Owner}", ServerLog.MessageType.Inventory);
}
item.CreateDroppedStack(matchingItems, allowClientExecute: true);
}
}
@@ -203,10 +217,5 @@ namespace Barotrauma
bool IsSlotIndexOutOfBound(int index) => index < 0 || index >= slots.Length;
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
SharedWrite(msg, extraData);
}
}
}
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
@@ -18,9 +19,23 @@ namespace Barotrauma
get { return base.Prefab?.Sprite; }
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
private readonly Dictionary<Client, CampaignMode.InteractionType> campaignInteractionTypePerClient = new Dictionary<Client, CampaignMode.InteractionType>();
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType, IEnumerable<Client> targetClients)
{
GameMain.NetworkMember.CreateEntityEvent(this, new AssignCampaignInteractionEventData());
if (Removed) { return; }
if (targetClients == null || targetClients.None())
{
campaignInteractionTypePerClient.Clear();
}
else
{
foreach (Client client in targetClients)
{
campaignInteractionTypePerClient[client] = interactionType;
}
}
GameMain.NetworkMember.CreateEntityEvent(this, new AssignCampaignInteractionEventData(targetClients));
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
@@ -33,7 +48,7 @@ namespace Barotrauma
}
if (extraData is null) { throw error("event data was null"); }
if (!(extraData is IEventData itemEventData)) { throw error($"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
if (extraData is not IEventData itemEventData) { throw error($"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
msg.WriteRangedInteger((int)itemEventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (itemEventData)
@@ -44,7 +59,7 @@ namespace Barotrauma
{
throw error($"component index out of range ({componentIndex})");
}
if (!(components[componentIndex] is IServerSerializable serializableComponent))
if (components[componentIndex] is not IServerSerializable serializableComponent)
{
throw error($"component \"{components[componentIndex]}\" is not server serializable");
}
@@ -57,20 +72,28 @@ namespace Barotrauma
{
throw error($"container index out of range ({containerIndex})");
}
if (!(components[containerIndex] is ItemContainer itemContainer))
if (components[containerIndex] is not ItemContainer itemContainer)
{
throw error("component \"" + components[containerIndex] + "\" is not server serializable");
}
msg.WriteRangedInteger(containerIndex, 0, components.Count - 1);
msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
itemContainer.Inventory.ServerEventWrite(msg, c);
itemContainer.Inventory.ServerEventWrite(msg, c, inventoryStateEventData);
break;
case ItemStatusEventData statusEvent:
msg.WriteBoolean(statusEvent.LoadingRound);
msg.WriteSingle(condition);
break;
case AssignCampaignInteractionEventData _:
msg.WriteByte((byte)CampaignInteractionType);
case AssignCampaignInteractionEventData campaignInteractionData:
bool isVisibleToClient =
campaignInteractionData.TargetClients == null ||
campaignInteractionData.TargetClients.IsEmpty ||
campaignInteractionData.TargetClients.Contains(c);
msg.WriteBoolean(isVisibleToClient);
if (isVisibleToClient)
{
msg.WriteByte((byte)CampaignInteractionType);
}
break;
case ApplyStatusEffectEventData applyStatusEffectEventData:
{
@@ -131,6 +154,13 @@ namespace Barotrauma
}
}
break;
case DroppedStackEventData droppedStackEventData:
msg.WriteRangedInteger(droppedStackEventData.Items.Length, 0, Inventory.MaxPossibleStackSize);
foreach (Item droppedItem in droppedStackEventData.Items)
{
msg.WriteUInt16(droppedItem.ID);
}
break;
default:
throw error($"Unsupported event type {itemEventData.GetType().Name}");
}
@@ -257,10 +287,10 @@ namespace Barotrauma
msg.WriteInt32(idCardComponent.SubmarineSpecificID);
msg.WriteString(idCardComponent.OwnerName);
msg.WriteString(idCardComponent.OwnerTags);
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerBeardIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerHairIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerBeardIndex + 1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerHairIndex + 1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex + 1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex + 1));
msg.WriteColorR8G8B8(idCardComponent.OwnerHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerFacialHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerSkinColor);
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma;
partial class Item
{
private readonly struct DroppedStackEventData : IEventData
{
public EventType EventType => EventType.DroppedStack;
public readonly ImmutableArray<Item> Items;
public DroppedStackEventData(IEnumerable<Item> items)
{
Items = items.Distinct().ToImmutableArray();
}
}
}
@@ -0,0 +1,13 @@
using Barotrauma.Networking;
using System;
namespace Barotrauma
{
partial class ItemInventory : Inventory
{
public void ServerEventWrite(IWriteMessage msg, Client c, Item.InventoryStateEventData inventoryData)
{
SharedWrite(msg, inventoryData.SlotRange);
}
}
}