Jump to content

Replace blocks while the world generates


Insane96MCP

Recommended Posts

Just to check:

How are you storing these additional properties? 

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

Well, first check the if the block at that location is farmland at that BlockPos than set the BlockState at that BlockPos to your block if it is -- these methods are in the World object.  You will need to hook into the world generator, probably using the event system to tell when a chunk is generating, and get the World and the coords for the chunk.  To be thorough you'd need to search the chunks blocks, though finding the top block is probably good enough and more efficient (checking every block in a chunk is a lot of work for your CPU and I can't imagine it not being a performance issue).

 

I don't know if there is an event for the generator placing a single block -- really, no idea. Some structure such as villages do have such events.  I don't know all the details, by a long shot -- I've not done that much with the event system, but have used it to detect villages being generated.

 

Another option is the mod the vanilla structures (I only know of one) to use this, which would be more efficient but have compatibility issues.  I've never tried to modify villages and can't.

 

The specifics are up to you to figure out and decide on.  This all the help I can give -- and one of the Forge gurus might very well jump in to tell all sorts of things I'm wrong about!  (If there are typos, sorry, I'm still half asleep this morning.)

Developer of Doomlike Dungeons.

Link to comment
Share on other sites

If it is your own custom dimension you can just make sure your chunk generator generates what you want. Assuming you want to do this for vanilla or other mod dimensions then I've had success by handling the PopulateChunkEvent.Pre event. 

 

Something like this has worked for me in the past (in this example changing grass to stone):

@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public void onEvent(PopulateChunkEvent.Pre event)
{
    // replace all blocks of a type with another block type
    // diesieben07 came up with this method (http://www.minecraftforge.net/forum/index.php/topic,21625.0.html)
        
    Chunk chunk = event.world.getChunkFromChunkCoords(event.chunkX, event.chunkZ);
    Block fromBlock = Blocks.grass; // change this to suit your need
    Block toBlock = Blocks.stone; // change this to suit your need

    for (ExtendedBlockStorage storage : chunk.getBlockStorageArray()) 
    {
        if (storage != null) 
        {
            for (int x = 0; x < 16; ++x) 
            {
                for (int y = 0; y < 256; ++y) 
                {
                    for (int z = 0; z < 16; ++z) 
                    {
                        if (storage.getBlockByExtId(x, y, z) == fromBlock) 
                        {
                            storage.func_150818_a(x, y, z, toBlock);
                        }
                    }
                }
            }
        }
    }  
    chunk.isModified = true; // this is important as it marks it to be saved
}

 

I last used this in 1.8 so possible some of the methods have changed in newer versions, but you get the idea hopefully.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

What do you want to have happen when a player uses a hoe to turn dirt into farmland?

 

Once upon a time, there was a block substitution system in Forge, but there were bugs in it. I wonder if it was ever fixed...

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Link to comment
Share on other sites

7 hours ago, diesieben07 said:

So, please actually clarify what you want. Logs? Farmland? Both? Something else? We can't help you, if you don't actually specify what you want.

Wow so many answers. So. First off, I want a general way to change generation blocks.

4 hours ago, JaredBGreat said:

Well, first check the if the block at that location is farmland at that BlockPos than set the BlockState at that BlockPos to your block if it is -- these methods are in the World object.  You will need to hook into the world generator, probably using the event system to tell when a chunk is generating, and get the World and the coords for the chunk.  To be thorough you'd need to search the chunks blocks, though finding the top block is probably good enough and more efficient (checking every block in a chunk is a lot of work for your CPU and I can't imagine it not being a performance issue).

 

I don't know if there is an event for the generator placing a single block -- really, no idea. Some structure such as villages do have such events.  I don't know all the details, by a long shot -- I've not done that much with the event system, but have used it to detect villages being generated.

 

Another option is the mod the vanilla structures (I only know of one) to use this, which would be more efficient but have compatibility issues.  I've never tried to modify villages and can't.

 

