Jump to content

[Solved] Overriding getLightValue() has no effect


Lycanus Darkbinder

Recommended Posts

Greetings,

 

I have a TileEntity with two methods:

 

int getCurrentLightVal()

void setCurrentLightVal(int val);

 

the setter is called in onBlockActivated and sets the new light value, rolling over to 4 if the value is greater than 15 (ie: the light value will always be between 4 and 15).

 

My problem is that in my block's override of getLightValue(), returning getCurrentLightValue() from the TileEntity does not actually cause the light in the world to change. In fact the block does not light up at all.

 

If I remove the TileEntity and create a separate "lit" block and use setBlock() to swap between the two, the lighting changes properly but naturally does not save state between sessions.

 

 

Is there something I'm missing in regards to getLightValue()? It seems pretty straight forward to me. I stepped through the code in the debugger and fail to see why this is not working even though the function is returning the proper value.

Link to comment
Share on other sites

How are you getting your TileEntity if you have no access to a World object? You aren't, and are using static methods/fields? Don't.

 

You can use one of Forge's added methods to the World class:

public int getLightValue(IBlockAccess iBlockAccess, int x, int y, int z)

IBlockAccess is all you need, as that type has the method getBlockTileEntity(x,y,z) (IBlockAccess is implemented by World).

Author of PneumaticCraft, MineChess, Minesweeper Mod and Sokoban Mod. Visit www.minemaarten.com to take a look at them.

Link to comment
Share on other sites

How are you getting your TileEntity if you have no access to a World object? You aren't, and are using static methods/fields? Don't.

 

You can use one of Forge's added methods to the World class:

public int getLightValue(IBlockAccess iBlockAccess, int x, int y, int z)

IBlockAccess is all you need, as that type has the method getBlockTileEntity(x,y,z) (IBlockAccess is implemented by World).

 

Crap, I made a mistake in my OP. I was thinking of getIcon() for some reason when I wrote the post. I am in fact using the getLightValue() as you suggested.

 

The tile entity returns 4,5,6, etc. depending on the internal value it has but when getLightValue passes that back to the super class, nothing happens. It goes through a bunch of calculations but the light value in the world never changes.

 

I edited the OP accordingly.

 

    @Override
    public int getLightValue(IBlockAccess iba, int x, int y, int z)
    {
        // TODO
        //  Figure out why the return result has no effect on lighting
        //   for some reason the light value stays at 10 where it started
        
        TileEntitySmartLight te = getTileEntity(iba, x, y, z);
        
        if (te != null)
        {
            if (te.getIsLit())
                return te.getCurrentLightValInt(); // no matter the value, world light never changes
            else
                return 0;
        }
        else
        {
            // For some reason we can't get a valid TileEntity,
            //  let the super class deal with the light value
            return super.getLightValue(iba, x, y, z);
        }
        
    }

    // Support functions to reduce typing

    private TileEntitySmartLight getTileEntity(IBlockAccess iba, int x, int y, int z)
    {
        // Returns a TileEntitySmartLight retrieved by a IBlockAccess object
        
        // Grab a generic tileentity
        TileEntity te = iba.getBlockTileEntity(x, y, z);
        
        // Check if it is the proper type and cast it for the return
        //  otherwise return NULL
        
        if (te instanceof TileEntitySmartLight)
            return (TileEntitySmartLight)te;
        else
            return null;
    }

    private TileEntitySmartLight getTileEntity(World world, int x, int y, int z)
    {
        // Returns a TileEntitySmartLight retrieved by a WORLD object
        
        // Grab a generic tileentity
        TileEntity te = world.getBlockTileEntity(x, y, z);
        
        // Check if it is the proper type and cast it for the return
        //  otherwise return NULL
        
        if (te instanceof TileEntitySmartLight)
            return (TileEntitySmartLight)te;
        else
            return null;
    }

Link to comment
Share on other sites

The getIsLit() method in your tileEntity, and the field that returns the value: Is that set on both client and server side?

 

Hmm, no. I always use the isRemote check before interacting with the TileEntity. I was under the impression that only GUI related stuff needed to be done client side because the server would handle sending block information and lighting to all players in the chunk.

 

