Jump to content

Editing Spawners On Vanilla World gen


jredfox

Recommended Posts

I need to edit to change nbt of a spawner from vanilla world gen for a mod I plan on making. No sending in a resource location of an entity isn't good enough I want jockie spawners and pink sheep spawners abilities along with custom delays from configurations. I can figure out the config stuff I just need to know do I have to replace the world gen and if so what event, if not how do I get the spawner from the dungeon and what event would I use.

 

Dungeons I am looking to modify: dungeon, mineshaft, stronghold, nether fortress 

Mod Support: father toasts dangerous world dungeon(main), and battle towers


Event doesn't fire Literally I call register this with my event handler and nothing happens I have @ subsribe event and everything please help code below
 MinecraftForge.EVENT_BUS.register(new ReplaceGen() );

Edited by jredfox
Link to comment
Share on other sites

3 minutes ago, diesieben07 said:

You can modify dungeon generation using PopulateChunkEvent.Populate with EventType.DUNGEON. If you want the dungeon being generated to have your custom mob, set the event result to DENY and spawn your own dungeon with your custom spawner.

To customize mineshaft, stronghold and nether fortress you need to use InitMapGenEvent and provide your own implementation.

So your saying I have to replace the worldgen with my own for the regular dungeon? Is this the same with the other event because I don't know what classes the others are generated in?

Link to comment
Share on other sites

1 minute ago, diesieben07 said:

Yes, there is currently no way to replace just the spawner.

So the other classes don't even store the blockpos for tile entities seems kind of stupid. Seems like something forge would fix. Ok well I guess I will be replacing all vanilla worldgen just to change a tile entity data

Link to comment
Share on other sites

You can also handle the PopulateChunkEvent. If you handle the one for type EventType.ANIMALS, it happens immediately after the dungeons are generated in the vanilla overworld chunk generator. So you can scan the chunk for any spawners at that point and replace them with your own. 

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

Link to comment
Share on other sites

13 minutes ago, jabelar said:

You can also handle the PopulateChunkEvent. If you handle the one for type EventType.ANIMALS, it happens immediately after the dungeons are generated in the vanilla overworld chunk generator. So you can scan the chunk for any spawners at that point and replace them with your own. 

 

 

Choonster said this on my older forum I think it's possible just not sure if it's the right thing to do. For example how would I know if this A: fires on chunk load rather then new chunk creation B: what type of the spawner is it going to be everything but, I have separate lists for each structure. I like this idea better though Maybe I could flag some booleans on other events to say what the type is and if it's any type meaning modded then use the any list if size != 05a943f5e2c3c5_fordynamicspawners.PNG.2da26e8319b35e53ad285fcb0e484848.PNG

what if gamerule says not to spawn that type will never fire I need a more precise type.

 

I was thinking something like that to but, would that type event work for say nether fortress because, the start of the structure is almost never in the same chunk as the spawner or does that event fire every chunk? I am trying to be compatible by not replacing vanilla world gen.

 

Edited by jredfox
Link to comment
Share on other sites

You should look at the ChunkGeneratorOverworld code. It will answer all your questions. There are a variety of generation-based events that fire and you can see exactly when, where and what parameters are accessible. In this specific case I think you'll find that the ANIMALS always fires. Note that this is for Overworld. If you're doing nether or end you'll have to look at those generators to see what is available.

 

But I only recommended the ANIMALS because it is immediately after the dungeon generation. Instead, as Choonster mentions it is robably even better to use the Post event which will happen after everything and furthermore fires in all the vanilla chunk generators.

 

Regarding generation versus loading, it shouldn't really matter. Whatever loads should be whatever was saved which should have been replaced during generation anyway. You do need to be a bit careful with performance though, so you might only want to check the chunk for spawners if you're confident there is dungeon or whatever present.

 

Overall I know you're a bit scared about the implications of each approach, but you can confirm it all yourself by inspecting the chunk generator code. There are lots of forge hooks existing that probably get you close enough to do what you want, even if not totally elegant.

 

Also, replacing the dungeon generator with your own isn't that big of a deal. It seems like a heavy hammer, but you can simply make yours an extension of the vanilla one and selectively @Override those methods and fields that you need to change the behavior. You can insert your custom generator either directly (the generator fields are public in the vanilla chunk generators) or by handling the PopulateChunkEvent and looking for EventType.DUNGEON.

 

 

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

Link to comment
Share on other sites

4 minutes ago, jabelar said:

