Merge remote-tracking branch 'upstream/1.6.2-code'

This commit is contained in:
SenseiKiwi
2013-12-20 17:28:50 -04:00
351 changed files with 1970 additions and 1632 deletions

View File

@@ -0,0 +1,78 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import java.io.FileNotFoundException;
import StevenDimDoors.mod_pocketDim.dungeon.pack.DungeonType;
import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper;
import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException;
public class DungeonData
{
private final int weight;
private final boolean isOpen;
private final boolean isInternal;
private final String schematicPath;
private final String schematicName;
private final DungeonType dungeonType;
public DungeonData(String schematicPath, boolean isInternal, DungeonType dungeonType, boolean isOpen, int weight)
{
this.schematicPath = schematicPath;
this.schematicName = getSchematicName(schematicPath);
this.dungeonType = dungeonType;
this.isInternal = isInternal;
this.isOpen = isOpen;
this.weight = weight;
}
private static String getSchematicName(String schematicPath)
{
int indexA = schematicPath.lastIndexOf('\\');
int indexB = schematicPath.lastIndexOf('/');
indexA = Math.max(indexA, indexB) + 1;
return schematicPath.substring(indexA, schematicPath.length() - DungeonHelper.SCHEMATIC_FILE_EXTENSION.length());
}
public int weight()
{
return weight;
}
public boolean isOpen()
{
return isOpen;
}
public boolean isInternal()
{
return isInternal;
}
public String schematicPath()
{
return schematicPath;
}
public DungeonType dungeonType()
{
return dungeonType;
}
public String schematicName()
{
return schematicName;
}
public DungeonSchematic loadSchematic() throws InvalidSchematicException, FileNotFoundException
{
if (isInternal)
{
return DungeonSchematic.readFromResource(schematicPath);
}
else
{
return DungeonSchematic.readFromFile(schematicPath);
}
}
}

View File

