LootTweaker

LootTweaker

17M Downloads

RuntimeException: Attempted to add a duplicate entry to pool

Dabombber opened this issue ยท 3 comments

commented

Describe the bug
When removing and re-adding a loot table entry, the game crashes when trying to create the world. I guess this is happening because V-Tweaks injects its item directly instead of adding a pool linking its own loot table, but loottweaker should probably be able to handle this.

To Reproduce
Steps to reproduce the behavior:

  1. Clean minecraft 1.12 install with forge/crafttweaker/loottweaker + V-Tweaks
  2. Add a script to change the loot drops
vtweaks.zs

#modloaded vtweaks

// Tone down the loot drops a bit
val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
pool.addItemEntryJson(<minecraft:enchanted_book>, 2, 0, [{"tag": "{StoredEnchantments:[{lvl:1s,id:38s}]}", "function": "minecraft:set_nbt"}, {"count": 1.0, "function": "minecraft:set_count"}], [], "vtweaks:imperishable_book");

  1. Start a new game
  2. See error

Expected behavior
The mod created loot table entry should be deleted, and re-created.

Logs & scripts

crash-2020-01-01_13.35.39-server.txt

---- Minecraft Crash Report ----
// Uh... Did I do that?

Time: 1/1/20 1:35 PM
Description: Exception in server tick loop

com.google.common.util.concurrent.UncheckedExecutionException: java.lang.RuntimeException: Attempted to add a duplicate entry to pool: vtweaks:imperishable_book
	at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2217)
	at com.google.common.cache.LocalCache.get(LocalCache.java:4154)
	at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:4158)
	at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:5147)
	at com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:5153)
	at net.minecraft.world.storage.loot.LootTableManager.func_186521_a(LootTableManager.java:40)
	at net.minecraft.world.storage.loot.LootTableManager.func_186522_a(LootTableManager.java:49)
	at net.minecraft.world.storage.loot.LootTableManager.<init>(LootTableManager.java:35)
	at net.minecraft.world.WorldServer.func_175643_b(WorldServer.java:155)
	at net.minecraft.server.integrated.IntegratedServer.func_71247_a(IntegratedServer.java:122)
	at net.minecraft.server.integrated.IntegratedServer.func_71197_b(IntegratedServer.java:156)
	at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:486)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: Attempted to add a duplicate entry to pool: vtweaks:imperishable_book
	at net.minecraft.world.storage.loot.LootPool.addEntry(LootPool.java:132)
	at leviathan143.loottweaker.common.zenscript.wrapper.ZenLootPoolWrapper.tweak(ZenLootPoolWrapper.java:325)
	at leviathan143.loottweaker.common.zenscript.wrapper.ZenLootTableWrapper.lambda$getPool$0(ZenLootTableWrapper.java:49)
	at leviathan143.loottweaker.common.zenscript.wrapper.ZenLootTableWrapper$$Lambda$192/325536552.tweak(Unknown Source)
	at leviathan143.loottweaker.common.zenscript.wrapper.ZenLootTableWrapper.applyTweakers(ZenLootTableWrapper.java:97)
	at leviathan143.loottweaker.common.zenscript.LootTableTweakManager.tweakTable(LootTableTweakManager.java:51)
	at leviathan143.loottweaker.common.zenscript.ZenLootTableTweakManager.onTableLoad(ZenLootTableTweakManager.java:37)
	at net.minecraftforge.fml.common.eventhandler.ASMEventHandler_4_ZenLootTableTweakManager_onTableLoad_LootTableLoadEvent.invoke(.dynamic)
	at net.minecraftforge.fml.common.eventhandler.ASMEventHandler.invoke(ASMEventHandler.java:90)
	at net.minecraftforge.fml.common.eventhandler.EventBus.post(EventBus.java:182)
	at net.minecraftforge.event.ForgeEventFactory.loadLootTable(ForgeEventFactory.java:775)
	at net.minecraftforge.common.ForgeHooks.loadLootTable(ForgeHooks.java:1186)
	at net.minecraft.world.storage.loot.LootTableManager$Loader.func_186518_c(LootTableManager.java:156)
	at net.minecraft.world.storage.loot.LootTableManager$Loader.load(LootTableManager.java:72)
	at net.minecraft.world.storage.loot.LootTableManager$Loader.load(LootTableManager.java:53)
	at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3716)
	at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2424)
	at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2298)
	at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2211)
	... 12 more


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- System Details --
Details:
	Minecraft Version: 1.12.2
	Operating System: Windows 7 (amd64) version 6.1
	Java Version: 1.8.0_51, Oracle Corporation
	Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
	Memory: 191727040 bytes (182 MB) / 503316480 bytes (480 MB) up to 4294967296 bytes (4096 MB)
	JVM Flags: 8 total; -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump -Xmx4G -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M
	IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
	FML: MCP 9.42 Powered by Forge 14.23.5.2847 9 mods loaded, 9 mods active
	States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored

	| State  | ID              | Version      | Source                         | Signature |
	|:------ |:--------------- |:------------ |:------------------------------ |:--------- |
	| LCHIJA | minecraft       | 1.12.2       | minecraft.jar                  | None      |
	| LCHIJA | mcp             | 9.42         | minecraft.jar                  | None      |
	| LCHIJA | FML             | 8.0.99.99    | forge-1.12.2-14.23.5.2847.jar  | None      |
	| LCHIJA | forge           | 14.23.5.2847 | forge-1.12.2-14.23.5.2847.jar  | None      |
	| LCHIJA | crafttweaker    | 4.1.19       | CraftTweaker2-1.12-4.1.19.jar  | None      |
	| LCHIJA | ctgui           | 1.0.0        | CraftTweaker2-1.12-4.1.19.jar  | None      |
	| LCHIJA | crafttweakerjei | 2.0.3        | CraftTweaker2-1.12-4.1.19.jar  | None      |
	| LCHIJA | loottweaker     | 0.1.4        | LootTweaker-0.1.4+MC1.12.2.jar | None      |
	| LCHIJA | vtweaks         | 2.0.5        | VTweaks-1.12.2-2.0.5.jar       | None      |

	Loaded coremods (and transformers): 
	GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
	Profiler Position: N/A (disabled)
	Player Count: 0 / 8; []
	Type: Integrated Server (map_client.txt)
	Is Modded: Definitely; Client brand changed to 'fml,forge'

