Jump to content

How to Replace a Vanilla Biome with a Custom Biome


Andrew Murdza

Recommended Posts

I want to make it so that each of my custom biomes spawns over the corresponding vanilla biomes.

 

In the past, this could be done with GameRegistry.remove(oldbiome) and GameRegistry.add(newbiome) or BiomeGenBase.oldbiome=newbiome.

I tried three things: 

1. 

ImmutableList<BiomeEntry> coolbiomes=BiomeManager.getBiomes(BiomeManager.BiomeType.COOL);

int lengthcool= coolbiomes.size();
for(int i=lengthcool-1;i>=0;i--) {
            BiomeManager.removeBiome(BiomeType.COOL, coolbiomes.get(i));

}

same for icy, warm, and desert biomes

then add new biomes

 

This worked with the following exceptions

1. I could not replace any of the desert biomes, because that list is already empty and is set in the GenLayerBiomes class rather than in the BiomeManagerClass

2. I could not replace the mesa, ocean, jungle, mega taiga, and mushroom island biomes because they are stored differently (which is determined in GenLayerBiomes class rather than the BiomeManagerClass)

 

 

2. I copied the registerbiomes() method in the Biome minecraft class, but this did not work because net.minecraftforge.registries.GameData.getWrapper(Biome.class) is a locked registry. It also would not have allowed me to edit the how frequently the mesa, ocean, jungle, etc. biomes generate (which is less important)

 

3. I tried using java reflection to overwrite the static final fields in the biomes class, but this was not going to work because when a static final field is overwritten its new value is only accessible when it is referred to with reflection. This also would not allow me to edit how frequently the jungle, etc. biomes generate (which is less important)

 

I would like to somehow override the GenLayerBiomeClass. I know this is possible if I create my own dimension, but I want this to work for the overworld dimension. Can I do this?

 

Thanks, Andrew Murdza

Link to comment
Share on other sites

I think you can use events to accomplish a similar effect -- not changing the actual biome but intercepting all the biome-related behavior to match your biome. For example, you can handle the ChunkGeneratorEvent.ReplaceBiomeBlocks to place your own blocks. However, all the various biome events are more about tweaking the vanilla biomes not replacing them. For example, they will still be called the same thing.

 

I thought I saw a pull request one time where you could intercept the biome provider more fundamentally, but I can't seem to find that event.

 

So I think the easiest way is to use reflection to change the protected biomeProvider field in the WorldProvider to point to an instance of your own custom BiomeProvider which could copy or extend the vanilla code and decide to place your own biomes instead of the vanilla where you want to. That should cause proper replacement and should cover all the places where the biome is checked (since biome is used for a lot of things including generation but also things like coloring the grass and such).

 

Alternatively, maybe someone knows of a way that the new versions of the registries can simply replace the vanilla registered biomes.

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

Link to comment
Share on other sites

This is Perfect! This is even better than registering because I can completely customize how often biomes such as jungle, mesa, etc. spawn. Using BiomeProvider is equivalent to the best possible situation of editing the GenLayerBiome class.

 

I think that the line I want to edit is int[] aint = this.genBiomes.getInts(x, z, width, height), because this calls the method getInts(...) of the GenLayerBiome class I originally wanted to edit (which I can now indirectly edit with this line).

 

Thanks so much jabelar!

 

Also I am familiar with java reflection (I have used it several times in other components of my mod) but I am not sure where I should change the field. Usually when I change the field of a non-static class with reflection I do it for a specific instance of that class

 

Like I know that the code will look like 

newbiomeprovider = new net.minecraft.world.biome.BiomeProviderNew(world.getWorldInfo());

Field biomeprovider=WorldProvider.class.getDeclaredField("biomeProvider");

biomeprovider.setAccessible(true);

biomeprovider.set(oldworldproviderinstance,newbiomeprovider);

 

I am not sure what oldworldproviderinstance should be and under what subscribeevent I should type all of this code (i.e. under what event e.g. OnEntityJoinWorld)

 

Thanks, Andrew Murdza

Link to comment
Share on other sites

You can do it almost anywhere you have the world instance as long as you check whether you've already replaced it and only replace it if the BiomeProvider instance isn't an instanceof your custom one. It shouldn't be that much of a performance hit even if you check it unnecessarily but I think you could just check whenever a chunk is generated like the biome event I already mentioned above.

 