So, for example, when onBlockActivated() gets called, I only set the isLit property after checking !world.isRemote. Admittedly I watched several tutorials on YouTube and checked the Forge wiki but most information seems outdated. I pieced together what I thought would work.

Link to comment
Share on other sites

I was under the impression that only GUI related stuff needed to be done client side because the server would handle sending block information and lighting to all players in the chunk.

No, on both the client and server is the same game running. Most of the things should be executed on both client and server. There are things that are side specific yes. GUI's and rendering are examples of things that only should run on the client. Some things only should be executed by the server, and most of the times these are things that if they were executed by both client and server, desyncs will happen (they both execute in a different way). This is the case for example when random numbers are being used. And that is for instance with explosions, as the blocks removed in the world by an explosion is determined by a random number generator. Therefore explosions only should be executed on the server (with a !worldObj.isRemote).

 

About your problem: Even if the server would update the information on the client, the client will still execute the same piece of code to calculate the lighting, which means also the client will get its light value of the getIsLit() method in the (client sided) TileEntity. Solution: Remove the check of !worldObj.isRemote.

Author of PneumaticCraft, MineChess, Minesweeper Mod and Sokoban Mod. Visit www.minemaarten.com to take a look at them.

Link to comment
Share on other sites

Unfortunately neither change made any difference.

 

Since markBlockForUpdate() is not available from IBlockAccess, I tried putting it in updateTick() but there was no effect. The only way to get any light at all was to return the result from calling super.getLightValue().

 

That's the part I don't understand. My TileEntity returns 14 which is the same as the result from calling super.getLightValue() but it does nothing. I even took out all checks such as isLit() and simply had it return the value from TileEntity.

 

Note:

 

I also tried world.markBlockForRenderUpdate() because the comments say it is used for lighting changes but it also had no effect.

Link to comment
Share on other sites

@Mazetar

 

Thanks for that but unfortunately it doesn't remedy my problem.

 

I had already used Eclipse to step through what's happening and watched Minecraft calculate the light value:

 

  • When I return 14 from my tile entity the calculated light value is not updated in the world.
  • When I return 14 from super.getLightValue() the calculated light value is updated in the world.

It doesn't make sense to me.

 

Link to comment
Share on other sites

Maybe you need to tell the world to re-calculate lightning?

I know from working with schematic API's that there are many which have problems correcting light issues, due to it not updating properly after being set

If you guys dont get it.. then well ya.. try harder...

Link to comment
Share on other sites

Maybe you need to tell the world to re-calculate lightning?

I know from working with schematic API's that there are many which have problems correcting light issues, due to it not updating properly after being set

 

That's the thing, it does recalculate the lighting. The function getLightValue() is called from world.computeLightValue():

 

int blockLight = (block == null ? 0 : block.getLightValue(this, par1, par2, par3));

 

the debugger shows blockLight is 14 regardless of how I return from getLightValue(). Everything is identical except for the fact that it isn't visually updating for the player.

Link to comment
Share on other sites

Still no luck. I took a look at what setBlock() did because it always refreshes the light and pulled these functions out and stuck them in updateTick():

 

  • updateAllLightTypes()
  • markBlockForUpdate()
  • notifyBlockChange()

The last call actually ends up calling notifyBlocksOfNeighborChange() but for whatever reason this still had no effect.

 

Unfortunately setBlock() destroys the TileEntity and copying it and calling setBlockTileEntity() after doesn't work because the TileEntity starts off with no light value and it also doesn't trigger getLightValue().

Link to comment
Share on other sites

Ok, so I tested getLightValue() using this:

 

    public int getLightValue(IBlockAccess iba, int x, int y, int z)
    {
        Random rand = new Random();
        
        int Low = 1;
        int High = 15;
        int val = rand.nextInt(High-Low) + Low;
        
        return val;
    }

 

which makes the individual sides of the block strobe on and off at varying intensity which in itself is kinda neat. The problem is the only way I could trigger it was setBlock() which as mentioned above destroys and recreates the TileEntity thereby losing all the stored info.

 

I've ripped all sorts of functions out of setBlock() and tried to override them but to no avail. I even tried overriding shouldRefresh() in TileEntity which can return FALSE to prevent destroying the TileEntity but it never got called.

