using Microsoft.Xna.Framework; using System; namespace Barotrauma.Networking { partial class OrderChatMessage : ChatMessage { public readonly Order Order; //who was this order given to public readonly Character TargetCharacter; //which entity is this order referring to (hull, reactor, railgun controller, etc) public ISpatialEntity TargetEntity => Order.TargetSpatialEntity; //additional instructions (power up, fire at will, etc) public Identifier OrderOption => Order.Option; public int OrderPriority => Order.ManualPriority; /// /// Used when the order targets a wall /// public int? WallSectionIndex => Order.WallSectionIndex; public bool IsNewOrder { get; } /// /// Same as calling , /// but the text parameter is set using /// public OrderChatMessage(Order order, Character targetCharacter, Character sender, bool isNewOrder = true) : this(order, order?.GetChatMessage(targetCharacter?.DisplayName, (order.TargetEntity as Hull ?? sender?.CurrentHull)?.DisplayName?.Value, givingOrderToSelf: targetCharacter == sender, orderOption: order.Option, isNewOrder: isNewOrder), targetCharacter, sender, isNewOrder) { } public OrderChatMessage(Order order, string text, Character targetCharacter, Entity sender, bool isNewOrder = true) : base(NameFromEntityOrNull(sender), text, ChatMessageType.Order, sender, GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == sender)) { Order = order; TargetCharacter = targetCharacter; IsNewOrder = isNewOrder; } public static string NameFromEntityOrNull(Entity entity) => entity switch { null => null, Character character => character.DisplayName, Item it => it.Name, _ => throw new ArgumentException("Entity is not a character or item", nameof(entity)) }; public static void WriteOrder(IWriteMessage msg, Order order, Entity targetCharacter, bool isNewOrder) { msg.WriteIdentifier(order.Prefab.Identifier); msg.WriteUInt16(targetCharacter == null ? (UInt16)0 : targetCharacter.ID); msg.WriteUInt16(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0); // The option of a Dismiss order is written differently so we know what order we target // now that the game supports multiple current orders simultaneously if (!order.IsDismissal) { msg.WriteByte((byte)order.Options.IndexOf(order.Option)); } else { if (order.Option != Identifier.Empty) { msg.WriteBoolean(true); string[] dismissedOrder = order.Option.Value.Split('.'); msg.WriteByte((byte)dismissedOrder.Length); if (dismissedOrder.Length > 0) { Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier(); var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier]; msg.WriteIdentifier(dismissedOrderIdentifier); if (dismissedOrder.Length > 1) { Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier(); msg.WriteByte((byte)orderPrefab.Options.IndexOf(dismissedOrderOption)); } } } else { // If the order option is not specified for a Dismiss order, // we dismiss all current orders for the character msg.WriteBoolean(false); } } msg.WriteByte((byte)order.ManualPriority); msg.WriteByte((byte)order.TargetType); if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget) { msg.WriteBoolean(true); msg.WriteSingle(orderTarget.Position.X); msg.WriteSingle(orderTarget.Position.Y); msg.WriteUInt16(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID); } else { msg.WriteBoolean(false); if (order.TargetType == Order.OrderTargetType.WallSection) { msg.WriteByte((byte)(order.WallSectionIndex ?? 0)); } } msg.WriteBoolean(isNewOrder); } private void WriteOrder(IWriteMessage msg) { WriteOrder(msg, Order, TargetCharacter, IsNewOrder); } public readonly struct OrderMessageInfo { public Identifier OrderIdentifier { get; } public OrderPrefab OrderPrefab => OrderPrefab.Prefabs[OrderIdentifier]; public Identifier OrderOption { get; } public int? OrderOptionIndex { get; } public Character TargetCharacter { get; } public Order.OrderTargetType TargetType { get; } public Entity TargetEntity { get; } public OrderTarget TargetPosition { get; } public int? WallSectionIndex { get; } public int Priority { get; } public bool IsNewOrder { get; } public OrderMessageInfo(Identifier orderIdentifier, Identifier orderOption, int? orderOptionIndex, Character targetCharacter, Order.OrderTargetType targetType, Entity targetEntity, OrderTarget targetPosition, int? wallSectionIndex, int orderPriority, bool isNewOrder) { OrderIdentifier = orderIdentifier; OrderOption = orderOption; OrderOptionIndex = orderOptionIndex; TargetCharacter = targetCharacter; TargetType = targetType; TargetEntity = targetEntity; TargetPosition = targetPosition; WallSectionIndex = wallSectionIndex; Priority = orderPriority; IsNewOrder = isNewOrder; } } public static OrderMessageInfo ReadOrder(IReadMessage msg) { Identifier orderIdentifier = msg.ReadIdentifier(); ushort targetCharacterId = msg.ReadUInt16(); Character targetCharacter = targetCharacterId != Entity.NullEntityID ? Entity.FindEntityByID(targetCharacterId) as Character : null; ushort targetEntityId = msg.ReadUInt16(); Entity targetEntity = targetEntityId != Entity.NullEntityID ? Entity.FindEntityByID(targetEntityId) : null; int? optionIndex = null; Identifier orderOption = Identifier.Empty; // The option of a Dismiss order is written differently so we know what order we target // now that the game supports multiple current orders simultaneously if (orderIdentifier != Identifier.Empty) { var orderPrefab = OrderPrefab.Prefabs[orderIdentifier]; if (!orderPrefab.IsDismissal) { optionIndex = msg.ReadByte(); } // Does the dismiss order have a specified target? else if (msg.ReadBoolean()) { int identifierCount = msg.ReadByte(); if (identifierCount > 0) { Identifier dismissedOrderIdentifier = msg.ReadIdentifier(); OrderPrefab dismissedOrderPrefab = null; if (dismissedOrderIdentifier != Identifier.Empty) { dismissedOrderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier]; orderOption = dismissedOrderPrefab.Identifier; } if (identifierCount > 1) { int dismissedOrderOptionIndex = msg.ReadByte(); if (dismissedOrderPrefab != null) { var options = dismissedOrderPrefab.Options; if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length) { orderOption = $"{orderOption.Value}.{options[dismissedOrderOptionIndex]}".ToIdentifier(); } } } } } } else { optionIndex = msg.ReadByte(); } int orderPriority = msg.ReadByte(); OrderTarget orderTargetPosition = null; Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte(); int? wallSectionIndex = null; if (msg.ReadBoolean()) { float x = msg.ReadSingle(); float y = msg.ReadSingle(); ushort hullId = msg.ReadUInt16(); var hull = hullId != Entity.NullEntityID ? Entity.FindEntityByID(hullId) as Hull : null; orderTargetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true); } else if (orderTargetType == Order.OrderTargetType.WallSection) { wallSectionIndex = msg.ReadByte(); } bool isNewOrder = msg.ReadBoolean(); return new OrderMessageInfo(orderIdentifier, orderOption, optionIndex, targetCharacter, orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority, isNewOrder); } } }