Merge remote-tracking branch 'upstream/1.6.2-code'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user