Link to comment
Share on other sites

Interesting problem.

 

First you shoud use at getLightValue (BlockSide) every time you do not want light 0.

 

Now i post some code which i found at eloraams code just copy and paste it in your TileEntity and call it over the UpdateEntity Function. That solve your problem. I tested it with the BaconMod^^"

 

and a hint you do not need worldObj is remote

 

   public void updateBlock()
    {
        int var1 = this.worldObj.getBlockMetadata(this.xCoord, this.yCoord, this.zCoord);
        this.worldObj.markBlockForRenderUpdate(this.xCoord, this.yCoord, this.zCoord);
        markBlockDirty(this.worldObj, this.xCoord, this.yCoord, this.zCoord);
    }

    public void markBlockDirty(World var0, int var1, int var2, int var3)
    {
        if (var0.blockExists(var1, var2, var3))
        {
            var0.getChunkFromBlockCoords(var1, var3).setChunkModified();
        }
    }

 

Don't ask my why this function is asking for blockMetadata. just leave it as it is eloraam know what she did^^"

Its her code i copied it and it works perfect^^"

Link to comment
Share on other sites

Don't ask my why this function is asking for blockMetadata. just leave it as it is eloraam know what she did^^"

Its her code i copied it and it works perfect^^"

:'(

Copy pasting and not understanding the code...

That var1 is completely useless.

 

By the way, since markBlockForUpdate(x,y,z) didn't work, that method won't either.

 

The issue is within the TileEntity.

getCurrentLightValInt() probably doesn't return same value on both sides.

Link to comment
Share on other sites

@Mazetar

 

The problem with that is setBlock() causes a call to getLightValue() but at that time the TileEntity has been reset because of a call to removeTileEntity() somewhere in the chain. That means that copying the tileEntity back after setBlock() doesn't work unless you can trigger a call to getLightValue() which is my problem.

 

@Moritz

 

If that's from RedPower, it's from Minecraft 1.4.6 and does not apply to 1.5.2. There is no updateBlock() to override in TileEntity.

 

@GotoLink

 

No, getLightValueInt() is returning the same for both sides. What I did notice however is that sometimes in getLightValue() the getBlockTileEntity occasionally returns a NULL TileEntity.

 

The big roadblock here is how to tell Minecraft that the block has changed to trigger a call to getLightValue() without actually changing it by destroying the block and tileentity

 

 

@Anyone who's interested

 

If you follow the call stack after calling setBlock() there are a bunch of private methods that trigger light recalculations. I wonder, if I create a class to extend World and override setBlock() if that may help. I could copy those private methods and just tell it to ignore the call to breakBlock() and removeTileEntity().

Link to comment
Share on other sites

@GotoLink

 

No, getLightValueInt() is returning the same for both sides. What I did notice however is that sometimes in getLightValue() the getBlockTileEntity occasionally returns a NULL TileEntity.

 

The big roadblock here is how to tell Minecraft that the block has changed to trigger a call to getLightValue() without actually changing it by destroying the block and tileentity

 

 

@Anyone who's interested

 

If you follow the call stack after calling setBlock() there are a bunch of private methods that trigger light recalculations. I wonder, if I create a class to extend World and override setBlock() if that may help. I could copy those private methods and just tell it to ignore the call to breakBlock() and removeTileEntity().

Unneeded, seriously unneeded.

Use world.markBlockForUpdate(x,y,z). It triggers light calculation.

If that fails, something is wrong in your tileentity.

Link to comment
Share on other sites

Unneeded, seriously unneeded.

Use world.markBlockForUpdate(x,y,z). It triggers light calculation.

If that fails, something is wrong in your tileentity.

 

I think I figured out the problem: IBlockAccess in getLightValue() always gets a client version of the TileEntity, it never gets a server version and World in updateTick() always gets a server version so the two TileEntities are not the same.

 

