Jump to content

Recommended Posts

Posted

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?

Posted

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.

Posted (edited)

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
Posted
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.

Posted

Is there a way to make it only happen when the GUI is closed, rather than when the button is pressed? I'm realizing that might make for a better user experience with this. If not, I'll try making it work on button press.

Posted

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?

Posted

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 :)

Posted

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

 

 

Posted (edited)

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
Posted

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)

Posted

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.

Posted (edited)

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
Posted (edited)
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
Posted (edited)

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
Posted

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);
	}

 

Posted

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.

Posted

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

Posted

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.

 

Posted (edited)

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

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



×
×
  • Create New...

Important Information

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