Merge remote-tracking branch 'upstream/master' into develop
This commit is contained in:
@@ -18,10 +18,13 @@ namespace Barotrauma.Items.Components
|
||||
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"/>.
|
||||
/// When in multiplayer and the circuit box are loaded from the player inventory,
|
||||
/// We only load the components from XML on the 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;
|
||||
@@ -280,6 +283,15 @@ namespace Barotrauma.Items.Components
|
||||
CreateServerEvent(data with { Size = Vector2.Max(data.Size, CircuitBoxLabelNode.MinSize) });
|
||||
break;
|
||||
}
|
||||
case CircuitBoxOpcode.RenameConnections:
|
||||
{
|
||||
var data = INetSerializableStruct.Read<CircuitBoxRenameConnectionLabelsEvent>(msg);
|
||||
if (!CanAccessAndUnlocked(c)) { break; }
|
||||
|
||||
RenameConnectionLabelsInternal(data.Type, data.Override.ToDictionary());
|
||||
CreateServerEvent(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
|
||||
}
|
||||
@@ -327,6 +339,7 @@ namespace Barotrauma.Items.Components
|
||||
Components: Components.Select(EventFromComponent).ToImmutableArray(),
|
||||
Wires: Wires.Select(EventFromWire).ToImmutableArray(),
|
||||
Labels: Labels.Select(EventFromLabel).ToImmutableArray(),
|
||||
LabelOverrides: InputOutputNodes.Select(EventFromLabelOverride).ToImmutableArray(),
|
||||
InputPos: inputPos,
|
||||
OutputPos: outputPos);
|
||||
|
||||
@@ -347,6 +360,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
static CircuitBoxServerAddLabelEvent EventFromLabel(CircuitBoxLabelNode label)
|
||||
=> new(label.ID, label.Position, label.Size, label.Color, label.HeaderText, label.BodyText);
|
||||
|
||||
static CircuitBoxRenameConnectionLabelsEvent EventFromLabelOverride(CircuitBoxInputOutputNode node)
|
||||
=> new(node.NodeType, node.ConnectionLabelOverrides.ToNetDictionary());
|
||||
}
|
||||
|
||||
// we don't care about updating the view on server
|
||||
|
||||
+13
-13
@@ -98,7 +98,7 @@ namespace Barotrauma.Items.Components
|
||||
//existing wire not in the list of new wires -> disconnect it
|
||||
if (!wires[i].Contains(existingWire))
|
||||
{
|
||||
if (existingWire.Locked)
|
||||
if (existingWire.Locked || existingWire.Item.IsLayerHidden)
|
||||
{
|
||||
//this should not be possible unless the client is running a modified version of the game
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " attempted to disconnect a locked wire from " +
|
||||
@@ -166,18 +166,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Wire disconnectedWire in DisconnectedWires.ToList())
|
||||
{
|
||||
if (disconnectedWire.Connections[0] == null &&
|
||||
disconnectedWire.Connections[1] == null &&
|
||||
!clientSideDisconnectedWires.Contains(disconnectedWire) &&
|
||||
disconnectedWire.Item.ParentInventory == null)
|
||||
{
|
||||
disconnectedWire.Item.Drop(c.Character);
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " dropped " + disconnectedWire.Name, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
}
|
||||
|
||||
//go through new wires
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
@@ -205,6 +193,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Wire disconnectedWire in DisconnectedWires.ToList())
|
||||
{
|
||||
if (disconnectedWire.Connections[0] == null &&
|
||||
disconnectedWire.Connections[1] == null &&
|
||||
!clientSideDisconnectedWires.Contains(disconnectedWire) &&
|
||||
disconnectedWire.Item.ParentInventory == null)
|
||||
{
|
||||
disconnectedWire.Item.Drop(c.Character);
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " dropped " + disconnectedWire.Name, ServerLog.MessageType.Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
|
||||
@@ -11,153 +11,42 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly Dictionary<Client, List<ushort>[]> receivedItemIds = new Dictionary<Client, List<ushort>[]>();
|
||||
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
public void ServerEventRead(IReadMessage msg, Client sender)
|
||||
{
|
||||
if (!receivedItemIds.TryGetValue(c, out List<ushort>[] receivedItemIdsFromClient))
|
||||
// if the dictionary doesn't contain the client entry, create a new one
|
||||
if (!receivedItemIds.TryGetValue(sender, out List<ushort>[] receivedItemIdsFromClient))
|
||||
{
|
||||
receivedItemIdsFromClient = new List<ushort>[capacity];
|
||||
receivedItemIds.Add(c, receivedItemIdsFromClient);
|
||||
receivedItemIds.Add(sender, receivedItemIdsFromClient);
|
||||
}
|
||||
|
||||
// Read some item ids from the message. readyToApply waits for all the data from possible multiple messages.
|
||||
SharedRead(msg, receivedItemIdsFromClient, out bool readyToApply);
|
||||
if (!readyToApply) { return; }
|
||||
|
||||
if (c == null || c.Character == null) { return; }
|
||||
|
||||
bool accessible = c.Character.CanAccessInventory(this);
|
||||
if (this is CharacterInventory characterInventory && accessible)
|
||||
if (sender == null || sender.Character == null) { return; }
|
||||
|
||||
if (!IsInventoryAccessible())
|
||||
{
|
||||
if (Owner == null || Owner is not Character ownerCharacter)
|
||||
{
|
||||
accessible = false;
|
||||
}
|
||||
else if (!characterInventory.AccessibleWhenAlive && !ownerCharacter.IsDead && !characterInventory.AccessibleByOwner)
|
||||
{
|
||||
accessible = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accessible)
|
||||
{
|
||||
//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++)
|
||||
{
|
||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
||||
{
|
||||
if (Entity.FindEntityByID(id) is not Item item) { continue; }
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
if (item.ParentInventory != null && item.ParentInventory != this)
|
||||
{
|
||||
item.ParentInventory.CreateNetworkEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateCorrectiveNetworkEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
//we need to check which of the items the client can access at this point, before we start shuffling things around
|
||||
//otherwise if you're e.g. holding an item to access a cabinet, and picking up an item from the cabinet unequips the item you're holding,
|
||||
//you would fail to pick up the item because it gets unequipped before checking whether you can access the cabinet.
|
||||
Dictionary<Item, bool> canAccessItem = new Dictionary<Item, bool>();
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
||||
{
|
||||
if (Entity.FindEntityByID(id) is not Item item) { continue; }
|
||||
canAccessItem[item] = item.CanClientAccess(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<Item> prevItems = new List<Item>(AllItems.Distinct());
|
||||
List<Inventory> prevItemInventories = new List<Inventory>() { this };
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
foreach (Item item in slots[i].Items.ToList())
|
||||
{
|
||||
if (!receivedItemIdsFromClient[i].Contains(item.ID) && item.IsInteractable(c.Character))
|
||||
{
|
||||
Item droppedItem = item;
|
||||
Entity prevOwner = Owner;
|
||||
Inventory previousInventory = droppedItem.ParentInventory;
|
||||
droppedItem.Drop(null);
|
||||
droppedItem.PreviousParentInventory = previousInventory;
|
||||
|
||||
var previousCharacterInventory = prevOwner switch
|
||||
{
|
||||
Item itemInventory => itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory,
|
||||
Character character => character.Inventory,
|
||||
_ => null
|
||||
};
|
||||
//we need to check which of the items the client (sender) can access at this point, before we start shuffling things around
|
||||
//otherwise if you're e.g. holding an item to access a cabinet, and picking up an item from the cabinet unequips the item you're holding,
|
||||
//you would fail to pick up the item because it gets unequipped before checking whether you can access the cabinet.
|
||||
var itemAccessibility = GetItemAccessibility();
|
||||
|
||||
HandleRemovedItems();
|
||||
|
||||
if (previousCharacterInventory != null && previousCharacterInventory != c.Character?.Inventory)
|
||||
{
|
||||
GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousCharacterInventory, c, droppedItem);
|
||||
}
|
||||
|
||||
if (droppedItem.body != null && prevOwner != null)
|
||||
{
|
||||
droppedItem.body.SetTransform(prevOwner.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
HandleAddedItems();
|
||||
|
||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
||||
{
|
||||
Item newItem = id == 0 ? null : Entity.FindEntityByID(id) as Item;
|
||||
prevItemInventories.Add(newItem?.ParentInventory);
|
||||
}
|
||||
}
|
||||
EnsureItemsInBothHands(sender.Character);
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
||||
{
|
||||
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
|
||||
|
||||
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
||||
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(c.Character))
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
||||
item.Prefab.ContentPackage);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
||||
|
||||
if (!prevItems.Contains(item) && !canAccessItem[item] &&
|
||||
(c.Character == null || item.PreviousParentInventory == null || !c.Character.CanAccessInventory(item.PreviousParentInventory)))
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.NewMessage($"Client {c.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||
#endif
|
||||
if (item.body != null && !c.PendingPositionUpdates.Contains(item))
|
||||
{
|
||||
c.PendingPositionUpdates.Enqueue(item);
|
||||
}
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
TryPutItem(item, i, true, true, c.Character, false);
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
||||
{
|
||||
slots[j].RemoveItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnsureItemsInBothHands(c.Character);
|
||||
|
||||
receivedItemIds.Remove(c);
|
||||
receivedItemIds.Remove(sender);
|
||||
|
||||
CreateNetworkEvent();
|
||||
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
||||
@@ -165,43 +54,211 @@ namespace Barotrauma
|
||||
if (prevInventory != this) { prevInventory?.CreateNetworkEvent(); }
|
||||
}
|
||||
|
||||
foreach (Item item in AllItems.DistinctBy(it => it.Prefab))
|
||||
ServerLogAddedItems();
|
||||
|
||||
ServerLogRemovedItems();
|
||||
|
||||
#region local functions
|
||||
bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited);
|
||||
|
||||
void CreateCorrectiveNetworkEvent()
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (!prevItems.Contains(item))
|
||||
// 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++)
|
||||
{
|
||||
int amount = AllItems.Count(it => it.Prefab == item.Prefab && !prevItems.Contains(it));
|
||||
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
|
||||
if (Owner == c.Character)
|
||||
foreach (ushort itemId in receivedItemIdsFromClient[i])
|
||||
{
|
||||
HumanAIController.ItemTaken(item, c.Character);
|
||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} picked up {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
||||
if (Entity.FindEntityByID(itemId) is not Item item) { continue; }
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
if (item.ParentInventory != null && item.ParentInventory != this)
|
||||
{
|
||||
item.ParentInventory.CreateNetworkEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Item, bool> GetItemAccessibility()
|
||||
{
|
||||
Dictionary<Item, bool> itemAccessibility = new Dictionary<Item, bool>();
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
// for every item that the new inventory state contains
|
||||
foreach (ushort itemId in receivedItemIdsFromClient[i])
|
||||
{
|
||||
// if there is no such item, skip
|
||||
if (Entity.FindEntityByID(itemId) is not Item item) { continue; }
|
||||
// add entry: can the sender access the item?
|
||||
itemAccessibility[item] = item.CanClientAccess(sender);
|
||||
}
|
||||
}
|
||||
|
||||
// we now have accessibility for every item in the new inventory state
|
||||
// but not for the items that were in the inventory before and perhaps dropped, so let's add those as well
|
||||
foreach (var item in prevItems)
|
||||
{
|
||||
if (!itemAccessibility.ContainsKey(item))
|
||||
{
|
||||
itemAccessibility[item] = item.CanClientAccess(sender);
|
||||
}
|
||||
}
|
||||
|
||||
return itemAccessibility;
|
||||
}
|
||||
|
||||
void HandleRemovedItems()
|
||||
{
|
||||
for (int slotIndex = 0; slotIndex < capacity; slotIndex++)
|
||||
{
|
||||
foreach (Item item in slots[slotIndex].Items.ToList())
|
||||
{
|
||||
bool shouldBeRemoved = !receivedItemIdsFromClient[slotIndex].Contains(item.ID) &&
|
||||
item.IsInteractable(sender.Character); // item is interactable to sender: not hidden and player team
|
||||
if (shouldBeRemoved)
|
||||
{
|
||||
bool itemAccessDenied = prevItems.Contains(item) && // if the item was in the inventory before
|
||||
!itemAccessibility[item] && // and the sender is not allowed to access it
|
||||
(item.PreviousParentInventory == null || // and either the item has no previous inventory
|
||||
!sender.Character.CanAccessInventory(item.PreviousParentInventory)); // or the sender can't access the previous inventory
|
||||
|
||||
if (itemAccessDenied)
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.NewMessage($"Client {sender.Name} failed to drop item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
Item droppedItem = item;
|
||||
Entity prevOwner = Owner;
|
||||
Inventory previousInventory = droppedItem.ParentInventory;
|
||||
droppedItem.Drop(null);
|
||||
droppedItem.PreviousParentInventory = previousInventory;
|
||||
|
||||
var previousCharacterInventory = prevOwner switch
|
||||
{
|
||||
Item itemInventory => itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory,
|
||||
Character character => character.Inventory,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (previousCharacterInventory != null && previousCharacterInventory != sender.Character?.Inventory)
|
||||
{
|
||||
GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousCharacterInventory, sender, droppedItem);
|
||||
}
|
||||
|
||||
if (droppedItem.body != null && prevOwner != null)
|
||||
{
|
||||
droppedItem.body.SetTransform(prevOwner.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ushort id in receivedItemIdsFromClient[slotIndex])
|
||||
{
|
||||
Item newItem = id == 0 ? null : Entity.FindEntityByID(id) as Item;
|
||||
prevItemInventories.Add(newItem?.ParentInventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleAddedItems()
|
||||
{
|
||||
for (int slotIndex = 0; slotIndex < capacity; slotIndex++)
|
||||
{
|
||||
foreach (ushort id in receivedItemIdsFromClient[slotIndex])
|
||||
{
|
||||
if (Entity.FindEntityByID(id) is not Item item || slots[slotIndex].Contains(item)) { continue; }
|
||||
|
||||
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
||||
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(sender.Character))
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {sender.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
||||
item.Prefab.ContentPackage);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var holdable = item.GetComponent<Holdable>();
|
||||
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
||||
|
||||
bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] &&
|
||||
(sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
||||
|
||||
if (itemAccessDenied)
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.NewMessage($"Client {sender.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||
#endif
|
||||
if (item.body != null && !sender.PendingPositionUpdates.Contains(item))
|
||||
{
|
||||
sender.PendingPositionUpdates.Enqueue(item);
|
||||
}
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
TryPutItem(item, slotIndex, true, true, sender.Character, false);
|
||||
for (int j = 0; j < capacity; j++)
|
||||
{
|
||||
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
||||
{
|
||||
slots[j].RemoveItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerLogAddedItems()
|
||||
{
|
||||
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 == sender.Character)
|
||||
{
|
||||
HumanAIController.ItemTaken(item, sender.Character);
|
||||
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} picked up {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} placed {amountText}{item.Name} in the inventory of {Owner}", ServerLog.MessageType.Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerLogRemovedItems()
|
||||
{
|
||||
var droppedItems = prevItems.Where(it => it != null && !AllItems.Contains(it));
|
||||
foreach (Item item in droppedItems.DistinctBy(it => it.Prefab))
|
||||
{
|
||||
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 == sender.Character)
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} dropped {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} placed {amountText}{item.Name} in {Owner}", ServerLog.MessageType.Inventory);
|
||||
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} removed {amountText}{item.Name} from the inventory of {Owner}", ServerLog.MessageType.Inventory);
|
||||
}
|
||||
item.CreateDroppedStack(matchingItems, allowClientExecute: true);
|
||||
}
|
||||
}
|
||||
|
||||
var droppedItems = prevItems.Where(it => it != null && !AllItems.Contains(it));
|
||||
foreach (Item item in droppedItems.DistinctBy(it => it.Prefab))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
private void EnsureItemsInBothHands(Character character)
|
||||
{
|
||||
if (this is not CharacterInventory charInv) { return; }
|
||||
|
||||
Reference in New Issue
Block a user