You should look at the ChunkGeneratorOverworld code. It will answer all your questions. There are a variety of generation-based events that fire and you can see exactly when, where and what parameters are accessible. In this specific case I think you'll find that the ANIMALS always fires. Note that this is for Overworld. If you're doing nether or end you'll have to look at those generators to see what is available.

 

But I only recommended the ANIMALS because it is immediately after the dungeon generation. Instead, as Choonster mentions it is robably even better to use the Post event which will happen after everything and furthermore fires in all the vanilla chunk generators.

 

Regarding generation versus loading, it shouldn't really matter. Whatever loads should be whatever was saved which should have been replaced during generation anyway. You do need to be a bit careful with performance though, so you might only want to check the chunk for spawners if you're confident there is dungeon or whatever present.

 

Overall I know you're a bit scared about the implications of each approach, but you can confirm it all yourself by inspecting the chunk generator code. There are lots of forge hooks existing that probably get you close enough to do what you want, even if not totally elegant.

 

Also, replacing the dungeon generator with your own isn't that big of a deal. It seems like a heavy hammer, but you can simply make yours an extension of the vanilla one and selectively @Override those methods and fields that you need to change the behavior. You can insert your custom generator either directly (the generator fields are public in the vanilla chunk generators) or by handling the PopulateChunkEvent and looking for EventType.DUNGEON.

 

 

The main issue without replacing the world gen is how do I know what is what, sure I could hardcode if spawner has cobblestone/mossy under it it is dungeon type.

Also what am I suppose to be getting in this event a chunk one of the ichunkprovider things?

Anyways going to use this for the dynamic edition of arraylists regardless so I would like to know thanks

Edited by jredfox
Link to comment
Share on other sites

What is wrong with this code main class preinit

MinecraftForge.EVENT_BUS.register(new ReplaceGen() );

Replace Gen Class isn't printing:
 

package com.EvilNotch.dungeontweeks.main.EventHandlers;

import java.util.Iterator;
import java.util.Map;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityMobSpawner;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.event.terraingen.PopulateChunkEvent;
import net.minecraftforge.event.terraingen.PopulateChunkEvent.Populate.EventType;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

public class ReplaceGen {
	
	@SubscribeEvent
	public void replaceDungeon(PopulateChunkEvent.Populate e)
	{
		System.out.println("here");
		if(e.getType() != EventType.ANIMALS)
			return;
		
		World w = e.getWorld();
		if(w.isRemote)
			return;
		Chunk chunk = w.getChunkFromChunkCoords(e.getChunkX(), e.getChunkZ() );
		Map<BlockPos, TileEntity> map = chunk.getTileEntityMap();
		if(map == null)
			return;
		Iterator<Map.Entry<BlockPos, TileEntity>> it = map.entrySet().iterator();
		while(it.hasNext() )
		{
			Map.Entry<BlockPos, TileEntity> pair = it.next();
			BlockPos pos = pair.getKey();
			TileEntity tile = pair.getValue();
			if(tile instanceof TileEntityMobSpawner)
			{
				IBlockState old = w.getBlockState(pos);
				NBTTagCompound compound = new NBTTagCompound();
				NBTTagCompound data = new NBTTagCompound();
				data.setString("id", "minecraft:wither_skeleton");
				compound.setTag("SpawnData", data);
				tile.readFromNBT(compound);
				tile.markDirty();
				w.notifyBlockUpdate(pos, old, w.getBlockState(pos), 3);
			}
		}
	}

}

 

Link to comment
Share on other sites

Ok got the nbt working but, I need to know how to identify what dungeon is what so I don't override other dungeons with another arraylist

if(tile instanceof TileEntityMobSpawner)
{
	IBlockState old = w.getBlockState(pos);
	NBTTagCompound compound = new NBTTagCompound();
	tile.writeToNBT(compound);
	NBTTagCompound data = new NBTTagCompound();
	data.setString("id", "minecraft:creeper");
	data.setInteger("powered", 100);
	compound.setTag("SpawnData", data);
	compound.removeTag("SpawnPotentials");
	tile.readFromNBT(compound);
	tile.markDirty();
}

 

Edited by jredfox
Link to comment
Share on other sites

I'm not exactly sure what you mean by identifying the dungeon. Once you have modified the spawner why do you need to identify it later? 

 

But if you do, there are a few ways I suppose. One would be for your mod to extend the world save data and you can record the positions of each modified dungeon (or spawner) as a list.

 

What are you trying to do exactly?

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

Link to comment
Share on other sites

38 minutes ago, jabelar said:

