Minecraft 1.7.10: Ньюансы синхронизации контейнеров
Клиентский и серверный Container
синхронизируются довольно простым способом. Container#detectAndSendChanges()
запоминает содержимое каждого слота, который отсылается на клиент, а затем сравнивает их с действительным содержимым инвентаря. Если оно отличается - на клиент отсылается S2FPacketSetSlot
/**
* Looks for changes made in the container, sends them to every listener.
*/
public void detectAndSendChanges()
{
for (int i = 0; i < this.inventorySlots.size(); ++i)
{
ItemStack itemstack = ((Slot)this.inventorySlots.get(i)).getStack();
ItemStack itemstack1 = (ItemStack)this.inventoryItemStacks.get(i);
if (!ItemStack.areItemStacksEqual(itemstack1, itemstack))
{
itemstack1 = itemstack == null ? null : itemstack.copy();
this.inventoryItemStacks.set(i, itemstack1);
for (int j = 0; j < this.crafters.size(); ++j)
{
((ICrafting)this.crafters.get(j)).sendSlotContents(this, i, itemstack1);
}
}
}
}
В целом все довольно понятно. Но оссобенно инетересен вызов ((ICrafting)this.crafters.get(j)).sendSlotContents(this, i, itemstack1);
. Этот метод переслает данные слота на свой клиентский объект, чтобы успешно его отобразить. Чаще всего интерфейс ICrafting
реализует EntityPlayerMP
, т.к. основной "потребитель" gui - это игрок:
/**
* Sends the contents of an inventory slot to the client-side Container. This doesn't have to match the actual
* contents of that slot. Args: Container, slot number, slot contents
*/
public void sendSlotContents(Container p_71111_1_, int p_71111_2_, ItemStack p_71111_3_)
{
if (!(p_71111_1_.getSlot(p_71111_2_) instanceof SlotCrafting))
{
if (!this.isChangingQuantityOnly)
{
this.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(p_71111_1_.windowId, p_71111_2_, p_71111_3_));
}
}
}
Как видите, на клиент не будет выслан пакет, если EntityPlayerMP#isChangingQuantityOnly
= true. Но что это означает? Чтобы это выяснить, обратимся к методу Contaiiner#slotClick(...)
. Он вызывается сначала на клиенте, а потом на сервере когда тот получит C0EPacketClickWindow
. Когда писался функционал метода Contaiiner#slotClick(...)
, ребята из Mojang думали, что результат клика по слотам может быть одинакого вычислен и на клиенте, и на сервере. Поэтому когда запускается Container#detectAndSendChanges()
с EntityPlayerMP#isChangingQuantityOnly
= true, на клиенте уже был отображен верный результат, который получится из Contaiiner#slotClick(...)
. И слать пакет на клиенте было совершенно не нужно.
Так что если вы делаете какую-нибудь сложную механику для вашего контейнера, которую не реализует ванильный код (скажем, клик по мечу чинит его, удаляя 1 алмаз), то EntityPlayerMP#isChangingQuantityOnly
может очень сильно попортить вам жизнь. Как вариант решения этой проблемы, вы можете оверрайдить Container#detectAndSendChanges()
вашего контейнера и выставить всем игрокам EntityPlayerMP#isChangingQuantityOnly
= false:
@Override
public void detectAndSendChanges() {
for (int j = 0; j < crafters.size(); ++j)
{
ICrafting crafter = (ICrafting) crafters.get(j);
if (crafter instanceof EntityPlayerMP) {
((EntityPlayerMP) crafter).isChangingQuantityOnly = false;
}
}
super.detectAndSendChanges(); // Не боясь запускаем ванильную синхронизацию
}