Jump to content

[1.11.2/1.10.2] [UNSOLVED] Chunk Loader


Bektor

Recommended Posts

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 by Bektor
update code

Developer of Primeval Forest.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

1 hour ago, Draco18s said:

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 by Bektor

Developer of Primeval Forest.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 by Bektor

Developer of Primeval Forest.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

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

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



×
×
  • Create New...

Important Information

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