I'm not exactly sure what you mean by identifying the dungeon. Once you have modified the spawner why do you need to identify it later? 

 

But if you do, there are a few ways I suppose. One would be for your mod to extend the world save data and you can record the positions of each modified dungeon (or spawner) as a list.

 

What are you trying to do exactly?

I need to flag what dungeon is what before I modify it. Otherwise by just instantly saying spawner here in chunk override all spawners in other dungeons , strongholds, mineshaft, nether dungeons everything. I have different arraylists for different dungeons I need to know which one is what so I can switch which arraylist to use and don't use any if not specified

Edited by jredfox
Link to comment
Share on other sites

Hey, I just figured out a much easier way to do all this. There is already a class in Forge called DungeonHooks. It has a removeDungeonMob() and an addDungeonMob() method.

 

Secondly, can't you tell that it is a dungeon by the type of spawner that is there? Like if it is a silverfish spawner then it must be a stronghold, etc. 

 

However, if you need more control -- like you only want to change it in certain situations -- then I think really the best way is to continue to handle the PopulateChunkEvent.Populate for EventType.DUNGEON and fully replace the dungeon generator. It is actually very EASY. You just need to copy the WorldGenDungeons code into your own class and change the spawner part.  Then in your event handler you simply call the generation in your class and return Result.DENY to prevent the vanilla dungeon.

 

Another method would be to handle all the EventTypes you're worried about and set boolean variable(s) to figure out if a dungeon generated in the chunk before you try to replace the spawners. For example, you could have fields like generatedStronghold, generatedDungeon, etc. and clear them in the Pre event and set them in each Populate event. Then in the Post event you could check what generated and decide whether you should replace the spawners.

Edited by jabelar

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

Link to comment
Share on other sites

3 hours ago, jabelar said:

Hey, I just figured out a much easier way to do all this. There is already a class in Forge called DungeonHooks. It has a removeDungeonMob() and an addDungeonMob() method.

 

Secondly, can't you tell that it is a dungeon by the type of spawner that is there? Like if it is a silverfish spawner then it must be a stronghold, etc. 

 

However, if you need more control -- like you only want to change it in certain situations -- then I think really the best way is to continue to handle the PopulateChunkEvent.Populate for EventType.DUNGEON and fully replace the dungeon generator. It is actually very EASY. You just need to copy the WorldGenDungeons code into your own class and change the spawner part.  Then in your event handler you simply call the generation in your class and return Result.DENY to prevent the vanilla dungeon.

 

Another method would be to handle all the EventTypes you're worried about and set boolean variable(s) to figure out if a dungeon generated in the chunk before you try to replace the spawners. For example, you could have fields like generatedStronghold, generatedDungeon, etc. and clear them in the Pre event and set them in each Populate event. Then in the Post event you could check what generated and decide whether you should replace the spawners.

Dungeon Hooks no can do in a backport I will do will have support for wither skeletons for 1.7 sub mobs didn't have their own string. 

Another reason no dungeon hooks is what if people wanted jockie or pink sheeps not possible via current forge now if forge had the ability for nbt entries yes I would use it but, I made a class similar and much better then forges already. I have already improted all dungeon hooks into my array for support but, That still doesn't solve anything

 

Per entity dection to =  dungeon type I had thought about but, it doesn't always work for example install battle towers mod well woops it's now overriden battle towers because, skeletons spiders and zombie spawners turned into whatever user configures in default dungeon. This is a good idea but, not for the vanilla dungeon types.

I am going to post a pull for forge soon about proper spawner definitions. block isSpawner will return boolean and modders will be told to use the spawner interface to return nessary tag data such as, entity id,entity properties and mountins. As for dungeon tweeks I think they should have proper forge event fired and encourage modders to implement an interface as well to fire pre and post tile entity spawner events

 

Scanning only for event type . dungeon isn't good enough. A: it fires before spawners are built, B: I need to identify not just the dungeon mineshaft , stronghold and nether fortress 

Link to comment
Share on other sites

22 hours ago, diesieben07 said:

You can modify dungeon generation using PopulateChunkEvent.Populate with EventType.DUNGEON. If you want the dungeon being generated to have your custom mob, set the event result to DENY and spawn your own dungeon with your custom spawner.

To customize mineshaft, stronghold and nether fortress you need to use InitMapGenEvent and provide your own implementation.

Where and when does InitMapGenEvent occur pre or post? Regardless whether pre or post how do you know whether or not that it's been generated there? I don't know if that event just occurs always every chunk or what is going on with that. With the other I understand it

 

