Jump to content

Recommended Posts

Posted

Short version: Is there any way you can think of to prevent rain splash particles from appearing (short of canceling the weather entirely)? I've looked all through Forge's events and didn't see anything promising, but maybe I overlooked some event that lets me see what kind of particle is about to be spawned, or rendered, and cancel it.

 

Long version: If you've seen my recent posts, you know I'm playing around with weather. I've managed to make it snow and rain based on things like season, altitude, etc. However, when I make it snow in a biome that's normally warm, I get rain splash particles and rain sound effects. These come from EntityRenderer#addRainParticles. There is no Forge hook in that method or anything that calls that method, until you work up to the generic ClientTickEvent. Which is unfortunate, because there's a handy Forge hook in EntityRenderer#renderRainSnow—but the snow and rain itself is only part of the story. Seems kind of half-baked to be able to change how rain renders, but not the rain splashes.

 

I've already toyed around with changing the temperature of biomes via reflection, but it really doesn't work for me since I'm varying temperature, and therefore weather, based on latitude (Z position in the world). I'm planning to update world generation to prevent deserts spawning at extreme Z values.

 

I found this nice idea from diesieben7, but I'm hoping for something a little less drastic: 

 

I'm afraid I may have to use ASM to change EntityRenderer#addRainParticles. That makes me sad because I thought I was going to be able to avoid ASM.

 

(I haven't tried it yet, but I'm pretty sure I can cancel the rain sound using the SoundEvent.)

Posted (edited)

the entityRenderer field is public in Minecraft class. I think you might be able to replace it with your own version (that copies that class and changes the code related to the particles).

 

Another thing you could try is to make the particles invisible. Like maybe replace their texture with something fully transparent. 

 

You should also log an issue at the github MinecraftForge project requesting an event hook for this.

Edited by jabelar
  • Thanks 1

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

Posted
21 hours ago, jabelar said:

the entityRenderer field is public in Minecraft class. I think you might be able to replace it with your own version (that copies that class and changes the code related to the particles).

 

I went ahead and did this. I created a class that extends EntityRenderer, hoping to just override the one method. Unfortunately, EntityRenderer#addRainParticles is private, so I couldn't override it. I had to override its calling class, EntityRenderer#updateRenderer. That method refers to tons of private variables that are also modified in other methods. I ended up just duplicating the entire class to make sure I'm covering all my bases.

 

It seems to work, but it feels kinda yucky to me. I'm duplicating a ton of code that might get updated by Mojang or Forge in a future update. I'm not confident that I would remember to check and update my copied code. Plus this way of doing it prevents other mods from replacing the renderer.

 

22 hours ago, jabelar said:

Another thing you could try is to make the particles invisible. Like maybe replace their texture with something fully transparent. 

 

Can this be done dynamically? I don't want to remove the particles completely. I just want to control when they appear.

 

22 hours ago, jabelar said:

You should also log an issue at the github MinecraftForge project requesting an event hook for this.

 

Done. I hope they do it.

Posted
13 hours ago, Daeruin said:

an this be done dynamically? I don't want to remove the particles completely. I just want to control when they appear.

Probably. But you could also create a custom particle that has the original texture and explicitly generate it yourself. So basically let the original spawn since it is a bit tricky to intercept but have it invisible, and then spawn your own when appropriate.

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

Posted
16 hours ago, diesieben07 said:

It's not very drastic at all, to be honest.

 

There are other ideas in this path, such as adding two IWorldEventListener instances, before and after RenderGlobal in the list. The one before sets Minecraft::renderViewEntity to null if the particle is spawned, the one after resets it back to what it was. This would also stop RenderGlobal from spawning the particles. But this requires careful monitoring, in case someone else inserts a listener into the list, so that yours always stay in the correct position. This version could potentially be more compatible with other mods.

 

Fair enough. It's no more drastic than copying the entire EntityRenderer.

 

I want to understand the original idea better. This is the first time I've looked at IWorldEventListener. In that old post, you said to create a new IWorldAccess (now called IWorldEventListener, I guess) and farm out all the methods to RenderGlobal. Then use World#removeEventListener to remove RenderGlobal as a world event listener, and add my own.

 

RenderGlobal does a lot more than just the stuff involved in being an IWorldEventListener. If I remove it as an event listener, I presume it still continues doing all that other stuff?

Posted
6 hours ago, jabelar said:

Probably. But you could also create a custom particle that has the original texture and explicitly generate it yourself. So basically let the original spawn since it is a bit tricky to intercept but have it invisible, and then spawn your own when appropriate.

I kinda like this idea. It's not too invasive. It may not be compatible with texture packs if they're changing rain particles, but that's not horrible. Maybe I'll give this a try.

Posted

Well, I tried creating a new IWorldEventListener. I like the idea because it doesn't duplicate code like copying EntityRenderer. However, I must be doing it wrong. I keep getting errors—three different ones so far. A ConcurrentModificationException from RenderGlobal.updateClouds(RenderGlobal.java:1245), and then a ticking world exception and ticking block exception, both from PrimalWorldEventListener.notifyBlockUpdate.

 

Here's my code. I suspect I'm mixing up sides or incorrectly referring to RenderGlobal, or something like that. I don't know enough about what I'm doing here. :/

 

Spoiler

	@SubscribeEvent
	public void onEntityJoinWorld(EntityJoinWorldEvent event)
	{
		Entity entity = event.getEntity();

            if (entity instanceof EntityPlayer)
            {
                event.getWorld().removeEventListener(Minecraft.getMinecraft().renderGlobal);
                event.getWorld().addEventListener(new PrimalWorldEventListener());
            }
	}

public class PrimalWorldEventListener implements IWorldEventListener
{
    Minecraft MINECRAFT = Minecraft.getMinecraft();

