From 02b96c0c05e139993dc106a00d7b56978c46be21 Mon Sep 17 00:00:00 2001 From: SenseiKiwi Date: Sat, 24 Aug 2013 07:52:35 -0400 Subject: [PATCH] Improved Dungeon Loot and Selection DDLoot: Implemented a custom version of MC's generateChestContents() for our own chests. It avoids two notable bugs that affect MC's version. FillContainersOperation: Changed code to use DDLoot.generateChestContents() SchematicLoader: Fixed a bug in the way we calculated a seed for selecting our dungeons that would cause certain seeds to dominate all the others. Under certain circumstances, the function would only return -1. That would make our dungeon selection severely biased. That was resolved and the code was specifically tuned for seeding Java's Random even for doors with nearly identical positions. The result was an apparent major improvement in the randomness of dungeons. ruins\rules.txt: Changed the dungeon generation rules to precisely match the complicated scheme we had before. We're still using simple rules to choose dungeons - I used a program to derive the effective distribution of dungeon types that the old code would produce and converted it into the current rule system. --- StevenDimDoors/mod_pocketDim/DDLoot.java | 49 +++++++++++++++++++ .../mod_pocketDim/SchematicLoader.java | 49 +++++++++++-------- .../dungeon/FillContainersOperation.java | 5 +- schematics/ruins/rules.txt | 20 +++++--- 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/StevenDimDoors/mod_pocketDim/DDLoot.java b/StevenDimDoors/mod_pocketDim/DDLoot.java index 42266978..41d80a9a 100644 --- a/StevenDimDoors/mod_pocketDim/DDLoot.java +++ b/StevenDimDoors/mod_pocketDim/DDLoot.java @@ -4,7 +4,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Random; +import net.minecraft.inventory.IInventory; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.WeightedRandom; import net.minecraft.util.WeightedRandomChestContent; import net.minecraftforge.common.ChestGenHooks; @@ -33,6 +36,8 @@ public class DDLoot { private static final int RARE_LOOT_WEIGHT = 1; //Same weight as music discs, golden apple private static final int DUNGEON_CHEST_WEIGHT_INFLATION = 10; // (weight of iron ingots in dungeon) / (weight of iron ingots in other chests) + private DDLoot() { } + public static void registerInfo() { DDProperties properties = DDProperties.instance(); @@ -156,4 +161,48 @@ public class DDLoot { //System.out.println(item.theItemId.getDisplayName() + "\t" + item.itemWeight); } } + + public static void generateChestContents(ChestGenHooks chestInfo, IInventory inventory, Random random) + { + //This is a custom version of net.minecraft.util.WeightedRandomChestContent.generateChestContents() + //It's designed to avoid the following bugs in MC 1.5: + //1. The randomized filling algorithm will sometimes overwrite item stacks with other stacks + //2. If multiple enchanted books appear, then they will have the same enchantment + + //The prime number below is used for choosing chest slots in a seemingly-random pattern. Its value + //was selected specifically to achieve a spread-out distribution for chests with up to 104 slots. + //Choosing a prime number ensures that our increments are relatively-prime to the chest size, which + //means we'll cover all the slots before repeating any. This is mathematically guaranteed. + final int primeOffset = 239333; + + int count = chestInfo.getCount(random); + int size = inventory.getSizeInventory(); + WeightedRandomChestContent[] content = chestInfo.getItems(random); + + for (int k = 0; k < count; k++) + { + WeightedRandomChestContent selection = (WeightedRandomChestContent)WeightedRandom.getRandomItem(random, content); + + //Call getChestGenBase() to make sure we generate a different enchantment for books. + //Don't just use a condition to check if the item is an instance of ItemEnchantedBook because + //we don't know if other mods might add items that also need to be regenerated. + selection = selection.theItemId.getItem().getChestGenBase(chestInfo, random, selection); + + ItemStack[] stacks = ChestGenHooks.generateStacks(random, selection.theItemId, selection.theMinimumChanceToGenerateItem, selection.theMaximumChanceToGenerateItem); + + for (ItemStack item : stacks) + { + int limit = size; + int index = random.nextInt(size); + + while (limit > 0 && inventory.getStackInSlot(index) != null) + { + limit--; + index = (index + primeOffset) % size; + } + + inventory.setInventorySlotContents(index, item); + } + } + } } diff --git a/StevenDimDoors/mod_pocketDim/SchematicLoader.java b/StevenDimDoors/mod_pocketDim/SchematicLoader.java index 630b21ff..0cc771c3 100644 --- a/StevenDimDoors/mod_pocketDim/SchematicLoader.java +++ b/StevenDimDoors/mod_pocketDim/SchematicLoader.java @@ -42,7 +42,7 @@ public class SchematicLoader if (dimList.get(destDimID).dungeonGenerator == null) { //TODO: We should centralize RNG initialization and world-seed modifiers for each specific application. - final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L ^ computeDestinationHash(link); + final long localSeed = world.getSeed() ^ 0x2F50DB9B4A8057E4L ^ calculateDestinationSeed(link); final Random random = new Random(localSeed); dungeonHelper.generateDungeonLink(link, dungeonHelper.getDimDungeonPack(originDimID), random); @@ -155,43 +155,50 @@ public class SchematicLoader return dungeon; } - private static long computeDestinationHash(LinkData link) + private static long calculateDestinationSeed(LinkData link) { //Time for some witchcraft. //The code here is inspired by a discussion on Stack Overflow regarding hash codes for 3D. //Source: http://stackoverflow.com/questions/9858376/hashcode-for-3d-integer-coordinates-with-high-spatial-coherence - //Use 8 bits from Y and 24 bits from X and Z. Mix in 8 bits from the destination dim ID too - that means + //Use 8 bits from Y and 16 bits from X and Z. Mix in 8 bits from the destination dim ID too - that means //even if you aligned two doors perfectly between two pockets, it's unlikely they would lead to the same dungeon. + //We map bits in reverse order to produce more varied RNG output for nearly-identical points. The reason is + //that Java's Random outputs the 32 MSBs of its internal state to produce its output. If the differences + //between two seeds are small (i.e. in the LSBs), then they will tend to produce similar random outputs anyway! + + //Only bother to assign the 48 least-significant bits since Random only takes those bits from its seed. + //NOTE: The casts to long are necessary to get the right results from the bit shifts!!! int bit; int index; long hash; - int w = link.destDimID; - int x = link.destXCoord; - int y = link.destYCoord; - int z = link.destZCoord; + final int w = link.destDimID; + final int x = link.destXCoord; + final int y = link.destYCoord; + final int z = link.destZCoord; hash = 0; - index = 0; + index = 48; for (bit = 0; bit < 8; bit++) { - hash |= ((w >> bit) & 1) << index; - index++; - hash |= ((x >> bit) & 1) << index; - index++; - hash |= ((y >> bit) & 1) << index; - index++; - hash |= ((z >> bit) & 1) << index; - index++; + hash |= (long) ((w >> bit) & 1) << index; + index--; + hash |= (long) ((x >> bit) & 1) << index; + index--; + hash |= (long) ((y >> bit) & 1) << index; + index--; + hash |= (long) ((z >> bit) & 1) << index; + index--; } - for (; bit < 24; bit++) + for (; bit < 16; bit++) { - hash |= ((x >> bit) & 1) << index; - index++; - hash |= ((z >> bit) & 1) << index; - index++; + hash |= (long) ((x >> bit) & 1) << index; + index--; + hash |= (long) ((z >> bit) & 1) << index; + index--; } + return hash; } } \ No newline at end of file diff --git a/StevenDimDoors/mod_pocketDim/dungeon/FillContainersOperation.java b/StevenDimDoors/mod_pocketDim/dungeon/FillContainersOperation.java index 7979eafc..c51df0e5 100644 --- a/StevenDimDoors/mod_pocketDim/dungeon/FillContainersOperation.java +++ b/StevenDimDoors/mod_pocketDim/dungeon/FillContainersOperation.java @@ -10,9 +10,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityChest; import net.minecraft.tileentity.TileEntityDispenser; -import net.minecraft.util.WeightedRandomChestContent; import net.minecraft.world.World; -import net.minecraftforge.common.ChestGenHooks; import StevenDimDoors.mod_pocketDim.DDLoot; import StevenDimDoors.mod_pocketDim.schematic.WorldOperation; @@ -42,8 +40,7 @@ public class FillContainersOperation extends WorldOperation TileEntityChest chest = (TileEntityChest) tileEntity; if (isInventoryEmpty(chest)) { - ChestGenHooks info = DDLoot.DungeonChestInfo; - WeightedRandomChestContent.generateChestContents(random, info.getItems(random), chest, info.getCount(random)); + DDLoot.generateChestContents(DDLoot.DungeonChestInfo, chest, random); } } diff --git a/schematics/ruins/rules.txt b/schematics/ruins/rules.txt index ef838c18..a14fe17d 100644 --- a/schematics/ruins/rules.txt +++ b/schematics/ruins/rules.txt @@ -22,14 +22,22 @@ Exit -> DeadEnd Exit DeadEnd -> DeadEnd Exit -? ? ? ? ? ? ? ? -> Trap#20 SimpleHall#40 ComplexHall#10 Exit#20 DeadEnd#10 +? ? ? ? ? ? ? ? ? -> DeadEnd#10 Exit#11 SimpleHall#24 ComplexHall#16 Trap#23 Maze#11 -? ? ? ? -> Trap#18 SimpleHall#40 ComplexHall#10 Exit#18 DeadEnd#10 Hub#4 +? ? ? ? ? ? ? ? -> DeadEnd#9 Exit#10 SimpleHall#24 ComplexHall#16 Trap#23 Maze#11 Hub#6 -? ? ? -> ComplexHall Hub Trap SimpleHall Maze +? ? ? ? ? ? ? -> DeadEnd#8 Exit#9 SimpleHall#25 ComplexHall#17 Trap#23 Maze#11 Hub#7 -? ? -> ComplexHall Hub Trap SimpleHall Maze +? ? ? ? ? ? -> DeadEnd#7 Exit#8 SimpleHall#26 ComplexHall#17 Trap#23 Maze#11 Hub#8 -? -> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10 +? ? ? ? ? -> DeadEnd#6 Exit#6 SimpleHall#27 ComplexHall#18 Trap#23 Maze#11 Hub#9 --> ComplexHall#40 Hub#30 Trap#10 SimpleHall#10 Maze#10 \ No newline at end of file +? ? ? ? -> DeadEnd#5 Exit#5 SimpleHall#28 ComplexHall#18 Trap#23 Maze#11 Hub#10 + +? ? ? -> DeadEnd#3 Exit#3 SimpleHall#29 ComplexHall#19 Trap#23 Maze#11 Hub#11 + +? ? -> SimpleHall#9 ComplexHall#8 Trap#17 Maze#23 Hub#44 + +? -> SimpleHall#9 ComplexHall#8 Trap#17 Maze#23 Hub#44 + +-> SimpleHall#2 ComplexHall#54 Trap#2 Maze#2 Hub#41