Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.85")]
[assembly: AssemblyFileVersion("0.1.85")]
[assembly: AssemblyVersion("0.1.86")]
[assembly: AssemblyFileVersion("0.1.86")]
2 changes: 1 addition & 1 deletion Source/Core/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace PraetorisClient
public class PraetorisClientPlugin : BaseUnityPlugin
{
private const string ModName = "PraetorisClient";
private const string ModVersion = "0.1.85";
private const string ModVersion = "0.1.86";
private const string Author = "warpalicious";
private const string ModGUID = Author + "." + ModName;
private const string EpicLootGuid = "randyknapp.mods.epicloot";
Expand Down
56 changes: 14 additions & 42 deletions Source/ServerChest/ServerChest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ internal sealed class ServerChest : MonoBehaviour
internal const string OwnerNameLookupKey = "ServerChestOwnerNameKey";
internal const string OwnerPlatformIdKey = "ServerChestOwnerPlatformId";
internal const string OwnerLookupKey = "ServerChestOwnerKey";
internal const int MaxColumns = 8;
internal const int MaxRows = 8;
internal const int MaxSlots = MaxColumns * MaxRows;
internal const int Columns = 8;

private static readonly Dictionary<Inventory, ServerChest> InventoryOwners = new();
private static readonly FieldInfoWrapper<int> InventoryWidth = new(typeof(Inventory), "m_width");
private static readonly FieldInfoWrapper<int> InventoryHeight = new(typeof(Inventory), "m_height");
private static bool _suppressInventoryChanged;

private Container? _container;
private Inventory? _inventory;
private ZNetView? _nview;
internal byte[]? LoadedItemData;

private void Awake()
{
Expand All @@ -43,7 +41,6 @@ private void OnDestroy()
{
if (_inventory != null)
{
_inventory.m_onChanged -= OnInventoryChanged;
InventoryOwners.Remove(_inventory);
}
}
Expand All @@ -63,28 +60,13 @@ private void RefreshInventoryRegistration()

if (_inventory != null)
{
_inventory.m_onChanged -= OnInventoryChanged;
InventoryOwners.Remove(_inventory);
}

_inventory = inventory;
InventoryOwners[inventory] = this;
ApplyMaxInventoryShape(inventory);
ResizeToFit(inventory);
ServerChestLog.Debug("registered live inventory zdo=" + GetZdoId() + " items=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture));
inventory.m_onChanged += OnInventoryChanged;
}

private void OnInventoryChanged()
{
if (_suppressInventoryChanged || _inventory == null || _nview == null || !_nview.IsValid() || !_nview.IsOwner())
{
return;
}

CompactInventory(_inventory);
ApplyMaxInventoryShape(_inventory);
SaveInventoryToZdo(_nview.GetZDO(), _inventory);
ServerChestLog.Debug("live inventory changed zdo=" + _nview.GetZDO().m_uid + " stacks=" + _inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " items=" + _inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture));
}

private void OnDestroyed()
Expand Down Expand Up @@ -198,9 +180,11 @@ internal static string OwnerLookup(ZDO zdo)
return zdo.GetString(OwnerLookupKey);
}

internal static void ApplyMaxInventoryShape(Inventory inventory)
internal static void ResizeToFit(Inventory inventory, int additionalSlots = 0)
{
ApplyInventoryShape(inventory, MaxColumns, MaxRows);
int slots = checked(inventory.NrOfItems() + additionalSlots);
int rows = Math.Max(1, slots / Columns + (slots % Columns == 0 ? 0 : 1));
ApplyInventoryShape(inventory, Columns, rows);
}

