Jump to content

Tile entity spawn stacks in world from GUI button


BlameTaw

Recommended Posts

Hello All,

 

What is necessary to make a Tile Entity spawn stacks in the world from a GUI button press? I can make it work in the client side only, so I get phantom stacks in the world, and the inventory slot for the TileEntity refills to what it was if the GUI is closed and reopened. How do I make it happen server-side, too? Is there a standard way of communicating client-side changes to server-side?

Link to comment
Share on other sites

Use the Simple Network Implementation to send a packet to the server telling it that the player clicked the button. In the packet handler, check that the player has permission to spawn the stacks (e.g. is within interaction range of the block) before spawning them.

Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.

Link to comment
Share on other sites

Thanks, this looks like what I need. Do I need to make the changes on the client side, too, in this case? Like, when they click the button should I be modifying the stack amounts client-side AND server-side, and then spawning the stacks server-side only? (I assume spawning client and server at the same time would cause one phantom stack and one real stack.)

Edited by BlameTaw
Link to comment
Share on other sites

31 minutes ago, BlameTaw said:

Thanks, this looks like what I need. Do I need to make the changes on the client side, too, in this case? Like, when they click the button should I be modifying the stack amounts client-side AND server-side, and then spawning the stacks server-side only? (I assume spawning client and server at the same time would cause one phantom stack and one real stack.)

 

Just modify it on the server, the changes will be synced to the necessary clients.

Please don't PM me to ask for help. Asking your question in a public thread preserves it for people who are having the same problem in the future.

Link to comment
Share on other sites

Oh wow I can't believe I missed that when I was looking at the other classes. Thanks!

 

For the message itself, is there a common way people structure them? Is there a reason not to just convert a string to a byte array, or is it a better practice to just send relevant values and decode them on the other side?

Link to comment
Share on other sites

A string will ultimately get converted to byte array anyway in the end, as will everything else you send. The only reason I see to not convert it manually is a chance that the charset can be messed up. For example if you are sending to the server a string client has typed and it contains unicode characters. If you are converting the arriving byte array to a string manually on the server you might use say, ASCII charset and mess that string up completely. There is a ByteBufUtils class that handles that for you though.

Apart from that I think that structuring your packets is just a personal preference. If your packets are intended to be sent very frequiently and contain not much data it is the best to just write the data 'raw'. If your packets are big and contain bunch of data, especially if that data can vary (so you never know which parts the server/client will recieve) it might just be best to structure your data into an NBTTagCompound and just write/read that.

Obviously sending only relevant data that has changed and needs to be synced is the best practice :)

Link to comment
Share on other sites

Ah that makes sense. I didn't think about character encodings. Either way I'm only sending a BlockPos so it's easy to just send three ints.

 

I'm having an InstantiationException when I close the GUI, however. Seems to be coming from a NoSuchMethodException on my BufferEjectMessage.<init>() implying an issue during construction, maybe? I don't know why that would be. (stacktrace in the spoiler below the code)

 

Here is my IMessage implemention and IMessageHandler implementation:

package com.blametaw.itembuffers.blocks;

import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldServer;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.items.CapabilityItemHandler;

public class BufferEjectMessage implements IMessage {

	public BlockPos pos; //TileEntity Block Coordinates
	public BufferEjectMessage(int x, int y, int z){
		this.pos = new BlockPos(x,y,z);
	}
	
	public BufferEjectMessage(BlockPos pos){
		this.pos = pos;
	}
	
	@Override
	public void fromBytes(ByteBuf buf) {
		this.pos = new BlockPos(buf.readInt(), buf.readInt(), buf.readInt());
	}

	@Override
	public void toBytes(ByteBuf buf) {
		buf.writeInt(pos.getX());
		buf.writeInt(pos.getY());
		buf.writeInt(pos.getZ());
	}
	
	public static class MyMessageHandler implements IMessageHandler<BufferEjectMessage, IMessage> {
		
		@Override
		public IMessage onMessage(BufferEjectMessage message, MessageContext ctx) {
			
			//I assume this is wrong because I'd be greatly surprised if I was allowed to access game objects from within messages like this...
			
			TileEntityBasicBuffer te = (TileEntityBasicBuffer) Minecraft.getMinecraft().world.getTileEntity(message.pos);
			BufferStackHandler handler = (BufferStackHandler)te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
			handler.ejectExcessItems();
			return null;
		}
		
	}

}

 