crafttweaker.log

[PREINITIALIZATION][CLIENT][INFO] Current loaders after merging: [[preinit]]
[PREINITIALIZATION][CLIENT][INFO] Loading scripts for loader with names [preinit]
[PREINITIALIZATION][CLIENT][INFO] [preinit | SIDE_CLIENT]: Skipping file {[0:crafttweaker]: vtweaks.zs} as we are currently loading with a different loader
[PREINITIALIZATION][CLIENT][INFO] Completed script loading in: 2ms
[INITIALIZATION][CLIENT][INFO] CraftTweaker: Building registry
[INITIALIZATION][CLIENT][INFO] CraftTweaker: Successfully built item registry
[INITIALIZATION][CLIENT][INFO] Current loaders after merging: [[preinit], [recipeevent | crafttweaker]]
[INITIALIZATION][CLIENT][INFO] Loading scripts for loader with names [crafttweaker | recipeevent]
[INITIALIZATION][CLIENT][INFO] [crafttweaker | SIDE_CLIENT]: Loading Script: {[0:crafttweaker]: vtweaks.zs}
[INITIALIZATION][CLIENT][INFO] Retrieved pool main from table minecraft:chests/nether_bridge
[INITIALIZATION][CLIENT][INFO] Queueing entry vtweaks:imperishable_book of pool main for removal
[INITIALIZATION][CLIENT][INFO] Queued item entry 'vtweaks:imperishable_book' for addition to pool main of table minecraft:chests/nether_bridge
[INITIALIZATION][CLIENT][INFO] Completed script loading in: 438ms
[POSTINITIALIZATION][CLIENT][INFO] Removing recipes for various outputs
[AVAILABLE][CLIENT][INFO] Fixed the RecipeBook

dumps\loot_tables\minecraft\chests\nether_bridge.json

