Posted September 24, 20205 yr Hi, I'm having trouble with opening my GUI, as the tile entity (extending ITickableTileEntity, among others) will erase all stored variables, retrieved from NBT. So this isn't a problem with NBT. Also, this is a 1.15 problem. I've tracked down the exact time that the variables reset, and it's when the GUI's screen is opened, createTileEntity() is triggered in the Block class, recreating the tile entity and overwriting the variables. Is there a way to stop this? Here's a snippet from my block's class. @Override public TileEntity createTileEntity(BlockState state, IBlockReader world) { return TileEntityTypes.TILE.get().create(); } @Override public boolean hasTileEntity(BlockState state) { return true; } Thanks for your help in advance, I'd be willing to send more code snippets and answer any questions.
September 24, 20205 yr Have you save and read the information you've stored in the tileentity using write() and read()?
September 24, 20205 yr 17 minutes ago, Jack Richard said: I'm having trouble with opening my GUI, as the tile entity (extending ITickableTileEntity, among others) will erase all stored variables It's like there are two sides of the game that need to be synchronized, hmmmmmmmmmmmmm. The client and server will not be synchronized under normal conditions, you must specify which variable you would like to sync and when (most likely in your container).
September 24, 20205 yr Author 41 minutes ago, poopoodice said: Have you save and read the information you've stored in the tileentity using write() and read()? Yes, the data is stored and read through read() and write()
September 24, 20205 yr Author 40 minutes ago, ChampionAsh5357 said: It's like there are two sides of the game that need to be synchronized, hmmmmmmmmmmmmm. The client and server will not be synchronized under normal conditions, you must specify which variable you would like to sync and when (most likely in your container). Does it help to say when I add... @Override public void tick() { System.out.println(variable); } ...I always get the result, whether or not world.isRemote()? I use... @Override public CompoundNBT getUpdateTag() { CompoundNBT tagCompound = new CompoundNBT(); tagCompound.putString("variable", variable); return tagCompound; } @Override public void handleUpdateTag(CompoundNBT tag) { this.read(tag); } @Override public void read(CompoundNBT compound) { super.read(compound); this.inventory = NonNullList.<ItemStack>withSize(this.getSizeInventory(), ItemStack.EMPTY); ItemStackHelper.loadAllItems(compound, this.inventory); setVariable(compound.getString("variable")); } @Override public CompoundNBT write(CompoundNBT compound) { super.write(compound); ItemStackHelper.saveAllItems(compound, this.inventory); compound.putString("variable", getVariable()); return compound; } to synchronize the data. Thanks.
September 24, 20205 yr 22 minutes ago, Jack Richard said: this.inventory = NonNullList.<ItemStack>withSize(this.getSizeInventory(), ItemStack.EMPTY); ItemStackHelper.loadAllItems(compound, this.inventory); This looks like copy and paste of an IInventory class, use the IItemHandler instead. You should not use the vanilla method to create your inventory. 23 minutes ago, Jack Richard said: @Override public CompoundNBT getUpdateTag() { CompoundNBT tagCompound = new CompoundNBT(); tagCompound.putString("variable", variable); return tagCompound; } @Override public void handleUpdateTag(CompoundNBT tag) { this.read(tag); } Ah yes, synchronization on block update. Something completely unnecessary when it comes to GUI. Those are synced through slots. This should only be used if you have a TER present. 25 minutes ago, Jack Richard said: ...I always get the result, whether or not world.isRemote()? Then you fell into a trap of confirmation bias. Just because you get the same result doesn't mean it was synced correctly. It just means you probably executed the same code on both sides when it should only be on one and then synced.
September 24, 20205 yr Author 7 minutes ago, ChampionAsh5357 said: Ah yes, synchronization on block update. Something completely unnecessary when it comes to GUI. Those are synced through slots. This should only be used if you have a TER present. Then you fell into a trap of confirmation bias. Just because you get the same result doesn't mean it was synced correctly. It just means you probably executed the same code on both sides when it should only be on one and then synced. I forgot to mention I send Messages from the client to the server, so I am doing some syncing, I believe.
September 25, 20205 yr 1 hour ago, Jack Richard said: I forgot to mention I send Messages from the client to the server, so I am doing some syncing, I believe. Yes. Synchronization the wrong direction. Always good. I doubt you need to synchronize from the client->server as this probably just uses a standard inventory and primitives. In that case, you only need to sync from server->client. Which, you are still neglecting to listen and do. Please show your entire tile entity, block, container, and screen code. I am curious on what you're doing and how you're doing it since I'm pretty sure it's some variation of vanilla code which will not work in most cases.
September 25, 20205 yr Author 4 minutes ago, ChampionAsh5357 said: Yes. Synchronization the wrong direction. Always good. I doubt you need to synchronize from the client->server as this probably just uses a standard inventory and primitives. In that case, you only need to sync from server->client. Which, you are still neglecting to listen and do. Please show your entire tile entity, block, container, and screen code. I am curious on what you're doing and how you're doing it since I'm pretty sure it's some variation of vanilla code which will not work in most cases. public class ServerContainer extends Container { public ServerTileEntity entity; public ServerContainer(final int windowId, final PlayerInventory playerInventory, final PacketBuffer data) { this(windowId, playerInventory, getTileEntity(playerInventory, data)); } public ServerContainer(final int windowId, final PlayerInventory playerInventory, final TileEntity tileEntity) { super(ContainerTypes.SERVER.get(), windowId); this.entity = (ServerTileEntity) tileEntity; int startX = 8; int startY = 84; int slotSizePlus2 = 18; for (int row = 0; row < 3; ++row) { for (int column = 0; column < 9; ++column) { this.addSlot(new Slot(playerInventory, 9 + (row * 9) + column, startX + (column * slotSizePlus2), startY + (row * slotSizePlus2))); } } int hotbarY = 142; for (int column = 0; column < 9; ++column) { this.addSlot(new Slot(playerInventory, column, startX + (column * slotSizePlus2), hotbarY)); } this.addSlot(new CustomSlot(entity, 0, 7, 35)); } @Override public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) { ItemStack itemstack = ItemStack.EMPTY; Slot slot = this.inventorySlots.get(index); if (slot != null && slot.getHasStack()) { ItemStack itemstack1 = slot.getStack(); itemstack = itemstack1.copy(); if (index < 36) { if (!this.mergeItemStack(itemstack1, 36, this.inventorySlots.size(), true)) { return ItemStack.EMPTY; } } else if (!this.mergeItemStack(itemstack1, 0, 36, false)) { return ItemStack.EMPTY; } if (itemstack1.isEmpty()) { slot.putStack(ItemStack.EMPTY); } else { slot.onSlotChanged(); } } return itemstack; } @Override public boolean canInteractWith(PlayerEntity playerIn) { return true; } private static ServerTileEntity getTileEntity(final PlayerInventory playerInventory, final PacketBuffer data) { Objects.requireNonNull(playerInventory, "playerInventory cannot be null"); Objects.requireNonNull(data, "data cannot be null"); final TileEntity tileAtPos = playerInventory.player.world.getTileEntity(data.readBlockPos()); if (tileAtPos instanceof ServerTileEntity) { return (ServerTileEntity) tileAtPos; } throw new IllegalStateException("Tile entity is not correct! " + tileAtPos); } } @OnlyIn(Dist.CLIENT) public class ServerScreen extends ContainerScreen<ServerContainer> { private static final ResourceLocation TEXTURES = new ResourceLocation("modid:textures/gui/server.png"); private final PlayerInventory player; private ServerTileEntity entity; private TextFieldWidget carrierName; public ServerScreen(ServerContainer container, PlayerInventory player, ITextComponent title) { super(container, player, title); this.player = player; this.entity = getTileEntity(player, container); this.guiLeft = 0; this.guiTop = 0; this.xSize = 176; this.ySize = 166; } @Override protected void init() { super.init(); int x = (this.width - this.xSize) / 2; int y = (this.height - this.ySize) / 2; this.carrierName = new TextFieldWidget(font, x + 84, y + 11, 86, 18, "Carrier Name"); this.carrierName.setMaxStringLength(13); this.carrierName.setEnableBackgroundDrawing(false); this.carrierName.setVisible(true); this.carrierName.setTextColor(16777215); this.carrierName.setText(this.entity.getCarrierName()); System.out.println(this.entity.getCarrierName()); System.out.println("Above set"); } private static ServerTileEntity getTileEntity(final PlayerInventory playerInventory, final ServerContainer container) { Objects.requireNonNull(playerInventory, "playerInventory cannot be null"); final TileEntity tileAtPos = playerInventory.player.world.getTileEntity(container.entity.getPos()); if (tileAtPos instanceof ServerTileEntity) { return (ServerTileEntity) tileAtPos; } throw new IllegalStateException("Tile entity is not correct! " + tileAtPos); } @Override public void render(final int mouseX, final int mouseY, final float partialTicks) { this.renderBackground(); this.carrierName.render(mouseX, mouseY, partialTicks); super.render(mouseX, mouseY, partialTicks); this.renderHoveredToolTip(mouseX, mouseY); } @Override public boolean mouseClicked(double p_mouseClicked_1_, double p_mouseClicked_3_, int p_mouseClicked_5_) { this.carrierName.mouseClicked(p_mouseClicked_1_, p_mouseClicked_3_, p_mouseClicked_5_); return super.mouseClicked(p_mouseClicked_1_, p_mouseClicked_3_, p_mouseClicked_5_); } @Override protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) { RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f); this.getMinecraft().getTextureManager().bindTexture(TEXTURES); int x = (this.width - this.xSize) / 2; int y = (this.height - this.ySize) / 2; this.blit(x, y, 0, 0, 176, 166); this.font.drawString("Server", x + 10, y + 10, 16777215); this.entity.setCarrierName(carrierName.getText()); ModClass.INSTANCE.sendToServer(new CarrierServerSync(carrierName.getText(), this.entity.getPos())); } @Override protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { super.drawGuiContainerForegroundLayer(mouseX, mouseY); int x = (this.width - this.xSize) / 2; int y = (this.height - this.ySize) / 2; this.addButton(new Button(x + 30, y + 33, 50, 20, "Register", new Button.IPressable() { @Override public void onPress(Button p_onPress_1_) { System.out.println("pressed"); if (!entity.getStackInSlot(0).isEmpty()) { System.out.println("not empty"); if (entity.getStackInSlot(0).getStack().getTag() != null) { System.out.println("not null"); CompoundNBT compoundNBT = entity.getStackInSlot(0).getTag().copy(); compoundNBT.putString("carrier", carrierName.getText()); entity.getStackInSlot(0).setTag(compoundNBT); } else { System.out.println("null"); CompoundNBT compoundNBT = new CompoundNBT(); compoundNBT.putString("carrier", carrierName.getText()); entity.getStackInSlot(0).getStack().setTag(compoundNBT); } } } })); this.addButton(carrierName); } } public class ServerTileEntity extends TileEntity implements INamedContainerProvider, ITickableTileEntity, IInventory { private NonNullList<ItemStack> inventory = NonNullList.<ItemStack>withSize(1, ItemStack.EMPTY); private IItemHandlerModifiable items = createHandler(); private LazyOptional<IItemHandlerModifiable> itemHandler = LazyOptional.of(() -> items); private String carrierName = ""; public ServerTileEntity(TileEntityType<?> tileEntityTypeIn) { super(tileEntityTypeIn); } public ServerTileEntity(){ super(TileEntityTypes.SERVER.get()); } @Override public ITextComponent getDisplayName() { return new TranslationTextComponent("container.server"); } @Override public Container createMenu(int p_createMenu_1_, PlayerInventory p_createMenu_2_, PlayerEntity p_createMenu_3_) { return new ServerContainer(p_createMenu_1_, p_createMenu_2_, this); } @Override public void tick() { } @Override public SUpdateTileEntityPacket getUpdatePacket() { CompoundNBT tagCompound = new CompoundNBT(); tagCompound.putString("carrier", carrierName); this.write(tagCompound); SUpdateTileEntityPacket pack = new SUpdateTileEntityPacket(pos, 0, tagCompound); return pack; } @Override public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) { this.read(pkt.getNbtCompound()); } @Override public CompoundNBT getUpdateTag() { CompoundNBT tagCompound = new CompoundNBT(); tagCompound.putString("carrier", carrierName); return tagCompound; } @Override public void handleUpdateTag(CompoundNBT tag) { this.read(tag); } public void setCarrierName(String carrierName) { this.carrierName = carrierName; } public String getCarrierName() { return this.carrierName; } @Override public int getSizeInventory() { return inventory.size(); } @Override public boolean isEmpty() { for(ItemStack stack : this.inventory) { if(!stack.isEmpty()) return false; } return true; } @Override public ItemStack getStackInSlot(int index) { return inventory.get(index); } @Override public ItemStack decrStackSize(int index, int count) { return ItemStackHelper.getAndSplit(this.inventory, index, count); } @Override public void setInventorySlotContents(int index, ItemStack stack) { this.inventory.set(index, stack); } @Override public ItemStack removeStackFromSlot(int index) { return ItemStackHelper.getAndRemove(this.inventory, index); } @Override public boolean isUsableByPlayer(PlayerEntity player) { return true; } @Override public void clear() { this.inventory.clear(); } @Override public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nonnull Direction side) { if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { return itemHandler.cast(); } return super.getCapability(cap, side); } @Override public void read(CompoundNBT compound) { super.read(compound); this.inventory = NonNullList.<ItemStack>withSize(this.getSizeInventory(), ItemStack.EMPTY); ItemStackHelper.loadAllItems(compound, this.inventory); setCarrierName(compound.getString("carrier")); } private IItemHandlerModifiable createHandler() { return new InvWrapper(this); } @Override public CompoundNBT write(CompoundNBT compound) { super.write(compound); ItemStackHelper.saveAllItems(compound, this.inventory); compound.putString("carrier", getCarrierName()); return compound; } } public class Server extends Block { private static final VoxelShape SHAPE = Block.makeCuboidShape(0.0D, 0.0D, 0.0D, 16.0D, 32.0D, 16.0D); public static final DirectionProperty FACING = HorizontalBlock.HORIZONTAL_FACING; public Server(Properties properties) { super(properties); this.setDefaultState(this.getStateContainer().getBaseState().with(FACING, Direction.NORTH)); } @Override public TileEntity createTileEntity(BlockState state, IBlockReader world) { return TileEntityTypes.SERVER.get().create(); } @Override public boolean hasTileEntity(BlockState state) { return true; } @Override protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder) { builder.add(FACING); } @Override public BlockState getStateForPlacement(BlockItemUseContext context) { return this.getDefaultState().with(FACING, context.getPlacementHorizontalFacing().getOpposite()); } @Override public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context) { return SHAPE; } @Override public ActionResultType onBlockActivated(BlockState state, World worldIn, BlockPos pos, PlayerEntity player, Hand handIn, BlockRayTraceResult hit) { TileEntity te = worldIn.getTileEntity(pos); if (te != null && te instanceof ServerTileEntity && !worldIn.isRemote) { NetworkHooks.openGui((ServerPlayerEntity) player, (ServerTileEntity) te, pos); } return super.onBlockActivated(state, worldIn, pos, player, handIn, hit); } } My goal is to get the data from the TextFieldWidget, and save it in NBT tags, and be able to retreive it later, putting the text back in the field. Yes, I know; most of this code won't be the "recommended way" of doing any of this, I've been really trying though. Let me know if you need any more information. Thanks.
September 25, 20205 yr 3 minutes ago, Jack Richard said: TileEntity implements INamedContainerProvider, ITickableTileEntity, IInventory You are using IInventory. Go and fix that and properly attach it with capabilities. But I won't be so stingy since you have been trying and cooperating so apologies for my agressiveness. As a precursor, try to avoid using Server or Client in your names, it confuses everyone. 7 minutes ago, Jack Richard said: tagCompound.putString("carrier", carrierName); this.write(tagCompound); You're writing the carrier name twice? 9 minutes ago, Jack Richard said: ModClass.INSTANCE.sendToServer(new CarrierServerSync(carrierName.getText(), this.entity.getPos())); You're doing this every tick, only send it when the button is pressed. 9 minutes ago, Jack Richard said: if (entity.getStackInSlot(0).getStack().getTag() != null) { System.out.println("not null"); CompoundNBT compoundNBT = entity.getStackInSlot(0).getTag().copy(); compoundNBT.putString("carrier", carrierName.getText()); entity.getStackInSlot(0).setTag(compoundNBT); } else { System.out.println("null"); CompoundNBT compoundNBT = new CompoundNBT(); compoundNBT.putString("carrier", carrierName.getText()); entity.getStackInSlot(0).getStack().setTag(compoundNBT); } This should not really be handled on the client once again. This is not necessary if you send the information to the server. You will need to resync the values anyways whenever the inventory is opened regardless. Finally, if your container is not registered using IForgeContainerType, then the packet information will never be sent to the client. You should also store the block pos and not the te itself. That's rather pointless to do as all it's used for sending the position back to the server. The tile entity will still not store the position anyways.
September 25, 20205 yr Author 1 minute ago, ChampionAsh5357 said: You are using IInventory. Go and fix that and properly attach it with capabilities. But I won't be so stingy since you have been trying and cooperating so apologies for my agressiveness. I understand that IInventory is not accepted, so I'll be working to fix that soon. No problem, I'm just happy with whatever you're able to contribute. 2 minutes ago, ChampionAsh5357 said: As a precursor, try to avoid using Server or Client in your names, it confuses everyone. Yeah, that wasn't the best choice. 3 minutes ago, ChampionAsh5357 said: You're writing the carrier name twice? I guess so. I was a little confused once functions like getUpdatePacket() and onDataPacket() were used. 4 minutes ago, ChampionAsh5357 said: You're doing this every tick, only send it when the button is pressed. This should not really be handled on the client once again. This is not necessary if you send the information to the server. You will need to resync the values anyways whenever the inventory is opened regardless. I'm sending the data every tick, the button writes the data to an item in the one slot of the GUI. Here's the code for the .sendtoServer() handler: public static void handle(CarrierServerSync msg, Supplier<NetworkEvent.Context> ctx) { ctx.get().enqueueWork(() -> { PlayerEntity sender = ctx.get().getSender(); ServerTileEntity server = getTileEntity(sender.inventory, msg.tileEntity); server.setCarrierName(msg.data); }); ctx.get().setPacketHandled(true); } private static ServerTileEntity getTileEntity(final PlayerInventory playerInventory, final BlockPos tileEntity) { Objects.requireNonNull(playerInventory, "playerInventory cannot be null"); final TileEntity tileAtPos = playerInventory.player.world.getTileEntity(tileEntity); if (tileAtPos instanceof ServerTileEntity) { return (ServerTileEntity) tileAtPos; } throw new IllegalStateException("Tile entity is not correct! " + tileAtPos); } This is probably not the best way to do this... 9 minutes ago, ChampionAsh5357 said: This should not really be handled on the client once again. This is not necessary if you send the information to the server. You will need to resync the values anyways whenever the inventory is opened regardless. Finally, if your container is not registered using IForgeContainerType, then the packet information will never be sent to the client. You should also store the block pos and not the te itself. That's rather pointless to do as all it's used for sending the position back to the server. The tile entity will still not store the position anyways. Luckily, I do have my container, messages, and tile entity registered. Looping back to my original theory, is Block#createTileEntity() run when you open a GUI? Thanks again.
September 25, 20205 yr 7 minutes ago, Jack Richard said: I'm sending the data every tick, the button writes the data to an item in the one slot of the GUI. Here's the code for the .sendtoServer() handler: That still makes no sense. You're just bottlenecking the network. The data doesn't change until you send it. Also, the button shouldn't be the one doing it. It should be handled on the server once again. NOTHING should be handled on the client itself. It all gets synchronized via the gui. Only thing that doesn't is the string which should be what you send on button click. 8 minutes ago, Jack Richard said: Luckily, I do have my container, messages, and tile entity registered. You missed the part where I said IForgeContainerType. Doesn't matter if their registered. If it's not using the correct type it won't send the packet. 9 minutes ago, Jack Richard said: Looping back to my original theory, is Block#createTileEntity() run when you open a GUI? Not being rude, the original theory is wrong. The client doesn't hold data from the server. It will be reinitialized since it's pulling information from the empty client container.
September 25, 20205 yr Author 7 minutes ago, ChampionAsh5357 said: That still makes no sense. You're just bottlenecking the network. The data doesn't change until you send it. Also, the button shouldn't be the one doing it. It should be handled on the server once again. NOTHING should be handled on the client itself. It all gets synchronized via the gui. Only thing that doesn't is the string which should be what you send on button click. You missed the part where I said IForgeContainerType. Doesn't matter if their registered. If it's not using the correct type it won't send the packet. Not being rude, the original theory is wrong. The client doesn't hold data from the server. It will be reinitialized since it's pulling information from the empty client container. I know, it's very confusing, and frankly, I might just rework the GUI if I can't get this to work, anyway. Is it possible to get data from the Screen class to the TileEntity class? Also, should I implement IForgeContainerType in my Container class? I understand, the original theory was a little far-fetched. So an empty client container could be it? Just trying to understand. Thanks again.
September 25, 20205 yr 10 minutes ago, Jack Richard said: Is it possible to get data from the Screen class to the TileEntity class? Yes, that's the use of a packet through the network. But that should only be sent as needed as otherwise you just bottleneck the network creating more issues. 11 minutes ago, Jack Richard said: Also, should I implement IForgeContainerType in my Container class? No, that should be used in your registry instance for your ContainerType. 12 minutes ago, Jack Richard said: So an empty client container could be it? Just trying to understand. Imagine it like this. You have two instances, a server and the client. The server creates a container with a specific window id. The client creates a container with the same window id and a screen that can get information from the container. The server container sends default information to the client container which is read by the screen. A client screen clicks something, which gets sent as a packet to the server container, which updates the client container, which updates the screen. Note that the client creates a new instance of the container on open so the information needs to be synced. By default, only slots and tracked integers are synced. If you want to handle something else, you need to send it yourself.
September 25, 20205 yr You can send data from client (in this case screen), to the server (container/tileentity) using packets. And no, please read what he said again: Quote Finally, if your container is not registered using IForgeContainerType, then the packet information will never be sent to the client.
September 25, 20205 yr Author 16 minutes ago, ChampionAsh5357 said: Yes, that's the use of a packet through the network. But that should only be sent as needed as otherwise you just bottleneck the network creating more issues. No, that should be used in your registry instance for your ContainerType. Got it, and got it. 16 minutes ago, ChampionAsh5357 said: Imagine it like this. You have two instances, a server and the client. The server creates a container with a specific window id. The client creates a container with the same window id and a screen that can get information from the container. The server container sends default information to the client container which is read by the screen. A client screen clicks something, which gets sent as a packet to the server container, which updates the client container, which updates the screen. Note that the client creates a new instance of the container on open so the information needs to be synced. By default, only slots and tracked integers are synced. If you want to handle something else, you need to send it yourself. Thanks for explaining it to me, I'll try to wrap my head around that, and send an update soon. Thank you!
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.