Error stacktrace:

Spoiler

[15:49:57] [Netty Server IO #1/ERROR] [FML]: SimpleChannelHandlerWrapper exception
io.netty.handler.codec.DecoderException: java.lang.InstantiationException: com.blametaw.itembuffers.blocks.BufferEjectMessage
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:99) ~[MessageToMessageDecoder.class:4.0.23.Final]
	at io.netty.handler.codec.MessageToMessageCodec.channelRead(MessageToMessageCodec.java:111) ~[MessageToMessageCodec.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787) [DefaultChannelPipeline.class:4.0.23.Final]
	at io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:169) [EmbeddedChannel.class:4.0.23.Final]
	at net.minecraftforge.fml.common.network.internal.FMLProxyPacket.processPacket(FMLProxyPacket.java:111) [FMLProxyPacket.class:?]
	at net.minecraft.network.NetworkManager.channelRead0(NetworkManager.java:157) [NetworkManager.class:?]
	at net.minecraft.network.NetworkManager.channelRead0(NetworkManager.java:51) [NetworkManager.class:?]
	at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105) [SimpleChannelInboundHandler.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at net.minecraftforge.fml.common.network.handshake.NetworkDispatcher.handleServerSideCustomPacket(NetworkDispatcher.java:452) [NetworkDispatcher.class:?]
	at net.minecraftforge.fml.common.network.handshake.NetworkDispatcher.channelRead0(NetworkDispatcher.java:274) [NetworkDispatcher.class:?]
	at net.minecraftforge.fml.common.network.handshake.NetworkDispatcher.channelRead0(NetworkDispatcher.java:73) [NetworkDispatcher.class:?]
	at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105) [SimpleChannelInboundHandler.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319) [AbstractChannelHandlerContext.class:4.0.23.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787) [DefaultChannelPipeline.class:4.0.23.Final]
	at io.netty.channel.local.LocalChannel.finishPeerRead(LocalChannel.java:326) [LocalChannel.class:4.0.23.Final]
	at io.netty.channel.local.LocalChannel.access$400(LocalChannel.java:45) [LocalChannel.class:4.0.23.Final]
	at io.netty.channel.local.LocalChannel$5.run(LocalChannel.java:312) [LocalChannel$5.class:4.0.23.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:380) [SingleThreadEventExecutor.class:4.0.23.Final]
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:357) [NioEventLoop.class:4.0.23.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116) [SingleThreadEventExecutor$2.class:4.0.23.Final]
	at java.lang.Thread.run(Unknown Source) [?:1.8.0_131]
Caused by: java.lang.InstantiationException: com.blametaw.itembuffers.blocks.BufferEjectMessage
	at java.lang.Class.newInstance(Unknown Source) ~[?:1.8.0_131]
	at net.minecraftforge.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:101) ~[FMLIndexedMessageToMessageCodec.class:?]
	at net.minecraftforge.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:40) ~[FMLIndexedMessageToMessageCodec.class:?]
	at io.netty.handler.codec.MessageToMessageCodec$2.decode(MessageToMessageCodec.java:81) ~[MessageToMessageCodec$2.class:4.0.23.Final]
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:89) ~[MessageToMessageDecoder.class:4.0.23.Final]
	... 25 more
Caused by: java.lang.NoSuchMethodException: com.blametaw.itembuffers.blocks.BufferEjectMessage.<init>()
	at java.lang.Class.getConstructor0(Unknown Source) ~[?:1.8.0_131]
	at java.lang.Class.newInstance(Unknown Source) ~[?:1.8.0_131]
	at net.minecraftforge.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:101) ~[FMLIndexedMessageToMessageCodec.class:?]
	at net.minecraftforge.fml.common.network.FMLIndexedMessageToMessageCodec.decode(FMLIndexedMessageToMessageCodec.java:40) ~[FMLIndexedMessageToMessageCodec.class:?]
	at io.netty.handler.codec.MessageToMessageCodec$2.decode(MessageToMessageCodec.java:81) ~[MessageToMessageCodec$2.class:4.0.23.Final]
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:89) ~[MessageToMessageDecoder.class:4.0.23.Final]
	... 25 more

 

 