If you answer what I think you answer pre and it is being generated then I can flag this and use chunk populate post to do my spawner overrides.

Edited by jredfox
Link to comment
Share on other sites

10 minutes ago, diesieben07 said:

Neither. It is fired when the generator instances are created, not when they generate.

Well I could scan for if I am inside of a structure and if isn't null scan for tile entities.

 

Question is that method in versions < 1.12 I like to backport mods and if so what version were they added I am thinking 1.8.9 ish

Link to comment
Share on other sites

Actually how do I use that method isInsideStructure() without scanning through every block of the chunk?

 

Also should I be using isPositionInStructure() instead which is much smaller but, I don't know what the differences are.

Edited by jredfox
Link to comment
Share on other sites

 

9 hours ago, diesieben07 said:

Neither. It is fired when the generator instances are created, not when they generate.

Ok I got everything working except for nether spawners some work some don't please help I don't know why it's not working all the other structures works and the nether spawners works if it's on the bridge is the a is pos in structure bug returning false when it should be true? 

Edit: debug the print line not even firing chunk populate post what is going on here? Sometimes it does other times it don't

 

Steps to reproduce:

create world seed of 2
go to nether
/tp -142 70 41 and see blaze spawner when all the other spawners in the nether I encountered was wither skeleton spawners. I hard coded the spawners if type enum == nether to be wither skeleton. It doesn't print the nbt unless the spawner does work so I know it's in the chunk populate that is causing the error I just don't know why or how that is

 

My custom forge event implementation

@SubscribeEvent
	public void dungeonHandler(EventDungeon.Post e)
	{
		NBTTagCompound nbt = new NBTTagCompound();
		e.tile.writeToNBT(nbt);
		NBTTagCompound data = nbt.getCompoundTag("SpawnData");
		if(e.type == Type.DUNGEON)
		{
			data.setString("id", "sheep");
			data.setInteger("Color", 1);
		}
		if(e.type == Type.MINESHAFT)
		{
			data.setString("id", "sheep");
			data.setInteger("Color", 2);
		}
		if(e.type == Type.STRONGHOLD)
		{
			data.setString("id", "sheep");
			data.setInteger("Color", 3);
		}
		if(e.type == Type.MANSION)
		{
			data.setString("id", "sheep");
			data.setInteger("Color", 6);
		}
		if(e.type == Type.NETHERFORTRESS)
		{
			data.setString("id", "wither_skeleton");
			System.out.println(nbt);
		}
		
		nbt.removeTag("SpawnPotentials");
		e.tile.readFromNBT(nbt);
		e.tile.markDirty();
	}


My actual forge event to fire dungeon event

	/**
	 * For everything except default dungeon
	 * @param e
	 */
	@SubscribeEvent
	public void dungeonDetect(PopulateChunkEvent.Post e)
	{
		World w = e.getWorld();
		if(w.isRemote)
			return;
		Chunk chunk = w.getChunkFromChunkCoords(e.getChunkX(), e.getChunkZ() );
		Map<BlockPos, TileEntity> map = chunk.getTileEntityMap();
		IChunkGenerator gen = e.getGenerator();
		if(map == null)
			return;
		Iterator<Map.Entry<BlockPos, TileEntity>> it = map.entrySet().iterator();
		while(it.hasNext() )
		{
			Map.Entry<BlockPos, TileEntity> pair = it.next();
			BlockPos pos = pair.getKey();
			TileEntity tile = pair.getValue();
			boolean mineshaft = false;
			boolean stronghold = false;
			boolean netherfortress = false;
			boolean mansion = false;//has spawner in secret room here
			
			if(gen instanceof ChunkGeneratorOverworld)
			{
				ChunkGeneratorOverworld gen2 = (ChunkGeneratorOverworld)gen;
				mineshaft = gen2.isInsideStructure(w, "Mineshaft", pos);
				stronghold = gen2.isInsideStructure(w, "Stronghold", pos);
				mansion = gen2.isInsideStructure(w, "Mansion", pos);
			}
			else if(gen instanceof ChunkGeneratorHell)
			{
				ChunkGeneratorHell gen3 = (ChunkGeneratorHell)gen;
				netherfortress = gen3.isInsideStructure(w, "Fortress", pos);
			}
			if(!mineshaft && !stronghold && !mansion &&!netherfortress)
				return;
			if(tile instanceof TileEntityMobSpawner)
			{				
				EventDungeon.Type type = mineshaft ? EventDungeon.Type.MINESHAFT : stronghold ? EventDungeon.Type.STRONGHOLD : netherfortress ? EventDungeon.Type.NETHERFORTRESS : mansion ? EventDungeon.Type.MANSION : null;
				System.out.println(type + " " + pos);
				EventDungeon.Post d = new EventDungeon.Post(tile,pos,type);
				MinecraftForge.EVENT_BUS.post(d);
			}
		}
	}

 