    @Override
    public void notifyBlockUpdate(World worldIn, BlockPos pos, IBlockState oldState, IBlockState newState, int flags)
    {
        MINECRAFT.renderGlobal.notifyBlockUpdate(worldIn, pos, oldState, newState, flags);
    }

    @Override
    public void notifyLightSet(BlockPos pos)
    {
        MINECRAFT.renderGlobal.notifyLightSet(pos);
    }

    @Override
    public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2)
    {
        MINECRAFT.renderGlobal.markBlockRangeForRenderUpdate(x1, y1, z1, x2, y2, z2);
    }

    @Override
    public void playSoundToAllNearExcept(@Nullable EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, float pitch)
    {
        MINECRAFT.renderGlobal.playSoundToAllNearExcept(player, soundIn, category, x, y, z, volume, pitch);
    }

    @Override
    public void playRecord(SoundEvent soundIn, BlockPos pos)
    {
        MINECRAFT.renderGlobal.playRecord(soundIn, pos);
    }

    @Override
    public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xSpeed, double ySpeed, double zSpeed, int... parameters)
    {
        BlockPos pos = new BlockPos(xCoord, yCoord, zCoord);

        if (particleID == 39 && PrimalTemperature.coldEnoughToSnow(pos, MINECRAFT.world))
        {
            return;
        }

        MINECRAFT.renderGlobal.spawnParticle(particleID, ignoreRange, xCoord, yCoord, zCoord, xSpeed, ySpeed, xSpeed, parameters);
    }

    @Override
    public void spawnParticle(int id, boolean ignoreRange, boolean p_190570_3_, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed, int... parameters)
    {
        BlockPos pos = new BlockPos(x, y, z);

        if (id == 39 && PrimalTemperature.coldEnoughToSnow(pos, MINECRAFT.world))
        {
            return;
        }

        MINECRAFT.renderGlobal.spawnParticle(id, ignoreRange, x, y, z, xSpeed, ySpeed, xSpeed, parameters);
    }

    @Override
    public void onEntityAdded(Entity entityIn)
    {
        MINECRAFT.renderGlobal.onEntityAdded(entityIn);
    }

    @Override
    public void onEntityRemoved(Entity entityIn)
    {
        MINECRAFT.renderGlobal.onEntityRemoved(entityIn);
    }

    @Override
    public void broadcastSound(int soundID, BlockPos pos, int data)
    {
        MINECRAFT.renderGlobal.broadcastSound(soundID, pos, data);
    }

    @Override
    public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data)
    {
        MINECRAFT.renderGlobal.playEvent(player, type, blockPosIn, data);
    }

    @Override
    public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress)
    {
        MINECRAFT.renderGlobal.sendBlockBreakProgress(breakerId, pos, progress);
    }
}

 

 

Posted
2 hours ago, Daeruin said:

I kinda like this idea. It's not too invasive. It may not be compatible with texture packs if they're changing rain particles, but that's not horrible. Maybe I'll give this a try.

I think it would be compatible with texture packs. Your replacement particle would use whatever texture the rain would have been, which would include any texture pack replacement. At least you should be able to do so depending on when you assign the texture to your particle.

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

Posted
2 hours ago, jabelar said:

I think it would be compatible with texture packs. Your replacement particle would use whatever texture the rain would have been, which would include any texture pack replacement. At least you should be able to do so depending on when you assign the texture to your particle.

I just read through your article on particles. I have the general idea of how to create a custom particle. However, I don't understand how I would make the vanilla particle invisible. How would I replace the vanilla texture with something transparent, while still using the vanilla texture for my custom particle?

Posted

I'm traveling at the moment so won't have chance to helpmuch this weekend. I would not worry about the other resource packs at the moment, but just put in your own modified texture file for the vanilla particles edited so the splash is transparent. Confirm that works. Then if the texture field is accessible, even if you have to use reflection, you could swap it dynamically. Otherwise you need to create the custom particle. I'm sure it is a bit tricky as vanilla uses indexes to pick out the texture.

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

Posted (edited)
11 hours ago, jabelar said:

Hey, I think there is an easy way. There is a set alpha method I think in the particle fx class. You can probably just set that to make fully transparent and then back when you want them visible.

 

I still don't understand when I would do this. Is there some Forge event that gives me access to the particle? 

Edited by Daeruin
Posted

I have technically solved this issue by replacing EntityRenderer with my own that duplicates basically everything except some customization in addRainParticles. It seems to be working fine, but it feels like I've used a sledgehammer when I'd rather be using a chisel. I would welcome any more insight into making your other suggestions work. Otherwise I'll mark this solved and move on. Hopefully Forge provides a hook into that method at some point.

Posted (edited)
8 hours ago, Daeruin said:

I have technically solved this issue by replacing EntityRenderer with my own that duplicates basically everything except some customization in addRainParticles. It seems to be working fine, but it feels like I've used a sledgehammer when I'd rather be using a chisel. I would welcome any more insight into making your other suggestions work. Otherwise I'll mark this solved and move on. Hopefully Forge provides a hook into that method at some point.

 

With modding you've only got a few alternatives to change vanilla behavior. Either Forge provides a hook, or they don't. If they don't if the vanilla fields/methods have public scope field available then you take advantage of them as you can. Lastly, you can use Java reflection to try to access fields/methods that are not public.

 

Basically what you're experiencing is the immense value of the Forge hooks. Without them, most modding would require very invasive "sledgehammer" code replacement. I suggest you actually try making a Pull Request for MinecraftForge to add hooks you're interested in. It is a great way to give back to the community. I admit I only started doing it recently, and there are some tricks to it, but feels good.

Edited by jabelar

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

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.