@@ -0,0 +1,343 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.TreeMap;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import StevenDimDoors.mod_pocketDim.DDProperties;
import StevenDimDoors.mod_pocketDim.Point3D;
import StevenDimDoors.mod_pocketDim.core.DimLink;
import StevenDimDoors.mod_pocketDim.core.LinkTypes;
import StevenDimDoors.mod_pocketDim.core.NewDimData;
import StevenDimDoors.mod_pocketDim.core.PocketManager;
import StevenDimDoors.mod_pocketDim.schematic.BlockRotator;
import StevenDimDoors.mod_pocketDim.schematic.CompoundFilter;
import StevenDimDoors.mod_pocketDim.schematic.InvalidSchematicException;
import StevenDimDoors.mod_pocketDim.schematic.ReplacementFilter;
import StevenDimDoors.mod_pocketDim.schematic.Schematic;
import StevenDimDoors.mod_pocketDim.ticking.MobMonolith;
import StevenDimDoors.mod_pocketDim.ticking.MonolithSpawner;
import StevenDimDoors.mod_pocketDim.util.Point4D;
public class DungeonSchematic extends Schematic {
private static final short MAX_VANILLA_BLOCK_ID = 158;
private static final short STANDARD_FABRIC_OF_REALITY_ID = 1973;
private static final short STANDARD_ETERNAL_FABRIC_ID = 220;
private static final short STANDARD_WARP_DOOR_ID = 1975;
private static final short STANDARD_DIMENSIONAL_DOOR_ID = 1970;
private static final short MONOLITH_SPAWN_MARKER_ID = (short) Block.endPortalFrame.blockID;
private static final short EXIT_DOOR_MARKER_ID = (short) Block.sandStone.blockID;
private int orientation;
private Point3D entranceDoorLocation;
private ArrayList<Point3D> exitDoorLocations;
private ArrayList<Point3D> dimensionalDoorLocations;
private ArrayList<Point3D> monolithSpawnLocations;
private static final short[] MOD_BLOCK_FILTER_EXCEPTIONS = new short[] {
STANDARD_FABRIC_OF_REALITY_ID,
STANDARD_ETERNAL_FABRIC_ID,
STANDARD_WARP_DOOR_ID,
STANDARD_DIMENSIONAL_DOOR_ID
};
private DungeonSchematic(Schematic source)
{
super(source);
}
public int getOrientation()
{
return orientation;
}
public Point3D getEntranceDoorLocation()
{
return entranceDoorLocation.clone();
}
private DungeonSchematic()
{
//Used to create a dummy instance for readFromResource()
super((short) 0, (short) 0, (short) 0, null, null, null);
}
public static DungeonSchematic readFromFile(String schematicPath) throws FileNotFoundException, InvalidSchematicException
{
return readFromFile(new File(schematicPath));
}
public static DungeonSchematic readFromFile(File schematicFile) throws FileNotFoundException, InvalidSchematicException
{
// TODO: fix resource leak
return readFromStream(new FileInputStream(schematicFile));
}
public static DungeonSchematic readFromResource(String resourcePath) throws InvalidSchematicException
{
//We need an instance of a class in the mod to retrieve a resource
DungeonSchematic empty = new DungeonSchematic();
InputStream schematicStream = empty.getClass().getResourceAsStream(resourcePath);
return readFromStream(schematicStream);
}
public static DungeonSchematic readFromStream(InputStream schematicStream) throws InvalidSchematicException
{
return new DungeonSchematic(Schematic.readFromStream(schematicStream));
}
public void applyImportFilters(DDProperties properties)
{
//Search for special blocks (warp doors, dim doors, and end portal frames that mark Monolith spawn points)
SpecialBlockFinder finder = new SpecialBlockFinder(STANDARD_WARP_DOOR_ID, STANDARD_DIMENSIONAL_DOOR_ID,
MONOLITH_SPAWN_MARKER_ID, EXIT_DOOR_MARKER_ID);
applyFilter(finder);
//Flip the entrance's orientation to get the dungeon's orientation
orientation = BlockRotator.transformMetadata(finder.getEntranceOrientation(), 2, Block.doorWood.blockID);
entranceDoorLocation = finder.getEntranceDoorLocation();
exitDoorLocations = finder.getExitDoorLocations();
dimensionalDoorLocations = finder.getDimensionalDoorLocations();
monolithSpawnLocations = finder.getMonolithSpawnLocations();
//Filter out mod blocks except some of our own
CompoundFilter standardizer = new CompoundFilter();
standardizer.addFilter(new ModBlockFilter(MAX_VANILLA_BLOCK_ID, MOD_BLOCK_FILTER_EXCEPTIONS,
(short) properties.FabricBlockID, (byte) 0));
//Also convert standard DD block IDs to local versions
Map<Short, Short> mapping = getAssignedToStandardIDMapping(properties);
for (Entry<Short, Short> entry : mapping.entrySet())
{
if (entry.getKey() != entry.getValue())
{
standardizer.addFilter(new ReplacementFilter(entry.getValue(), entry.getKey()));
}
}
applyFilter(standardizer);
}
public void applyExportFilters(DDProperties properties)
{
//Check if some block IDs assigned by Forge differ from our standard IDs
//If so, change the IDs to standard values
CompoundFilter standardizer = new CompoundFilter();
Map<Short, Short> mapping = getAssignedToStandardIDMapping(properties);
for (Entry<Short, Short> entry : mapping.entrySet())
{
if (entry.getKey() != entry.getValue())
{
standardizer.addFilter(new ReplacementFilter(entry.getKey(), entry.getValue()));
}
}
//Filter out mod blocks except some of our own
//This comes after ID standardization because the mod block filter relies on standardized IDs
standardizer.addFilter(new ModBlockFilter(MAX_VANILLA_BLOCK_ID, MOD_BLOCK_FILTER_EXCEPTIONS,
STANDARD_FABRIC_OF_REALITY_ID, (byte) 0));
applyFilter(standardizer);
}
private Map<Short, Short> getAssignedToStandardIDMapping(DDProperties properties)
{
//If we ever need this broadly or support other mods, this should be moved to a separate class
TreeMap<Short, Short> mapping = new TreeMap<Short, Short>();
mapping.put((short) properties.FabricBlockID, STANDARD_FABRIC_OF_REALITY_ID);
mapping.put((short) properties.PermaFabricBlockID, STANDARD_ETERNAL_FABRIC_ID);
mapping.put((short) properties.WarpDoorID, STANDARD_WARP_DOOR_ID);
mapping.put((short) properties.DimensionalDoorID, STANDARD_DIMENSIONAL_DOOR_ID);
return mapping;
}
public static DungeonSchematic copyFromWorld(World world, int x, int y, int z, short width, short height, short length, boolean doCompactBounds)
{
return new DungeonSchematic(Schematic.copyFromWorld(world, x, y, z, width, height, length, doCompactBounds));
}
public void copyToWorld(World world, Point3D pocketCenter, int targetOrientation, DimLink entryLink, Random random)
{
//TODO: This function is an improvised solution so we can get the release moving. In the future,
//we should generalize block transformations and implement support for them at the level of Schematic,
//then just use that support from DungeonSchematic instead of making this local fix.
//It might be easiest to support transformations using a WorldOperation
final int turnAngle = targetOrientation - this.orientation;
int index;
int count;
int blockID;
int blockMeta;
int dx, dy, dz;
Point3D pocketPoint = new Point3D(0, 0, 0);
//Copy blocks and metadata into the world
index = 0;
for (dy = 0; dy < height; dy++)
{
for (dz = 0; dz < length; dz++)
{
for (dx = 0; dx < width; dx++)
{
pocketPoint.setX(dx);
pocketPoint.setY(dy);
pocketPoint.setZ(dz);
blockID = blocks[index];
BlockRotator.transformPoint(pocketPoint, entranceDoorLocation, turnAngle, pocketCenter);
blockMeta = BlockRotator.transformMetadata(metadata[index], turnAngle, blockID);
//In the future, we might want to make this more efficient by building whole chunks at a time
setBlockDirectly(world, pocketPoint.getX(), pocketPoint.getY(), pocketPoint.getZ(), blockID, blockMeta);
index++;
}
}
}
//Copy tile entities into the world
count = tileEntities.tagCount();
for (index = 0; index < count; index++)
{
NBTTagCompound tileTag = (NBTTagCompound) tileEntities.tagAt(index);
//Rewrite its location to be in world coordinates
pocketPoint.setX(tileTag.getInteger("x"));
pocketPoint.setY(tileTag.getInteger("y"));
pocketPoint.setZ(tileTag.getInteger("z"));
BlockRotator.transformPoint(pocketPoint, entranceDoorLocation, turnAngle, pocketCenter);
tileTag.setInteger("x", pocketPoint.getX());
tileTag.setInteger("y", pocketPoint.getY());
tileTag.setInteger("z", pocketPoint.getZ());
//Load the tile entity and put it in the world
world.setBlockTileEntity(pocketPoint.getX(), pocketPoint.getY(), pocketPoint.getZ(), TileEntity.createAndLoadEntity(tileTag));
}
setUpDungeon(PocketManager.getDimensionData(world), world, pocketCenter, turnAngle, entryLink, random);
}
private void setUpDungeon(NewDimData dimension, World world, Point3D pocketCenter, int turnAngle, DimLink entryLink, Random random)
{
//Transform dungeon corners
Point3D minCorner = new Point3D(0, 0, 0);
Point3D maxCorner = new Point3D(width - 1, height - 1, length - 1);
transformCorners(entranceDoorLocation, pocketCenter, turnAngle, minCorner, maxCorner);
//Fill empty chests and dispensers
FillContainersOperation filler = new FillContainersOperation(random);
filler.apply(world, minCorner, maxCorner);
//Set up entrance door rift
createEntranceReverseLink(dimension, pocketCenter, entryLink, world);
//Set up link data for dimensional doors
for (Point3D location : dimensionalDoorLocations)
{
createDimensionalDoorLink(dimension, location, entranceDoorLocation, turnAngle, pocketCenter,world);
}
//Set up link data for exit door
for (Point3D location : exitDoorLocations)
{
createExitDoorLink(world, dimension, location, entranceDoorLocation, turnAngle, pocketCenter);
}
//Remove end portal frames and spawn Monoliths, if allowed
boolean canSpawn = MonolithSpawner.isMobSpawningAllowed();
for (Point3D location : monolithSpawnLocations)
{
spawnMonolith(world, location, entranceDoorLocation, turnAngle, pocketCenter, canSpawn);
}
}
private static void transformCorners(Point3D schematicEntrance, Point3D pocketCenter, int turnAngle, Point3D minCorner, Point3D maxCorner)
{
int temp;
BlockRotator.transformPoint(minCorner, schematicEntrance, turnAngle, pocketCenter);
BlockRotator.transformPoint(maxCorner, schematicEntrance, turnAngle, pocketCenter);
if (minCorner.getX() > maxCorner.getX())
{
temp = minCorner.getX();
minCorner.setX(maxCorner.getX());
maxCorner.setX(temp);
}
if (minCorner.getY() > maxCorner.getY())
{
temp = minCorner.getY();
minCorner.setY(maxCorner.getY());
maxCorner.setY(temp);
}
if (minCorner.getZ() > maxCorner.getZ())
{
temp = minCorner.getZ();
minCorner.setZ(maxCorner.getZ());
maxCorner.setZ(temp);
}
}
private static void createEntranceReverseLink(NewDimData dimension, Point3D pocketCenter, DimLink entryLink,World world)
{
int orientation = world.getBlockMetadata(pocketCenter.getX(), pocketCenter.getY()-1, pocketCenter.getZ());
DimLink reverseLink = dimension.createLink(pocketCenter.getX(), pocketCenter.getY(), pocketCenter.getZ(), LinkTypes.REVERSE,orientation);
Point4D destination = entryLink.source();
NewDimData prevDim = PocketManager.getDimensionData(destination.getDimension());
prevDim.setDestination(reverseLink, destination.getX(), destination.getY(), destination.getZ());
}
private static void createExitDoorLink(World world, NewDimData dimension, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter)
{
//Transform the door's location to the pocket coordinate system
Point3D location = point.clone();
BlockRotator.transformPoint(location, entrance, rotation, pocketCenter);
int orientation = world.getBlockMetadata(location.getX(), location.getY()-1, location.getZ());
dimension.createLink(location.getX(), location.getY(), location.getZ(), LinkTypes.DUNGEON_EXIT,orientation);
//Replace the sandstone block under the exit door with the same block as the one underneath it
int x = location.getX();
int y = location.getY() - 3;
int z = location.getZ();
if (y >= 0)
{
int blockID = world.getBlockId(x, y, z);
int metadata = world.getBlockMetadata(x, y, z);
setBlockDirectly(world, x, y + 1, z, blockID, metadata);
}
}
private static void createDimensionalDoorLink(NewDimData dimension, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter,World world)
{
//Transform the door's location to the pocket coordinate system
Point3D location = point.clone();
BlockRotator.transformPoint(location, entrance, rotation, pocketCenter);
int orientation = world.getBlockMetadata(location.getX(), location.getY()-1, location.getZ());
dimension.createLink(location.getX(), location.getY(), location.getZ(), LinkTypes.DUNGEON,orientation);
}
private static void spawnMonolith(World world, Point3D point, Point3D entrance, int rotation, Point3D pocketCenter, boolean canSpawn)
{
//Transform the frame block's location to the pocket coordinate system
Point3D location = point.clone();
BlockRotator.transformPoint(location, entrance, rotation, pocketCenter);
//Remove frame block
setBlockDirectly(world, location.getX(), location.getY(), location.getZ(), 0, 0);
//Spawn Monolith
if (canSpawn)
{
Entity mob = new MobMonolith(world);
mob.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), 1, 1);
world.spawnEntityInWorld(mob);
}
}
}