Illustration:

 

    @Override
    public void onBlockPlacedBy(World world, int x, int y, int z, EntityLiving entityLiving, ItemStack itemStack)
    {
        // Initialize the TileEntity
        TileEntitySmartLight te = (TileEntitySmartLight)world.getBlockTileEntity(x, y, z);
        
        if (te != null)
        {
            te.Init();
            
            if (world.isRemote)
                te.setIsServer(false);
            else
                te.setIsServer(true);
        }
    }

    @Override
    public void updateTick(World world, int x, int y, int z, Random random)
    {
        TileEntitySmartLight te = (TileEntitySmartLight)world.getBlockTileEntity(x, y, z);
        
        if (te != null)
        {
            // Always TRUE (server)
            SmartLights.getDebugger().PrintToConsole("updateTick(" + x + ", " + y + ", " + z + ") TileEntity.isServer = " + te.isServer());
        }
    }

    @Override
    public int getLightValue(IBlockAccess iba, int x, int y, int z)
    {
        TileEntitySmartLight te = (TileEntitySmartLight)iba.getBlockTileEntity(x, y, z);
        
        if (te != null)
        {
            // Always FALSE (client)
            SmartLights.getDebugger().PrintToConsole("getLightValue(" + x + ", " + y + ", " + z + ") TileEntity.isServer = " + te.isServer());
        }
    }

 

so this indicates that when updateTick() modifies the TileEntity, it is not modifying the one that getLightValue() is going to use.

Link to comment
Share on other sites

Though this isn't very elegant, it fixes the problem:

 

public class BlockSmartLight extends Block implements ITileEntityProvider
{
    // World mirrors for TileEntity retrieval functions to ensure we're
    //  always getting the same TileEntity. IBlockAccess usually only works
    //  with CLIENT versions while "World" typically works with server versions
    
    World worldCli;
    World worldSrv;
    
    @Override
    public void updateTick(World world, int x, int y, int z, Random random)
    {
        // Update both TileEntities so the one retrieved by IBlockAccess
        //  in getLightValue() will be current

        TileEntitySmartLight teCli = (TileEntitySmartLight)worldCli.getBlockTileEntity(x, y, z);
        TileEntitySmartLight teSrv = (TileEntitySmartLight)worldSrv.getBlockTileEntity(x, y, z);
        boolean newIsLit = isOnTime(world, x, y, z);
        
        if (teCli != null)
            teCli.setIsLit(newIsLit);

        if (teSrv != null)
            teSrv.setIsLit(newIsLit);
        
        world.markBlockForUpdate(x, y, z);
        world.scheduleBlockUpdate(x, y, z, this.blockID, SmartLights.getConfig().getTickRate());
    }

    @Override
    public TileEntity createNewTileEntity(World world)
    {
        // Initialize the WORLD mirrors if necessary.
        if (world.isRemote)
        {
            if (worldCli == null)
                worldCli = world;
        }
        else
        {
            if (worldSrv == null)
                worldSrv = world;
        }
        
        return new TileEntitySmartLight();
    }
}

 

By using World mirrors in updateTick() to force update both TileEntities, it allows the TileEntity retrieved by the IBlockAccess in getLightValue() to behave properly.

Link to comment
Share on other sites

I still find this a very strange behaviour. In my UV Lightbox TileEntity (from my PneumaticCraft mod), I've done something similar. The only thing I've done is overriding getLightValue() :

    @Override
    public int getLightValue(IBlockAccess world, int x, int y, int z){
        Block block = blocksList[world.getBlockId(x, y, z)];
        if(block != null && block != this) { //checks that are also done in the super method.
            return block.getLightValue(world, x, y, z);
        }
        TileEntity te = world.getBlockTileEntity(x, y, z);
        if(te != null && te instanceof TileEntityUVLightBox) {
            return ((TileEntityUVLightBox)te).areLightsOn ? 15 : 0;
        } else {
            return 0;
        }
    }

 

The areLightsOn boolean variable in TileEntityUVLightBox is managed server side, and is updated in the client via packets.

Author of PneumaticCraft, MineChess, Minesweeper Mod and Sokoban Mod. Visit www.minemaarten.com to take a look at them.

Link to comment
Share on other sites