The specifics are up to you to figure out and decide on.  This all the help I can give -- and one of the Forge gurus might very well jump in to tell all sorts of things I'm wrong about!  (If there are typos, sorry, I'm still half asleep this morning.)

 

2 hours ago, jabelar said:

If it is your own custom dimension you can just make sure your chunk generator generates what you want. Assuming you want to do this for vanilla or other mod dimensions then I've had success by handling the PopulateChunkEvent.Pre event. 

 

Something like this has worked for me in the past (in this example changing grass to stone):


@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public void onEvent(PopulateChunkEvent.Pre event)
{
    // replace all blocks of a type with another block type
    // diesieben07 came up with this method (http://www.minecraftforge.net/forum/index.php/topic,21625.0.html)
        
    Chunk chunk = event.world.getChunkFromChunkCoords(event.chunkX, event.chunkZ);
    Block fromBlock = Blocks.grass; // change this to suit your need
    Block toBlock = Blocks.stone; // change this to suit your need

    for (ExtendedBlockStorage storage : chunk.getBlockStorageArray()) 
    {
        if (storage != null) 
        {
            for (int x = 0; x < 16; ++x) 
            {
                for (int y = 0; y < 256; ++y) 
                {
                    for (int z = 0; z < 16; ++z) 
                    {
                        if (storage.getBlockByExtId(x, y, z) == fromBlock) 
                        {
                            storage.func_150818_a(x, y, z, toBlock);
                        }
                    }
                }
            }
        }
    }  
    chunk.isModified = true; // this is important as it marks it to be saved
}

 

I last used this in 1.8 so possible some of the methods have changed in newer versions, but you get the idea hopefully.

Isn't that really slow, as Jared pointed out?

1 hour ago, jeffryfisher said:

What do you want to have happen when a player uses a hoe to turn dirt into farmland?

I've already resolved this with the Hoe Event.
 

1 hour ago, jeffryfisher said:

Once upon a time, there was a block substitution system in Forge, but there were bugs in it. I wonder if it was ever fixed...

I've tried lots of times to replace the vanilla block, but never succeded

Link to comment
Share on other sites

Regarding the substitution system, it was broken for a long time, briefly I think it was working then it has been abandoned as far as I know.

 

Regarding the performance of the replacement method, the reality is personal computers are getting way faster every year. On my computers (admittedly I have good gaming PCs) the method I used above is not noticeable. Also, if performance is a concern you can simply modify the algorithm to target more specifically. For example, the farmland should really be the top block so you don't need to check an entire chunk. If you just check a layer or two it should be invisible. And if you want to mitigate the lag you can distribute the checking over a couple of ticks. Lastly, in older versions like 1.7.10 most of the performance issues related to block placement were due to lighting updates. I think this is fixed now, but if really necessary you can edit the data in a more "raw" way rather than through the individual block placement methods and bypass the lighting update until the operation is complete.

 

Personally I would just try my way first because you can pretty much cut and past, then modify the Y range to be more specific (maybe do top block instead) to make it faster. If it works for you like that then you're done. No point in worrying about something that might be a problem when you can confirm quickly whether it is an actual problem.

 

Alternatively: if your block looks exactly the same then you can do other tricks. For example, you can just change it at the time a player interacts with it.

Edited by jabelar

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Another thought: In what instances is farmland generated? Isn't it only as part of a village, and then always with crops on top? If so, then there should be event in that process that gets you closer to what you want to affect.

The debugger is a powerful and necessary tool in any IDE, so learn how to use it. You'll be able to tell us more and get better help here if you investigate your runtime problems in the debugger before posting.

Link to comment
Share on other sites

On 13/12/2017 at 10:06 PM, jabelar said:

And if you want to mitigate the lag you can distribute the checking over a couple of ticks

I really don't know how to do this.

On 13/12/2017 at 10:06 PM, jabelar said:

