Jump to content

Intercepting WorldServer.addWeatherEffect Argument


Caffeinated Pinkie

Recommended Posts

I am creating a mod that intercepts the strike location of a lightning bolt. The only way I could get the lightning strikes is with WorldTickEvent and even then the client renders lightning before I can move it. The best, and most efficient, way I can see to combat this is to intercept the argument for WorldServer.addWeatherEffect(Entity). All I would need to do is set the position and one of the variables in the Object.

 

What is the best way to get the instance of the Entity being passed in before the method code is executed?

 

Here is the code for the method if it would help. I want to get entityIn before the method starts executing.

/**
* adds a lightning bolt to the list of lightning bolts in this world.
*/
public boolean addWeatherEffect(Entity entityIn)
{
	if (super.addWeatherEffect(entityIn))
	{
		this.mcServer.getPlayerList().sendToAllNearExcept((EntityPlayer)null, entityIn.posX, entityIn.posY, entityIn.posZ, 512.0D, this.provider.getDimension(), new SPacketSpawnGlobalEntity(entityIn));
		return true;
	}
	else
	{
		return false;
	}
}

 

Link to comment
Share on other sites

What are you trying to do, exactly? Move the lightning? You will need to use the tick event to detect the lightning in the weather effects list and move it. Potentially you might also need to update the lightning's position on the clients with a custom packet, I am not sure whether the game keeps the lightning's position in sync or not.

Link to comment
Share on other sites

It looks like the method posted in the initial post is where the sync is happening.

 

The lightning is being created here, in WorldServer.updateBlocks():

