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.
This commit is contained in:
SenseiKiwi 2013-08-24 07:52:35 -04:00
parent a1a9e39caa
commit 02b96c0c05
4 changed files with 92 additions and 31 deletions

View file

@ -4,7 +4,10 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Random; import java.util.Random;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.WeightedRandom;
import net.minecraft.util.WeightedRandomChestContent; import net.minecraft.util.WeightedRandomChestContent;
import net.minecraftforge.common.ChestGenHooks; 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 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 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() public static void registerInfo()
{ {
DDProperties properties = DDProperties.instance(); DDProperties properties = DDProperties.instance();
@ -156,4 +161,48 @@ public class DDLoot {
//System.out.println(item.theItemId.getDisplayName() + "\t" + item.itemWeight); //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);
}
}
}
} }

View file

@ -42,7 +42,7 @@ public class SchematicLoader
if (dimList.get(destDimID).dungeonGenerator == null) if (dimList.get(destDimID).dungeonGenerator == null)
{ {
//TODO: We should centralize RNG initialization and world-seed modifiers for each specific application. //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); final Random random = new Random(localSeed);
dungeonHelper.generateDungeonLink(link, dungeonHelper.getDimDungeonPack(originDimID), random); dungeonHelper.generateDungeonLink(link, dungeonHelper.getDimDungeonPack(originDimID), random);
@ -155,43 +155,50 @@ public class SchematicLoader
return dungeon; return dungeon;
} }
private static long computeDestinationHash(LinkData link) private static long calculateDestinationSeed(LinkData link)
{ {
//Time for some witchcraft. //Time for some witchcraft.
//The code here is inspired by a discussion on Stack Overflow regarding hash codes for 3D. //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 //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. //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 bit;
int index; int index;
long hash; long hash;
int w = link.destDimID; final int w = link.destDimID;
int x = link.destXCoord; final int x = link.destXCoord;
int y = link.destYCoord; final int y = link.destYCoord;
int z = link.destZCoord; final int z = link.destZCoord;
hash = 0; hash = 0;
index = 0; index = 48;
for (bit = 0; bit < 8; bit++) for (bit = 0; bit < 8; bit++)
{ {
hash |= ((w >> bit) & 1) << index; hash |= (long) ((w >> bit) & 1) << index;
index++; index--;
hash |= ((x >> bit) & 1) << index; hash |= (long) ((x >> bit) & 1) << index;
index++; index--;
hash |= ((y >> bit) & 1) << index; hash |= (long) ((y >> bit) & 1) << index;
index++; index--;
hash |= ((z >> bit) & 1) << index; hash |= (long) ((z >> bit) & 1) << index;
index++; index--;
} }
for (; bit < 24; bit++) for (; bit < 16; bit++)
{ {
hash |= ((x >> bit) & 1) << index; hash |= (long) ((x >> bit) & 1) << index;
index++; index--;
hash |= ((z >> bit) & 1) << index; hash |= (long) ((z >> bit) & 1) << index;
index++; index--;
} }
return hash; return hash;
} }
} }

View file

@ -10,9 +10,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest; import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.WeightedRandomChestContent;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.ChestGenHooks;
import StevenDimDoors.mod_pocketDim.DDLoot; import StevenDimDoors.mod_pocketDim.DDLoot;
import StevenDimDoors.mod_pocketDim.schematic.WorldOperation; import StevenDimDoors.mod_pocketDim.schematic.WorldOperation;
@ -42,8 +40,7 @@ public class FillContainersOperation extends WorldOperation
TileEntityChest chest = (TileEntityChest) tileEntity; TileEntityChest chest = (TileEntityChest) tileEntity;
if (isInventoryEmpty(chest)) if (isInventoryEmpty(chest))
{ {
ChestGenHooks info = DDLoot.DungeonChestInfo; DDLoot.generateChestContents(DDLoot.DungeonChestInfo, chest, random);
WeightedRandomChestContent.generateChestContents(random, info.getItems(random), chest, info.getCount(random));
} }
} }

View file

@ -22,14 +22,22 @@ Exit -> DeadEnd Exit
DeadEnd -> 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 ? ? ? ? -> 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