diff --git a/src/main/java/mcmultipart/MCMPCommonProxy.java b/src/main/java/mcmultipart/MCMPCommonProxy.java index 866d05f..a194253 100644 --- a/src/main/java/mcmultipart/MCMPCommonProxy.java +++ b/src/main/java/mcmultipart/MCMPCommonProxy.java @@ -1,17 +1,11 @@ package mcmultipart; -import java.util.List; - -import javax.annotation.Nonnull; - -import org.apache.commons.lang3.tuple.Pair; - import com.google.common.collect.Lists; - import mcmultipart.api.item.ItemBlockMultipart; import mcmultipart.api.multipart.IMultipart; import mcmultipart.multipart.MultipartRegistry; import mcmultipart.multipart.MultipartRegistry.WrappedBlock; +import mcmultipart.network.MultipartNetworkHandler; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemBucket; @@ -30,15 +24,18 @@ import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.relauncher.Side; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import java.util.List; public class MCMPCommonProxy { - public void preInit() { - } + public void preInit() { } - public void init() { - } + public void init() { } public EntityPlayer getPlayer() { return null; @@ -54,6 +51,13 @@ public void scheduleTick(Runnable runnable, Side side) { } } + @SubscribeEvent + public void onServerTick(TickEvent.ServerTickEvent e) { + if (e.phase == TickEvent.Phase.END) { + MultipartNetworkHandler.flushChanges(); + } + } + @SubscribeEvent public void onPlayerRightClickBlock(PlayerInteractEvent.RightClickBlock event) { EntityPlayer player = event.getEntityPlayer(); @@ -74,7 +78,7 @@ public void onPlayerRightClickBlock(PlayerInteractEvent.RightClickBlock event) { } private EnumActionResult placePart(@Nonnull ItemStack itemstack, @Nonnull EntityPlayer player, @Nonnull World world, @Nonnull BlockPos pos, - @Nonnull EnumFacing side, float hitX, float hitY, float hitZ, @Nonnull EnumHand hand, @Nonnull Pair info) { + @Nonnull EnumFacing side, float hitX, float hitY, float hitZ, @Nonnull EnumHand hand, @Nonnull Pair info) { int meta = itemstack.getItemDamage(); int size = itemstack.getCount(); NBTTagCompound nbt = null; diff --git a/src/main/java/mcmultipart/api/item/ItemBlockMultipart.java b/src/main/java/mcmultipart/api/item/ItemBlockMultipart.java index dc4e550..c8c299d 100644 --- a/src/main/java/mcmultipart/api/item/ItemBlockMultipart.java +++ b/src/main/java/mcmultipart/api/item/ItemBlockMultipart.java @@ -38,14 +38,14 @@ public ItemBlockMultipart(T block) { @Override public EnumActionResult onItemUse(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, - float hitY, float hitZ) { + float hitY, float hitZ) { return place(player, world, pos, hand, facing, hitX, hitY, hitZ, this, this.block::getStateForPlacement, multipartBlock, this::placeBlockAtTested, ItemBlockMultipart::placePartAt); } public static EnumActionResult place(EntityPlayer player, World world, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, - float hitY, float hitZ, Item item, IBlockPlacementInfo stateProvider, IMultipart multipartBlock, - IBlockPlacementLogic blockLogic, IPartPlacementLogic partLogic) { + float hitY, float hitZ, Item item, IBlockPlacementInfo stateProvider, IMultipart multipartBlock, + IBlockPlacementLogic blockLogic, IPartPlacementLogic partLogic) { ItemStack stack = player.getHeldItem(hand); if (!stack.isEmpty()) { @@ -65,14 +65,15 @@ public static EnumActionResult place(EntityPlayer player, World world, BlockPos if (!player.capabilities.isCreativeMode) { stack.shrink(1); } + return EnumActionResult.SUCCESS; } return EnumActionResult.FAIL; } public static boolean placeAt(ItemStack stack, EntityPlayer player, EnumHand hand, World world, BlockPos pos, EnumFacing facing, - float hitX, float hitY, float hitZ, IBlockPlacementInfo stateProvider, int meta, IMultipart multipartBlock, - IBlockPlacementLogic blockLogic, IPartPlacementLogic partLogic) { + float hitX, float hitY, float hitZ, IBlockPlacementInfo stateProvider, int meta, IMultipart multipartBlock, + IBlockPlacementLogic blockLogic, IPartPlacementLogic partLogic) { IBlockState state = stateProvider.getStateForPlacement(world, pos, facing, hitX, hitY, hitZ, meta, player, hand); AxisAlignedBB bb = state.getCollisionBoundingBox(world, pos); if ((bb == null || world.checkNoEntityCollision(bb.offset(pos))) @@ -85,14 +86,14 @@ public static boolean placeAt(ItemStack stack, EntityPlayer player, EnumHand han } public boolean placeBlockAtTested(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing facing, float hitX, - float hitY, float hitZ, IBlockState newState) { + float hitY, float hitZ, IBlockState newState) { return player.canPlayerEdit(pos, facing, stack) && world.getBlockState(pos).getBlock().isReplaceable(world, pos) && block.canPlaceBlockAt(world, pos) && block.canPlaceBlockOnSide(world, pos, facing) && super.placeBlockAt(stack, player, world, pos, facing, hitX, hitY, hitZ, newState); } public static boolean placePartAt(ItemStack stack, EntityPlayer player, EnumHand hand, World world, BlockPos pos, EnumFacing facing, - float hitX, float hitY, float hitZ, IMultipart multipartBlock, IBlockState state) { + float hitX, float hitY, float hitZ, IMultipart multipartBlock, IBlockState state) { IPartSlot slot = multipartBlock.getSlotForPlacement(world, pos, state, facing, hitX, hitY, hitZ, player); if (!multipartBlock.canPlacePartAt(world, pos) || !multipartBlock.canPlacePartOnSide(world, pos, facing, slot)) return false; @@ -156,28 +157,28 @@ public static boolean setMultipartTileNBT(EntityPlayer player, ItemStack stack, public static interface IPartPlacementLogic { public boolean placePart(ItemStack stack, EntityPlayer player, EnumHand hand, World world, BlockPos pos, EnumFacing facing, - float hitX, float hitY, float hitZ, IMultipart multipartBlock, IBlockState state); + float hitX, float hitY, float hitZ, IMultipart multipartBlock, IBlockState state); } public static interface IBlockPlacementLogic { public boolean place(ItemStack stack, EntityPlayer player, World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, - float hitZ, IBlockState newState); + float hitZ, IBlockState newState); } public static interface IBlockPlacementInfo { public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, - EntityLivingBase placer, EnumHand hand); + EntityLivingBase placer, EnumHand hand); } public static interface IExtendedBlockPlacementInfo { public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, - EntityLivingBase placer, EnumHand hand, IBlockState state); + EntityLivingBase placer, EnumHand hand, IBlockState state); } diff --git a/src/main/java/mcmultipart/api/multipart/MultipartHelper.java b/src/main/java/mcmultipart/api/multipart/MultipartHelper.java index aacdd0c..169d448 100644 --- a/src/main/java/mcmultipart/api/multipart/MultipartHelper.java +++ b/src/main/java/mcmultipart/api/multipart/MultipartHelper.java @@ -1,20 +1,14 @@ package mcmultipart.api.multipart; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; - import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; - import mcmultipart.api.container.IMultipartContainer; import mcmultipart.api.container.IMultipartContainerBlock; import mcmultipart.api.container.IPartInfo; import mcmultipart.api.ref.MCMPCapabilities; import mcmultipart.api.slot.IPartSlot; +import mcmultipart.network.MultipartNetworkHandler; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.tileentity.TileEntity; @@ -22,6 +16,12 @@ import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + public final class MultipartHelper { private MultipartHelper() { @@ -45,6 +45,7 @@ public static boolean addPart(World world, BlockPos pos, IPartSlot slot, IBlockS if (container.canAddPart(slot, state, tile)) { if (!simulated && !world.isRemote) { container.addPart(slot, state, tile); + MultipartNetworkHandler.flushChanges(world, pos); } return true; } diff --git a/src/main/java/mcmultipart/block/TileMultipartContainer.java b/src/main/java/mcmultipart/block/TileMultipartContainer.java index 648f4d6..7ff3880 100644 --- a/src/main/java/mcmultipart/block/TileMultipartContainer.java +++ b/src/main/java/mcmultipart/block/TileMultipartContainer.java @@ -7,7 +7,6 @@ import mcmultipart.api.container.IPartInfo; import mcmultipart.api.multipart.IMultipart; import mcmultipart.api.multipart.IMultipartTile; -import mcmultipart.api.multipart.MultipartHelper; import mcmultipart.api.multipart.MultipartOcclusionHelper; import mcmultipart.api.ref.MCMPCapabilities; import mcmultipart.api.slot.IPartSlot; @@ -16,9 +15,8 @@ import mcmultipart.client.TESRMultipartContainer; import mcmultipart.multipart.MultipartRegistry; import mcmultipart.multipart.PartInfo; +import mcmultipart.network.MultipartAction; import mcmultipart.network.MultipartNetworkHandler; -import mcmultipart.network.PacketMultipartAdd; -import mcmultipart.network.PacketMultipartRemove; import mcmultipart.util.WorldExt; import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; @@ -143,43 +141,12 @@ public boolean canAddPart(IPartSlot slot, IBlockState state, IMultipartTile tile @Override public void addPart(IPartSlot slot, IBlockState state, IMultipartTile tile) { - int currentTicking = countTickingParts(); - int newTicking = currentTicking + (tile != null && tile.isTickable() ? 1 : 0); - - TileMultipartContainer container = this; - if (currentTicking == 0 && newTicking > 0) { - container = new TileMultipartContainer.Ticking(getWorld(), getPos()); - transferTo(container); - } - - if (!isInWorld) { - PartInfo info = parts.values().iterator().next(); - info.setContainer(container); - if (!container.isInWorld) { - info.refreshWorld(); - } - } - IMultipart part = MultipartRegistry.INSTANCE.getPart(state.getBlock()); Preconditions.checkState(part != null, "The blockstate " + state + " could not be converted to a multipart!"); - container.addPartDo(slot, state, part, tile); - IBlockState prevSt = getWorld().getBlockState(getPos()); + addPartDo(slot, state, part, tile); - if (container != this) { // If we require ticking, place a ticking TE - isInWorld = false; - WorldExt.setBlockStateHack(getWorld(), getPos(), MCMultiPart.multipart.getDefaultState()// - .withProperty(BlockMultipartContainer.PROPERTY_TICKING, true), 0); - getWorld().setTileEntity(getPos(), container); - } else if (!isInWorld) { // If we aren't in the world, place the TE - WorldExt.setBlockStateHack(getWorld(), getPos(), MCMultiPart.multipart.getDefaultState()// - .withProperty(BlockMultipartContainer.PROPERTY_TICKING, this instanceof Ticking), 0); - getWorld().setTileEntity(getPos(), this); - } - - IBlockState st = getWorld().getBlockState(getPos()); - getWorld().markAndNotifyBlock(getPos(), null, prevSt, st, 1); // Only cause a block update, clients are notified through a packet - getWorld().checkLight(getPos()); + updateWorldState(); } private void addPartDo(IPartSlot slot, IBlockState state, IMultipart part, IMultipartTile tile) { @@ -202,38 +169,15 @@ private void addPartDo(IPartSlot slot, IBlockState state, IMultipart part, IMult } }); - MultipartNetworkHandler.sendToAllWatching(new PacketMultipartAdd(info), getWorld(), getPos()); + MultipartNetworkHandler.queuePartChange(getWorld(), new MultipartAction.Add(info)); } } @Override public void removePart(IPartSlot slot) { PartInfo info = parts.get(slot); - IMultipartTile tile = info.getTile(); - - int currentTicking = countTickingParts(); - int newTicking = currentTicking - (tile != null && tile.isTickable() ? 1 : 0); - - TileMultipartContainer container = this; - if (currentTicking > 0 && newTicking == 0) { - container = new TileMultipartContainer(getWorld(), getPos()); - transferTo(container); - } - - container.removePartDo(slot, info); - - IBlockState prevSt = getWorld().getBlockState(getPos()); - - if (container != this) { // If we don't require ticking, place a normal TE - isInWorld = false; - getWorld().setBlockState(getPos(), MCMultiPart.multipart.getDefaultState()// - .withProperty(BlockMultipartContainer.PROPERTY_TICKING, false), 0); - getWorld().setTileEntity(getPos(), container); - } - - IBlockState st = getWorld().getBlockState(getPos()); - getWorld().markAndNotifyBlock(getPos(), null, prevSt, st, 1); // Only cause a block update, clients are notified through a packet - getWorld().checkLight(getPos()); + removePartDo(slot, info); + updateWorldState(); } private void removePartDo(IPartSlot slot, PartInfo info) { @@ -248,18 +192,65 @@ private void removePartDo(IPartSlot slot, PartInfo info) { info.getPart().onRemoved(info); parts.values().forEach(i -> i.getPart().onPartRemoved(i, info)); - MultipartNetworkHandler.sendToAllWatching(new PacketMultipartRemove(getPos(), slot), getWorld(), getPos()); + MultipartNetworkHandler.queuePartChange(getWorld(), new MultipartAction.Remove(getPos(), slot)); } + } + + protected void updateWorldState() { + IBlockState prevSt = getWorld().getBlockState(getPos()); if (parts.size() == 1) { PartInfo part = parts.values().iterator().next(); + + // After breaking a block, Minecraft automatically sends an update packet to update the block the player + // destroyed. This causes the TE to get lost, since setting a new block state removes the old TE. + // We don't want this to happen, so we flush the part changes before Minecraft sends the update packet so + // the block replacing can be handled by MCMultiPart and therefore the TE is kept. + MultipartNetworkHandler.flushChanges(getWorld(), getPos()); + getWorld().setBlockState(getPos(), part.getState(), 0); if (part.getTile() != null) { TileEntity te = part.getTile().getTileEntity(); te.validate(); getWorld().setTileEntity(getPos(), te); } + + this.isInWorld = false; + } else { + int currentTicking = countTickingParts(); + boolean isTETicking = this instanceof ITickable; + TileMultipartContainer container = this; + boolean needsBlockUpdate = false; + + if (currentTicking == 0 && isTETicking) { + needsBlockUpdate = true; + container = new TileMultipartContainer(getWorld(), getPos()); + } else if (currentTicking > 0 && !isTETicking) { + needsBlockUpdate = true; + container = new TileMultipartContainer.Ticking(getWorld(), getPos()); + } else if (prevSt.getBlock() != MCMultiPart.multipart) { + needsBlockUpdate = true; + parts.values().forEach(it -> { + it.setContainer(this); + it.refreshWorld(); + }); + } + + if (needsBlockUpdate) { + if (container != this) transferTo(container); + + WorldExt.setBlockStateHack(getWorld(), getPos(), MCMultiPart.multipart.getDefaultState() + .withProperty(BlockMultipartContainer.PROPERTY_TICKING, container instanceof ITickable), 0); + getWorld().setTileEntity(getPos(), container); + + this.isInWorld = false; + container.isInWorld = true; + } } + + IBlockState st = getWorld().getBlockState(getPos()); + getWorld().markAndNotifyBlock(getPos(), null, prevSt, st, 1); // Only cause a block update, clients are notified through a packet + getWorld().checkLight(getPos()); } private int countTickingParts() { @@ -280,7 +271,6 @@ protected void clear() { } protected void transferTo(TileMultipartContainer container) { - container.isInWorld = isInWorld; parts.forEach(container::add); // Doing it like this to add them to the ticking list if needed if (missingParts != null) { container.missingParts = missingParts; @@ -462,13 +452,13 @@ public void updateContainingBlockInfo() { @Override public double getMaxRenderDistanceSquared() { - return parts.values().stream().map(IPartInfo::getTile).filter(t -> t != null).mapToDouble(IMultipartTile::getMaxPartRenderDistanceSquared) + return parts.values().stream().map(IPartInfo::getTile).filter(Objects::nonNull).mapToDouble(IMultipartTile::getMaxPartRenderDistanceSquared) .max().orElse(super.getMaxRenderDistanceSquared()); } @Override public AxisAlignedBB getRenderBoundingBox() { - return parts.values().stream().map(IPartInfo::getTile).filter(t -> t != null)// + return parts.values().stream().map(IPartInfo::getTile).filter(Objects::nonNull)// .reduce(super.getRenderBoundingBox(), (a, b) -> a.union(b.getPartRenderBoundingBox()), (a, b) -> b); } @@ -479,10 +469,7 @@ public boolean canRenderBreaking() { @Override public boolean hasFastRenderer() { - if (FMLCommonHandler.instance().getEffectiveSide().isClient()) { - return hasFastRendererC(); - } - return true; + return !FMLCommonHandler.instance().getEffectiveSide().isClient() || hasFastRendererC(); } @SideOnly(Side.CLIENT) @@ -566,14 +553,7 @@ public Ticking() { @Override public void update() { if (tickingParts.isEmpty()) { - IBlockState state = MCMultiPart.multipart.getDefaultState().withProperty(BlockMultipartContainer.PROPERTY_TICKING, false); - getPartWorld().setBlockState(getPartPos(), state, 0); - TileMultipartContainer container = (TileMultipartContainer) MultipartHelper.getContainer(getWorld(), getPos()).get(); - transferTo(container); - - getWorld().notifyBlockUpdate(getPos(), state, state, 3); - getWorld().checkLight(getPos()); - return; + updateWorldState(); } tickingParts.forEach(ITickable::update); } diff --git a/src/main/java/mcmultipart/network/ChangeList.java b/src/main/java/mcmultipart/network/ChangeList.java new file mode 100644 index 0000000..622d916 --- /dev/null +++ b/src/main/java/mcmultipart/network/ChangeList.java @@ -0,0 +1,36 @@ +package mcmultipart.network; + +import mcmultipart.slot.SlotRegistry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ChangeList { + private final List changes = new ArrayList<>(); + private final List changesView = Collections.unmodifiableList(changes); + + public void addChange(MultipartAction action) { + changes.add(action); + } + + public void sort() { + // group by same position and same slot id + changes.sort((a1, a2) -> { + int i = a1.pos.compareTo(a2.pos); + if (i != 0) { + return i; + } else { + return SlotRegistry.INSTANCE.getSlotID(a2.slot) - SlotRegistry.INSTANCE.getSlotID(a1.slot); + } + }); + } + + public void clear() { + changes.clear(); + } + + public List getChanges() { + return changesView; + } +} diff --git a/src/main/java/mcmultipart/network/MultipartAction.java b/src/main/java/mcmultipart/network/MultipartAction.java new file mode 100644 index 0000000..699a22e --- /dev/null +++ b/src/main/java/mcmultipart/network/MultipartAction.java @@ -0,0 +1,110 @@ +package mcmultipart.network; + +import com.google.common.base.Throwables; +import mcmultipart.api.container.IPartInfo; +import mcmultipart.api.slot.IPartSlot; +import mcmultipart.multipart.PartInfo; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.play.server.SPacketUpdateTileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.relauncher.ReflectionHelper; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.function.Function; + +public abstract class MultipartAction { + public static final class Add extends DataCarrier { + public static final int TYPE = 0; + + public Add(BlockPos pos, IPartSlot slot, IBlockState state, NBTTagCompound data) { + super(TYPE, pos, slot, state, data); + } + + public Add(IPartInfo info) { + this(info.getPartPos(), info.getSlot(), info.getState(), info.getTile() != null ? info.getTile().getPartUpdateTag() : null); + } + + @Override + public void handlePacket(EntityPlayer player) { + PartInfo.handleAdditionPacket(player.world, pos, slot, state, data); + } + } + + public static final class Change extends DataCarrier { + public static final int TYPE = 1; + + public Change(BlockPos pos, IPartSlot slot, IBlockState state, NBTTagCompound data) { + super(TYPE, pos, slot, state, data); + } + + public Change(IPartInfo info) { + this(info.getPartPos(), info.getSlot(), info.getState(), getUpdateTag.apply(info.getTile().getPartUpdatePacket())); + } + + @Override + public void handlePacket(EntityPlayer player) { + PartInfo.handleUpdatePacket(player.world, pos, slot, state, new SPacketUpdateTileEntity(pos, 0, data)); + } + + private static final MethodHandle SPacketUpdateTileEntity$nbt; + private static final Function getUpdateTag; + + static { + try { + Field f = ReflectionHelper.findField(SPacketUpdateTileEntity.class, "field_148860_e", "nbt"); + SPacketUpdateTileEntity$nbt = MethodHandles.lookup().unreflectGetter(f); + } catch (Exception ex) { + throw Throwables.propagate(ex); + } + + getUpdateTag = it -> { + try { + return (NBTTagCompound) SPacketUpdateTileEntity$nbt.invokeExact(it); + } catch (Throwable ex) { + throw Throwables.propagate(ex); + } + }; + } + } + + public static final class Remove extends MultipartAction { + public static final int TYPE = 2; + + public Remove(BlockPos pos, IPartSlot slot) { + super(TYPE, pos, slot); + } + + @Override + public void handlePacket(EntityPlayer player) { + PartInfo.handleRemovalPacket(player.world, pos, slot); + } + } + + public final BlockPos pos; + public final IPartSlot slot; + + public final int type; + + private MultipartAction(int type, BlockPos pos, IPartSlot slot) { + this.pos = pos; + this.slot = slot; + this.type = type; + } + + public abstract void handlePacket(EntityPlayer player); + + public static abstract class DataCarrier extends MultipartAction { + public final IBlockState state; + public final NBTTagCompound data; + + private DataCarrier(int type, BlockPos pos, IPartSlot slot, IBlockState state, NBTTagCompound data) { + super(type, pos, slot); + this.state = state; + this.data = data; + } + } +} diff --git a/src/main/java/mcmultipart/network/MultipartNetworkHandler.java b/src/main/java/mcmultipart/network/MultipartNetworkHandler.java index 8c13058..de46f9d 100644 --- a/src/main/java/mcmultipart/network/MultipartNetworkHandler.java +++ b/src/main/java/mcmultipart/network/MultipartNetworkHandler.java @@ -7,27 +7,67 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.WorldServer; +import net.minecraftforge.common.DimensionManager; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; import net.minecraftforge.fml.relauncher.Side; +import org.apache.commons.lang3.tuple.Triple; + +import java.util.HashMap; +import java.util.Map; public class MultipartNetworkHandler { public static final SimpleNetworkWrapper wrapper = NetworkRegistry.INSTANCE.newSimpleChannel(MCMultiPart.MODID); + private static Map, ChangeList> changeList = new HashMap<>(); + public static void init() { - wrapper.registerMessage(PacketMultipartChange.class, PacketMultipartChange.class, 0, Side.CLIENT); - wrapper.registerMessage(PacketMultipartAdd.class, PacketMultipartAdd.class, 1, Side.CLIENT); - wrapper.registerMessage(PacketMultipartRemove.class, PacketMultipartRemove.class, 2, Side.CLIENT); + // wrapper.registerMessage(PacketMultipartChange.class, PacketMultipartChange.class, 0, Side.CLIENT); + // wrapper.registerMessage(PacketMultipartAdd.class, PacketMultipartAdd.class, 1, Side.CLIENT); + // wrapper.registerMessage(PacketMultipartRemove.class, PacketMultipartRemove.class, 2, Side.CLIENT); + wrapper.registerMessage(PacketMultipartAction.class, PacketMultipartAction.class, 3, Side.CLIENT); + } + + public static void queuePartChange(World world, MultipartAction action) { + if (world.isRemote) return; + + int chunkX = action.pos.getX() >> 4; + int chunkZ = action.pos.getZ() >> 4; + + Triple key = Triple.of(chunkX, chunkZ, world.provider.getDimension()); + ChangeList cl = changeList.getOrDefault(key, new ChangeList()); + changeList.put(key, cl); + + cl.addChange(action); } - public static void sendToAllWatching(Packet message, World world, BlockPos pos) { - if (world instanceof WorldServer) { - PlayerChunkMap manager = ((WorldServer) world).getPlayerChunkMap(); - for (EntityPlayer player : world.playerEntities) { - if (manager.isPlayerWatchingChunk((EntityPlayerMP) player, pos.getX() >> 4, pos.getZ() >> 4)) { - wrapper.sendTo(message, (EntityPlayerMP) player); - } + public static void flushChanges() { + for (Map.Entry, ChangeList> list : changeList.entrySet()) { + flushChanges(list.getKey().getLeft(), list.getKey().getMiddle(), list.getKey().getRight(), list.getValue()); + } + changeList.clear(); + } + + public static void flushChanges(World world, BlockPos pos) { + if (world.isRemote) return; + int chunkX = pos.getX() >> 4; + int chunkY = pos.getZ() >> 4; + int dim = world.provider.getDimension(); + Triple key = Triple.of(chunkX, chunkY, dim); + ChangeList cl = changeList.get(key); + if (cl != null) { + flushChanges(chunkX, chunkY, dim, cl); + changeList.remove(key); + } + } + + private static void flushChanges(int chunkX, int chunkY, int dim, ChangeList list) { + WorldServer world = DimensionManager.getWorld(dim); + PlayerChunkMap manager = world.getPlayerChunkMap(); + for (EntityPlayer player : world.playerEntities) { + if (manager.isPlayerWatchingChunk((EntityPlayerMP) player, chunkX, chunkY)) { + wrapper.sendTo(new PacketMultipartAction(list), (EntityPlayerMP) player); } } } @@ -35,5 +75,4 @@ public static void sendToAllWatching(Packet message, World world, BlockPos po public static void sendToServer(Packet message) { wrapper.sendToServer(message); } - } \ No newline at end of file diff --git a/src/main/java/mcmultipart/network/Packet.java b/src/main/java/mcmultipart/network/Packet.java index 825ebab..a252d08 100644 --- a/src/main/java/mcmultipart/network/Packet.java +++ b/src/main/java/mcmultipart/network/Packet.java @@ -1,7 +1,6 @@ package mcmultipart.network; import com.google.common.base.Throwables; - import io.netty.buffer.ByteBuf; import mcmultipart.MCMultiPart; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/mcmultipart/network/PacketMultipartAction.java b/src/main/java/mcmultipart/network/PacketMultipartAction.java new file mode 100644 index 0000000..1381065 --- /dev/null +++ b/src/main/java/mcmultipart/network/PacketMultipartAction.java @@ -0,0 +1,132 @@ +package mcmultipart.network; + +import mcmultipart.MCMultiPart; +import mcmultipart.api.slot.IPartSlot; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; + +import java.util.List; + +public class PacketMultipartAction extends Packet { + + private final ChangeList changes; + + public PacketMultipartAction() { + this(new ChangeList()); + } + + public PacketMultipartAction(ChangeList changes) { + this.changes = changes; + } + + @Override + public void handleClient(EntityPlayer player) { + changes.getChanges().forEach(it -> it.handlePacket(player)); + } + + @Override + public void handleServer(EntityPlayer player) { } + + /* + * Format: + * size: short + * size * { + * bits: byte (bit 7: repeat coords, bit 6: repeat slot id, bit 0-1: entry type) + * [pos: BlockPos] if there's a new position + * [slotid: var int] if there's a new slot id + * [ + * stateid: var int + * data: NBT? + * ] if type is ADD or CHANGE + * } + */ + + @Override + public void toBytes(PacketBuffer buf) { + changes.sort(); + List l = changes.getChanges(); + buf.writeShort(l.size()); + boolean prevHasSameCoords = false; + boolean prevHasSameSlot = false; + for (int i = 0; i < l.size(); i++) { + MultipartAction entry = l.get(i); + int entrySlot = MCMultiPart.slotRegistry.getID(entry.slot); + + boolean nextHasSameCoords = false; + boolean nextHasSameSlot = false; + if (i + 1 < l.size()) { + MultipartAction next = l.get(i + 1); + int nextSlot = MCMultiPart.slotRegistry.getID(next.slot); + nextHasSameCoords = next.pos.equals(entry.pos); + nextHasSameSlot = nextSlot == entrySlot; + } + + buf.writeByte(entry.type | + (prevHasSameCoords ? 128 : 0) | + (prevHasSameSlot ? 64 : 0)); + + if (!prevHasSameCoords) + buf.writeBlockPos(entry.pos); + if (!prevHasSameSlot) + buf.writeVarInt(entrySlot); + + if (entry instanceof MultipartAction.DataCarrier) { + MultipartAction.DataCarrier dc = (MultipartAction.DataCarrier) entry; + buf.writeVarInt(MCMultiPart.stateMap.get(dc.state)); + buf.writeCompoundTag(dc.data); + } + + prevHasSameCoords = nextHasSameCoords; + prevHasSameSlot = nextHasSameSlot; + } + } + + @Override + public void fromBytes(PacketBuffer buf) throws Exception { + changes.clear(); + int partsCount = buf.readUnsignedShort(); + BlockPos prevCoords = BlockPos.ORIGIN; + int prevSlotId = 0; + for (int i = 0; i < partsCount; i++) { + short bits = buf.readUnsignedByte(); + boolean prevHasSameCoords = (bits & 128) != 0; + boolean prevHasSameSlotId = (bits & 64) != 0; + int type = bits & 31; + + BlockPos coords = prevCoords; + int slotId = prevSlotId; + if (!prevHasSameCoords) coords = buf.readBlockPos(); + if (!prevHasSameSlotId) slotId = buf.readVarInt(); + + IPartSlot slot = MCMultiPart.slotRegistry.getValue(slotId); + + IBlockState state = null; + NBTTagCompound nbt = null; + + if (type == MultipartAction.Add.TYPE || type == MultipartAction.Change.TYPE) { + int value = buf.readVarInt(); + state = MCMultiPart.stateMap.getByValue(value); + nbt = buf.readCompoundTag(); + } + + switch (type) { + case MultipartAction.Add.TYPE: + changes.addChange(new MultipartAction.Add(coords, slot, state, nbt)); + break; + case MultipartAction.Change.TYPE: + changes.addChange(new MultipartAction.Change(coords, slot, state, nbt)); + break; + case MultipartAction.Remove.TYPE: + changes.addChange(new MultipartAction.Remove(coords, slot)); + break; + default: + MCMultiPart.log.fatal("Error while decoding packet: Invalid action type {}", type); + } + prevCoords = coords; + prevSlotId = slotId; + } + } +} diff --git a/src/main/java/mcmultipart/network/PacketMultipartAdd.java b/src/main/java/mcmultipart/network/PacketMultipartAdd.java deleted file mode 100644 index b802a28..0000000 --- a/src/main/java/mcmultipart/network/PacketMultipartAdd.java +++ /dev/null @@ -1,60 +0,0 @@ -package mcmultipart.network; - -import mcmultipart.MCMultiPart; -import mcmultipart.api.slot.IPartSlot; -import mcmultipart.multipart.PartInfo; -import net.minecraft.block.state.IBlockState; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.BlockPos; - -public class PacketMultipartAdd extends Packet { - - private BlockPos pos; - private IPartSlot slot; - private IBlockState state; - private NBTTagCompound data; - - public PacketMultipartAdd(PartInfo info) { - pos = info.getPartPos(); - slot = info.getSlot(); - state = info.getState(); - if (info.getTile() != null) { - data = info.getTile().getPartUpdateTag(); - } - } - - public PacketMultipartAdd() { - } - - @Override - public void handleClient(EntityPlayer player) { - PartInfo.handleAdditionPacket(player.world, pos, slot, state, data); - } - - @Override - public void handleServer(EntityPlayer player) { - - } - - @Override - public void toBytes(PacketBuffer buf) { - buf.writeBlockPos(pos); - buf.writeInt(MCMultiPart.slotRegistry.getID(slot)); - buf.writeInt(MCMultiPart.stateMap.get(state)); - buf.writeBoolean(data != null); - if (data != null) { - buf.writeCompoundTag(data); - } - } - - @Override - public void fromBytes(PacketBuffer buf) throws Exception { - pos = buf.readBlockPos(); - slot = MCMultiPart.slotRegistry.getValue(buf.readInt()); - state = MCMultiPart.stateMap.getByValue(buf.readInt()); - data = buf.readBoolean() ? buf.readCompoundTag() : null; - } - -} diff --git a/src/main/java/mcmultipart/network/PacketMultipartChange.java b/src/main/java/mcmultipart/network/PacketMultipartChange.java deleted file mode 100644 index 0bcc007..0000000 --- a/src/main/java/mcmultipart/network/PacketMultipartChange.java +++ /dev/null @@ -1,88 +0,0 @@ -package mcmultipart.network; - -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; -import java.util.function.Function; - -import com.google.common.base.Throwables; - -import mcmultipart.MCMultiPart; -import mcmultipart.api.slot.IPartSlot; -import mcmultipart.multipart.PartInfo; -import net.minecraft.block.state.IBlockState; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.network.PacketBuffer; -import net.minecraft.network.play.server.SPacketUpdateTileEntity; -import net.minecraft.util.math.BlockPos; -import net.minecraftforge.fml.relauncher.ReflectionHelper; - -public class PacketMultipartChange extends Packet { - - private static final Function getPacketNBT; - static { - try { - Field f = ReflectionHelper.findField(SPacketUpdateTileEntity.class, "field_148860_e", "nbt"); - f.setAccessible(true); - getPacketNBT = packet -> { - try { - return (NBTTagCompound) MethodHandles.lookup().unreflectGetter(f).invokeExact(packet); - } catch (Throwable ex) { - throw Throwables.propagate(ex); - } - }; - } catch (Exception ex) { - throw Throwables.propagate(ex); - } - } - - private BlockPos pos; - private IPartSlot slot; - private IBlockState state; - private NBTTagCompound data; - - public PacketMultipartChange(PartInfo info) { - pos = info.getPartPos(); - slot = info.getSlot(); - state = info.getState(); - if (info.getTile() != null) { - SPacketUpdateTileEntity packet = info.getTile().getPartUpdatePacket(); - if (packet != null) { - data = getPacketNBT.apply(packet); - } - } - } - - public PacketMultipartChange() { - } - - @Override - public void handleClient(EntityPlayer player) { - PartInfo.handleUpdatePacket(player.world, pos, slot, state, new SPacketUpdateTileEntity(pos, 0, data)); - } - - @Override - public void handleServer(EntityPlayer player) { - - } - - @Override - public void toBytes(PacketBuffer buf) { - buf.writeBlockPos(pos); - buf.writeInt(MCMultiPart.slotRegistry.getID(slot)); - buf.writeInt(MCMultiPart.stateMap.get(state)); - buf.writeBoolean(data != null); - if (data != null) { - buf.writeCompoundTag(data); - } - } - - @Override - public void fromBytes(PacketBuffer buf) throws Exception { - pos = buf.readBlockPos(); - slot = MCMultiPart.slotRegistry.getValue(buf.readInt()); - state = MCMultiPart.stateMap.getByValue(buf.readInt()); - data = buf.readBoolean() ? buf.readCompoundTag() : null; - } - -} diff --git a/src/main/java/mcmultipart/network/PacketMultipartRemove.java b/src/main/java/mcmultipart/network/PacketMultipartRemove.java deleted file mode 100644 index d9c81b7..0000000 --- a/src/main/java/mcmultipart/network/PacketMultipartRemove.java +++ /dev/null @@ -1,45 +0,0 @@ -package mcmultipart.network; - -import mcmultipart.MCMultiPart; -import mcmultipart.api.slot.IPartSlot; -import mcmultipart.multipart.PartInfo; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.BlockPos; - -public class PacketMultipartRemove extends Packet { - - private BlockPos pos; - private IPartSlot slot; - - public PacketMultipartRemove(BlockPos pos, IPartSlot slot) { - this.pos = pos; - this.slot = slot; - } - - public PacketMultipartRemove() { - } - - @Override - public void handleClient(EntityPlayer player) { - PartInfo.handleRemovalPacket(player.world, pos, slot); - } - - @Override - public void handleServer(EntityPlayer player) { - - } - - @Override - public void toBytes(PacketBuffer buf) { - buf.writeBlockPos(pos); - buf.writeInt(MCMultiPart.slotRegistry.getID(slot)); - } - - @Override - public void fromBytes(PacketBuffer buf) throws Exception { - pos = buf.readBlockPos(); - slot = MCMultiPart.slotRegistry.getValue(buf.readInt()); - } - -} diff --git a/src/main/java/mcmultipart/util/MCMPWorldWrapper.java b/src/main/java/mcmultipart/util/MCMPWorldWrapper.java index 93b2d43..004cc39 100644 --- a/src/main/java/mcmultipart/util/MCMPWorldWrapper.java +++ b/src/main/java/mcmultipart/util/MCMPWorldWrapper.java @@ -18,8 +18,8 @@ import mcmultipart.api.world.IWorldView; import mcmultipart.multipart.MultipartRegistry; import mcmultipart.multipart.PartInfo; +import mcmultipart.network.MultipartAction; import mcmultipart.network.MultipartNetworkHandler; -import mcmultipart.network.PacketMultipartChange; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; @@ -264,7 +264,7 @@ public void notifyBlockUpdate(BlockPos pos, IBlockState oldState, IBlockState ne }); } if ((flags & 0b00010) != 0) { - MultipartNetworkHandler.sendToAllWatching(new PacketMultipartChange(part), part.getActualWorld(), pos); + MultipartNetworkHandler.queuePartChange(part.getActualWorld(), new MultipartAction.Change(part)); } if ((flags & 0b00100) == 0) { markBlockRangeForRenderUpdate(pos, pos);