/**
* This class was created by <Vazkii>. It's distributed as
* part of the Pillar Mod. Get the Source Code in github:
* https://github.com/Vazkii/Pillar
*
* Pillar is Open Source and distributed under the
* CC-BY-NC-SA 3.0 License: https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB
*
* File Created @ [25/06/2016, 18:42:33 (GMT)]
*/
package vazkii.pillar;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.tuple.Pair;
import net.minecraft.block.Block;
import net.minecraft.block.BlockChest;
import net.minecraft.block.BlockStoneBrick;
import net.minecraft.block.state.IBlockState;
import net.minecraft.command.CommandResultStats.Type;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.Entity;
import net.minecraft.init.Blocks;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.tileentity.TileEntityMobSpawner;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Rotation;
import net.minecraft.util.WeightedRandom;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraft.world.gen.structure.template.PlacementSettings;
import net.minecraft.world.gen.structure.template.Template;
import net.minecraft.world.gen.structure.template.TemplateManager;
import net.minecraftforge.fml.common.FMLCommonHandler;
import vazkii.pillar.schema.FillingType;
import vazkii.pillar.schema.StructureSchema;
public final class StructureGenerator {
private static final Pattern FUNCTION_PATTERN = Pattern.compile("\\$(.*?)\\((.*?)\\)\\$");
private static final Pattern TOKENIZING_PATTERN = Pattern.compile("\\s*(?<!\\\\);\\s*");
private static final HashMap<String, DataHandler> dataHandlers = new HashMap();
private static final HashMap<String, Function> functions = new HashMap();
private static int iteration;
static {
dataHandlers.put("run", StructureGenerator::commandRun);
dataHandlers.put("chest", StructureGenerator::commandChest);
dataHandlers.put("spawner", StructureGenerator::commandSpawner);
dataHandlers.put("struct", StructureGenerator::commandStruct);
dataHandlers.put("load_loot_table", StructureGenerator::commandLoadLootTable);
functions.put("rand_i", StructureGenerator::functionRandomInteger);
functions.put("rand_s", StructureGenerator::functionRandomString);
functions.put("run_if", StructureGenerator::functionRunIf);
}
public static boolean placeStructureAtPosition(Random rand, StructureSchema schema, Rotation baseRotation, WorldServer world, BlockPos pos, boolean useSchemaRotation) {
return placeStructureAtPosition(rand, schema, baseRotation, world, pos, 0, useSchemaRotation);
}
public static boolean placeStructureAtPosition(Random rand, StructureSchema schema, Rotation baseRotation, WorldServer world, BlockPos pos, int iteration, boolean useSchemaRotation) {
if(pos == null)
return false;
if(iteration > Pillar.maximumGenerationIterations)
return false;
MinecraftServer minecraftserver = world.getMinecraftServer();
TemplateManager templatemanager = Pillar.templateManager;
Template template = templatemanager.getTemplate(minecraftserver, new ResourceLocation(schema.structureName));
if(template == null)
return false;
BlockPos size = template.getSize();
int top = pos.getY() + size.getY();
if(top >= 256) {
int shift = top - 256;
pos.add(0, -shift, 0);
}
if(Pillar.devMode && iteration == 0)
Pillar.log("Generating Structure " + schema.structureName + " at " + pos);
PlacementSettings settings = new PlacementSettings();
settings.setMirror(schema.getMirrorType());
Rotation rot;
if(useSchemaRotation && baseRotation != null) {
rot = schema.getRotation();
if(schema.rotation == null)
rot = Rotation.values()[rand.nextInt(Rotation.values().length)];
rot = rot.add(baseRotation);
} else {
rot = baseRotation;
if(rot == null)
rot = Rotation.NONE;
}
settings.setRotation(rot);
settings.setIgnoreEntities(schema.ignoreEntities);
settings.setChunk((ChunkPos) null);
settings.setReplacedBlock((Block) null);
settings.setIgnoreStructureBlock(false);
settings.setIntegrity(MathHelper.clamp(schema.integrity, 0.0F, 1.0F));
BlockPos offset = template.transformedBlockPos(settings, new BlockPos(schema.offsetX, schema.offsetY, schema.offsetZ));
BlockPos finalPos = pos.add(offset);
template.addBlocksToWorldChunk(world, finalPos, settings);
if(schema.decay > 0) {
for(int i = 0; i < size.getX(); i++)
for(int j = 0; j < size.getY(); j++)
for(int k = 0; k < size.getZ(); k++) {
BlockPos currPos = finalPos.add(template.transformedBlockPos(settings, new BlockPos(i, j, k)));
IBlockState state = world.getBlockState(currPos);
if(state.getBlock() == Blocks.STONEBRICK && state.getValue(BlockStoneBrick.VARIANT) == BlockStoneBrick.EnumType.DEFAULT && rand.nextFloat() < schema.decay)
world.setBlockState(currPos, state.withProperty(BlockStoneBrick.VARIANT, rand.nextBoolean() ? BlockStoneBrick.EnumType.MOSSY : BlockStoneBrick.EnumType.CRACKED));
}
}
if(schema.filling != null && !schema.filling.isEmpty()) {
Block block = Block.getBlockFromName(schema.filling);
if(block != null)
for(int i = 0; i < size.getX(); i++)
for(int j = 0; j < size.getZ(); j++) {
BlockPos currPos = finalPos.add(template.transformedBlockPos(settings, new BlockPos(i, 0, j)));
IBlockState currState = world.getBlockState(currPos);
if(currState.getBlock().isAir(currState, world, currPos) || currState.getBlock() == Blocks.STRUCTURE_BLOCK)
continue;
FillingType type = schema.fillingType;
if(type == null)
type = FillingType.AIR;
int k = -1;
while(true) {
BlockPos checkPos = currPos.add(0, k, 0);
IBlockState state = world.getBlockState(checkPos);
if(type.canFill(world, state, checkPos)) {
IBlockState newState = block.getStateFromMeta(schema.fillingMetadata);
if(schema.decay > 0 && newState.getBlock() == Blocks.STONEBRICK && newState.getValue(BlockStoneBrick.VARIANT) == BlockStoneBrick.EnumType.DEFAULT && rand.nextFloat() < schema.decay)
newState = newState.withProperty(BlockStoneBrick.VARIANT, rand.nextBoolean() ? BlockStoneBrick.EnumType.MOSSY : BlockStoneBrick.EnumType.CRACKED);
world.setBlockState(checkPos, newState);
} else break;
if(checkPos.getY() == 0)
break;
k--;
}
}
}
Map<BlockPos, String> dataBlocks = template.getDataBlocks(finalPos, settings);
for(Entry<BlockPos, String> entry : dataBlocks.entrySet()) {
BlockPos entryPos = entry.getKey();
String data = entry.getValue();
world.setBlockState(entryPos, Blocks.AIR.getDefaultState());
handleData(rand, schema, settings, entryPos, data, world, iteration);
}
return true;
}
private static void handleData(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
if(data == null || data.isEmpty())
return;
data = handleFunctions(rand, data);
data = data.replaceAll("\\/\\*\\*.*", "").trim();
String command = data.replaceAll("\\s.*", "").toLowerCase();
if(dataHandlers.containsKey(command)) {
data = data.replaceAll("^.*?\\s", "");
dataHandlers.get(command).handleData(rand, schema, settings, pos, data, world, iteration);
}
}
public static String handleFunctions(Random rand, String data) {
while(true) {
Pair<Integer, Integer> boundaries = findFunction(data);
if(boundaries == null)
break;
String functionStr = data.substring(boundaries.getLeft(), boundaries.getRight());
functionStr = functionStr.substring(1, functionStr.length() - 2);
int opener = functionStr.indexOf("(");
String functionName = functionStr.substring(0, opener);
String params = functionStr.substring(opener + 1);
String result = "";
Function function = functions.get(functionName.toLowerCase());
if(function != null)
try {
result = function.handle(tokenize(params), rand);
} catch(IllegalArgumentException e) {
e.printStackTrace();
}
data = data.substring(0, boundaries.getLeft()) + result + data.substring(boundaries.getRight());
}
return data;
}
private static void commandRun(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
StructureCommandSender.world = world;
StructureCommandSender.position = pos;
if(data.startsWith("/"))
data = data.substring(1);
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
server.getCommandManager().executeCommand(StructureCommandSender.INSTANCE, data);
}
private static void commandChest(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
String[] tokens = data.split("\\s");
if(tokens.length == 0)
return;
String orientation = tokens.length == 1 ? "" : tokens[0];
String lootTable = tokens.length == 1 ? tokens[0] : tokens[1];
EnumFacing facing = EnumFacing.byName(orientation);
if(facing == null)
facing = EnumFacing.NORTH;
facing = settings.getRotation().rotate(facing);
world.setBlockState(pos, Blocks.CHEST.getDefaultState().withProperty(BlockChest.FACING, facing));
TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos);
ResourceLocation res = new ResourceLocation(lootTable);
if(res.getResourceDomain().equals("pillar"))
StructureLoader.copyNeededLootTable(world, res.getResourcePath());
chest.setLootTable(res, rand.nextLong());
}
private static void commandLoadLootTable(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
String[] tokens = data.split("\\s");
if(tokens.length == 0)
return;
String lootTable = tokens[0];
StructureLoader.copyNeededLootTable(world, lootTable);
}
private static void commandSpawner(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
String[] tokens = data.split("\\s");
if(tokens.length == 0)
return;
world.setBlockState(pos, Blocks.MOB_SPAWNER.getDefaultState());
TileEntityMobSpawner spawner = (TileEntityMobSpawner) world.getTileEntity(pos);
spawner.getSpawnerBaseLogic().setEntityId(new ResourceLocation(tokens[0]));
}
private static void commandStruct(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration) {
String[] tokens = data.split("\\s");
if(tokens.length == 0)
return;
String structureName = tokens[0];
StructureSchema newSchema = StructureLoader.loadedSchemas.get(structureName);
if(newSchema == null || newSchema == schema)
return;
int offX = 0, offY = 0, offZ = 0;
if(tokens.length >= 4) {
offX = toInt(tokens[1], 0);
offY = toInt(tokens[2], 0);
offZ = toInt(tokens[3], 0);
}
Rotation rotation = Rotation.NONE;
if(tokens.length >= 5) {
String s = tokens[4];
switch(s) {
case "90":
case "-270":
rotation = Rotation.CLOCKWISE_90;
break;
case "180":
case "-180":
rotation = Rotation.CLOCKWISE_180;
break;
case "270":
case "-90":
rotation = Rotation.COUNTERCLOCKWISE_90;
break;
}
}
rotation = rotation.add(settings.getRotation());
BlockPos finalPos = pos.add(offX, offY, offZ);
placeStructureAtPosition(rand, newSchema, rotation, world, finalPos, iteration + 1, false);
}
private static String functionRandomInteger(String[] params, Random rand) {
if(params.length != 2)
throw new IllegalArgumentException("rand_i function needs two number parameters");
int lower = Integer.parseInt(params[0]);
int upper = Integer.parseInt(params[1]);
if(upper < lower) {
int i = lower;
lower = upper;
upper = i;
}
int diff = upper - lower;
if(diff == 0)
return Integer.toString(lower);
return Integer.toString(rand.nextInt(diff) + lower);
}
private static String functionRandomString(String[] params, Random rand) {
if(params.length % 2 != 0)
throw new IllegalArgumentException("rand_s function needs an even number of parameters");
List<WeightedString> strings = new ArrayList();
int len = params.length / 2;
for(int i = 0; i < len; i++) {
String s = params[i * 2];
int w = Integer.parseInt(params[i * 2 + 1]);
strings.add(new WeightedString(w, s));
}
return WeightedRandom.getRandomItem(rand, strings).s;
}
private static String functionRunIf(String[] params, Random rand) {
if(params.length != 1)
throw new IllegalArgumentException("run_if function needs a single parameter");
double chance = Double.parseDouble(params[0]);
if(rand.nextDouble() < chance)
return "/**";
return "";
}
private static String[] tokenize(String data) {
Matcher matcher = TOKENIZING_PATTERN.matcher(data);
if(!matcher.find())
return new String[] { data };
return data.split(TOKENIZING_PATTERN.pattern());
}
private static Pair<Integer, Integer> findFunction(String s) {
Matcher matcher = FUNCTION_PATTERN.matcher(s);
if(!matcher.find())
return null;
return Pair.of(matcher.start(), matcher.end());
}
private static int toInt(String s, int def) {
try {
int i = Integer.parseInt(s);
return i;
} catch(NumberFormatException e) {
return def;
}
}
private static interface DataHandler {
public void handleData(Random rand, StructureSchema schema, PlacementSettings settings, BlockPos pos, String data, WorldServer world, int iteration);
}
private static interface Function {
public String handle(String[] params, Random rand);
}
private static class WeightedString extends WeightedRandom.Item {
public final String s;
public WeightedString(int itemWeightIn, String s) {
super(itemWeightIn);
this.s = s;
}
}
public static class StructureCommandSender implements ICommandSender {
public static final StructureCommandSender INSTANCE = new StructureCommandSender();
public static World world;
public static BlockPos position;
@Override
public void sendMessage(ITextComponent p_145747_1_) {
// NO-OP
}
@Override
public boolean canUseCommand(int p_70003_1_, String p_70003_2_) {
return p_70003_1_ <= 2;
}
@Override
public World getEntityWorld() {
return world;
}
@Override
public String getName() {
return "Pillar-executor";
}
@Override
public ITextComponent getDisplayName() {
return null;
}
@Override
public BlockPos getPosition() {
return position;
}
@Override
public Entity getCommandSenderEntity() {
return null;
}
@Override
public boolean sendCommandFeedback() {
return false;
}
@Override
public void setCommandStat(Type type, int amount) {
// NO-OP
}
@Override
public Vec3d getPositionVector() {
return new Vec3d(position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5);
}
@Override
public MinecraftServer getServer() {
return world.getMinecraftServer();
}
}
}