{
  "pools": [
    {
      "name": "main",
      "entries": [
        {
          "entryName": "minecraft:diamond",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "count": {
                "min": 1.0,
                "max": 3.0
              },
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:diamond"
        },
        {
          "entryName": "minecraft:iron_ingot",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "count": {
                "min": 1.0,
                "max": 5.0
              },
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:iron_ingot"
        },
        {
          "entryName": "minecraft:gold_ingot",
          "weight": 15,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "count": {
                "min": 1.0,
                "max": 3.0
              },
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:gold_ingot"
        },
        {
          "entryName": "minecraft:golden_sword",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "name": "minecraft:golden_sword"
        },
        {
          "entryName": "minecraft:golden_chestplate",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "name": "minecraft:golden_chestplate"
        },
        {
          "entryName": "minecraft:flint_and_steel",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "name": "minecraft:flint_and_steel"
        },
        {
          "entryName": "minecraft:nether_wart",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "count": {
                "min": 3.0,
                "max": 7.0
              },
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:nether_wart"
        },
        {
          "entryName": "minecraft:saddle",
          "weight": 10,
          "quality": 0,
          "type": "item",
          "name": "minecraft:saddle"
        },
        {
          "entryName": "minecraft:golden_horse_armor",
          "weight": 8,
          "quality": 0,
          "type": "item",
          "name": "minecraft:golden_horse_armor"
        },
        {
          "entryName": "minecraft:iron_horse_armor",
          "weight": 5,
          "quality": 0,
          "type": "item",
          "name": "minecraft:iron_horse_armor"
        },
        {
          "entryName": "minecraft:diamond_horse_armor",
          "weight": 3,
          "quality": 0,
          "type": "item",
          "name": "minecraft:diamond_horse_armor"
        },
        {
          "entryName": "minecraft:obsidian",
          "weight": 2,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "count": {
                "min": 2.0,
                "max": 4.0
              },
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:obsidian"
        },
        {
          "entryName": "vtweaks:imperishable_book",
          "weight": 20,
          "quality": 0,
          "type": "item",
          "functions": [
            {
              "tag": "{StoredEnchantments:[{lvl:1s,id:11s}]}",
              "function": "minecraft:set_nbt"
            },
            {
              "count": 1.0,
              "function": "minecraft:set_count"
            }
          ],
          "name": "minecraft:enchanted_book"
        }
      ],
      "rolls": {
        "min": 2.0,
        "max": 4.0
      }
    }
  ]
}

V-Tweaks source: lootLoad(LootTableLoadEvent event)

Version Info (Exact versions only):
LootTweaker: 0.1.4
CraftTweaker/Minetweaker: 4.1.19
Forge: 14.23.5.2847
Minecraft: 1.12.2

commented

This has nothing to do with how V-Tweaks injects its loot, its loot code is slightly redundant, but fine.
This is actually due to LootTweaker's current internal implementation applying tweaks in an order other than declaration order. This results in the original vtweaks:imperishable_book not being removed before the modified version is added.
It may be tricky to fix, as LT's internals are less straightforward than you might expect due to Forge loot API design decisions. I'll try, but this may just be an issue that has to be worked around until I can rework the user-facing API in 1.14 so I can make certain changes to the internals.

For now you can work around the issue by not giving the replacement entry the same name as the original. The entryName tag is actually a Forge addition so mods can uniquely identify (usually vanilla) loot entries that they wish to remove or edit. You don't have to keep it the same when "editing" entry properties like you are here.

Finally, the set count to 1 is redundant, as that's the default.
You might also like to check out this LootTweaker feature, and the following parts of the CraftTweaker API:

They'll make your script more readable and allow you to avoid depending on int ids.
Int IDs are not stable, they can and will change under certain common conditions (e.g adding or removing a mod that adds enchantments). Always prefer string ids. If a mod doesn't allow you to use string ids, ask the author to fix that, it will cause issues.

commented

Ah, that's slightly more horrible than I was expecting. Thanks for the suggestions, I had just copied the table as it was, tidying up is for when/if it works. I see what you mean though, I didn't even notice it drop from 38 to 11 in a minimal config. The crafttweaker wiki is a bit dodgy in some places, and even loottweakers wiki has examples like addItemEntryJSON instead of addItemEntryJson, so it's hard to know when I'm doing something stupid.

Wouldn't addEnchantment on a book enchant it instead of adding it as a stored enchantment?
I'm not sure that 1.12 supports string IDs, /give @p minecraft:enchanted_book 1 0 {StoredEnchantments:[{id:"minecraft:mending",lvl:1}]} gives protection instead.

Some things I've tried and their results:

addItemEntryJson

val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
pool.addItemEntryJson(<minecraft:enchanted_book>, 2, 0, [{"tag": "{StoredEnchantments:[{lvl:1s,id:" + <enchantment:vtweaks:imperishable>.id + "s}]}", "function": "minecraft:set_nbt"}], [], "vtweaks:imperishable_book_modded");
{
  "entryName": "vtweaks:imperishable_book_modded",
  "weight": 2,
  "quality": 0,
  "type": "item",
  "functions": [
	{
	  "tag": "{StoredEnchantments:[{lvl:1s,id:11s}]}",
	  "function": "minecraft:set_nbt"
	}
  ],
  "name": "minecraft:enchanted_book"
}

