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.



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • rp.crazyheal.xyz mods  
    • I'm developing a dimension, but it's kinda resource intensive so some times during player teleporting it lags behind making the player phase down into the void, so im trying to implement some kind of pregeneration to force the game loading a small set of chunks in the are the player will teleport to. Some of the things i've tried like using ServerLevel and ServerChunkCache methods like getChunk() dont actually trigger chunk generation if the chunk isn't already on persistent storage (already generated) or placing tickets, but that doesn't work either. Ideally i should be able to check when the task has ended too. I've peeked around some pregen engines, but they're too complex for my current understanding of the system of which I have just a basic understanding (how ServerLevel ,ServerChunkCache  and ChunkMap work) of. Any tips or other classes I should be looking into to understand how to do this correctly?
    • https://mclo.gs/4UC49Ao
    • Way back in the Forge 1.17 days, work started for adding JPMS (Java Platform Module Support) to ModLauncher and ForgeModLoader. This has been used internally by Forge and some libraries for a while now, but mods (those with mods.toml specifically) have not been able to take advantage of it. As of Forge 1.21.1 and 1.21.3, this is now possible!   What is JPMS and what does it mean for modders? JPMS is the Java Platform Module System, introduced in Java 9. It allows you to define modules, which are collections of packages and resources that can be exported or hidden from other modules. This allows for much more fine-tuned control over visibility, cleaner syntax for service declarations and support for sealed types across packages. For example, you might have a mod with a module called `com.example.mod` that exports `com.example.mod.api` and `com.example.mod.impl` to other mods, but hides `com.example.mod.internal` from them. This would allow you to have a clean API for other mods to use, while keeping your internal implementation details hidden from IDE hints, helping prevent accidental usage of internals that might break without prior notice. This is particularly useful if you'd like to use public records with module-private constructors or partially module-private record components, as you can create a sealed interface that only your record implements, having the interface be exported and the record hidden. It's also nice for declaring and using services, as you'll get compile-time errors from the Java compiler for typos and the like, rather than deferring to runtime errors. In more advanced cases, you can also have public methods that are only accessible to specific other modules -- handy if you want internal interactions between multiple of your own mods.   How do I bypass it? We understand there may be drama in implementing a system that prevents mods from accessing each other's internals when necessary (like when a mod is abandoned or you need to fix a compat issue) -- after all, we are already modding a game that doesn't have explicit support for Java mods yet. We have already thought of this and are offering APIs from day one to selectively bypass module restrictions. Let me be clear: Forge mods are not required to use JPMS. If you don't want to use it, you don't have to. The default behaviour is to have fully open, fully exported automatic modules. In Java, you can use the `Add-Opens` and `Add-Exports` manifest attributes to selectively bypass module restrictions of other mods at launch time, and we've added explicit support for these when loading your Forge mods. At compile-time, you can use existing solutions such as the extra-java-module-info Gradle plugin to deal with non-modular dependencies and add extra opens and exports to other modules. Here's an example on how to make the internal package `com.example.examplemod.internal` open to your mod in your build.gradle: tasks.named('jar', Jar) { manifest { attributes([ 'Add-Opens' : 'com.example.examplemod/com.example.examplemod.internal' 'Specification-Title' : mod_id, 'Specification-Vendor' : mod_authors // (...) ]) } } With the above in your mod's jar manifest, you can now reflectively access the classes inside that internal package. Multiple entries are separated with a space, as per Java's official spec. You can also use Add-Exports to directly call without reflection, however you'd need to use the Gradle plugin mentioned earlier to be able to compile. The syntax for Add-Exports is the same as Add-Opens, and instructions for the compile-time step with the Gradle plugin are detailed later in this post. Remember to prefer the opens and exports keywords inside module-info.java for sources you control. The Add-Opens/Add-Exports attributes are only intended for forcing open other mods.   What else is new with module support? Previously, the runtime module name was always forced to the first mod ID in your `mods.toml` file and all packages were forced fully open and exported. Module names are now distinguished from mod IDs, meaning the module name in your module-info.java can be different from the mod ID in your `mods.toml`. This allows you to have a more descriptive module name that doesn't have to be the same as your mod ID, however we strongly recommend including your mod ID as part of your module name to aid troubleshooting. The `Automatic-Module-Name` manifest attribute is now also honoured, allowing you to specify a module name for your mod without needing to create a `module-info.java` file. This is particularly useful for mods that don't care about JPMS features but want to have a more descriptive module name and easier integration with other mods that do use JPMS.   How do I use it? The first step is to create a `module-info.java` file in your mod's source directory. This file should be in the same package as your main mod class, and should look something like this: open module com.example.examplemod { requires net.minecraftforge.eventbus; requires net.minecraftforge.fmlcore; requires net.minecraftforge.forge; requires net.minecraftforge.javafmlmod; requires net.minecraftforge.mergetool.api; requires org.slf4j; requires logging; } For now, we're leaving the whole module open to reflection, which is a good starting point. When we know we want to close something off, we can remove the open modifier from the module and open or export individual packages instead. Remember that you need to be open to Forge (module name net.minecraftforge.forge), otherwise it can't call your mod's constructor. Next is fixing modules in Gradle. While Forge and Java support modules properly, Gradle does not put automatic modules on the module path by default, meaning that the logging module (from com.mojang:logging) is not found. To fix this, add the Gradle plugin and add a compile-time module definition for that Mojang library: plugins { // (...) id 'org.gradlex.extra-java-module-info' version "1.9" } // (...) extraJavaModuleInfo { failOnMissingModuleInfo = false automaticModule("com.mojang:logging", "logging") } The automatic module override specified in your build.gradle should match the runtime one to avoid errors. You can do the same for any library or mod dependency that is missing either a module-info or explicit Automatic-Module-Name, however be aware that you may need to update your mod once said library adds one. That's all you need to get started with module support in your mods. You can learn more about modules and how to use them at dev.java.
    • Faire la mise à jour grâce à ce lien m'a aider personnellement, merci à @Paint_Ninja. https://www.amd.com/en/support 
  • Topics

×
×
  • Create New...

Important Information

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