Spoiler

                        if (this.getGameRules().getBoolean("doMobSpawning") && this.rand.nextDouble() < (double)difficultyinstance.getAdditionalDifficulty() * 0.01D)
                        {
                            EntitySkeletonHorse entityskeletonhorse = new EntitySkeletonHorse(this);
                            entityskeletonhorse.setTrap(true);
                            entityskeletonhorse.setGrowingAge(0);
                            entityskeletonhorse.setPosition((double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ());
                            this.spawnEntity(entityskeletonhorse);
                            this.addWeatherEffect(new EntityLightningBolt(this, (double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ(), true));
                        }
                        else
                        {
                            this.addWeatherEffect(new EntityLightningBolt(this, (double)blockpos.getX(), (double)blockpos.getY(), (double)blockpos.getZ(), false));
                        }

 

I left out most of the method because it's pretty long.  There are some more conditions before what's posted but nothing that looks to me like it would be easy to alter, but you can look at it in your IDE if you want (line 392 in WorldServer class).

 

I just wanted to point out that since the lightning entities are being newly created within the method, and being pushed directly to the method where they are sent to (nearby) clients, you won't be able to do an easy reflection hack to change a field for this.  In order to literally change the course of lightning bolts, you would probably have to replace either the entire World class or the entire WorldServer class, neither of which I really recommend.

 

I can also think of a byte code edit that would do it, but would also very likely make your mod incompatible with many other mods, so that's not a good approach either.

 

All that being said, I JUST thought up a possible solution.  This method in WorldProvider is part of the check which allows or disallows normal lightning behavior:

    public boolean canDoLightning(net.minecraft.world.chunk.Chunk chunk)
    {
        return true;
    }

If you're ok with just redirecting lightning bolts in the overworld (and any modded dimension that uses DimensionType.OVERWORLD / WorldProviderSurface), you can create your own edited version of WorldProviderSurface which contains all the original fields and methods from the original WorldProviderSurface (there is actually only 1 field and 1 method; the rest are simply redirected to the WorldProvider superclass), and add in an overridden method for canDoLightning that returns "false" instead of "true".  This will disable normal lightning completely.

 

After all that, you'll have to add your own system to the game to reintroduce lightning.  I would just copy the existing system in WorldServer.updateBlocks() as much as possible but add a special condition for your redirected bolts.

 

If you decide to overwrite WorldProviderSurface with your own version of it, you'll need to replace the enum field for it in the DimensionType enum class, like this:

        Field f;
        try {
            f = ReflectionHelper.findField(DimensionType.class, "field_186077_g");
        }
        catch (ReflectionHelper.UnableToFindFieldException e)
        {
            f = ReflectionHelper.findField(DimensionType.class, "clazz");
        }

        Field modifiersField = ReflectionHelper.findField(Field.class, "modifiers");
        try {
            modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
            f.set(DimensionType.OVERWORLD, WorldProviderSurfaceEdit.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

 

And if all this looks too complicated, you might want to reconsider redirecting lightning bolts!

 

Edit 1 ==========================

Almost forgot to say that Voidwalker is the one who familiarized me with the internal structure of enum classes, and how to properly reflect into them, so if you use this method you can thank him for that.

Edited by Laike_Endaril
Link to comment
Share on other sites

37 minutes ago, Laike_Endaril said:

 

I can also think of a byte code edit that would do it, but would also very likely make your mod incompatible with many other mods, so that's not a good approach either.

No, we don’t talk about ASM here. Also it’s less likely to make your mod incomparable than replacing the class with reflection if done right.

 

Also, don’t catch & print, crash the game!

 

You could also create a PR to forge to add a hook? What’s wrong with the onEntityJoinWorld event?

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

20 minutes ago, Cadiboo said:

What’s wrong with the onEntityJoinWorld event?

Doesn't fire for lightnings since they are weather effects and not normal entities - the World#spawnEntity is never called for them.

My suggestion still stands

1 hour ago, V0idWa1k3r said:

You will need to use the tick event to detect the lightning in the weather effects list and move it. Potentially you might also need to update the lightning's position on the clients with a custom packe

 

10 minutes ago, Laike_Endaril said:

e.printStackTrace() crashes the game after printing.

What? Throwable#printStackTrace just dumps the stacktrace to the console, it doesn't crash anything(in fact I would assume it can't)

Edited by V0idWa1k3r
Link to comment
Share on other sites

 

24 minutes ago, Cadiboo said:

No, we don’t talk about ASM here. Also it’s less likely to make your mod incomparable than replacing the class with reflection if done right.

 

Also, don’t catch & print, crash the game!

 

You could also create a PR to forge to add a hook? What’s wrong with the onEntityJoinWorld event?

I do want to point out that I have tried the onEntityJoinWorld event and it does not include EntityLightningBolt.

 

2 hours ago, V0idWa1k3r said:

What are you trying to do, exactly? Move the lightning? You will need to use the tick event to detect the lightning in the weather effects list and move it. Potentially you might also need to update the lightning's position on the clients with a custom packet, I am not sure whether the game keeps the lightning's position in sync or not.

I was initially using WorldTickEvent to just get the latest Entity in World.weatherEffects. It could work, but it is fairly roundabout. The position of lightning is not kept in sync, and I have to send a packet to the client, or directly access the lightning from the client side to change the position.

 

The main issue I had with WorldTickEvent is that it only calls from the server side. I can modify lightning and everything there, but the client side gets the lightning a couple ticks later. By the time ClientTickEvent calls, the lightning has already been rendered client side and gets a glitchy rubber band effect or just jumps.

 

Edit: I did also try EntityConstructingEvent and lightning is called for it. The problem there is that it is called at the end of the Entity constructor. EntityLightningBolt calls super then afterwards sets its position and other variables. Also, by that point Entity.getEntityWorld() is null.

Edited by Caffeinated Pinkie
Link to comment
Share on other sites

8 minutes ago, V0idWa1k3r said:

What? Throwable#printStackTrace just dumps the stacktrace to the console, it doesn't crash anything(in fact I would assume it can't)

You're right.  It just doesn't prevent the game from crashing.

 

Edit: Earlier I should have said "The game still crashes after e.printStackTrace prints its output."

Edited by Laike_Endaril
Link to comment
Share on other sites

9 hours ago, Laike_Endaril said:

Edit: Earlier I should have said "The game still crashes after e.printStackTrace prints its output."

... but it doesn’t...

you catch the exception, and print it. The key word there is catch - the exception doesn’t get thrown & doesn’t crash the game.

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

I'd like to inform everyone that I've figured out how to redirect the lightning. Basically, I noticed that in WorldServer.addWeatherEffect(), before sending a packet, super.addWeatherEffect() is called. So I used reflection to set World.weatherEffects to an ArrayList with the add() overridden to set the position of an Entity when it is added. I just did this on the WorldEvent.Load when the world is not remote.

 

I essentially added in a listener to when a lightning bolt is added to weatherEffects.

 

@SubscribeEvent
public static void worldLoaded(Load event) {
	World world = event.getWorld();
	if (!world.isRemote)
		FieldAccess.set(World.class, world, "weatherEffects", new ArrayList<Entity>() {
			@Override
			public boolean add(Entity e) {
				BlockPos pos = SearchBlocks.search(world, e.getPosition());
				e.setPosition(pos.getX(), pos.getY(), pos.getZ());
				FieldAccess.set(e, "effectOnly",
					BlockAttributeConductive.isMetallic(world.getBlockState(pos.down())));
				return super.add(e);
			}
		});
}

 

  • Like 1
Link to comment
Share on other sites

Nice!  That's a far cleaner solution than what I had suggested.  I'm pretty curious what you'll make using that system.

 

 

 

 

8 hours ago, Cadiboo said:

you catch the exception, and print it. The key word there is catch - the exception doesn’t get thrown & doesn’t crash the game.

Sorry about that.  It does, in fact, crash when I test it (with a bad field name), but not directly.  It crashes because I'm using that code in the constructor of my main mod class, so even though that particular error was caught, it ends up crashing (because the mod didn't load properly and not because of the exception I had caught).

Link to comment
Share on other sites

1 hour ago, Laike_Endaril said:

Nice!  That's a far cleaner solution than what I had suggested.  I'm pretty curious what you'll make using that system.

 

 

 

 

Sorry about that.  It does, in fact, crash when I test it (with a bad field name), but not directly.  It crashes because I'm using that code in the constructor of my main mod class, so even though that particular error was caught, it ends up crashing (because the mod didn't load properly and not because of the exception I had caught).

I don't really see the harm in doing this, so I'll give a brief description. The majority of the mod is causing lightning to act better. By better I mean it will strike with a preference to taller objects, things made of metal, less cubelike blocks like iron bars, and entities. I'm also going to have a block that, when placed near anything that acts as a lightning rod, will absorb the lightning that hits it and convert that to RF.

Link to comment
Share on other sites

You should turn that listener into a hook that posts an event for compatibility

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

That sounds pretty cool.  I've been working on a magic-themed pack so the first thing I thought of was some kind of spell that made an entity more prone to being struck by lightning or something, but I hadn't considered the idea of making the lightning more realistic.

 

Edit: If you finish the mod I'd be interested in using it.  Already have a mod name in mind for me to look for?  (That is...if you're ok with others using it in a modpack?)

Edited by Laike_Endaril
Link to comment
Share on other sites

2 hours ago, Laike_Endaril said:

That sounds pretty cool.  I've been working on a magic-themed pack so the first thing I thought of was some kind of spell that made an entity more prone to being struck by lightning or something, but I hadn't considered the idea of making the lightning more realistic.

 

Edit: If you finish the mod I'd be interested in using it.  Already have a mod name in mind for me to look for?  (That is...if you're ok with others using it in a modpack?)