Link to comment
Share on other sites

I actually just figured it out. I didn't have a default constructor on the BufferEjectMessage. It is ejecting items now, however they are still phantom items. Is my message not actually executing server side, even though I'm calling sendToServer: Reference.networkWrapperInstance.sendToServer(new BufferEjectMessage(container.getTileEntity().getPos()));

 

Does Minecraft.getMinecraft() not work for server-side? Is there another way I should be referencing the tile entity itself to call the eject functions? Also, is my assumption correct that I shouldn't be directly referencing the TileEntity from the message? That just seems like a threading nightmare waiting to happen...

Edited by BlameTaw
Link to comment
Share on other sites

Yeah, Minecraft::getMinecraft() is client-side only. As is Minecraft class in general. Use FMLCommonHandler::getMinecraftServerInstance() to obtain the server in general if you need it, or DimensionManager::getWorld(int id) to get a specific WorldServer.

All networking is done on a specific thread so you should not do anything on the networking thread that is world/entities related. Wrap your actions in a Runnable and pass that runnable to a valid IThreadListener using IThreadListener::addScheduledTask(Runnable). A IThreadListener is either a WorldServer instance(server-side) or Minecraft instance(client-side)

Link to comment
Share on other sites

Ah okay that makes sense! So do I have to have the world ID passed in with the BlockPos in order to know which world to get? I see that both MinecraftServer and DimensionManager require an ID.

 

Thanks for all the quick responses! Sorry for so many questions, I'm still very new to this API.

Link to comment
Share on other sites

Well, to be fair, if you simply want your items to be dropped upon your gui being closed: if you are using a custom Container for your inventory you can simply override container's onContainerClosed method and drop your items there. No packets needed as Container is server-sided.

Granted, that will only work if you are using a container in the first place :)

You haven't specified if you are using a container or not in your question, so I haven't thought about it immidiately

Edited by V0idWa1k3r
Link to comment
Share on other sites

16 minutes ago, V0idWa1k3r said:

Well, to be fair, if you simply want your items to be dropped upon your gui being closed: if you are using a custom Container for your inventory you can simply override container's onContainerClosed method and drop your items there. No packets needed as Container is server-sided.

Granted, that will only work if you are using a container in the first place :)

You haven't specified if you are using a container or not in your question, so I haven't thought about it immidiately

Oh well maybe I'll try putting it there then...

 

In the mean time, I just found that if you save/quit and reopen the game after modifying the TE's inventory through the GUI, the changes aren't saved so you can duplicate items... Why would changes to the slots not be happening server-side? Isn't that handled by the slot-click method in either Container or GuiContainer? (I forget what it's called and which one)

 

Edit: it DOES make the proper item changes when ejecting items, it's only when manually moving stacks that changes are not made server-side.

Edited by BlameTaw
Link to comment
Share on other sites

You are either not changing the inventory of your tileentity in your packet handling (as in you are spawning items in the world but not settings the itemstacks to empty in your tile entity... or setting them to empty but only on client side) or there is an error in saving/loading your Tile's inventory. You can debug those two spots in your code using breakpoints to see where the problem lies

Edited by V0idWa1k3r
Link to comment
Share on other sites

In that case, maybe I have no idea how to ensure my entity's packet handling is correct...It's certainly something I didn't fully understand when researching about it.

 

Here is everything I have for the packet handling in my tile entity class. Is there more that I need than just this? I will fully admit that I copy-pasted this from an example class I found, though I did make an effort to understand what it is doing. I assumed these functions are being automatically called by the Container and GuiContainer manipulations. Am I wrong?

 