Edited by jredfox
Link to comment
Share on other sites

Well, to debug it I would put more console statements in. For example, your dungeon event won't fire if the if statement before is true and it returns. So why not put a print statement there to see if it is returning before firing the event? Also, why not put a console statement in the constructor of the event so you can see when it fires? And so fortth... If you put console statements in every method/constructor as well as where each if statement is, you can very quickly determine what is going wrong.

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

Link to comment
Share on other sites

3 hours ago, jabelar said:

Well, to debug it I would put more console statements in. For example, your dungeon event won't fire if the if statement before is true and it returns. So why not put a print statement there to see if it is returning before firing the event? Also, why not put a console statement in the constructor of the event so you can see when it fires? And so fortth... If you put console statements in every method/constructor as well as where each if statement is, you can very quickly determine what is going wrong.

I did if you read it's not firing at all when it should be I said print all tile entities if w.provider.isHell . Either A: it's not firing at all or B: the tile entity map isn't returning everything it should be. As you can see nothing prints when you do the steps to reproduce the bug. It's not overriding a specific room in the nether bridge and I don't know why

 

@SubscribeEvent
public void dungeonDetectNether(PopulateChunkEvent.Post e)
{
	World w = e.getWorld();
	if(w.isRemote || !w.provider.isNether())
		return;
	Chunk chunk = w.getChunkFromChunkCoords(e.getChunkX(), e.getChunkZ() );
	Map<BlockPos, TileEntity> map = chunk.getTileEntityMap();
	IChunkGenerator gen = e.getGenerator();
	if(map == null)
		return;
	Iterator<Map.Entry<BlockPos, TileEntity>> it = map.entrySet().iterator();
	if(map == null)
		System.out.println(e.getChunkX() + ", " + e.getChunkZ());
	while(it.hasNext() )
	{
		Map.Entry<BlockPos, TileEntity> pair = it.next();
		BlockPos pos = pair.getKey();
		TileEntity tile = pair.getValue();
		if(tile instanceof TileEntityMobSpawner)
			System.out.println("Spawner:" + pos);
		EventDungeon d = new EventDungeon.Post(tile,pos, Type.NETHERFORTRESS);
		MinecraftForge.EVENT_BUS.post(d);
	}
}

[02:42:33] [Server thread/INFO]: Player622 has made the advancement [We Need to Go Deeper]
[02:42:33] [main/INFO]: [CHAT] Player622 has made the advancement [We Need to Go Deeper]
[02:42:33] [Server thread/WARN]: Can't keep up! Did the system time change, or is the server overloaded? Running 11636ms behind, skipping 232 tick(s)
[02:42:34] [main/INFO]: Loaded 17 advancements
[02:42:46] [Server thread/INFO]: [Player622: Teleported Player622 to -141.5, 70.0, 41.5]
[02:42:46] [main/INFO]: [CHAT] Teleported Player622 to -141.5, 70.0, 41.5
[02:42:50] [Server thread/INFO]: Player622 has made the advancement [A Terrible Fortress]
[02:42:50] [main/INFO]: [CHAT] Player622 has made the advancement [A Terrible Fortress]
[02:42:50] [main/INFO]: Loaded 19 advancements

 

However if you go out to the actual bridge area and not the blaze dungon room it prints but, never working for the actual blaze room inside the fortress at -142 70 41

Spawner:BlockPos{x=-162, y=70, z=145}

Edited by jredfox
Link to comment
Share on other sites

On 2/27/2018 at 11:26 PM, jabelar said:

Well, to debug it I would put more console statements in. For example, your dungeon event won't fire if the if statement before is true and it returns. So why not put a print statement there to see if it is returning before firing the event? Also, why not put a console statement in the constructor of the event so you can see when it fires? And so fortth... If you put console statements in every method/constructor as well as where each if statement is, you can very quickly determine what is going wrong.

Got this working scan chunks radius of 1 if loaded grab else grab dormant populated chunks. Now I just need to finish setting up what choonster said to identify what dungeon is what via entity id. And also setup tile entity properties for has scanned

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.



×
×
  • Create New...

Important Information

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