I think the only issue with this whole approach is that if another mod replaces the BiomeProvider then you're going to clobber theirs. But in modding there is nothing really preventing mods from clobbering other mods even when using event handling, so you just need to work with other mod authors if you come across an incompatibility.

 

If you just wanted to add your biomes in addition to the vanilla ones then you can just use the registries. And if you're trying to simply tweak the vanilla ones then there are plenty of events for that. But for a holistic replacement you do want full control of the BiomeProvider instance.

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

Link to comment
Share on other sites

Dear Jabelar,

 

Sorry for the late reply. I am a college student and I only mod during weekends after I finish most of my homework.

 

I was able to replace the BiomeProvider class with my custom biomeprovider class. This allowed me to completely control the biome placement mechanics, which is awesome.

 

There were two issues I had though.

 

1. Although I could completely control where biomes spawned, the my new biomes did not replace the corresponding old biomes. I found that the reason for this is that for chunk generation and most of the other cases where the biome type at a given location is needed, the biome is determined by the World.getBiome(BlockPos pos) method.

 

This method eventually trickles down to the method Chunk.getBiome(BlockPos pos, BiomeProvider provider)

The method is as follows:

 

    public Biome getBiome(BlockPos pos, BiomeProvider provider)
    {
        int i = pos.getX() & 15;
        int j = pos.getZ() & 15;
        int k = this.blockBiomeArray[j << 4 | i] & 255;

        if (k == 255)
        {
            Biome biome = provider.getBiome(pos, Biomes.PLAINS);
            k = Biome.getIdForBiome(biome);
            this.blockBiomeArray[j << 4 | i] = (byte)(k & 255);
        }

        Biome biome1 = Biome.getBiome(k);
        return biome1 == null ? Biomes.PLAINS : biome1;
    }
 

The problem is that the biome that is eventually returned is from Biome.getBiome(k), which fetches the villa Biome corresponding to a particular ID. It would be perfect if I could replace biome1 = Biome.getBiome(k); with biome1 = BiomesNew.biomemap.get(k)

 

I need to extend either the Chunk class, the ChunkProvider class (which is how the chunk object used in World.getBiome() is instantiated), or the WorldClient and WorldServer classes. I tried replacing the WorldClient, WorldServer, and WorldServerMulti classes with my custom versions in the OnDimensionLoad subscribe event. I know that the corresponding classes are placed because of System.out.println I inserted, but for some reason when the getBiome method is called it is not called from my custom classes.

To me this probably means that the WorldClient, WorldServer, and WorldServerMulti classes are somehow changed after the dimension is loaded and before the chunks are loaded, but I am not sure about this.

 

    @SubscribeEvent
    public void OnDimensionLoad(WorldEvent event) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
        World oldworld = event.getWorld();
        WorldProvider oldworldprovider=event.getWorld().provider;
        Field biomeprovidersetter=WorldProvider.class.getDeclaredField("biomeProvider");
        Field worldsetter=WorldEvent.class.getDeclaredField("world");
        Field netsetter=WorldClient.class.getDeclaredField("connection");
        Field servergetter=WorldServer.class.getDeclaredField("mcServer");
        Field delegategetter=WorldServerMulti.class.getDeclaredField("delegate");
        netsetter.setAccessible(true);
        biomeprovidersetter.setAccessible(true);
        worldsetter.setAccessible(true);
        servergetter.setAccessible(true);
        delegategetter.setAccessible(true);
        BiomeProviderNew newbiomeprovider = new BiomeProviderNew(event.getWorld().getWorldInfo());
        biomeprovidersetter.set(oldworldprovider,(BiomeProvider)newbiomeprovider);
        System.out.println("this ran");
        System.out.println(oldworld.getClass().getSimpleName());
        if(oldworld instanceof WorldClientNew) {

        }
        else if(oldworld instanceof WorldClient) {
            System.out.println("this is a client world");
            int dimension= oldworldprovider.getDimension();
            Profiler profiler = oldworld.profiler;
            WorldInfo worldinfo = oldworld.getWorldInfo();
            NetHandlerPlayClient connection = (NetHandlerPlayClient)netsetter.get(oldworldprovider);
            World newworld = new WorldClientNew(connection, worldinfo, dimension, worldinfo.getDifficulty(), profiler);
            worldsetter.set(event, newworld);
            System.out.println("this ran 1");
        }
        else if(oldworld instanceof WorldServerMultiNew) {
            
        }
        else if(oldworld instanceof WorldServerMulti) {
            int dimension= oldworldprovider.getDimension();
            Profiler profiler = oldworld.profiler;
            ISaveHandler handler = oldworld.getSaveHandler();
            MinecraftServer server= (MinecraftServer)servergetter.get((WorldServer)oldworld);
            //WorldServer worldserver = (WorldServer)delegategetter.get(oldworld);
            WorldServer worldserver= (WorldServer)oldworld;

            World newworld = new WorldServerMultiNew(server, handler, dimension, worldserver, profiler);
            worldsetter.set(event, newworld);
            System.out.println("this ran 2");
            
            System.out.println("this ran 2.1");
        }
        else if(oldworld instanceof WorldServerNew){

        }
        else if(oldworld instanceof WorldServer) {
            int dimension= oldworldprovider.getDimension();
            Profiler profiler = oldworld.profiler;
            WorldInfo worldinfo = oldworld.getWorldInfo();
            ISaveHandler handler = oldworld.getSaveHandler();
            MinecraftServer server= (MinecraftServer)servergetter.get((WorldServer)oldworld);
            World newworld = new WorldServerNew(server, handler, worldinfo, dimension, profiler);
            worldsetter.set(event, newworld);
            System.out.println("this ran 3");
        }

 

