Jump to content

Recommended Posts

Posted

I would like to control where biomes spawn. I'd like them only to spawn at certain Z values depending on their temperature. I think I'm poking around in the right set of classes (BiomeProvider and GenLayerBiome), but this world gen stuff is so convoluted, it's taking me forever to make tiny bits of progress. If someone can point me in the right direction and tell me which methods to look at and what bits to tweak, I'd much appreciate it.

Posted

I have a fair bit of tutorial information on custom dimensions, biomes and such. See: http://jabelarminecraft.blogspot.com/p/minecraft-forge-1721710-biome-quick-tips.html. There is a link to a fully custom dimension tutorial in there as well.

 

You're right that the vanilla code is extremely convoluted -- it has redundant fields that point at each other, it uses terms that sound similar like generate, decorate, populate, and there are things called "world gen" that are actually just featues, but not full worlds. There are recursive structures, and so on.

 

But the thing to remember is that after all the generation is simply placing blocks and actually as long as you get blocks where you want them it is all good.

 

However, in your case you might be able to get by mostly by using the biome "type" both in the BiomeManager and BiomeDictionary. Those both have category tags for your biome like "COOL" to help the vanilla generators properly mix biomes.

 

Regarding it only generating on certain Z values though that might be a bit trickier. One way might be to have the chunk generator's biome map replace your biome with another similar vanilla one whenever the Z doesn't match. Not sure how easy that would be exactly.

 

The most control would come from making a custom dimension. You could basically copy the overworld but when it is selecting the biomes during generation you could check the Z and force your biome when desired.

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

Posted

I've read your tutorial. In fact, your site is often my first stopping point when doing research. That's what got me looking at BiomeProvider and GenLayerBiome. I'm pretty sure this is where the biomes are actually chosen, but most of the variables and numbers are totally cryptic to me at this point.

 

I already have a custom dimension and chunk generator that mostly mimics ChunkGeneratorOverworld (the only change I've made so far is to remove some vanilla structures). It looks like setBlocksInChunk gets the biomes that are supposed to be generated for a new chunk. I think it gives a list with a biome assigned to each column in the chunk. I could edit the list right there, except it seems like I'd have a hard time making sure I stitch the biomes together correctly without creating a complete mess. I'd be guessing about which biome to use and still match neighboring chunks. It seems like I need to intercept the process earlier, before it decides which biome to use.

 

So from there I have followed the trail to BiomeProvider#getBiomesForGeneration. But then I get lost. It seems to be getting a list of biomes from some GenLayer class that's supplied by GenLayer#initializeAllBiomeGenerators. That GenLayer class seems to be the first one in a list that initializeAllBiomeGenerators creates through some insane amount of other GenLayer calls. One of which is GenLayerBiome, which seems promising because it seems to be the only place that refers to Biome type (cool, warm, etc.).

 

That's as far as I've gotten in several hours of research.

Posted

It really depends on how nicely you want to play with the other vanilla biomes. The biome dictionary is intended to allow you to add biomes that are categorized such that they will generate naturally in a way that "makes sense" in terms of whether they are dry, cold, etc. That is the best way to hook into the vanilla generation without getting caught up in the intricate generation details.

 

To add to the complication, I think there is some "abandoned" code. Like in the generateChunk() method near the end there is an local field for an array called abyte[] which seems to store all the biome IDs. however I don't see it ever being used.

 

But since you also have the Z position requirement, I would guess there is no good way to fully integrate that into the vanilla generation which uses some randomness and some categorization to blend things together. So I think there is nothing wrong with forcing it in the chunk generator setBlocksInChunk() method, although maybe it would feel more logical to do it in your BiomeProvider's getBiomesForGeneration() and I think also the getBiomes() methods. You just need to make sure that the array that tracks actual biome is also properly updated. 

 

So in summary since you want your biome to generate semi-naturally, I would make sure it is tagged properly in the biome manager and biome dictionary type registrations and then to control the Z I would check and/or force that in the biome provider.

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

Posted

oh lord man, been a while since I worked on Minecraft.  Gave up modding because the source code kept changing too much and yadayada decided to work on my own game.


If it's still somewhat the same as when I was working on it,  I had to pretty much rewrite the ChunkProvider, and in the provideChunk function you can insert checks on height there.  I had a funky biome that would leave large areas of water so I wanted to rename these areas like "Lake" or something, so I inserted a check, if this biome, i'd do checks to see if there was water and if the water checked passed I changed that area to the biome Lake.  I started to get fed up with playing within the confines of the Minecraft generation rules after that and looking into my own engine but yeah it almost needs a whole new generation system to do sophisticated stuff without bogging down the code.     If you simply just want to do checks on which biome is next to which biome you need to start breaking down the GenLayer files.   I'd recc. playing around with it all anyway, I learned so much coool stuff messing around with it, like making nice beautiful huge rivers.

Posted (edited)

I think I have figured this out. First I want to make it clear I wasn't doing this for a custom biome. My intent was to control where vanilla biomes appear, based on latitude (how far in the Z direction you go). I wanted hot biomes only to appear near Z=0, and get progressively cooler the further you go in either Z direction. I established an arbitrary "north/south pole" beyond which everything is cold and icy no matter how far you go.

 

I didn't try messing around with the BiomeProvider#getBiomesForGeneration or BiomeProvider#getBiomes since at that level we're working only with chunks. It seemed to difficult to me to change the biome for a given chunk and still ensure neighboring chunks also get updated with the correct biome, without doing some weird storing or searching of what biomes were assigned nearby. Perhaps I was wrong about that assumption, but in any case I felt the need to interrupt the regular biome choice much earlier in the process, during the GenLayer construction. I ended up doing a deep dive into world generation and learned a lot of stuff about how GenLayers work.

 

To solve this, I ended up creating my own world type and overrode WorldType#getBiomeLayer to supply a custom GenLayerBiome class instead of the vanilla GenLayerBiome. In the GenLayerBiome class, during the GenLayerBiome#getInts method, it checks to see which biome categories have been chosen (DESERT, WARM, COOL, or ICY). I jacked into the process there and overrode that method, where I checked the current Z position and swapped the category as desired. The tricky part was that the position values at this point are not chunk coordinates or block coordinates—they have been adjusted relative to biome size during the GenLayer process. Default biome size is 4, but I'm using a biome size of 6 (aka Large Biomes). It took me some time to figure out how to translate the position values into actual block coordinates so I could figure out a rough location on the Z axis.

 

I have only done a small amount of testing so far, but it's looking promising. I noticed a weird biome boundary at one point, where there was an unnatural cliff that hadn't been smoothed out. So I'm sure I will have tweaking to do. I may have to change something in the GenLayerEdge or GenLayerBiomeEdge classes. Not sure.

Edited by Daeruin

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

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

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