internal static void ApplyInventoryShape(Inventory inventory, int columns, int rows)
Expand All @@ -211,7 +195,7 @@ internal static void ApplyInventoryShape(Inventory inventory, int columns, int r

internal static void CompactInventory(Inventory inventory)
{
CompactInventory(inventory, MaxColumns);
CompactInventory(inventory, Columns);
}

internal static void CompactInventory(Inventory inventory, int columns)
Expand All @@ -225,22 +209,23 @@ internal static void CompactInventory(Inventory inventory, int columns)

internal static Inventory LoadInventoryFromZdo(ZDO zdo)
{
Inventory inventory = new("ServerChest", null, MaxColumns, MaxRows);
Inventory inventory = new("ServerChest", null, Columns, 1);
byte[]? data = zdo.GetByteArray(ZDOVars.s_items);
if (data != null && data.Length > 0)
{
try
{
inventory.Load(new ZPackage(data));
ServerChestStorage.Load(inventory, new ZPackage(data));
}
catch (Exception ex)
{
PraetorisClientPlugin.Log.LogWarning("Failed to load ServerChest inventory from ZDO " + zdo.m_uid + ": " + ex.Message);
throw;
}
}

ApplyMaxInventoryShape(inventory);
CompactInventory(inventory);
ResizeToFit(inventory);
ServerChestLog.Debug("loaded inventory zdo=" + zdo.m_uid + " stacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " items=" + inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture) + " dataLength=" + (data?.Length ?? 0).ToString(CultureInfo.InvariantCulture));
return inventory;
}
Expand All @@ -249,7 +234,8 @@ internal static void SaveInventoryToZdo(ZDO zdo, Inventory inventory)
{
CompactInventory(inventory);
ZPackage package = new();
inventory.Save(package);
ResizeToFit(inventory);
ServerChestStorage.Save(inventory, package);
byte[] data = package.GetArray();
zdo.Set(ZDOVars.s_items, data);
ServerChestLog.Debug("saved inventory zdo=" + zdo.m_uid + " stacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " items=" + inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture) + " dataLength=" + data.Length.ToString(CultureInfo.InvariantCulture));
Expand All @@ -272,20 +258,6 @@ internal static List<ZDO> FindAllZdos()
return result;
}

internal static void WithSuppressedInventoryChanged(Action action)
{
bool previous = _suppressInventoryChanged;
_suppressInventoryChanged = true;
try
{
action();
}
finally
{
_suppressInventoryChanged = previous;
}
}

internal static void ShowMessage(string message)
{
if (Player.m_localPlayer != null)
Expand Down
7 changes: 4 additions & 3 deletions Source/ServerChest/ServerChestPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private static bool Prefix(InventoryGrid __instance, Inventory inventory)
return true;
}

ServerChest.ApplyMaxInventoryShape(inventory);
ServerChest.ResizeToFit(inventory);
return true;
}
}
Expand All @@ -53,6 +53,7 @@ private static bool Prefix(InventoryGrid __instance, Inventory inventory)
internal static class ServerChestInventoryGuiUpdateContainerPatch
{
private static readonly FieldInfo? CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");
private static readonly FieldInfo GridElementsField = AccessTools.Field(typeof(InventoryGrid), "m_elements");
private static bool GridWasCompacted;

private static void Postfix(InventoryGui __instance)
Expand Down Expand Up @@ -86,7 +87,7 @@ private static void Postfix(InventoryGui __instance)
if (containerGrid.m_gridRoot != null)
{
containerGrid.m_gridRoot.gameObject.SetActive(visibleSlots > 0);
float visibleRows = visibleSlots <= 0 ? 0f : (float)Math.Ceiling(visibleSlots / (double)ServerChest.MaxColumns);
float visibleRows = visibleSlots <= 0 ? 0f : (float)Math.Ceiling(visibleSlots / (double)ServerChest.Columns);
containerGrid.m_gridRoot.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, visibleRows * containerGrid.m_elementSpace);
}

Expand All @@ -106,7 +107,7 @@ private static void RestoreGrid(InventoryGrid inventoryGrid)

private static void SetGridElementsActive(InventoryGrid inventoryGrid, int visibleSlots)
{
List<InventoryElement> elements = inventoryGrid.m_elements;
List<InventoryElement> elements = (List<InventoryElement>)GridElementsField.GetValue(inventoryGrid);
for (int index = 0; index < elements.Count; index++)
{
InventoryElement element = elements[index];
Expand Down
4 changes: 2 additions & 2 deletions Source/ServerChest/ServerChestPiece.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ private static void ConfigurePrefab(GameObject prefab)
if (container != null)
{
container.m_name = "Server Chest";
container.m_width = ServerChest.MaxColumns;
container.m_height = ServerChest.MaxRows;
container.m_width = ServerChest.Columns;
container.m_height = 1;
container.m_defaultItems = new DropTable();
container.m_autoDestroyEmpty = false;
container.m_discoverStat = PlayerStatType.None;
Expand Down
105 changes: 25 additions & 80 deletions Source/ServerChest/ServerChestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,44 +105,27 @@ internal static CommandResult SendItems(string characterName, IReadOnlyList<Send
return lookup;
}

Inventory inventory = ServerChest.LoadInventoryFromZdo(zdo);
ServerChestLog.Debug("send start owner=" + characterName + " zdo=" + zdo.m_uid + " requestItems=" + DescribeSendItems(items) + " existingStacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " existingItems=" + inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture));
int requiredAdditionalSlots = CountRequiredNewSlots(inventory, items);
ServerChestLog.Debug("send capacity owner=" + characterName + " requiredAdditionalSlots=" + requiredAdditionalSlots.ToString(CultureInfo.InvariantCulture) + " currentStacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " maxSlots=" + ServerChest.MaxSlots.ToString(CultureInfo.InvariantCulture));
if (inventory.NrOfItems() + requiredAdditionalSlots > ServerChest.MaxSlots)
Inventory inventory;
try
{
return CommandResult.Fail("ServerChest does not have enough delivery capacity for this request.");
inventory = ServerChest.LoadInventoryFromZdo(zdo);
}

catch (Exception)
{
return CommandResult.Fail("ServerChest contents could not be loaded. No delivery was saved. Check the server log for details.");
}
ServerChestLog.Debug("send start owner=" + characterName + " zdo=" + zdo.m_uid + " requestItems=" + DescribeSendItems(items) + " existingStacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " existingItems=" + inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture));
zdo.SetOwner(ZNet.GetUID());
bool addFailed = false;
string addFailure = "";
ServerChest.WithSuppressedInventoryChanged(() =>
foreach (SendItem item in items)
{
ServerChest.ApplyMaxInventoryShape(inventory);
foreach (SendItem item in items)
if (!TryAddItemAmount(inventory, item, out string error))
{
if (!TryAddItemAmount(inventory, item, out string error))
{
addFailed = true;
addFailure = error;
return;
}
return CommandResult.Fail("ServerChest delivery failed while adding items: " + error + " No delivery was saved.");
}

if (!addFailed)
{
ServerChest.SaveInventoryToZdo(zdo, inventory);
}
});

if (addFailed)
{
ServerChestLog.Debug("send failed owner=" + characterName + " zdo=" + zdo.m_uid + " error=" + addFailure);
return CommandResult.Fail("ServerChest delivery failed while adding items: " + addFailure + " No delivery was saved.");
}
ServerChest.SaveInventoryToZdo(zdo, inventory);

int totalAmount = items.Sum(item => item.Amount);
long totalAmount = items.Sum(item => (long)item.Amount);
ServerChestLog.Debug("send complete owner=" + ServerChest.OwnerName(zdo) + " zdo=" + zdo.m_uid + " delivered=" + totalAmount.ToString(CultureInfo.InvariantCulture) + " finalStacks=" + inventory.NrOfItems().ToString(CultureInfo.InvariantCulture) + " finalItems=" + inventory.NrOfItemsIncludingStacks().ToString(CultureInfo.InvariantCulture));
return CommandResult.Ok("Delivered " + totalAmount.ToString(CultureInfo.InvariantCulture) + " item(s) to ServerChest for " + ServerChest.OwnerName(zdo) + ".");
}
Expand All @@ -155,11 +138,19 @@ internal static CommandResult Status(string characterName)
return lookup;
}

Inventory inventory = ServerChest.LoadInventoryFromZdo(zdo);
Inventory inventory;
try
{
inventory = ServerChest.LoadInventoryFromZdo(zdo);
}
catch (Exception)
{
return CommandResult.Fail("ServerChest contents could not be loaded. Status is unavailable. Check the server log for details.");
}
int stackCount = inventory.NrOfItems();
int itemCount = inventory.NrOfItemsIncludingStacks();
int width = stackCount <= 0 ? 0 : ServerChest.MaxColumns;
int height = stackCount <= 0 ? 0 : Math.Max(1, (int)Math.Ceiling(stackCount / (double)ServerChest.MaxColumns));
int width = stackCount <= 0 ? 0 : ServerChest.Columns;
int height = stackCount <= 0 ? 0 : inventory.GetHeight();
Vector3 position = zdo.GetPosition();
string message =
"ServerChest owner=" + ServerChest.OwnerName(zdo) +
Expand Down Expand Up @@ -269,53 +260,6 @@ internal static CommandResult ValidateItems(IReadOnlyList<SendItem> items)
return CommandResult.Ok("");
}

private static int CountRequiredNewSlots(Inventory inventory, IReadOnlyList<SendItem> items)
{
int required = 0;
Dictionary<string, int> freeStackSpace = new();
foreach (ItemDrop.ItemData existing in inventory.GetAllItems())
{
string key = StackKey(existing.m_shared.m_name, existing.m_quality, existing.m_worldLevel);
int free = Math.Max(0, existing.m_shared.m_maxStackSize - existing.m_stack);
if (!freeStackSpace.ContainsKey(key))
{
freeStackSpace[key] = 0;
}

freeStackSpace[key] += free;
}

foreach (SendItem item in items)
{
GameObject prefab = ObjectDB.instance.GetItemPrefab(item.PrefabName);
ItemDrop itemDrop = prefab.GetComponent<ItemDrop>();
int maxStack = Math.Max(1, itemDrop.m_itemData.m_shared.m_maxStackSize);
int worldLevel = (int)(byte)Game.m_worldLevel;
string key = StackKey(itemDrop.m_itemData.m_shared.m_name, item.Quality, worldLevel);
int remaining = item.Amount;
if (freeStackSpace.TryGetValue(key, out int free) && free > 0)
{
int used = Math.Min(remaining, free);
remaining -= used;
freeStackSpace[key] = free - used;
}

if (remaining > 0)
{
int newSlots = (int)Math.Ceiling(remaining / (double)maxStack);
required += newSlots;
freeStackSpace[key] = newSlots * maxStack - remaining;
}
}

return required;
}

private static string StackKey(string sharedName, int quality, int worldLevel)
{
return sharedName + "|" + quality.ToString(CultureInfo.InvariantCulture) + "|" + worldLevel.ToString(CultureInfo.InvariantCulture);
}

private static bool TryAddItemAmount(Inventory inventory, SendItem item, out string error)
{
error = "";
Expand All @@ -331,6 +275,7 @@ private static bool TryAddItemAmount(Inventory inventory, SendItem item, out str
while (remaining > 0)
{
int stackAmount = Math.Min(remaining, maxStack);
ServerChest.ResizeToFit(inventory, 1);
int chunkBeforeAmount = CountMatchingAmount(inventory, sharedName, item.Quality, worldLevel);
ItemDrop.ItemData added = inventory.AddItem(item.PrefabName, stackAmount, item.Quality, 0, 0L, "", cheated: false);
int chunkAfterAmount = CountMatchingAmount(inventory, sharedName, item.Quality, worldLevel);
Expand Down
Loading
Loading