For example, you can just change it at the time a player interacts with it.

Maybe I'll think on using this.

 

13 hours ago, jeffryfisher said:

Another thought: In what instances is farmland generated? Isn't it only as part of a village, and then always with crops on top? If so, then there should be event in that process that gets you closer to what you want to affect.

I think it's generated only in villages, but I want other cases too with other mods

Link to comment
Share on other sites

7 hours ago, Insane96MCP said:
On 12/13/2017 at 1:06 PM, jabelar said:

And if you want to mitigate the lag you can distribute the checking over a couple of ticks

I really don't know how to do this.

 

Well, in the loop for checking the layers you can do half the checking in one tick and half in the next. For example, you can check the world time and if it is odd just check the odd layers and if it is even just check the even layers. You also need a boolean to indicate when you've finished checking. So it would be like this -- in your event handler you'd have a static boolean called something like finishedReplacement which would intialize to true. Then in your PopulateChunkEvent handling method you would set finishedReplacement to false. Then you would also handle the ServerTick event and in that you would check if !finishedReplacement and then check if the world time is odd or even and then cycle through the layers accordingly.

 

But before you do any of this, have you tried my original suggestion and confirmed how much lag it causes? It might be acceptable, especially if you don't process the entire chunk.

 

Anyway there are LOTS of ways of working around performance issues if you think about it. For example, you could just process blocks that are within certain distance from the players. No need to check all the air blocks and bedrock locations within a chunk.

 

By the way, blocks can be placed in the world by players too, so technically you probably need to also intercept block placement by handling the event and changing any placed farmland with yours. And if you allow creative mode then probably best to also make sure your farmland shows up and not the vanilla, although you can also leave it and substitute as the player picks it up.

Edited by jabelar

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

I just tried it with my code. Actually for 1.12.2 some of the methods have changed so it needs a bit of work.

 

@diesieben07the code I posted was actually recommended by you in the past. I just updated it to 1.12.2 which was pretty easy -- just had to change some field accesses to the getters. However, two problems occurred.

 

1) The ExtendedBlockStorage now crashes if you use a get() where the Y-value is greater than 16. It seems that the index must be less than 4096 (16 * 16 * 16) so somehow the storage is now broken up into smaller pieces in the Y direction. However it is not clear to me how to then properly access the y values.

 

2) Concurrent modification errors can occur. What's the safe way to avoid that? I mean I understand that setting values in collections in certain cases can cause that, but that code isn't something I wrote -- there is no collection field in the modded code, I'm simply using the getters and setters provided. I'm guessing this is a bug? The setter should be thread-safe in my opinion.

 