//	// When the world loads from disk, the server needs to send the TileEntity information to the client
//	//  it uses getUpdatePacket(), getUpdateTag(), onDataPacket(), and handleUpdateTag() to do this
	@Override
	@Nullable
	public SPacketUpdateTileEntity getUpdatePacket(){
		NBTTagCompound updateTagDescribingTileEntityState = getUpdateTag();
		final int METADATA = 0;
		return new SPacketUpdateTileEntity(this.pos, METADATA, updateTagDescribingTileEntityState);
	}

	@Override
	public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
		NBTTagCompound updateTagDescribingTileEntityState = pkt.getNbtCompound();
		handleUpdateTag(updateTagDescribingTileEntityState);
	}

  /* Creates a tag containing the TileEntity information, used by vanilla to transmit from server to client
     Warning - although our getUpdatePacket() uses this method, vanilla also calls it directly, so don't remove it.
   */
	@Override
	public NBTTagCompound getUpdateTag(){
		NBTTagCompound nbtTagCompound = new NBTTagCompound();
		writeToNBT(nbtTagCompound);
		return nbtTagCompound;
	}

  /* Populates this TileEntity with information from the tag, used by vanilla to transmit from server to client
   Warning - although our onDataPacket() uses this method, vanilla also calls it directly, so don't remove it.
 */
	@Override
	public void handleUpdateTag(NBTTagCompound tag){
		this.readFromNBT(tag);
	}

 

Link to comment
Share on other sites

Oh, when you are manually moving items? Then most likely there is an error in your container implementation. How exactly are you opening you gui + container? You need to do it using your proxy, providing a Container instance for the server and a GuiContainer instance for your client.

Link to comment
Share on other sites

I have an IGuiHandler that gets the server/client GUI elements.

package com.blametaw.gui;

import com.blametaw.itembuffers.blocks.TileEntityBasicBuffer;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.IGuiHandler;

public class BasicBufferGuiHandler implements IGuiHandler {

	private static final int GUIID = 1;
	public static int getGuiID(){return GUIID;}
	
	@Override
	public Object getServerGuiElement(int ID, EntityPlayer player, World world,
			int x, int y, int z) {
		if (ID != getGuiID()) {
			System.err.println("Invalid ID: expected " + getGuiID() + ", received " + ID);
		}
		
		BlockPos xyz = new BlockPos(x, y, z);
		TileEntity tileEntity = world.getTileEntity(xyz);
		if (tileEntity instanceof TileEntityBasicBuffer) {
			TileEntityBasicBuffer tebb = (TileEntityBasicBuffer) tileEntity;
			return new ContainerBasicBuffer(player.inventory, tebb);
		}
		return null;
	}

	@Override
	public Object getClientGuiElement(int ID, EntityPlayer player, World world,
			int x, int y, int z) {
		if (ID != getGuiID()) {
			System.err.println("Invalid ID: expected " + getGuiID() + ", received " + ID);
		}
		
		BlockPos xyz = new BlockPos(x, y, z);
		TileEntity tileEntity = world.getTileEntity(xyz);
		if (tileEntity instanceof TileEntityBasicBuffer) {
			TileEntityBasicBuffer tebb = (TileEntityBasicBuffer) tileEntity;
			return new GuiContainerBasicBuffer(new ContainerBasicBuffer(player.inventory, tebb));
		}
		return null;
	}

}

 

 

And I'm calling it with this function in my Block:

@Override
	public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand,
			EnumFacing side, float hitX, float hitY, float hitZ){
		if (worldIn.isRemote) return true;
		playerIn.openGui(ItemBuffers.instance, BasicBufferGuiHandler.getGuiID(), worldIn, pos.getX(), pos.getY(), pos.getZ());
		return true;
	}

 

 

From what I understand, this should be opening it on both server and client. Also, I'd be surprised if this was the issue because moving things from the TE slots into the player inventory slots DOES work, and those changes are persistent. It's just that quit/reload resets the TE's NBT data.

 

Also there are GUI buttons that update some other values in the NBT data, so I assume that should be done with IMessages as well, right? I didn't think about that before... When you quit/reload those numbers are also reset which implies they're also only happening client side.

 

I've attached a picture of my GUI to give you more of an idea what I'm referring to. That might help a bit.

2017-05-12 18_10_53-Minecraft 1.11.2.png

Link to comment
Share on other sites

Yes, the buttons need an IMessage to sync their presses.

I'm not really sure why that issue is happening then. Try just debugging your code in 'problematic' spots that could potentially be causing the issue. See if any TileEntity data is persistent at all upon reloading the world. If it is not then the problem is within saving/loading your tileentity. Also check the log for any error messages - your tile entity will not be saved if it is not registered with GameRegistry::registerTileEntity. Container indeed handles all slot itemstacks changes, and your IGuiHandler seems fine.

 