I have attached the relevant files (but not all the files). These files do not include my main class for the classes of the biomes I changed.

 

2. The biome height did not generate properly (for example, the elevation of rivers and oceans was roughly the same height as the surrounding terrain, which meant that they were not filled with water) Although this is important, I want to fix the other problem first.
 

Thanks again, Andrew Murdza

BiomeDecoratorNew.java

BiomeNew.java

BiomeProviderNew.java

BiomeScores.java

BiomesNew.java

CustomBiomeProvider.java

GenLayerBiomeNew.java

WorldClientNew.java

WorldServerMultiNew.java

WorldServerNew.java

Link to comment
Share on other sites

6 minutes ago, Andrew Murdza said:

The problem is that the biome that is eventually returned is from Biome.getBiome(k), which fetches the villa Biome corresponding to a particular ID. It would be perfect if I could replace biome1 = Biome.getBiome(k); with biome1 = BiomesNew.biomemap.get(k)

This isn't a problem. You are just skipping the vital part of this method.

 

7 minutes ago, Andrew Murdza said:

            Biome biome = provider.getBiome(pos, Biomes.PLAINS);
            k = Biome.getIdForBiome(biome);

k is the ID of the biome gotten from the BiomeProvider. With successful reflection and replacement of the BiomeProvider field you will be able to fully decide which biome gets placed where by overriding BiomeProvider#getBiome within your BiomeProvider class. You also need to register your Biomes as there own biomes in order for this to truly work.

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

Dear Animefan8888, 

 

This makes a lot of sense!

 

But how to I give my custom biomes an ID? The methods I am aware of are ForgeRegistries.BIOMES.registerBiome(mybiome), BiomeManager.addBiome, BiomeManager.addSpawnBiome, and BiomeDictionary.addType. None of these take an ID as an input. I'm sure there is such a method though.

How do I set or determine the id of my custom biomes?

 

Thanks, Andrew Murdza

Link to comment
Share on other sites

11 minutes ago, Andrew Murdza said:

How do I set or determine the id of my custom biomes?

First off you should never interact with any of the registries in the ForgeRegistries class. You should instead use the Event called Register<type> and pass the Object type you want to register ie Register<Item>. The numerical I'd is abstracted away from modders as it is much better and easier for humans to deal with strings such as the registry name of an Item. Instead of a "magic number". 

VANILLA MINECRAFT CLASSES ARE THE BEST RESOURCES WHEN MODDING

I will be posting 1.15.2 modding tutorials on this channel. If you want to be notified of it do the normal YouTube stuff like subscribing, ect.

Forge and vanilla BlockState generator.

Link to comment
Share on other sites

I registered all of the biomes and changed the BiomeProvider getBiomesForGeneration and now it works!!!

 

Thank you so so so much!! I have been working on this for weeks!! 

 

I really appreciate your help Jabelar and Animefan8888!! If I post my mod online I will attach Jabelar's tutorial on biomes and Animefan G.'s youtube channel and I will give you credit for your role in it.

 

I am not completely sure I will post it outline (I was originally intended for personal use and for my family if they could learn minecraft, but I might make it public.

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.