bf87006bc3
The clients delay applying the received remote state of an inventory they just put an item inside, because otherwise the inventory may revert to an old state if items are moved in rapid succession. I think the problem was that the delay was not applied to the inventory the item is _taken from_, so it was possible that the clients applied an old delayed state even though an item had just been moved out of the inventory. Another problem was that the server didn't create an event for the inventory the item was removed from, so if there were any wrong items in a client-side inventory, they would just be dropped instead of being moved back to the correct inventory.
308 lines
10 KiB
C#
308 lines
10 KiB
C#
using Barotrauma.Items.Components;
|
|
using Barotrauma.Networking;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class Inventory : IServerSerializable, IClientSerializable
|
|
{
|
|
public readonly Entity Owner;
|
|
|
|
protected int capacity;
|
|
|
|
public Item[] Items;
|
|
|
|
private bool isSubInventory;
|
|
|
|
public bool Locked;
|
|
|
|
private ushort[] receivedItemIDs;
|
|
protected float syncItemsDelay;
|
|
private CoroutineHandle syncItemsCoroutine;
|
|
|
|
public Inventory(Entity owner, int capacity, Vector2? centerPos = null, int slotsPerRow=5)
|
|
{
|
|
this.capacity = capacity;
|
|
|
|
this.Owner = owner;
|
|
|
|
|
|
Items = new Item[capacity];
|
|
|
|
#if CLIENT
|
|
this.slotsPerRow = slotsPerRow;
|
|
CenterPos = (centerPos==null) ? new Vector2(0.5f, 0.5f) : (Vector2)centerPos;
|
|
#endif
|
|
}
|
|
|
|
public int FindIndex(Item item)
|
|
{
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
if (Items[i] == item) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// Returns true if the item owns any of the parent inventories
|
|
public virtual bool ItemOwnsSelf(Item item)
|
|
{
|
|
if (Owner == null) return false;
|
|
if (!(Owner is Item)) return false;
|
|
Item ownerItem = Owner as Item;
|
|
if (ownerItem == item) return true;
|
|
if (ownerItem.ParentInventory == null) return false;
|
|
return ownerItem.ParentInventory.ItemOwnsSelf(item);
|
|
}
|
|
|
|
public virtual int FindAllowedSlot(Item item)
|
|
{
|
|
if (ItemOwnsSelf(item)) return -1;
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
//item is already in the inventory!
|
|
if (Items[i] == item) return -1;
|
|
}
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
if (Items[i] == null) return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public virtual bool CanBePut(Item item, int i)
|
|
{
|
|
if (ItemOwnsSelf(item)) return false;
|
|
if (i < 0 || i >= Items.Length) return false;
|
|
return (Items[i] == null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
|
|
/// </summary>
|
|
public virtual bool TryPutItem(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
|
{
|
|
int slot = FindAllowedSlot(item);
|
|
if (slot < 0) return false;
|
|
|
|
PutItem(item, slot, user, true, createNetworkEvent);
|
|
return true;
|
|
}
|
|
|
|
public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
|
|
{
|
|
if (Owner == null) return false;
|
|
if (CanBePut(item, i))
|
|
{
|
|
PutItem(item, i, user, true, createNetworkEvent);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
#if CLIENT
|
|
if (slots != null && createNetworkEvent) slots[i].ShowBorderHighlight(Color.Red, 0.1f, 0.9f);
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected virtual void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true)
|
|
{
|
|
if (Owner == null) return;
|
|
|
|
Inventory prevInventory = item.ParentInventory;
|
|
|
|
if (removeItem)
|
|
{
|
|
item.Drop(user);
|
|
if (item.ParentInventory != null) item.ParentInventory.RemoveItem(item);
|
|
}
|
|
|
|
Items[i] = item;
|
|
item.ParentInventory = this;
|
|
|
|
#if CLIENT
|
|
if (slots != null) slots[i].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
|
|
#endif
|
|
|
|
if (item.body != null)
|
|
{
|
|
item.body.Enabled = false;
|
|
}
|
|
|
|
if (createNetworkEvent)
|
|
{
|
|
CreateNetworkEvent();
|
|
//also delay syncing the inventory the item was inside
|
|
if (prevInventory != null && prevInventory != this) prevInventory.syncItemsDelay = 1.0f;
|
|
}
|
|
}
|
|
|
|
protected virtual void CreateNetworkEvent()
|
|
{
|
|
if (GameMain.Server != null)
|
|
{
|
|
GameMain.Server.CreateEntityEvent(Owner as IServerSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
|
}
|
|
#if CLIENT
|
|
else if (GameMain.Client != null)
|
|
{
|
|
syncItemsDelay = 1.0f;
|
|
GameMain.Client.CreateEntityEvent(Owner as IClientSerializable, new object[] { NetEntityEvent.Type.InventoryState });
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public Item FindItem(string itemName)
|
|
{
|
|
if (itemName == null) return null;
|
|
return Items.FirstOrDefault(i => i != null && (i.Prefab.NameMatches(itemName) || i.HasTag(itemName)));
|
|
}
|
|
|
|
public Item FindItem(string[] itemNames)
|
|
{
|
|
if (itemNames == null) return null;
|
|
|
|
foreach (string itemName in itemNames)
|
|
{
|
|
var item = FindItem(itemName);
|
|
if (item != null) return item;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public virtual void RemoveItem(Item item)
|
|
{
|
|
if (item == null) return;
|
|
|
|
//go through the inventory and remove the item from all slots
|
|
for (int n = 0; n < capacity; n++)
|
|
{
|
|
if (Items[n] != item) continue;
|
|
|
|
Items[n] = null;
|
|
item.ParentInventory = null;
|
|
}
|
|
}
|
|
|
|
public void ClientWrite(NetBuffer msg, object[] extraData = null)
|
|
{
|
|
ServerWrite(msg, null);
|
|
}
|
|
|
|
public void ServerRead(ClientNetObject type, NetBuffer msg, Barotrauma.Networking.Client c)
|
|
{
|
|
List<Item> prevItems = new List<Item>(Items);
|
|
ushort[] newItemIDs = new ushort[capacity];
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
newItemIDs[i] = msg.ReadUInt16();
|
|
}
|
|
|
|
if (c == null || c.Character == null) return;
|
|
|
|
if (!c.Character.CanAccessInventory(this))
|
|
{
|
|
//create a network event to correct the client's inventory state
|
|
//otherwise they may have an item in their inventory they shouldn't have been able to pick up,
|
|
//and receiving an event for that inventory later will cause the item to be dropped
|
|
CreateNetworkEvent();
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
var item = Entity.FindEntityByID(newItemIDs[i]) as Item;
|
|
if (item == null) continue;
|
|
if (item.ParentInventory != null && item.ParentInventory != this)
|
|
{
|
|
item.ParentInventory.CreateNetworkEvent();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
List<Inventory> prevItemInventories = new List<Inventory>(Items.Select(i => i?.ParentInventory));
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
Item newItem = newItemIDs[i] == 0 ? null : Entity.FindEntityByID(newItemIDs[i]) as Item;
|
|
prevItemInventories.Add(newItem?.ParentInventory);
|
|
|
|
if (newItemIDs[i] == 0 || (newItem != Items[i]))
|
|
{
|
|
if (Items[i] != null) Items[i].Drop();
|
|
System.Diagnostics.Debug.Assert(Items[i] == null);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
if (newItemIDs[i] > 0)
|
|
{
|
|
var item = Entity.FindEntityByID(newItemIDs[i]) as Item;
|
|
if (item == null || item == Items[i]) continue;
|
|
|
|
if (GameMain.Server != null)
|
|
{
|
|
var holdable = item.GetComponent<Holdable>();
|
|
if (holdable != null && !holdable.CanBeDeattached()) continue;
|
|
|
|
if (!item.CanClientAccess(c)) continue;
|
|
}
|
|
TryPutItem(item, i, true, true, c.Character, false);
|
|
}
|
|
}
|
|
|
|
CreateNetworkEvent();
|
|
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
|
{
|
|
if (prevInventory != this) prevInventory?.CreateNetworkEvent();
|
|
}
|
|
|
|
foreach (Item item in Items.Distinct())
|
|
{
|
|
if (item == null) continue;
|
|
if (!prevItems.Contains(item))
|
|
{
|
|
if (Owner == c.Character)
|
|
{
|
|
GameServer.Log(c.Character.LogName+ " picked up " + item.Name, ServerLog.MessageType.Inventory);
|
|
}
|
|
else
|
|
{
|
|
GameServer.Log(c.Character.LogName + " placed " + item.Name + " in " + Owner, ServerLog.MessageType.Inventory);
|
|
}
|
|
}
|
|
}
|
|
foreach (Item item in prevItems.Distinct())
|
|
{
|
|
if (item == null) continue;
|
|
if (!Items.Contains(item))
|
|
{
|
|
if (Owner == c.Character)
|
|
{
|
|
GameServer.Log(c.Character.LogName + " dropped " + item.Name, ServerLog.MessageType.Inventory);
|
|
}
|
|
else
|
|
{
|
|
GameServer.Log(c.Character.LogName + " removed " + item.Name + " from " + Owner, ServerLog.MessageType.Inventory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
|
{
|
|
for (int i = 0; i < capacity; i++)
|
|
{
|
|
msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID));
|
|
}
|
|
}
|
|
}
|
|
}
|