In any case it seems that something has changed with ExtendedBlockStorage (I'm assuming some performance improvements) that have made the ability for mod to use the getter and setter broken. What is the alternative?

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

For reference, you can scan an entire chunk and replace blocks in about 400,000 nanos (0.4 ms). You don't really need to worry about time slicing. 

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:

For reference, you can scan an entire chunk and replace blocks in about 400,000 nanos (0.4 ms). You don't really need to worry about time slicing. 

I agree.

 

Do you have any insight into the two issues I mentioned above? Seems like the storage has changed a bit and i can't find the safe get() and set() methods. get() seems to fail for Y values above 16 and the set() method seems to run into concurrent modification issues.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Okay, so after digging around for a bit, it is a bit tricky to intercept block gen after everything has been populated. The most efficient place is where the ChunkPrimer is directly accessible, but the replaceBiomeBlocks event is too early for many types of surface biome-based blocks -- the event seems to be intended for entirely replacing the generation. I think I'll file an issue and maybe a pull request to allow the ChunkPrimer be availabile in most gen events and also ensure there is an event that is fired just before the ChunkPrimer is copied into the Chunk thereby allowing editing after everything else is complete.

 

In any case, it seems that the most consistent place where you have access to all the blocks after they are freshly created is the ChunkEvent.Load event which is called both after generation as well as actual loading.

 

So the following example worked for me -- for fun I replaced all grass with slime blocks:

  public static Block fromBlock = Blocks.GRASS; // change this to suit your need
    public static Block toBlock = Blocks.SLIME_BLOCK; // change this to suit your need
     
  @SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
  public static void onEvent(ChunkEvent.Load event)
  { 
      
      Chunk theChunk = event.getChunk();
      
      // replace all blocks of a type with another block type
  
      for (int x = 0; x < 16; ++x) 
      {
          for (int z = 0; z < 16; ++z) 
          {
              for (int y = theChunk.getHeightValue(x, z)-20; y < theChunk.getHeightValue(x, z)+1; ++y) 
              {
                if (theChunk.getBlockState(x, y, z).getBlock() == fromBlock)
                {
                    theChunk.setBlockState(new BlockPos(x, y, z), toBlock.getDefaultState());
                }
              }
          }
      }
      theChunk.markDirty();
  }

 

 

How deep you go from the top block is up to you. For replacing grass I just needed to find the surface blocks, but I found some cases where grass would be under a floating island or other overhang and so technically wasn't the top block. If you were replacing ores for example you'd want to go deeper and such.

 

I didn't notice any lag, but I've got a decent computer.

 

For very specific cases, there are other events that are better. But in the generic case it seems that currently the load event is best.

Edited by jabelar
  • Like 2

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

Thanks to everyone for replying

 

On 16/12/2017 at 11:53 PM, jabelar said:

Okay, so after digging around for a bit, it is a bit tricky to intercept block gen after everything has been populated. The most efficient place is where the ChunkPrimer is directly accessible, but the replaceBiomeBlocks event is too early for many types of surface biome-based blocks -- the event seems to be intended for entirely replacing the generation. I think I'll file an issue and maybe a pull request to allow the ChunkPrimer be availabile in most gen events and also ensure there is an event that is fired just before the ChunkPrimer is copied into the Chunk thereby allowing editing after everything else is complete.

 

In any case, it seems that the most consistent place where you have access to all the blocks after they are freshly created is the ChunkEvent.Load event which is called both after generation as well as actual loading.

 

So the following example worked for me -- for fun I replaced all grass with slime blocks:


  public static Block fromBlock = Blocks.GRASS; // change this to suit your need
    public static Block toBlock = Blocks.SLIME_BLOCK; // change this to suit your need
     
  @SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
  public static void onEvent(ChunkEvent.Load event)
  { 
      
      Chunk theChunk = event.getChunk();
      
      // replace all blocks of a type with another block type
  
      for (int x = 0; x < 16; ++x) 
      {
          for (int z = 0; z < 16; ++z) 
          {
              for (int y = theChunk.getHeightValue(x, z)-20; y < theChunk.getHeightValue(x, z)+1; ++y) 
              {
                if (theChunk.getBlockState(x, y, z).getBlock() == fromBlock)
                {
                    theChunk.setBlockState(new BlockPos(x, y, z), toBlock.getDefaultState());
                }
              }
          }
      }
      theChunk.markDirty();
  }

 

 

How deep you go from the top block is up to you. For replacing grass I just needed to find the surface blocks, but I found some cases where grass would be under a floating island or other overhang and so technically wasn't the top block. If you were replacing ores for example you'd want to go deeper and such.

 

I didn't notice any lag, but I've got a decent computer.

 

For very specific cases, there are other events that are better. But in the generic case it seems that currently the load event is best.

This will come in handy if I'll need to change more than a Property on a block.

As now, Replacing the vanilla farmland with modded one on interact does the job.

 

 

On 15/12/2017 at 7:00 PM, Draco18s said:

For reference, you can scan an entire chunk and replace blocks in about 400,000 nanos (0.4 ms). You don't really need to worry about time slicing. 

Good to know.

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.