View File

@@ -0,0 +1,72 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.block.BlockContainer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.world.World;
import StevenDimDoors.mod_pocketDim.DDLoot;
import StevenDimDoors.mod_pocketDim.schematic.WorldOperation;
public class FillContainersOperation extends WorldOperation
{
private Random random;
public FillContainersOperation(Random random)
{
super("FillContainersOperation");
this.random = random;
}
@Override
protected boolean applyToBlock(World world, int x, int y, int z)
{
int blockID = world.getBlockId(x, y, z);
//Fill empty chests and dispensers
if (Block.blocksList[blockID] instanceof BlockContainer)
{
TileEntity tileEntity = world.getBlockTileEntity(x, y, z);
//Fill chests
if (tileEntity instanceof TileEntityChest)
{
TileEntityChest chest = (TileEntityChest) tileEntity;
if (isInventoryEmpty(chest))
{
DDLoot.generateChestContents(DDLoot.DungeonChestInfo, chest, random);
}
}
//Fill dispensers
if (tileEntity instanceof TileEntityDispenser)
{
TileEntityDispenser dispenser = (TileEntityDispenser) tileEntity;
if (isInventoryEmpty(dispenser))
{
dispenser.addItem(new ItemStack(Item.arrow, 64));
}
}
}
return true;
}
private static boolean isInventoryEmpty(IInventory inventory)
{
int size = inventory.getSizeInventory();
for (int index = 0; index < size; index++)
{
if (inventory.getStackInSlot(index) != null)
{
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,51 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import net.minecraft.block.Block;
import StevenDimDoors.mod_pocketDim.schematic.SchematicFilter;
public class ModBlockFilter extends SchematicFilter {
private short maxVanillaBlockID;
private short[] exceptions;
private short replacementBlockID;
private byte replacementMetadata;
public ModBlockFilter(short maxVanillaBlockID, short[] exceptions, short replacementBlockID, byte replacementMetadata)
{
super("ModBlockFilter");
this.maxVanillaBlockID = maxVanillaBlockID;
this.exceptions = exceptions;
this.replacementBlockID = replacementBlockID;
this.replacementMetadata = replacementMetadata;
}
@Override
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
int k;
short currentID = blocks[index];
if (currentID > maxVanillaBlockID || (currentID != 0 && Block.blocksList[currentID] == null))
{
//This might be a mod block. Check if an exception exists.
for (k = 0; k < exceptions.length; k++)
{
if (currentID == exceptions[k])
{
//Exception found, not considered a mod block
return false;
}
}
//No matching exception found. Replace the block.
blocks[index] = replacementBlockID;
metadata[index] = replacementMetadata;
return true;
}
return false;
}
@Override
protected boolean terminates()
{
return false;
}
}

View File

@@ -0,0 +1,115 @@
package StevenDimDoors.mod_pocketDim.dungeon;
import java.util.ArrayList;
import StevenDimDoors.mod_pocketDim.Point3D;
import StevenDimDoors.mod_pocketDim.schematic.Schematic;
import StevenDimDoors.mod_pocketDim.schematic.SchematicFilter;
public class SpecialBlockFinder extends SchematicFilter {
private short warpDoorID;
private short dimensionalDoorID;
private short monolithSpawnMarkerID;
private short exitMarkerID;
private int entranceOrientation;
private Schematic schematic;
private Point3D entranceDoorLocation;
private ArrayList<Point3D> exitDoorLocations;
private ArrayList<Point3D> dimensionalDoorLocations;
private ArrayList<Point3D> monolithSpawnLocations;
public SpecialBlockFinder(short warpDoorID, short dimensionalDoorID, short monolithSpawnMarkerID, short exitMarkerID)
{
super("SpecialBlockFinder");
this.warpDoorID = warpDoorID;
this.dimensionalDoorID = dimensionalDoorID;
this.monolithSpawnMarkerID = monolithSpawnMarkerID;
this.exitMarkerID = exitMarkerID;
this.entranceDoorLocation = null;
this.entranceOrientation = 0;
this.exitDoorLocations = new ArrayList<Point3D>();
this.dimensionalDoorLocations = new ArrayList<Point3D>();
this.monolithSpawnLocations = new ArrayList<Point3D>();
this.schematic = null;
}
public int getEntranceOrientation() {
return entranceOrientation;
}
public Point3D getEntranceDoorLocation() {
return entranceDoorLocation;
}
public ArrayList<Point3D> getExitDoorLocations() {
return exitDoorLocations;
}
public ArrayList<Point3D> getDimensionalDoorLocations() {
return dimensionalDoorLocations;
}
public ArrayList<Point3D> getMonolithSpawnLocations() {
return monolithSpawnLocations;
}
@Override
protected boolean initialize(Schematic schematic, short[] blocks, byte[] metadata)
{
this.schematic = schematic;
return true;
}
@Override
protected boolean applyToBlock(int index, short[] blocks, byte[] metadata)
{
int indexBelow;
int indexDoubleBelow;
if (blocks[index] == monolithSpawnMarkerID)
{
monolithSpawnLocations.add(schematic.calculatePoint(index));
return true;
}
if (blocks[index] == dimensionalDoorID)
{
indexBelow = schematic.calculateIndexBelow(index);
if (indexBelow >= 0 && blocks[indexBelow] == dimensionalDoorID)
{
dimensionalDoorLocations.add(schematic.calculatePoint(index));
return true;
}
else
{
return false;
}
}
if (blocks[index] == warpDoorID)
{
indexBelow = schematic.calculateIndexBelow(index);
if (indexBelow >= 0 && blocks[indexBelow] == warpDoorID)
{
indexDoubleBelow = schematic.calculateIndexBelow(indexBelow);
if (indexDoubleBelow >= 0 && blocks[indexDoubleBelow] == exitMarkerID)
{
exitDoorLocations.add(schematic.calculatePoint(index));
return true;
}
else if (entranceDoorLocation == null)
{
entranceDoorLocation = schematic.calculatePoint(index);
entranceOrientation = (metadata[indexBelow] & 3);
return true;
}
}
}
return false;
}
@Override
protected boolean terminates()
{
return false;
}
}

View File

@@ -0,0 +1,90 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
import java.util.ArrayList;
import java.util.HashMap;
import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
public class DungeonChainRule
{
private final int[] condition;
private final ArrayList<WeightedContainer<DungeonType>> products;
public DungeonChainRule(DungeonChainRuleDefinition source, HashMap<String, DungeonType> nameToTypeMapping)
{
ArrayList<String> conditionNames = source.getCondition();
ArrayList<WeightedContainer<String>> productNames = source.getProducts();
//Validate the data, just in case
if (conditionNames == null)
{
throw new NullPointerException("source cannot have null conditions");
}
if (productNames == null)
{
throw new NullPointerException("source cannot have null products");
}
if (productNames.isEmpty())
{
throw new IllegalArgumentException("products cannot be an empty list");
}
for (WeightedContainer<String> product : productNames)
{
//Check for weights less than 1. Those could cause Minecraft's random selection algorithm to throw an exception.
//At the very least, they're useless values.
if (product.itemWeight < 1)
{
throw new IllegalArgumentException("products cannot contain items with weights less than 1");
}
}
//Obtain the IDs of dungeon types in reverse order. Reverse order makes comparing against chain histories easy.
condition = new int[conditionNames.size()];
for (int src = 0, dst = condition.length - 1; src < condition.length; src++, dst--)
{
condition[dst] = nameToTypeMapping.get(conditionNames.get(src)).ID;
}
products = new ArrayList<WeightedContainer<DungeonType>>(productNames.size());
for (WeightedContainer<String> product : productNames)
{
products.add(new WeightedContainer<DungeonType>(nameToTypeMapping.get(product.getData()), product.itemWeight ));
}
}
public int length()
{
return condition.length;
}
public boolean evaluate(int[] typeHistory)
{
if (typeHistory.length >= condition.length)
{
for (int k = 0; k < condition.length; k++)
{
if (condition[k] != 0 && typeHistory[k] != condition[k])
{
return false;
}
}
return true;
}
else
{
return false;
}
}
public ArrayList<WeightedContainer<DungeonType>> products()
{
//Create a deep copy of the internal list of products. That way, if the list is modified externally,
//it won't affect the reference copy inside this rule.
ArrayList<WeightedContainer<DungeonType>> copy = new ArrayList<WeightedContainer<DungeonType>>(products.size());
for (WeightedContainer<DungeonType> container : products)
{
copy.add(container.clone());
}
return copy;
}
}

View File

@@ -0,0 +1,47 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
import java.util.ArrayList;
import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
public class DungeonChainRuleDefinition
{
private ArrayList<String> conditions;
private ArrayList<WeightedContainer<String>> products;
public DungeonChainRuleDefinition(ArrayList<String> conditions, ArrayList<WeightedContainer<String>> products)
{
//Validate the arguments, just in case
if (conditions == null)
{
throw new NullPointerException("conditions cannot be null");
}
if (products.isEmpty())
{
throw new IllegalArgumentException("products cannot be an empty list");
}
for (WeightedContainer<String> product : products)
{
//Check for weights less than 1. Those could cause Minecraft's random selection algorithm to throw an exception.
//At the very least, they're useless values.
if (product.itemWeight < 1)
{
throw new IllegalArgumentException("products cannot contain items with weights less than 1");
}
}
this.conditions = conditions;
this.products = products;
}
public ArrayList<String> getCondition()
{
return conditions;
}
public ArrayList<WeightedContainer<String>> getProducts()
{
return products;
}
}

View File

@@ -0,0 +1,270 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import net.minecraft.util.WeightedRandom;
import StevenDimDoors.mod_pocketDim.core.NewDimData;
import StevenDimDoors.mod_pocketDim.dungeon.DungeonData;
import StevenDimDoors.mod_pocketDim.helpers.DungeonHelper;
import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
public class DungeonPack
{
//There is no precaution against having a dungeon type removed from a config file after dungeons of that type
//have been generated. That would likely cause one or two problems. It's hard to guard against when I don't know
//what the save format will be like completely. How should this class behave if it finds a "disowned" type?
//The ID numbers would be a problem since it couldn't have a valid number, since it wasn't initialized by the pack instance.
//FIXME: Do not release this code as an update without dealing with disowned types!
private static final int MAX_HISTORY_LENGTH = 30;
private final String name;
private final HashMap<String, DungeonType> nameToTypeMapping;
private final ArrayList<ArrayList<DungeonData>> groupedDungeons;
private final ArrayList<DungeonData> allDungeons;
private final DungeonPackConfig config;
private final int maxRuleLength;
private final ArrayList<DungeonChainRule> rules;
public DungeonPack(DungeonPackConfig config)
{
config.validate();
this.config = config.clone(); //Store a clone of the config so that the caller can't change it externally later
this.name = config.getName();
int index;
int maxLength = 0;
int typeCount = config.getTypeNames().size();
this.allDungeons = new ArrayList<DungeonData>();
this.nameToTypeMapping = new HashMap<String, DungeonType>(typeCount);
this.groupedDungeons = new ArrayList<ArrayList<DungeonData>>(typeCount);
this.groupedDungeons.add(allDungeons); //Make sure the list of all dungeons is placed at index 0
this.nameToTypeMapping.put(DungeonType.WILDCARD_TYPE.Name, DungeonType.WILDCARD_TYPE);
index = 1;
for (String typeName : config.getTypeNames())
{
String standardName = typeName.toUpperCase();
this.nameToTypeMapping.put(standardName, new DungeonType(this, standardName, index));
this.groupedDungeons.add(new ArrayList<DungeonData>());
index++;
}
//Construct optimized rules from definitions
ArrayList<DungeonChainRuleDefinition> definitions = config.getRules();
this.rules = new ArrayList<DungeonChainRule>(definitions.size());
for (DungeonChainRuleDefinition definition : definitions)
{
DungeonChainRule rule = new DungeonChainRule(definition, nameToTypeMapping);
this.rules.add(rule);
if (maxLength < rule.length())
{
maxLength = rule.length();
}
}
this.maxRuleLength = maxLength;
//Remove unnecessary references to save a little memory - we won't need them here
this.config.setRules(null);
this.config.setTypeNames(null);
}
public String getName()
{
return name;
}
public DungeonPackConfig getConfig()
{
return config.clone();
}
public boolean isEmpty()
{
return allDungeons.isEmpty();
}
public DungeonType getType(String typeName)
{
DungeonType result = nameToTypeMapping.get(typeName.toUpperCase());
if (result.Owner == this) //Filter out the wildcard dungeon type
{
return result;
}
else
{
return null;
}
}
public boolean isKnownType(String typeName)
{
return (this.getType(typeName) != null);
}
public void addDungeon(DungeonData dungeon)
{
//Make sure this dungeon really belongs in this pack
DungeonType type = dungeon.dungeonType();
if (type.Owner == this)
{
allDungeons.add(dungeon);
groupedDungeons.get(type.ID).add(dungeon);
}
else
{
throw new IllegalArgumentException("The dungeon type of generator must belong to this instance of DungeonPack.");
}
}
public DungeonData getNextDungeon(NewDimData dimension, Random random)
{
if (allDungeons.isEmpty())
{
return null;
}
//Retrieve a list of the previous dungeons in this chain.
//If we're not going to check for duplicates in chains, restrict the length of the history to the length
//of the longest rule we have. Getting any more data would be useless. This optimization could be significant
//for dungeon packs that can extend arbitrarily deep. We should probably set a reasonable limit anyway.
int maxSearchLength = config.allowDuplicatesInChain() ? maxRuleLength : MAX_HISTORY_LENGTH;
ArrayList<DungeonData> history = DungeonHelper.getDungeonChainHistory(dimension.parent(), this, maxSearchLength);
return getNextDungeon(history, random);
}
private DungeonData getNextDungeon(ArrayList<DungeonData> history, Random random)
{
//Extract the dungeon types that have been used from history and convert them into an array of IDs
int index;
int[] typeHistory = new int[history.size()];
HashSet<DungeonData> excludedDungeons = null;
for (index = 0; index < typeHistory.length; index++)
{
typeHistory[index] = history.get(index).dungeonType().ID;
}
for (DungeonChainRule rule : rules)
{
if (rule.evaluate(typeHistory))
{
//Pick a random dungeon type to be generated next based on the rule's products
ArrayList<WeightedContainer<DungeonType>> products = rule.products();
DungeonType nextType;
do
{
nextType = getRandomDungeonType(random, products, groupedDungeons);
if (nextType != null)
{
//Initialize the set of excluded dungeons if needed
if (excludedDungeons == null && !config.allowDuplicatesInChain())
{
excludedDungeons = new HashSet<DungeonData>(history);
}
//List which dungeons are allowed
ArrayList<DungeonData> candidates;
ArrayList<DungeonData> group = groupedDungeons.get(nextType.ID);
if (excludedDungeons != null && !excludedDungeons.isEmpty())
{
candidates = new ArrayList<DungeonData>(group.size());
for (DungeonData dungeon : group)
{
if (!excludedDungeons.contains(dungeon))
{
candidates.add(dungeon);
}
}
}
else
{
candidates = group;
}
if (!candidates.isEmpty())
{
return getRandomDungeon(random, candidates);
}
//If we've reached this point, then a dungeon was not selected. Discard the type and try again.
for (index = 0; index < products.size(); index++)
{
if (products.get(index).getData().equals(nextType))
{
products.remove(index);
break;
}
}
}
}
while (nextType != null);
}
}
//None of the rules were applicable. Simply return a random dungeon.
return getRandomDungeon(random);
}
public DungeonData getRandomDungeon(Random random)
{
if (!allDungeons.isEmpty())
{
return getRandomDungeon(random, allDungeons);
}
else
{
return null;
}
}
private static DungeonType getRandomDungeonType(Random random, Collection<WeightedContainer<DungeonType>> types,
ArrayList<ArrayList<DungeonData>> groupedDungeons)
{
//TODO: Make this faster? This algorithm runs in quadratic time in the worst case because of the random-selection
//process and the removal search. Might be okay for normal use, though. ~SenseiKiwi
//Pick a random dungeon type based on weights. Repeat this process until a non-empty group is found or all groups are checked.
while (!types.isEmpty())
{
//Pick a random dungeon type
@SuppressWarnings("unchecked")
WeightedContainer<DungeonType> resultContainer = (WeightedContainer<DungeonType>) WeightedRandom.getRandomItem(random, types);
//Check if there are any dungeons of that type
DungeonType selectedType = resultContainer.getData();
if (!groupedDungeons.get(selectedType.ID).isEmpty())
{
//Choose this type
return selectedType;
}
else
{
//We can't use this type because there are no dungeons of this type
//Remove it from the list of types and try again
types.remove(resultContainer);
}
}
//We have run out of types to try
return null;
}
private static DungeonData getRandomDungeon(Random random, Collection<DungeonData> dungeons)
{
//Use Minecraft's WeightedRandom to select our dungeon. =D
ArrayList<WeightedContainer<DungeonData>> weights =
new ArrayList<WeightedContainer<DungeonData>>(dungeons.size());
for (DungeonData dungeon : dungeons)
{
weights.add(new WeightedContainer<DungeonData>(dungeon, dungeon.weight()));
}
@SuppressWarnings("unchecked")
WeightedContainer<DungeonData> resultContainer = (WeightedContainer<DungeonData>) WeightedRandom.getRandomItem(random, weights);
return (resultContainer != null) ? resultContainer.getData() : null;
}
}

View File

@@ -0,0 +1,126 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
import java.util.ArrayList;
public class DungeonPackConfig
{
private String name;
private ArrayList<String> typeNames;
private boolean allowDuplicatesInChain;
private boolean allowPackChangeIn;
private boolean allowPackChangeOut;
private boolean distortDoorCoordinates;
private int packWeight;
private ArrayList<DungeonChainRuleDefinition> rules;
public DungeonPackConfig() { }
@SuppressWarnings("unchecked")
private DungeonPackConfig(DungeonPackConfig source)
{
this.name = (source.name != null) ? source.name : null;
this.typeNames = (source.typeNames != null) ? (ArrayList<String>) source.typeNames.clone() : null;
this.allowDuplicatesInChain = source.allowDuplicatesInChain;
this.allowPackChangeIn = source.allowPackChangeIn;
this.allowPackChangeOut = source.allowPackChangeOut;
this.distortDoorCoordinates = source.distortDoorCoordinates;
this.packWeight = source.packWeight;
this.rules = (source.rules != null) ? (ArrayList<DungeonChainRuleDefinition>) source.rules.clone() : null;
}
public void validate()
{
if (this.name == null)
throw new NullPointerException("name cannot be null");
if (this.typeNames == null)
throw new NullPointerException("typeNames cannot be null");
if (this.rules == null)
throw new NullPointerException("rules cannot be null");
}
@Override
public DungeonPackConfig clone()
{
return new DungeonPackConfig(this);
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public ArrayList<String> getTypeNames()
{
return typeNames;
}
public void setTypeNames(ArrayList<String> typeNames)
{
this.typeNames = typeNames;
}
public boolean allowDuplicatesInChain()
{
return allowDuplicatesInChain;
}
public void setAllowDuplicatesInChain(boolean value)
{
allowDuplicatesInChain = value;
}
public void setRules(ArrayList<DungeonChainRuleDefinition> rules)
{
this.rules = rules;
}
public ArrayList<DungeonChainRuleDefinition> getRules()
{
return rules;
}
public boolean allowPackChangeIn()
{
return allowPackChangeIn;
}
public void setAllowPackChangeIn(boolean value)
{
this.allowPackChangeIn = value;
}
public boolean allowPackChangeOut()
{
return allowPackChangeOut;
}
public void setAllowPackChangeOut(boolean value)
{
this.allowPackChangeOut = value;
}
public int getPackWeight()
{
return packWeight;
}
public void setPackWeight(int packWeight)
{
this.packWeight = packWeight;
}
public boolean doDistortDoorCoordinates()
{
return distortDoorCoordinates;
}
public void setDistortDoorCoordinates(boolean value)
{
this.distortDoorCoordinates = value;
}
}

View File

@@ -0,0 +1,412 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import StevenDimDoors.mod_pocketDim.util.BaseConfigurationProcessor;
import StevenDimDoors.mod_pocketDim.util.ConfigurationProcessingException;
import StevenDimDoors.mod_pocketDim.util.WeightedContainer;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.primitives.Ints;
public class DungeonPackConfigReader extends BaseConfigurationProcessor<DungeonPackConfig>
{
private interface ILineProcessor
{
public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException;
}
//Note: These constants aren't static so that the memory will be released once
//we're done using it an instance. These aren't objects that we need to hold
//onto throughout the lifetime of MC, only at loading time.
private final int CONFIG_VERSION = 1;
private final int LOOKAHEAD_LIMIT = 1024;
private final int MAX_PRODUCT_WEIGHT = 10000;
private final int MIN_PRODUCT_WEIGHT = 1;
private final int DEFAULT_PRODUCT_WEIGHT = 100;
private final int MAX_DUNGEON_PACK_WEIGHT = 10000;
private final int MIN_DUNGEON_PACK_WEIGHT = 1;
private final int DEFAULT_DUNGEON_PACK_WEIGHT = 100;
private final int MAX_CONDITION_LENGTH = 20;
private final int MAX_PRODUCT_COUNT = MAX_CONDITION_LENGTH;
private final String COMMENT_MARKER = "##";
private final Pattern DUNGEON_TYPE_PATTERN = Pattern.compile("[A-Za-z0-9_\\-]{1,20}");
private final Splitter WHITESPACE_SPLITTER = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings();
private final String SETTING_SEPARATOR = "=";
private final String RULE_SEPARATOR = "->";
private final String WEIGHT_SEPARATOR = "#";
public DungeonPackConfigReader() { }
@SuppressWarnings("resource")
@Override
public DungeonPackConfig readFromStream(InputStream inputStream) throws ConfigurationProcessingException
{
BufferedReader reader = null;
try
{
DungeonPackConfig config = new DungeonPackConfig();
reader = new BufferedReader(new InputStreamReader(inputStream));
//Check the config format version
int version = readVersion(reader);
if (version != CONFIG_VERSION)
{
throw new ConfigurationProcessingException("The dungeon pack config has an incompatible version.");
}
config.setTypeNames(new ArrayList<String>());
config.setRules(new ArrayList<DungeonChainRuleDefinition>());
//Read the dungeon types
if (findSection("Types", reader))
{
processLines(reader, config, new DungeonTypeProcessor());
}
//Load default settings
config.setAllowDuplicatesInChain(true);
config.setAllowPackChangeIn(true);
config.setAllowPackChangeOut(true);
config.setDistortDoorCoordinates(false);
config.setPackWeight(DEFAULT_DUNGEON_PACK_WEIGHT);
//Read the settings section
if (findSection("Settings", reader))
{
processLines(reader, config, new DungeonSettingsParser());
}
//Read the rules section
if (findSection("Rules", reader))
{
processLines(reader, config, new RuleDefinitionParser());
}
return config;
}
catch (ConfigurationProcessingException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new ConfigurationProcessingException("An unexpected error occurred while trying to read the configuration file.", ex);
}
finally
{
if (reader != null)
{
try
{
reader.close();
}
catch (IOException ex) { }
}
}
}
private int readVersion(BufferedReader reader) throws ConfigurationProcessingException, IOException
{
String firstLine = reader.readLine();
String[] parts = firstLine.split("\\s", 0);
Integer version = null;
if (parts.length == 2 && parts[0].equalsIgnoreCase("version"))
{
version = Ints.tryParse(parts[1]);
}
if (version == null)
{
throw new ConfigurationProcessingException("Could not parse the config format version.");
}
return version;
}
private void processLines(BufferedReader reader, DungeonPackConfig config, ILineProcessor processor) throws IOException, ConfigurationProcessingException
{
String line;
while (reader.ready())
{
reader.mark(LOOKAHEAD_LIMIT);
line = reader.readLine();
if (!line.startsWith(COMMENT_MARKER))
{
line = line.trim();
if (line.length() > 0)
{
if (line.endsWith(":"))
{
//Consider this line a section header, reset the reader to undo consuming it
reader.reset();
break;
}
else
{
processor.process(line, config);
}
}
}
}
}
private boolean findSection(String name, BufferedReader reader) throws IOException, ConfigurationProcessingException
{
boolean found = false;
boolean matched = false;
String line = null;
String label = name + ":";
//Find the next section header
//Ignore blank lines and comment lines, stop for headers, and throw an exception for anything else
while (!found && reader.ready())
{
reader.mark(LOOKAHEAD_LIMIT);
line = reader.readLine();
if (!line.startsWith(COMMENT_MARKER))
{
line = line.trim();
if (line.length() > 0)
{
if (line.endsWith(":"))
{
//Consider this line a section header
found = true;
matched = line.equalsIgnoreCase(label);
}
else
{
//This line is invalid
throw new ConfigurationProcessingException("The dungeon pack config has an incorrect line where a section was expected: " + line);
}
}
}
}
//Check if the header matches the one we're looking for.
//If it doesn't match, undo consuming the line so it can be read later.
if (found && !matched)
{
reader.reset();
}
return found;
}
private class DungeonTypeProcessor implements ILineProcessor
{
@Override
public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException
{
List<String> typeNames = config.getTypeNames();
//Check if the dungeon type has a name that meets our restrictions
if (DUNGEON_TYPE_PATTERN.matcher(line).matches())
{
//Ignore duplicate dungeon types
line = line.toUpperCase();
if (!typeNames.contains(line))
{
typeNames.add(line);
}
}
else
{
throw new ConfigurationProcessingException("The dungeon pack config has a dungeon type with illegal characters in its name: " + line);
}
}
}
private class DungeonSettingsParser implements ILineProcessor
{
@Override
public void process(String line, DungeonPackConfig config) throws ConfigurationProcessingException
{
//The various settings that we support will be hardcoded here.
//In the future, if we get more settings, then this should be
//refactored to use a more lookup-driven approach.
boolean valid = true;
String[] settingParts = line.split(SETTING_SEPARATOR, 2);
if (settingParts.length == 2)
{
try
{
String name = settingParts[0].trim();
String value = settingParts[1].trim();
if (name.equalsIgnoreCase("AllowDuplicatesInChain"))
{
config.setAllowDuplicatesInChain(parseBoolean(value));
}
else if (name.equalsIgnoreCase("AllowPackChangeOut"))
{
config.setAllowPackChangeOut(parseBoolean(value));
}
else if (name.equalsIgnoreCase("AllowPackChangeIn"))
{
config.setAllowPackChangeIn(parseBoolean(value));
}
else if (name.equalsIgnoreCase("DistortDoorCoordinates"))
{
config.setDistortDoorCoordinates(parseBoolean(value));
}
else if (name.equalsIgnoreCase("PackWeight"))
{
int weight = Integer.parseInt(value);
if (weight >= MIN_DUNGEON_PACK_WEIGHT && weight <= MAX_DUNGEON_PACK_WEIGHT)
{
config.setPackWeight(weight);
}
else
{
valid = false;
}
}
else
{
valid = false;
}
}
catch (Exception e)
{
valid = false;
}
}
else
{
valid = false;
}
if (!valid)
{
throw new ConfigurationProcessingException("The dungeon pack config has an invalid setting: " + line);
}
}
}
private class RuleDefinitionParser implements ILineProcessor
{
@Override
public void process(String definition, DungeonPackConfig config) throws ConfigurationProcessingException
{
String[] ruleParts;
String[] productParts;
String ruleCondition;
String ruleProduct;
ArrayList<String> condition;
ArrayList<WeightedContainer<String>> products;
List<String> typeNames = config.getTypeNames();
ruleParts = definition.toUpperCase().split(RULE_SEPARATOR, -1);
if (ruleParts.length != 2)
{
throw new ConfigurationProcessingException("The dungeon pack config has an invalid rule: " + definition);
}
ruleCondition = ruleParts[0];
ruleProduct = ruleParts[1];
condition = new ArrayList<String>();
products = new ArrayList<WeightedContainer<String>>();
for (String typeName : WHITESPACE_SPLITTER.split(ruleCondition))
{
if (isKnownDungeonType(typeName, typeNames))
{
condition.add(typeName);
}
else
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule condition with an unknown dungeon type: " + typeName);
}
if (condition.size() > MAX_CONDITION_LENGTH)
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule condition that is too long: " + definition);
}
}
for (String product : WHITESPACE_SPLITTER.split(ruleProduct))
{
Integer weight;
String typeName;
productParts = product.split(WEIGHT_SEPARATOR, -1);
if (productParts.length > 2 || productParts.length == 0)
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule with an invalid product: " + product);
}
typeName = productParts[0];
if (isKnownDungeonType(typeName, typeNames))
{
if (productParts.length > 1)
{
weight = Ints.tryParse(productParts[1]);
if (weight == null || (weight > MAX_PRODUCT_WEIGHT) || (weight < MIN_PRODUCT_WEIGHT))
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule with an invalid product weight: " + product);
}
}
else
{
weight = DEFAULT_PRODUCT_WEIGHT;
}
products.add(new WeightedContainer<String>(typeName, weight));
}
else
{
throw new ConfigurationProcessingException("The dungeon pack config has an unknown dungeon type in a rule: " + typeName);
}
if (products.size() > MAX_PRODUCT_COUNT)
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule with too many products: " + definition);
}
}
if (products.isEmpty())
{
throw new ConfigurationProcessingException("The dungeon pack config has a rule with no products: " + definition);
}
config.getRules().add( new DungeonChainRuleDefinition(condition, products) );
}
}
private static boolean isKnownDungeonType(String typeName, List<String> typeNames)
{
return typeName.equals(DungeonType.WILDCARD_TYPE.Name) || typeNames.contains(typeName);
}
private static boolean parseBoolean(String value)
{
if (value.equalsIgnoreCase("true"))
return true;
if (value.equalsIgnoreCase("false"))
return false;
throw new IllegalArgumentException("The boolean value must be either \"true\" or \"false\", ignoring case.");
}
@Override
public boolean canWrite()
{
return false;
}
@Override
public void writeToStream(OutputStream outputStream, DungeonPackConfig data) throws ConfigurationProcessingException
{
throw new UnsupportedOperationException("DungeonPackConfigReader does not support writing.");
}
}

View File

@@ -0,0 +1,48 @@
package StevenDimDoors.mod_pocketDim.dungeon.pack;
public class DungeonType implements Comparable<DungeonType>
{
public static final DungeonType WILDCARD_TYPE = new DungeonType(null, "?", 0);
public static final DungeonType UNKNOWN_TYPE = new DungeonType(null, "!", -1);
public final DungeonPack Owner;
public final String Name;
public final int ID;
public DungeonType(DungeonPack owner, String name, int id)
{
Owner = owner;
Name = name;
this.ID = id;
}
@Override
public int compareTo(DungeonType other)
{
return this.ID - other.ID;
}
@Override
public boolean equals(Object other)
{
return equals((DungeonType) other);
}
public boolean equals(DungeonType other)
{
if (this == other)
return true;
if (this == null || other == null)
return false;
return (this.ID == other.ID);
}
@Override
public int hashCode()
{
final int prime = 2039;
return prime * ID;
}
}