Posted August 24, 20178 yr Hi, I've created a basic ChunkLoader and would like to get some feedback on the code. (The code should be optimized for performance using Java 8). I've also got the following problem: Quote Reloading Chunk Loader at BlockPos{x=2169, y=56, z=-554} because XXXX-XXXX-XXXX-XXXX placed or interacted with it. The mod XXX attempted to force load a chunk with an invalid ticket. This is not permitted. The error occurs in the setTicket method of the TileEntity. It should be noted that I haven't figured out which chunks should be loaded except for the chunk the chunk loader is in (see ChunkLoaderManager), so if anyone knows how to achieve this, let me know. BlockChunkLoader: public class BlockChunkLoader extends Block implements ITileEntityProvider { public static PropertyEnum<ChunkLoaderTypes> META_PROPERTY = PropertyEnum.create("loader", ChunkLoaderTypes.class); public BlockChunkLoader() { super(Material.IRON); this.setDefaultState(this.blockState.getBaseState().withProperty(META_PROPERTY, ChunkLoaderTypes.LOAD_01)); } @Override public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) { if(worldIn.isRemote) return; TileEntity tile = worldIn.getTileEntity(pos); if(tile != null && tile instanceof TileEntityChunkLoader) { TileEntityChunkLoader tileCL = (TileEntityChunkLoader) tile; if(placer instanceof EntityPlayer) { EntityPlayer player = (EntityPlayer) placer; tileCL.setOwnerId(player.getUniqueID()); final ForgeChunkManager.Ticket ticket = ForgeChunkManager.requestPlayerTicket(JustAnotherEnergy.getInstance(), tileCL.getOwnerIdString(), worldIn, Type.NORMAL); if(ticket == null) { // Forge will log errors here, too JustAnotherEnergy.getLogger().warn("Chunkloading at {} failed. Most likely the limit was reached. {}", pos, ForgeChunkManager.ticketCountAvailableFor(player.getName())); return; } final NBTTagCompound modData = ticket.getModData(); modData.setTag("blockPosition", NBTUtil.createPosTag(pos)); if(this.getMetaFromState(state) == ChunkLoaderTypes.LOAD_01.getID()) modData.setInteger("size", 1); else if(this.getMetaFromState(state) == ChunkLoaderTypes.LOAD_09.getID()) modData.setInteger("size", 9); else if(this.getMetaFromState(state) == ChunkLoaderTypes.LOAD_25.getID()) modData.setInteger("size", 25); tileCL.setTicket(ticket); //tileCL.setDefaultTicket(ticket); } } } @Override public void breakBlock(World worldIn, BlockPos pos, IBlockState state) { TileEntity tile = worldIn.getTileEntity(pos); if(tile != null && tile instanceof TileEntityChunkLoader) { TileEntityChunkLoader tileCL = (TileEntityChunkLoader) tile; ForgeChunkManager.releaseTicket(tileCL.getTicket()); } super.breakBlock(worldIn, pos, state); } @Override public TileEntity createNewTileEntity(World worldIn, int meta) { switch(meta) { case 0: return new TileEntityChunkLoader(500, 500); case 1: return new TileEntityChunkLoader(4500, 900); case 2: return new TileEntityChunkLoader(125000, 25000); default: return new TileEntityChunkLoader(500, 100); } } @Override public void getSubBlocks(Item itemIn, CreativeTabs tab, List<ItemStack> list) { for(final ChunkLoaderTypes type : ChunkLoaderTypes.values()) list.add(new ItemStack(this, 1, type.getID())); } @Override public int damageDropped(IBlockState state) { return this.getMetaFromState(state); } @Override protected BlockStateContainer createBlockState() { return new BlockStateContainer(this, META_PROPERTY); } @Override public int getMetaFromState(IBlockState state) { return state.getValue(META_PROPERTY).getID(); } @Override public IBlockState getStateFromMeta(int meta) { return this.getDefaultState().withProperty(META_PROPERTY, ChunkLoaderTypes.byMetadata(meta)); } } TileEntityChunkLoader public class TileEntityChunkLoader extends TileEntityEnergy { @Nullable private UUID ownerId = null; private static final String UUID_TAG = "UUID"; @Nullable private ForgeChunkManager.Ticket ticket = null; /* * ticket used as a default to stop conflicts with changing the actual ticket * implemented to allow the block to not call ChunkLoaderManager.startChunkLoading! */ @Nullable private ForgeChunkManager.Ticket dfTicket = null; private boolean wasActive = false; public TileEntityChunkLoader() { // this has to be here in order for correct saving and loading of NBT data } public TileEntityChunkLoader(int capacity, int maxTransfer) { super(capacity, maxTransfer, false); this.getEnergyManager().setTransferMode(EnergyTransfer.CONSUMER); } @Override public void update() { super.update(); if(!this.hasWorld() || this.getWorld().isRemote || this.ticket == null) return; boolean flag = false; flag = EnergyUtils.consumeEnergy(this, 100); if(flag) { if(this.wasActive) ChunkLoaderManager.startChunkLoading(this.getWorld(), this.ticket); //ChunkLoaderManager.startChunkLoading(this.getWorld(), this.dfTicket); this.getWorld().notifyBlockUpdate(this.getPos(), this.getWorld().getBlockState(this.getPos()), this.getWorld().getBlockState(this.getPos()), 3); this.markDirty(); } else if(this.ticket != null && !flag && !this.wasActive) ChunkLoaderManager.stopChunkLoading(this.getWorld(), this.ticket); if(this.wasActive != flag) this.wasActive = flag; } public void setDefaultTicket(@Nonnull Ticket ticket) { this.dfTicket = ticket; } public void setTicket(@Nonnull Ticket ticket) { if(this.ticket != null) ForgeChunkManager.releaseTicket(this.ticket); this.ticket = ticket; this.getWorld().addBlockEvent(this.getPos(), this.getBlockType(), 1, 1); JustAnotherEnergy.getLogger().info("Reloading Chunk Loader at {} because {} placed or interacted with it.", this.getPos(), this.ticket.getPlayerName()); } public Ticket getTicket() { return this.ticket; } @Override public SPacketUpdateTileEntity getUpdatePacket() { return new SPacketUpdateTileEntity(this.getPos(), 3, this.getUpdateTag()); } @Override public NBTTagCompound getUpdateTag() { return this.writeToNBT(new NBTTagCompound()); } @Override public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) { super.onDataPacket(net, pkt); this.handleUpdateTag(pkt.getNbtCompound()); } @Override public void readFromNBT(NBTTagCompound compound) { if(compound.hasKey(TileEntityChunkLoader.UUID_TAG)) this.ownerId = compound.getUniqueId(TileEntityChunkLoader.UUID_TAG); super.readFromNBT(compound); } @Override public NBTTagCompound writeToNBT(NBTTagCompound compound) { if(this.ownerId != null) compound.setUniqueId(TileEntityChunkLoader.UUID_TAG, this.ownerId); return super.writeToNBT(compound); } public void setOwnerId(UUID ownerId) { this.ownerId = ownerId; } public String getOwnerIdString() { return this.ownerId.toString(); } } ChunkManagerCallback public class ChunkManagerCallback implements PlayerOrderedLoadingCallback { @Override public void ticketsLoaded(List<Ticket> tickets, World world) { for(Ticket ticket : tickets) { final NBTTagCompound modData = ticket.getModData(); if(!modData.hasKey("blockPosition") || !modData.hasKey("size")) continue; ChunkLoaderManager.startChunkLoading(world, ticket); } } @Override public ListMultimap<String, Ticket> playerTicketsLoaded(ListMultimap<String, Ticket> tickets, World world) { final ListMultimap<String, Ticket> copyTickets = ArrayListMultimap.create(); for(String player : tickets.keySet()) { for(Ticket ticket : tickets.values()) { final NBTTagCompound modData = ticket.getModData(); if(modData.hasKey("blockPosition") && modData.hasKey("size")) copyTickets.put(player, ticket); } } return copyTickets; } } ChunkLoaderManager public class ChunkLoaderManager { public static void startChunkLoading(World world, Ticket ticket) { if(world == null || ticket == null) return; NBTTagCompound modData = ticket.getModData(); if(!modData.hasKey("blockPosition") || !modData.hasKey("size")) return; BlockPos pos = NBTUtil.getPosFromTag(modData.getCompoundTag("blockPosition")); TileEntity tile = world.getTileEntity(pos); if(!(tile instanceof TileEntityChunkLoader)) return; int size = modData.getInteger("size"); if(size == 1) ForgeChunkManager.forceChunk(ticket, new ChunkPos(pos)); // TODO: load radius, eg. size 9 = 9 chunks loaded ((TileEntityChunkLoader) tile).setTicket(ticket); } public static void stopChunkLoading(World world, Ticket ticket) { if(world == null || ticket == null) return; NBTTagCompound modData = ticket.getModData(); if(!modData.hasKey("blockPosition") || !modData.hasKey("size")) return; int size = modData.getInteger("size"); BlockPos pos = NBTUtil.getPosFromTag(modData.getCompoundTag("blockPosition")); if(size == 1) ForgeChunkManager.unforceChunk(ticket, new ChunkPos(pos)); } } The ChunkManagerCallback is registered after the blocks are registered in the preInit method. Thx in advance. Bektor Edited August 25, 20178 yr by Bektor update code Developer of Primeval Forest.
August 24, 20178 yr This may help you: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/industry/ExpandedIndustryBase.java#L180-L204 Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
August 24, 20178 yr Author 1 hour ago, Draco18s said: This may help you: https://github.com/Draco18s/ReasonableRealism/blob/master/src/main/java/com/draco18s/industry/ExpandedIndustryBase.java#L180-L204 I am wondering why you store your ticket in the main class and why you got a list? And what exactly does this code? I mean, I can see that it is there to request a ticket and force a chunk to load or unload, but for what is the logic around there? Note: I just implemented my logic by looking into what others did and reading the doc for ForgeChunkManager, like the 5 steps described there. So I know not really much about this hole topic of chunk loading. Edited August 24, 20178 yr by Bektor Developer of Primeval Forest.
August 24, 20178 yr My code uses a separate ticket for each chunk. I am not 100% sure at this point that that is required, but I know that my code can take advantage of that. This code is in my main class because the code is not sided. Both the client and server need to know about the chunks being loaded. I use a list (actually a HashMap) because I need to keep track of all of the tickets and I need to know if the ticket for a given chunk is active or not (if it's active, increment the number of active "references" to that ticket). Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
August 25, 20178 yr Author On 8/25/2017 at 0:05 AM, Draco18s said: My code uses a separate ticket for each chunk. I am not 100% sure at this point that that is required, but I know that my code can take advantage of that. This code is in my main class because the code is not sided. Both the client and server need to know about the chunks being loaded. I use a list (actually a HashMap) because I need to keep track of all of the tickets and I need to know if the ticket for a given chunk is active or not (if it's active, increment the number of active "references" to that ticket). Ah, ok that makes it clear. I don't think it is required to create a seperate ticket for reach chunk. I saw some chunkloader mods to load up to 27 chunks with one ticket. If I recall correctly 27 is also the default limit. I've just updated the code in the main post as I changed some lines after looking at your code, thought the problem still remains. (I couldn't yet figure out what causes the problem ) Thought it is interesting that when enough energy is there for one tick until it has to refill for one or two ticks, the problem also occurs. Hm, on my side the code is currently not called on the client side (as it relies on energy logic stuff which is server side only). See the flag boolean with the consume method. I also do not call forceChunk multiply times for one ticket (which is recommended) as my current code does not support this kind of stuff. Edited August 25, 20178 yr by Bektor Developer of Primeval Forest.
August 25, 20178 yr 10 minutes ago, Bektor said: Ah, ok that makes it clear. I don't think it is required to create a seperate ticket for reach chunk. I saw some chunkloader mods to load up to 27 chunks with one ticket. If I recall correctly 27 is also the default limit. I didn't have a good way of mapping multiple chunks to one ticket. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
August 25, 20178 yr Author Just now, Draco18s said: I didn't have a good way of mapping multiple chunks to one ticket. Well, I've got a method which could do it if I would finally figure out how to get a circle shape of 9 chunks in a damn for-loop. (and if I would know how to fix the problem) Might be you want to take a look at my method if you at some point want to store multiply chunks to one ticket. Developer of Primeval Forest.
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.