Same as before but reading the enchantment ID instead of using a number. Using shorts for the numbers because apparantly that's what loot tables do, although I don't know how that applies to 1.12. Works fine after using a new entry name, but it's quite ugly.


addItemEntry + addEnchantment

val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
var stack = <minecraft:enchanted_book>;
stack.addEnchantment(<enchantment:vtweaks:imperishable>.makeEnchantment(1));
pool.addItemEntry(stack, 2, "vtweaks:imperishable_book_modded");
{
  "entryName": "vtweaks:imperishable_book_modded",
  "weight": 2,
  "quality": 0,
  "type": "item",
  "functions": [
	{
	  "tag": "{ench:[{lvl:1s,id:11s}]}",
	  "function": "minecraft:set_nbt"
	}
  ],
  "name": "minecraft:enchanted_book"
}

Yup, this didn't work at all, same result when using a <minecraft:book>. The addEnchantment function itself is pretty cumbersome since it doesn't return a new itemstack to work with.


addItemEntry + withTag

val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
pool.addItemEntry(<minecraft:enchanted_book>.withTag({"StoredEnchantments": [{"lvl": 1 as short, "id": <enchantment:vtweaks:imperishable>.id as short}]}), 2, "vtweaks:imperishable_book_modded");
{
  "entryName": "vtweaks:imperishable_book_modded",
  "weight": 2,
  "quality": 0,
  "type": "item",
  "functions": [
	{
	  "tag": "{StoredEnchantments:[{lvl:1s,id:11s}]}",
	  "function": "minecraft:set_nbt"
	}
  ],
  "name": "minecraft:enchanted_book"
}

A little cleaner


addItemEntryJson + enchant_randomly

val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
pool.addItemEntryJson(<minecraft:book>, 2, 0, [{"function": "minecraft:enchant_randomly", "enchantments": ["vtweaks:imperishable"]}], [], "vtweaks:imperishable_book_modded");
{
  "entryName": "vtweaks:imperishable_book_modded",
  "weight": 2,
  "quality": 0,
  "type": "item",
  "functions": [
	{
	  "enchantments": [
		"vtweaks:imperishable"
	  ],
	  "function": "minecraft:enchant_randomly"
	}
  ],
  "name": "minecraft:book"
}

Avoiding IDs completely, this might be a better way of handling it.


addItemEntryHelper + enchantRandomly

val pool = loottweaker.vanilla.loot.LootTables.getTable("minecraft:chests/nether_bridge").getPool("main");
pool.removeEntry("vtweaks:imperishable_book");
pool.addItemEntryHelper(<minecraft:book>, 2, 0, [loottweaker.vanilla.loot.Functions.enchantRandomly(["vtweaks:imperishable"])], [], "vtweaks:imperishable_book_modded");

This one had me stumped for a while until i realised you're missing a @ZenMethod in LootFunctionFactory.java, but otherwise it would probably be the best way.


Ideally loottweaker would have functions to modify loot table entry properties without needing to recreate them, but that's another issue.

Given that forge loot tables are trying to go towards an event based system, perhaps that's the best way for loottweaker to handle it too. That way from loottweakers side, it's just running a zenscript function instead of queued actions, and from the script side it'd then be possible to read tables and make conditional changes.
Something along the lines of:

loottweaker.vanilla.loot.events.onLootGenerated(function(event as loottweaker.vanilla.loot.event.LootGeneratedEvent) {
	// Do looty things
});
commented

Wouldn't addEnchantment on a book enchant it instead of adding it as a stored enchantment?

Yeah, it would. Sorry didn't realise that.

I'm not sure that 1.12 supports string IDs, /give @p minecraft:enchanted_book 1 0 {StoredEnchantments:[{id:"minecraft:mending",lvl:1}]} gives protection instead.

I'm 95% sure it does, just not in NBT tags.

This one had me stumped for a while until i realised you're missing a @ZenMethod in LootFunctionFactory.java, but otherwise it would probably be the best way.

Please open a separate issue ticket for that, so I'll remember it needs fixing.

Given that forge loot tables are trying to go towards an event based system, perhaps that's the best way for loottweaker to handle it too.

I've been considering a very similar approach to that for the 1.14 API. Something like

LootTweaker.editTable("minecraft:entities/cow", function(cow)
{
    val main = cow.getPool("main");
    
});

It allows me to just store the passed function and execute it later, instead of having to record and later execute every individual modification.