mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-07-12 12:55:21 +00:00
InventoryManager: disentangle slot tracking from slot syncing
This commit is contained in:
parent
d3cea2ca7c
commit
8633804f15
@ -348,7 +348,7 @@ abstract class BaseInventory implements Inventory{
|
|||||||
if($invManager === null){
|
if($invManager === null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$invManager->syncSlot($this, $index);
|
$invManager->onSlotChange($this, $index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,51 +382,63 @@ class InventoryManager{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function onSlotChange(Inventory $inventory, int $slot) : void{
|
||||||
|
$currentItem = $inventory->getItem($slot);
|
||||||
|
$predictions = $this->initiatedSlotChanges[spl_object_id($inventory)] ?? null;
|
||||||
|
$clientSideItem = $predictions?->getSlot($slot);
|
||||||
|
if($clientSideItem === null || !$clientSideItem->equalsExact($currentItem)){
|
||||||
|
//no prediction or incorrect - do not associate this with the currently active itemstack request
|
||||||
|
$this->trackItemStack($inventory, $slot, $currentItem, null);
|
||||||
|
$this->syncSlot($inventory, $slot);
|
||||||
|
}else{
|
||||||
|
//correctly predicted - associate the change with the currently active itemstack request
|
||||||
|
$this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId);
|
||||||
|
}
|
||||||
|
$predictions?->remove($slot);
|
||||||
|
}
|
||||||
|
|
||||||
public function syncSlot(Inventory $inventory, int $slot) : void{
|
public function syncSlot(Inventory $inventory, int $slot) : void{
|
||||||
|
$itemStackInfo = $this->getItemStackInfo($inventory, $slot);
|
||||||
|
if($itemStackInfo === null){
|
||||||
|
throw new \LogicException("Cannot sync an untracked inventory slot");
|
||||||
|
}
|
||||||
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
|
$slotMap = $this->complexWindows[spl_object_id($inventory)] ?? null;
|
||||||
if($slotMap !== null){
|
if($slotMap !== null){
|
||||||
$windowId = ContainerIds::UI;
|
$windowId = ContainerIds::UI;
|
||||||
$netSlot = $slotMap->mapCoreToNet($slot) ?? null;
|
$netSlot = $slotMap->mapCoreToNet($slot) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null");
|
||||||
}else{
|
}else{
|
||||||
$windowId = $this->getWindowId($inventory);
|
$windowId = $this->getWindowId($inventory) ?? throw new AssumptionFailedError("We already have an ItemStackInfo, so this should not be null");
|
||||||
$netSlot = $slot;
|
$netSlot = $slot;
|
||||||
}
|
}
|
||||||
if($windowId !== null && $netSlot !== null){
|
|
||||||
$currentItem = $inventory->getItem($slot);
|
$itemStackWrapper = new ItemStackWrapper($itemStackInfo->getStackId(), $itemStackInfo->getItemStack());
|
||||||
$predictions = $this->initiatedSlotChanges[spl_object_id($inventory)] ?? null;
|
if($windowId === ContainerIds::OFFHAND){
|
||||||
$clientSideItem = $predictions?->getSlot($slot);
|
//TODO: HACK!
|
||||||
if($clientSideItem === null || !$clientSideItem->equalsExact($currentItem)){
|
//The client may sometimes ignore the InventorySlotPacket for the offhand slot.
|
||||||
$itemStackWrapper = $this->wrapItemStack($inventory, $slot, $currentItem);
|
//This can cause a lot of problems (totems, arrows, and more...).
|
||||||
if($windowId === ContainerIds::OFFHAND){
|
//The workaround is to send an InventoryContentPacket instead
|
||||||
//TODO: HACK!
|
//BDS (Bedrock Dedicated Server) also seems to work this way.
|
||||||
//The client may sometimes ignore the InventorySlotPacket for the offhand slot.
|
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
|
||||||
//This can cause a lot of problems (totems, arrows, and more...).
|
}else{
|
||||||
//The workaround is to send an InventoryContentPacket instead
|
if($this->currentItemStackRequestId !== null){
|
||||||
//BDS (Bedrock Dedicated Server) also seems to work this way.
|
//TODO: HACK!
|
||||||
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, [$itemStackWrapper]));
|
//When right-clicking to equip armour, the client predicts the content of the armour slot, but
|
||||||
}else{
|
//doesn't report it in the transaction packet. The server then sends an InventorySlotPacket to
|
||||||
if($this->currentItemStackRequestId !== null){
|
//the client, assuming the slot changed for some other reason, since there is no prediction for
|
||||||
//TODO: HACK!
|
//the slot.
|
||||||
//When right-clicking to equip armour, the client predicts the content of the armour slot, but
|
//However, later requests involving that itemstack will refer to the request ID in which the
|
||||||
//doesn't report it in the transaction packet. The server then sends an InventorySlotPacket to
|
//armour was equipped, instead of the stack ID provided by the server in the outgoing
|
||||||
//the client, assuming the slot changed for some other reason, since there is no prediction for
|
//InventorySlotPacket. (Perhaps because the item is already the same as the client actually
|
||||||
//the slot.
|
//predicted, but didn't tell us?)
|
||||||
//However, later requests involving that itemstack will refer to the request ID in which the
|
//We work around this bug by setting the slot to air and then back to the correct item. In
|
||||||
//armour was equipped, instead of the stack ID provided by the server in the outgoing
|
//theory, setting a different count and then back again (or changing any other property) would
|
||||||
//InventorySlotPacket. (Perhaps because the item is already the same as the client actually
|
//also work, but this is simpler.
|
||||||
//predicted, but didn't tell us?)
|
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, new ItemStackWrapper(0, ItemStack::null())));
|
||||||
//We work around this bug by setting the slot to air and then back to the correct item. In
|
|
||||||
//theory, setting a different count and then back again (or changing any other property) would
|
|
||||||
//also work, but this is simpler.
|
|
||||||
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, new ItemStackWrapper(0, ItemStack::null())));
|
|
||||||
}
|
|
||||||
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
|
|
||||||
}
|
|
||||||
}elseif($this->currentItemStackRequestId !== null){
|
|
||||||
$this->trackItemStack($inventory, $slot, $currentItem, $this->currentItemStackRequestId);
|
|
||||||
}
|
}
|
||||||
$predictions?->remove($slot);
|
$this->session->sendDataPacket(InventorySlotPacket::create($windowId, $netSlot, $itemStackWrapper));
|
||||||
}
|
}
|
||||||
|
$predictions = $this->initiatedSlotChanges[spl_object_id($inventory)] ?? null;
|
||||||
|
$predictions?->remove($slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncContents(Inventory $inventory) : void{
|
public function syncContents(Inventory $inventory) : void{
|
||||||
@ -437,26 +449,25 @@ class InventoryManager{
|
|||||||
$windowId = $this->getWindowId($inventory);
|
$windowId = $this->getWindowId($inventory);
|
||||||
}
|
}
|
||||||
if($windowId !== null){
|
if($windowId !== null){
|
||||||
|
unset($this->initiatedSlotChanges[spl_object_id($inventory)]);
|
||||||
|
$contents = [];
|
||||||
|
foreach($inventory->getContents(true) as $slot => $item){
|
||||||
|
$info = $this->trackItemStack($inventory, $slot, $item, null);
|
||||||
|
$contents[] = new ItemStackWrapper($info->getStackId(), $info->getItemStack());
|
||||||
|
}
|
||||||
if($slotMap !== null){
|
if($slotMap !== null){
|
||||||
$predictions = $this->initiatedSlotChanges[spl_object_id($inventory)] ?? null;
|
foreach($contents as $slotId => $info){
|
||||||
foreach($inventory->getContents(true) as $slotId => $item){
|
|
||||||
$packetSlot = $slotMap->mapCoreToNet($slotId) ?? null;
|
$packetSlot = $slotMap->mapCoreToNet($slotId) ?? null;
|
||||||
if($packetSlot === null){
|
if($packetSlot === null){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$predictions?->remove($slotId);
|
|
||||||
$this->session->sendDataPacket(InventorySlotPacket::create(
|
$this->session->sendDataPacket(InventorySlotPacket::create(
|
||||||
$windowId,
|
$windowId,
|
||||||
$packetSlot,
|
$packetSlot,
|
||||||
$this->wrapItemStack($inventory, $slotId, $inventory->getItem($slotId))
|
$info
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
unset($this->initiatedSlotChanges[spl_object_id($inventory)]);
|
|
||||||
$contents = [];
|
|
||||||
foreach($inventory->getContents(true) as $slotId => $item){
|
|
||||||
$contents[] = $this->wrapItemStack($inventory, $slotId, $item);
|
|
||||||
}
|
|
||||||
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, $contents));
|
$this->session->sendDataPacket(InventoryContentPacket::create($windowId, $contents));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -475,14 +486,13 @@ class InventoryManager{
|
|||||||
foreach($this->initiatedSlotChanges as $predictions){
|
foreach($this->initiatedSlotChanges as $predictions){
|
||||||
$inventory = $predictions->getInventory();
|
$inventory = $predictions->getInventory();
|
||||||
foreach($predictions->getSlots() as $slot => $expectedItem){
|
foreach($predictions->getSlots() as $slot => $expectedItem){
|
||||||
if(!$inventory->slotExists($slot)){
|
if(!$inventory->slotExists($slot) || $this->getItemStackInfo($inventory, $slot) === null){
|
||||||
continue; //TODO: size desync ???
|
continue; //TODO: size desync ???
|
||||||
}
|
}
|
||||||
$actualItem = $inventory->getItem($slot);
|
|
||||||
if(!$actualItem->equalsExact($expectedItem)){
|
//any prediction that still exists at this point is a slot that was predicted to change but didn't
|
||||||
$this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot");
|
$this->session->getLogger()->debug("Detected prediction mismatch in inventory " . get_class($inventory) . "#" . spl_object_id($inventory) . " slot $slot");
|
||||||
$this->syncSlot($inventory, $slot);
|
$this->syncSlot($inventory, $slot);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,11 +560,6 @@ class InventoryManager{
|
|||||||
return $this->itemStackInfos[spl_object_id($inventory)][$slotId] = $info;
|
return $this->itemStackInfos[spl_object_id($inventory)][$slotId] = $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function wrapItemStack(Inventory $inventory, int $slotId, Item $item) : ItemStackWrapper{
|
|
||||||
$info = $this->trackItemStack($inventory, $slotId, $item, null);
|
|
||||||
return new ItemStackWrapper($info->getStackId(), $info->getItemStack());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function matchItemStack(Inventory $inventory, int $slotId, int $clientItemStackId) : bool{
|
public function matchItemStack(Inventory $inventory, int $slotId, int $clientItemStackId) : bool{
|
||||||
$inventoryObjectId = spl_object_id($inventory);
|
$inventoryObjectId = spl_object_id($inventory);
|
||||||
if(!isset($this->itemStackInfos[$inventoryObjectId])){
|
if(!isset($this->itemStackInfos[$inventoryObjectId])){
|
||||||
|
Loading…
x
Reference in New Issue
Block a user