I still find this a very strange behaviour. In my UV Lightbox TileEntity (from my PneumaticCraft mod), I've done something similar. The only thing I've done is overriding getLightValue() :

    @Override
    public int getLightValue(IBlockAccess world, int x, int y, int z){
        Block block = blocksList[world.getBlockId(x, y, z)];
        if(block != null && block != this) { //checks that are also done in the super method.
            return block.getLightValue(world, x, y, z);
        }
        TileEntity te = world.getBlockTileEntity(x, y, z);
        if(te != null && te instanceof TileEntityUVLightBox) {
            return ((TileEntityUVLightBox)te).areLightsOn ? 15 : 0;
        } else {
            return 0;
        }
    }

 

The areLightsOn boolean variable in TileEntityUVLightBox is managed server side, and is updated in the client via packets.

 

Are you using a custom packet handler? I haven't done that yet, just started reading about it.

 

Anyway, there is another thread recently posted where someone realized that an IBlockAccess TileEntity is different than a World TileEntity. The main problem is that functions which use a World object instead of an IBlockAccess object simply don't act on the same TileEntity.

 

I traced through the code in Chunk.java that actually returns the TileEntity and interestingly they both use the same function but it does answer a few IF...THEN questions differently depending on how it was called.

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

    • They were already updated, and just to double check I even did a cleanup and fresh update from that same page. I'm quite sure drivers are not the problem here. 
    • i tried downloading the drivers but it says no AMD graphics hardware has been detected    
    • Update your AMD/ATI drivers - get the drivers from their website - do not update via system  
    • As the title says i keep on crashing on forge 1.20.1 even without any mods downloaded, i have the latest drivers (nvidia) and vanilla minecraft works perfectly fine for me logs: https://pastebin.com/5UR01yG9
    • Hello everyone, I'm making this post to seek help for my modded block, It's a special block called FrozenBlock supposed to take the place of an old block, then after a set amount of ticks, it's supposed to revert its Block State, Entity, data... to the old block like this :  The problem I have is that the system breaks when handling multi blocks (I tried some fix but none of them worked) :  The bug I have identified is that the function "setOldBlockFields" in the item's "setFrozenBlock" function gets called once for the 1st block of multiblock getting frozen (as it should), but gets called a second time BEFORE creating the first FrozenBlock with the data of the 1st block, hence giving the same data to the two FrozenBlock :   Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=head] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@73681674 BlockEntityData : id:"minecraft:bed",x:3,y:-60,z:-6} Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=3, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=2, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} here is the code inside my custom "freeze" item :    @Override     public @NotNull InteractionResult useOn(@NotNull UseOnContext pContext) {         if (!pContext.getLevel().isClientSide() && pContext.getHand() == InteractionHand.MAIN_HAND) {             BlockPos blockPos = pContext.getClickedPos();             BlockPos secondBlockPos = getMultiblockPos(blockPos, pContext.getLevel().getBlockState(blockPos));             if (secondBlockPos != null) {                 createFrozenBlock(pContext, secondBlockPos);             }             createFrozenBlock(pContext, blockPos);             return InteractionResult.SUCCESS;         }         return super.useOn(pContext);     }     public static void createFrozenBlock(UseOnContext pContext, BlockPos blockPos) {         BlockState oldState = pContext.getLevel().getBlockState(blockPos);         BlockEntity oldBlockEntity = oldState.hasBlockEntity() ? pContext.getLevel().getBlockEntity(blockPos) : null;         CompoundTag oldBlockEntityData = oldState.hasBlockEntity() ? oldBlockEntity.serializeNBT() : null;         if (oldBlockEntity != null) {             pContext.getLevel().removeBlockEntity(blockPos);         }         BlockState FrozenBlock = setFrozenBlock(oldState, oldBlockEntity, oldBlockEntityData);         pContext.getLevel().setBlockAndUpdate(blockPos, FrozenBlock);     }     public static BlockState setFrozenBlock(BlockState blockState, @Nullable BlockEntity blockEntity, @Nullable CompoundTag blockEntityData) {         BlockState FrozenBlock = BlockRegister.FROZEN_BLOCK.get().defaultBlockState();         ((FrozenBlock) FrozenBlock.getBlock()).setOldBlockFields(blockState, blockEntity, blockEntityData);         return FrozenBlock;     }  
  • Topics

×
×
  • Create New...

Important Information

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