jabelar
Members-
Posts
3266 -
Joined
-
Last visited
-
Days Won
39
Everything posted by jabelar
-
I ran into the same issue in my custom leaves. Your leaves block will not get the game settings automatically as far as your leaves are concerned you're on fast level. What you need to do is (a) stop overriding the methods, (b) explicitly set the fancy graphics in your block using a proxy method (because the call needs some sided code). You can see how I do it in my constructor: https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/blocks/BlockLeavesCloud.java EDIT: The reason why fancy graphics won't apply to custom leaves is the call to setFancyGraphics() is hard-coded to only be called on the vanilla Blocks.LEAVES and Blocks.LEAVES2 in the RenderGlobal#loadRenderers() method.
-
I think it has to do with whether you have "fancy" graphics setting on perhaps. The shouldSideBeRendered() method controls this, but you don't override it. So I think you have "fast" graphics on, but you have overridden the other stuff related to graphics level such as isOpaqueCube() and getBlockRenderLayer(). Basically you have mixed fancy and fast graphics. You need to override the shouldSideBeRendered() method.
-
Also please explain what "does not work" really means. How did you test it?
-
I realize not everyone is strong at math, so you can also do it sort of "dumb". You can literally just write code to place each layer of blocks. In fact, you can even just place it block by block. While it will be more lines of code, there is nothing logically wrong, and it doesn't take that much time since each line is mostly cut and paste.
-
I would probably say "Minecraft is blocky" and leave it with the imperfection. Otherwise if you're willing to work for the smoothness I think you can make a custom baked model, and if that doesn't work then yes a tileentity with special renderer. Others know more about it than me, but I'm certain you can achieve it with some work. The question is whether it is worth it or whether you can live with a Minecraft-typical "imperfection".
-
Maybe I don't understand everything you're doing but can't the capability system help here? It is exactly intended to adding "common" functionality including to vanilla things. Items themselves aren't ICapabilityProviders but ItemStack is. Seems like you should be able to make a socket capability.
-
EDIT: Corail31 helped me solve this. I kept testing with the same saved world with the same villager. So the villagers already generated never got my updated merchant lists. I didn't realize that merchant list was associated at time of villager generation, which makes sense now that I think that it needs to remember the offers and number of uses.
-
I have a tutorial on tree generation, but admit I don't go into any detail about the leaf gen itself. My tutorial is here: http://jabelarminecraft.blogspot.com/p/minecraft-modding-custom-tree-with.html Regarding leaf generation it isn't really that difficult. I find the best way is to copy the code into a new class and then refactor each of the variables with a more useful name as you figure each one out. Then the whole thing can become more clear rather than a bunch of fields named j1, k2 and such. For example, if I took the code for the leaf generation of a small oak tree and refactored it, the code might looks something like this: for (int foliageY = parBlockPos.getY() - 3 + height; foliageY <= parBlockPos.getY() + height; ++foliageY) { int foliageLayer = foliageY - (parBlockPos.getY() + minHeight); int foliageLayerRadius = 1 - foliageLayer / 2; for (int foliageX = parBlockPos.getX() - foliageLayerRadius; foliageX <= parBlockPos.getX() + foliageLayerRadius; ++foliageX) { int foliageRelativeX = foliageX - parBlockPos.getX(); for (int foliageZ = parBlockPos.getZ() - foliageLayerRadius; foliageZ <= parBlockPos.getZ() + foliageLayerRadius; ++foliageZ) { int foliageRelativeZ = foliageZ - parBlockPos.getZ(); // Fill in layer with some randomness if (Math.abs(foliageRelativeX) != foliageLayerRadius || Math.abs(foliageRelativeZ) != foliageLayerRadius || parRandom.nextInt(2) != 0 && foliageLayer != 0) { BlockPos blockPos = new BlockPos(foliageX, foliageY, foliageZ); IBlockState state = parWorld.getBlockState(blockPos); if (state.getBlock().isAir(state, parWorld, blockPos) || state.getBlock().isLeaves(state, parWorld, blockPos)) { setBlockAndNotifyAdequately(parWorld, blockPos, blockStateLeaves); } } } } } So in this case if you think about it, the outermost loop is working in the vertical (Y) direction from the base location to 3 below the height (of the tree) to the height. That makes sense right, cause if you think of a small minecraft tree there are three layers of leaves at the top. The foliageLayer indicates each of the layers as -2, -1 and 0 The radius of each layer is 1 - foliageLayer / 2. So for the bottom it is 2 (= 1 - (-2)/2), the middle is also 2 (because 1.5 as int will be rounded up) and the top is 1. Note that in the next for loop that this "radius" is subtracted and added to create the X range so it means the center is also added. So the full width of the bottom two layers is 5 and the top is 3. So the generation simply loops through all those positions and uses a bit of randomness to make a couple gaps. That's pretty much it, except the tree trunk is generated afterwords and will go right up through those leaves. I suggest you do similar investigation for other tree types. It really, really helps to have a picture of the tree type to compare with as the numbers will usually make more sense that way.
-
I have a custom villager profession "cloud enchanter" that is supposed to allow you to use emeralds to buy boots of "safe falling" which are golden boots with a custom enchantment (of "safe falling" of course). The enchantment works fine on its own -- I can print out the enchantment registry and see it listed, and I can use the /enchant command and it can enchant golden boots which begin to shimmer and also work as intended (prevent fall damage completely). The villager trade also generally works -- clicking on the villager brings up a trade GUI with a single trade that shows emeralds resulting in golden boots, and placing emeralds gets the boots. However, the problem is that the boots are not enchanted -- they don't glimmer and they don't protect the player. The weird thing is that if I print out the merchant recipe at the time it is "added" (which seems to be when the GUI is opened) the console shows that the recipe includes enchanted boots (11 is id of my custom enchantment): [com.blogspot.jabelarminecraft.examplemod.init.ModProfessions$TradeEmeraldsForEnchantedBoots:addMerchantRecipe:110]: Merchant recipe list = {Recipes:[{maxUses:7,buy:{id:"minecraft:emerald",Count:47b,Damage:0s},sell:{id:"minecraft:golden_boots",Count:1b,tag:{ench:[{lvl:2s,id:11s}]},Damage:0s},uses:0,rewardExp:1b}]} Even weirder is I tried the built-in merchant trades such as ListEnchantedItemForEmeralds and they also do not seem to work! I would get non-enchanted items in the GUI even though the merchant recipe prints out that the buy item is enchanted. I then tried to trace the call hierarchy and I'm now suspicious that the merchant recipe system may have a bug -- it creates the buying item with the ItemStack(NBTTagCompound) constructor. However, it seems to me that that constructor may not work for enchantment NBT data. If you look at the addEnchantment() method you'll see it adds a compound with key "ench". But the ItemStack(NBTTagCompound) doesn't seem to handle that at all. Am I missing something? In summary, the merchant recipe prints out (at time of GUI opening) and shows that the recipe sells an enchanted item but the GUI doesn't show the item as enchanted. Looking at how the GUI creates the item using ItemStack(NBTTagCompound) it seems to me that enchantment tags may not be applied. Here are some relevant classes: https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/init/ModProfessions.java https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/init/ModEnchantments.java https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/enchantments/EnchantmentSafeFalling.java
-
Possibly you can bookend the player tick event, in the Pre stage record the stuff that is necessary, then undo anything the standard tick does. Also there are probably a couple events related to beds and sleeping that might need handling. Basically Draco18s is saying you need to root through the the code and figure out clever ways to intercept each behavior. Sometimes it is surprisingly easy, sometimes almost impossible. Also depends on your java confidence.
-
Trouble with water entities natural spawn 1.12.2
jabelar replied to TORTlLLAS's topic in Modder Support
ItAre you using a custom chunk generator or biomes? Okay, I tried to trace the execution and it is actually kinda confusing. The initial natural generation is called by chunk generator populate method which calls the WorldEntitySpawner.performWorldGenSpawning() method. That method specifically pulls up entity lists based on the biome.getSpawnableList() and checks for EnumCreatureType.CREATURE only. The Biome class also has a getSpawnableList() method which picks the different lists based on the EnumCreatureType. So for EnumCreatureType.WATER_CREATURE it will return the biome's spawnableWaterCreatureList. The Forge EntityRegistry.addSpawn() method will call that getSpawnableList() for every biome you indicate. So basically it should all work. However, the key is the specification of the biomes. In your code example you have ocean.toArray(new Biome[0]) -- what exactly is ocean? Even then though, it should only spawn as water creature in those biomes. The ChunkGeneratorOverworld also calls up the spawn list for the biome with the getPossibleCreatures() methods which filters by creature type which is called (through a chain of calls) by WorldEntitySpawner.findChunksForSpawning() method. That cycles through all creature types but a very weird thing is that it checks if the creature would "collide" with any block or liquid and will kill it if it does. So I'm really not sure how water creatures really spawn. The EnumCreatureType class has a material field which is Material.WATER for WATER_CREATURE. However, it doesn't seem to be used anywhere in the code -- there is no getter method at all and the field is private. If I look at BiomeOcean and BiomeRiver, in their constructors they clear the regular spawn list, but not the water creature one. Therefore, after all that explanation I the way I think it all works is that you must make sure you do NOT add a water creature spawn to biome that also generates regular creatures, unless you handle the CanSpawn or related events to provide further judgement on the spawning. Otherwise, water creatures get no special extra treatment to check for being in water. Maybe someone else knows better, but that is what I concluded. I suggest double-checking the biomes you're actually adding spawn for. -
The problem is that your console statement doesn't prove that it generated. The WorldGenMineable still needs to decide if it should actually place the block. You might want to use debug mode in Eclipse with a breakpoint at the point where the block is actually placed to see what is going on. Alternatively, you can create your own copy of WorldGenMineable and add console statements throughout to get better sense of how it is executing. Also, the WorldGenMineable actually has a built in Predicate for stone where it also checks the variants to ensure it was natural stone -- I guess this prevents issues with ore generating into structures.
-
True, no one was trying to be rude, but he seemed to take it as insulting and acted defensive so was just smoothing things over. I'm Canadian so I tend to err on side of over-polite. (Pro tip, when Canadians are saying "sorry" they might still be thinking you're an idiot.) ;-) I also think the trick is there are lots of levels to "learning Java". Some people are still beginners but just need a nudge to get them to the next level. I also see a lot of people who seem to actually know Java enough but are somehow not effectively using their tools to investigate and debug. It is really hard to judge on this forum. This guy obviously didn't know enough to understand the instruction, but he also didn't seem to properly investigate. I'm hoping that giving him some guidance on the investigation will help him shore up his Java.
-
Well if you look at the drawStringWithShadow() method you'll see it ends up calling drawString() with shadow parameter which has code that does enableAlpha() as well as resetStyles(). It seems that the resetStyles() should only affect text. But the enableAlpha() I suppose could affect things afterwards. Maybe you can check alpha setting before your code and then restore it afterwards to preserve the setting value for whatever renders afterwards. Also, the overlay event has multiple sub-events. Maybe it would be "safer" to handle the Text event or the Chat event or one of those, where maybe there is a better clean up of any settings changes.
-
And actually I don't think it needs to be strictly called in the constructor or when creating the object. It should be able to be set at any time (of course prior to trying to repair it). I would probably do it to vanilla items either during item registration or even recipe registration and even init or post init should work. Basically just sometime during loading after vanilla instances exist. @uni52724sorry we're being a bit rude about the Java, but the use would be obvious if you were strong in programming. Let me teach you on how I take a suggestion and figure out the rest of the situation. First of all you should have found the implementation of the method in Item class and you'd see that it is a simple setter method (with chaining as it returns the item instance). Next I would use the call hierarchy to see how it is used. However, interestingly that method is never used in vanilla! But that is okay, that happens sometimes as there are some methods abandoned and some added by Forge. But it means we need to do more investigation to understand how it works. So, then I look at the actual field that the setter sets called canRepair. It is protected in scope so can't be accessed directly, but can by extended classes. I look at it's call hierarchy to see if it is accessed directly by vanilla, but it is not. So basically it means that all Items default to canRepair = true since that is the default value. Now you still need to know how recipes use this. It might be possible that it gets used during recipe registration or something, so you want to understand if the timing of the setNoRepair() matters much. In the call hierarchy for canRepair you can see that in addition to the setNoRepair() method there is a isRepairable() method -- this is the getter function. By following the call hierarchy for that method you can see that it is used by the RecipeRepairItem. Perfect -- so we know that we're on the right track. However, it is a good idea to confirm the implementation of the isRepairable() so I look at its declaration. It does check canRepair field but also need isDamageable() to be true. So checking the declaration of that method shows it just checks that the item isn't using damage for sub-types and also that there is a maxDamage field that > 0. Now this is in fact quite interesting because it means that another way to prevent repair is to set the maxDamage value to 0 (or less). If you follow the call hierarchy for maxDamage, there is a setter called setMaxDamage() and it is actually called on a lot of vanilla items. If you look at something you know is not damageable like ItemBed you see that it calls setMaxDamage(0) in the constructor. Therefore, all this means that vanilla items actually make things no repair by setMaxDamage(0) and do not actually use the setNoRepair() method. However, the setNoRepair() is equally valid. This is often the case in Minecraft because frankly the coding interface wasn't well managed over time and so there are often duplicate methods that achieve similar (but not always exactly) the same effect. So that is the type of investigation someone strong in Java does when given a hint like "use setNoRepair()". I just did all this investigation myself. It is important because you really need to trace the implementations and uses otherwise you can get fooled as the name of the function may not match what you think it does, and you might find other implementation details like I did. So hopefully that teaches you some techniques to help you solidify your own investigations into hints you get here.
-
I'm seeing #31813F in the area where your green arrow points to. Nice green. EDIT: Oh, you're saying the green isn't what he's talking about. You see the green, but that's not the issue. Got it. EDIT 2: Looks like he's changed his post. Now he references lighter and darker.
-
I see green and brown. Anyway, my point about debugging is the same. Just print out or watch the values and trace back to where the math/logic is going wrong for you. It is usually very obvious once you observe the behavior directly.
-
This brings up an important point. To update your mod/workspace you generally just update your build.gradle -- you should not be re-downloading the MDK generally. Rather every few weeks I suggest updating your build.gradle with the latest forge version as well as the mapping that you're discussing here, and rerunning the setup workspace and eclipse or idea to get all the latest stuff.
-
I'm not sure exactly what you mean by inverted. But why not just use regular debug methods (either run Eclipse in debugger mode or print out to console) to track what the Y-value and top block value is at the time it is being placed? I'm pretty sure you will be able to track down the problem easily that way. Is the findGroundY() function your own, or is it inherited from a parent class? That is obviously a key function if you feel your Y-values are getting messed up. Otherwise, maybe you just have your top block instance messed up? Where do you assign the top block?
-
It seems pretty clear. Your choices array has a null value at that index. But you haven't shown the code you use to create the choices array so we can't help you. But to debug you can just print out the values of the array to the console and you'll quickly see what the problem is.
-
Trying to get if chunk was generated for the first time
jabelar replied to IKnowImEZ's topic in Modder Support
I think they're saying that if the event fires then the chunk is new. You don't need to further check. Now I think you want the player to know which chunks are "new" in terms of generated since the last game load. I that case I would probably just make a List of ChunkPos elements that I add to each time there is a Populate event and then as the player moves around I would check if they've entered a chunk on that List. If they have, then they are exploring a newly generated chunk. Basically I'm saying you can make your own method for checking for newly generated chunks simply by keeping them in a list and then checking the list. -
Trying to get if chunk was generated for the first time
jabelar replied to IKnowImEZ's topic in Modder Support
I mean why do you want this? What are you wanting to do by knowing it is a new chunk? Do you really just want to make it jump continuously on a new chunk? -
Trying to get if chunk was generated for the first time
jabelar replied to IKnowImEZ's topic in Modder Support
What are you trying to achieve? -
PopulateChunkEvent.Post Never Fires Every Chunk In Nether
jabelar replied to jredfox's topic in Modder Support
Right but all they need to do is add a tag to their spanwner. I showed that that is all it takes to persist across loads with no additional scanning. -
PopulateChunkEvent.Post Never Fires Every Chunk In Nether
jabelar replied to jredfox's topic in Modder Support
Hey @jredfox, I know you've been working hard with all this scanning stuff, but I was able to get it all working with only 20 lines of new code and about five minutes. All I had to do was: 1) Copied the WorldGenDungeons code into a new class and changed the line for the spawner to make a wither spawner and to add a simple boolean tag to the spawner. See example code: https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/worldgen/WorldGenDungeonsModded.java 2) Handle the Populate event and check for dungeon type, then copied the code from the overworld chunk generator's populate() method that calls my dungeon generator. I also set result of the event to Result.DENY to prevent the vanilla dungeon. See example code: https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/TerrainGenEventHandler.java I could then access the world.getLoadedTileEntities at any time and filter for those with my tag to see what spawners I had modded. To test this I also handled the EntityJoinedWorldEvent, checked for the player, and then printed to console all the tile entities that had my tag. https://github.com/jabelar/ExampleMod-1.12/blob/master/src/main/java/com/blogspot/jabelarminecraft/examplemod/EventHandler.java#L1096 Here is what the console printed out when I created the new world: [23:42:40] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.worldgen.WorldGenDungeonsModded:generate:144]: Replacing dungeon mob spawner at BlockPos{x=22, y=58, z=384} [23:42:40] [Server thread/INFO]: Preparing spawn area: 15% [23:42:40] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.worldgen.WorldGenDungeonsModded:generate:144]: Replacing dungeon mob spawner at BlockPos{x=45, y=26, z=150} [23:42:41] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.worldgen.WorldGenDungeonsModded:generate:144]: Replacing dungeon mob spawner at BlockPos{x=61, y=26, z=70} [23:42:41] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.worldgen.WorldGenDungeonsModded:generate:144]: Replacing dungeon mob spawner at BlockPos{x=61, y=26, z=150} [23:42:41] [Server thread/INFO]: Preparing spawn area: 28% [23:42:42] [Server thread/INFO]: Preparing spawn area: 41% [23:42:43] [Server thread/INFO]: Preparing spawn area: 56% [23:42:44] [Server thread/INFO]: Preparing spawn area: 73% [23:42:45] [Server thread/INFO]: Preparing spawn area: 89% [23:42:46] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.MainMod:serverStarting:175]: Server starting [23:42:46] [Server thread/INFO]: Changing view distance to 12, from 10 [23:42:46] [Netty Local Client IO #1/INFO] [FML]: Server protocol version 2 [23:42:46] [Netty Server IO #3/INFO] [FML]: Client protocol version 2 [23:42:46] [Netty Server IO #3/INFO] [FML]: Client attempting to join with 5 mods : [email protected],[email protected],[email protected],[email protected],[email protected] [23:42:46] [Netty Local Client IO #1/INFO] [FML]: [Netty Local Client IO #1] Client side modded connection established [23:42:47] [Server thread/INFO] [FML]: [Server thread] Server side modded connection established [23:42:47] [Server thread/INFO]: Player738[local:E:0277a632] logged in with entity id 6503 at (177.5, 75.0, 246.5) [23:42:47] [Server thread/INFO]: Player738 joined the game [23:42:47] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.EventHandler:onEvent:1104]: World has modded dungeon spawners [net.minecraft.tileentity.TileEntityMobSpawner@53d837e5, net.minecraft.tileentity.TileEntityMobSpawner@46e1b33f, net.minecraft.tileentity.TileEntityMobSpawner@5a946ae9, net.minecraft.tileentity.TileEntityMobSpawner@78f0523e] You can see that it modded four spawners and that when I joined the world the server could find all four modded tile entities. In game I teleported to the spawner locations and confirmed they were spawning withers. Then I quit and saved and then loaded the same world and the console showed: [23:42:59] [Server thread/INFO]: Saving and pausing game... [23:42:59] [Server thread/INFO]: Saving chunks for level 'New World'/overworld [23:42:59] [Server thread/INFO]: Saving chunks for level 'New World'/the_nether [23:42:59] [Server thread/INFO]: Saving chunks for level 'New World'/the_end [23:42:59] [Server thread/INFO]: Saving chunks for level 'New World'/cloud [23:43:59] [Server thread/INFO]: Stopping server [23:43:59] [Server thread/INFO]: Saving players [23:43:59] [Server thread/INFO]: Player738 lost connection: Disconnected [23:43:59] [Server thread/INFO]: Player738 left the game [23:43:59] [Server thread/INFO]: Stopping singleplayer server as player logged out [23:43:59] [Server thread/INFO]: Saving worlds [23:43:59] [Server thread/INFO]: Saving chunks for level 'New World'/overworld [23:43:59] [Server thread/INFO]: Saving chunks for level 'New World'/the_nether [23:43:59] [Server thread/INFO]: Saving chunks for level 'New World'/the_end [23:43:59] [Server thread/INFO]: Saving chunks for level 'New World'/cloud [23:43:59] [Server thread/INFO] [FML]: Unloading dimension 0 [23:43:59] [Server thread/INFO] [FML]: Unloading dimension -1 [23:43:59] [Server thread/INFO] [FML]: Unloading dimension 1 [23:43:59] [Server thread/INFO] [FML]: Unloading dimension 2 [23:43:59] [Server thread/INFO] [FML]: Applying holder lookups [23:43:59] [Server thread/INFO] [FML]: Holder lookups applied [23:44:06] [Server thread/INFO]: Starting integrated minecraft server version 1.12.2 [23:44:06] [Server thread/INFO]: Generating keypair [23:44:06] [Server thread/INFO] [FML]: Injecting existing registry data into this server instance [23:44:06] [Server thread/INFO] [FML]: Applying holder lookups [23:44:06] [Server thread/INFO] [FML]: Holder lookups applied [23:44:06] [Server thread/INFO] [FML]: Loading dimension 0 (New World) (net.minecraft.server.integrated.IntegratedServer@73338cd5) [23:44:06] [Server thread/INFO]: Loaded 488 advancements [23:44:06] [Server thread/INFO] [FML]: Loading dimension 2 (New World) (net.minecraft.server.integrated.IntegratedServer@73338cd5) [23:44:06] [Server thread/INFO] [FML]: Loading dimension 1 (New World) (net.minecraft.server.integrated.IntegratedServer@73338cd5) [23:44:06] [Server thread/INFO] [FML]: Loading dimension -1 (New World) (net.minecraft.server.integrated.IntegratedServer@73338cd5) [23:44:06] [Server thread/INFO]: Preparing start region for level 0 [23:44:07] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.MainMod:serverStarting:175]: Server starting [23:44:07] [Server thread/INFO]: Changing view distance to 12, from 10 [23:44:07] [Netty Local Client IO #2/INFO] [FML]: Server protocol version 2 [23:44:07] [Netty Server IO #5/INFO] [FML]: Client protocol version 2 [23:44:07] [Netty Server IO #5/INFO] [FML]: Client attempting to join with 5 mods : [email protected],[email protected],[email protected],[email protected],[email protected] [23:44:07] [Netty Local Client IO #2/INFO] [FML]: [Netty Local Client IO #2] Client side modded connection established [23:44:07] [Server thread/INFO] [FML]: [Server thread] Server side modded connection established [23:44:07] [Server thread/INFO]: Player738[local:E:d39a95e0] logged in with entity id 11287 at (177.5, 75.0, 246.5) [23:44:07] [Server thread/INFO]: Player738 joined the game [23:44:07] [Server thread/INFO] [STDOUT]: [com.blogspot.jabelarminecraft.examplemod.EventHandler:onEvent:1104]: World has modded dungeon spawners [net.minecraft.tileentity.TileEntityMobSpawner@4011b568, net.minecraft.tileentity.TileEntityMobSpawner@453cb458, net.minecraft.tileentity.TileEntityMobSpawner@2ce4226f, net.minecraft.tileentity.TileEntityMobSpawner@56ec5e53] You can see that when I loaded the world it remembered that there were four modded spawners. It is really that simple. It has no perf impact at all and it will perfectly only modify spawners in the dungeon and will perfectly remember all the ones you've modified.