The name of the mod is "Lightning Tweaks". I do not have a release page for it yet, but I plan to make one in the near future. As far as I have been able to test (without any other mods), the lightning is correctly attracted to the blocks I specified before and entities. Most of the values used in this math is customizable through the in game config UI. I have yet to add the block to convert lightning to energy, but some placeholder values are in the config file.

 

I put a work-in-progress build of the mod below along with its source. Every method and variable is public and every method and class has documentation. You may use it in a modpack.

 

This is WIP build and has not been extensively tested with or without other mods.

lightningtweaks-1.12.2-0.0.4.0.jar

lightningtweaks-1.12.2-0.0.4.0-sources.jar

Edited by Caffeinated Pinkie
Link to comment
Share on other sites

Nice!  I won't be looking through your source though (or at least not anytime soon if I do); just wanted a mod name to look out for so I can try it out once you release.  I just stopped working on mods for a while to test out my modpack as it stands right now and see what I need to remove/tweak.  I'll put your mod name down on my modpack TODO list as something to try out when it releases.

Link to comment
Share on other sites

7 hours ago, Caffeinated Pinkie said:

FieldAccess.set(World.class, world, "weatherEffects", new ArrayList<Entity>()

Unless your FieldAccess somehow accesses the obfuscation database this code won't work in the obfuscated environment because that field will not be named weatherEffects, it will be field_73007_j.

 

7 hours ago, Caffeinated Pinkie said:

FieldAccess.set(e, "effectOnly",

Same here. EntityLigntningBolt.effectOnly will be field_184529_d in the obfuscated environment.

 

Actually now when I look at your FieldAccess and the way you use it it is broken. It grabs the class from the object passed, then tries to find a declared field with that name. Well, what if somebody creates a child class of EntityLightningBolt? Now your reflection will explode since the new class doesn't declare any of the fields you are asking it for. See https://stackoverflow.com/questions/16966629/what-is-the-difference-between-getfields-and-getdeclaredfields-in-java-reflectio

 

Also

} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
	    e.printStackTrace();
	}