Link to comment
Share on other sites

I am registering the TileEntity. No errors in the logs. From some trace messages in the writeToNBT and readFromNBT functions it seems that the client never calls either function, and the server only ever calls the writeToNBT function...That's confusing to me.

 

Okay I feel like I have messed up a lot of things...I'll have to play around with this a bit and get back to you. Whoops.

Edited by BlameTaw
Link to comment
Share on other sites

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.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Hi, the microphone mod is not working on my Mac. It says “launcher does not support MacOS microphone permissions” Thank you in advance for answering.
    • Make sure you have Optifine installed as a mod. Go into Options > Video Settings > Shaders > and then click the shader you want Make sure the shader.zip files are in the shaderpacks folder inside the minecraft folder
    • It sounds like you're probably registering the item in the wrong place, try looking at this tutorial for how to register items:  Forge Modding Tutorial - Minecraft 1.20: Custom Items & Creative Mode Tab | #2 This (free) tutorial series is excellent, by the way, and I'd highly recommend watching through some or all of the videos. There may also be an error in the code I showed above since I was in a hurry, but it should be enough for the general idea. I can't be more specific since I don't know exactly what you plan to do.
    • Realizing I was a victim of a scam was a devastating blow. My initial investment of $89,000, driven by dreams of financial success and the buzz surrounding a new cryptocurrency project, turned into a nightmare. The project promised high returns and rapid gains, attracting many eager investors like myself. However, as time passed and inconsistencies began to surface, it became evident that I had made a grave mistake by not thoroughly vetting the brokerage company handling the investment. Feeling anxious and betrayed, I desperately searched for a way to recover my funds. It was during this frantic search that I stumbled upon the Lee Ultimate Hacker tool through a Facebook post. With little left to lose, I decided to reach out to their team for help. To my relief, they were quick to respond and immediately started recovering my compromised email and regaining access to my cryptocurrency wallets. The team at Lee Ultimate Hacker was incredibly professional and transparent throughout the process. They meticulously traced the digital footprints left by the scammers, employing advanced technological methods to unravel the complex network that had ensnared my funds. Their expertise in cybersecurity and recovery strategies gradually began to turn the tide in my favor. Although the scammers had already siphoned off $30,000 worth of Bitcoin, Lee Ultimate Hacker was relentless in their pursuit. They managed to expose the fraudulent activities of the scam operators, revealing their identities and the mechanisms they used to lure investors. This exposure was crucial not only for my case but also as a warning to the wider community about the perils of unverified investment schemes. As we progressed, it became a race against time to retrieve the remaining $59,000 before the scammers could vanish completely. Each step forward was met with new challenges, as these criminals constantly shifted tactics and moved their digital assets to evade capture. Nonetheless, the determination and skill of the recovery team kept us hopeful. Throughout this ordeal, I learned the hard value of caution and due diligence in investment, especially within the volatile world of cryptocurrency. The experience has been incredibly taxing, both emotionally and financially, but the support and results provided by Lee Ultimate Hacker have been indispensable. The recovery process is ongoing, and while the final outcome remains uncertain, the progress made so far gives me hope. The battle to recover the full amount of my investment continues, and with the expertise of Lee Ultimate Hacker, I remain optimistic about the eventual recovery of my funds. Their commitment to their clients and proficiency in handling such complex cases truly sets them apart in the field of cyber recovery. LEEULTIMATEHACKER@ AOL. COM   Support @ leeultimatehacker . com.  telegram:LEEULTIMATE   wh@tsapp +1  (715) 314  -  9248     
    • Hi everyone. I’m excited to share my experience with CrackerWizard Recovery Firm. They helped me recover a substantial amount of crypto after falling victim to online scams disguised as Bitcoin investments. CrackerWizard’s exceptional service enabled me to retrieve my lost funds, despite the complex circumstances surrounding the case. With their dedicated team and advanced technology, they swiftly traced and recovered my assets. CrackerWizard is a reliable partner in the crypto world, highly recommended for anyone facing similar challenges. Contact them.
  • Topics

  • Who's Online (See full list)

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.