This is the worst way to handle exceptions. If something went horribly wrong don't just print the stacktrace into the console and continue as if nothing happened. Crash the game.

Link to comment
Share on other sites

15 hours ago, V0idWa1k3r said:

Unless your FieldAccess somehow accesses the obfuscation database this code won't work in the obfuscated environment because that field will not be named weatherEffects, it will be field_73007_j.

 

Same here. EntityLigntningBolt.effectOnly will be field_184529_d in the obfuscated environment.

I had not actually considered that. I suppose that will have to fixed now.

 

15 hours ago, V0idWa1k3r said:

Actually now when I look at your FieldAccess and the way you use it it is broken. It grabs the class from the object passed, then tries to find a declared field with that name. Well, what if somebody creates a child class of EntityLightningBolt? Now your reflection will explode since the new class doesn't declare any of the fields you are asking it for. See https://stackoverflow.com/questions/16966629/what-is-the-difference-between-getfields-and-getdeclaredfields-in-java-reflectio

I can possibly just use a loop or recursion to get the class that declared a field in that case.

 

15 hours ago, V0idWa1k3r said:

Also


} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
	    e.printStackTrace();
	}

This is the worst way to handle exceptions. If something went horribly wrong don't just print the stacktrace into the console and continue as if nothing happened. Crash the game.

I'll make sure to change that. Is there a specific way that I should crash the game besides System.exit(0)?

Link to comment
Share on other sites

@diesieben07 Ran into this article recently about exceptions. May be worth linking to in the common problems list.

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

8 hours ago, diesieben07 said:

You still need to specify the SRG name, not the MCP name.

Yeah, I found those. I'll see if they work.

 

weatherEffects -> field_73007_j
effectOnly -> field_184529_d

 

Edit: I built a version of it and stuck it into a minecraft mods folder. It works so far.

 

Edit 2: I can say for certain that the Local Weather, Storms & Tornadoes mod is not compatible with this. This is because they replaced EntityLightningBolt with their own version, among other things. I'll be working on making the two compatible.

 

Edit 3: Done. Found out also that Minecraft spawns fires around the strike point during the construction of the lightning, so I had to remove them and then readd them in new places after.

Edited by Caffeinated Pinkie
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.