Implemented schematic structure generation, and Mining laser fluid pumping

- finalized schematic support in structure generation, including blocks replacement and items insertions
note: implementation is functional but not optimized.
- added blockstate support for schematic insertions and replacements
- added maxRetries to item Insertion to try other slots.
- added fluid storage to mining laser pump upgrades, will still evaporate fluid if no tank was found.
This commit is contained in:
n507 2023-01-31 13:22:41 -05:00 committed by LemADEC
parent 474c756442
commit b691306a71
8 changed files with 491 additions and 83 deletions

View file

@ -7,9 +7,11 @@ import cr0s.warpdrive.block.TileEntityAbstractLaser;
import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.data.FluidWrapper; import cr0s.warpdrive.data.FluidWrapper;
import cr0s.warpdrive.data.InventoryWrapper; import cr0s.warpdrive.data.InventoryWrapper;
import cr0s.warpdrive.data.TankWrapper;
import cr0s.warpdrive.data.Vector3; import cr0s.warpdrive.data.Vector3;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List; import java.util.List;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -27,8 +29,9 @@ import net.minecraft.util.NonNullList;
import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldServer; import net.minecraft.world.WorldServer;
import net.minecraftforge.common.IPlantable; import net.minecraftforge.common.IPlantable;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser { public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser {
@ -55,11 +58,30 @@ public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser {
if (blockState.getBlock().isAir(blockState, world, blockPos)) { if (blockState.getBlock().isAir(blockState, world, blockPos)) {
return; return;
} }
if (FluidWrapper.isFluid(blockState)) {
final Fluid fluid = FluidWrapper.getFluid(blockState);
if (fluid != null) {// (this is a fluid block)
if ( WarpDriveConfig.MINING_LASER_PUMP_UPGRADE_HARVEST_FLUID
&& FluidWrapper.isSourceBlock(world, blockPos, blockState) ) {// (fluid collection is enabled & it's a source block)
final Collection<Object> connectedTanks = TankWrapper.getConnectedTanks(world, pos);
if (!connectedTanks.isEmpty()) {// (at least 1 tank is connected)
final FluidStack fluidStack = new FluidStack(fluid, 1000);
final boolean fluidOverflowed = TankWrapper.addToTanks(world, pos, connectedTanks, fluidStack);
if (fluidOverflowed) {
// assume player wants to collect the fluid, hence stop the mining in case of overflow
setIsEnabled(false);
}
// Collect Fluid
world.playSound(null, blockPos, fluid.getFillSound(fluidStack), SoundCategory.BLOCKS, 0.5F,
2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
}
} else {
// Evaporate fluid // Evaporate fluid
world.playSound(null, blockPos, net.minecraft.init.SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F, world.playSound(null, blockPos, net.minecraft.init.SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F,
2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F); 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
}
// remove without updating neighbours // remove without updating neighbours
world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 2); world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 2);
@ -78,14 +100,14 @@ public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser {
// try to replant the crop // try to replant the crop
if ( itemStackDrops != null if ( itemStackDrops != null
&& blockState.getBlock() instanceof IGrowable) { && blockState.getBlock() instanceof IGrowable ) {
for (final ItemStack itemStackPlant : itemStackDrops) { for (final ItemStack itemStackPlant : itemStackDrops) {
if (itemStackPlant.getItem() instanceof IPlantable) { if (itemStackPlant.getItem() instanceof IPlantable) {
final IPlantable plantable = (IPlantable) itemStackPlant.getItem(); final IPlantable plantable = (IPlantable) itemStackPlant.getItem();
final IBlockState blockStatePlant = plantable.getPlant(world, blockPos); final IBlockState blockStatePlant = plantable.getPlant(world, blockPos);
if (WarpDriveConfig.LOGGING_COLLECTION) { if (WarpDriveConfig.LOGGING_COLLECTION) {
WarpDrive.logger.info(String.format("Drop includes %s which is plantable %s as block %s", WarpDrive.logger.info(String.format("Drop includes %s which is plantable %s as block %s",
itemStackPlant, plantable, blockStatePlant )); itemStackPlant, plantable, blockStatePlant));
} }
final BlockPos blockPosSoil = blockPos.down(); final BlockPos blockPosSoil = blockPos.down();
final IBlockState blockStateSoil = getWorld().getBlockState(blockPosSoil); final IBlockState blockStateSoil = getWorld().getBlockState(blockPosSoil);

View file

@ -430,6 +430,7 @@ public class WarpDriveConfig {
public static double MINING_LASER_MINE_SILKTOUCH_ENERGY_FACTOR = 1.5; public static double MINING_LASER_MINE_SILKTOUCH_ENERGY_FACTOR = 1.5;
public static int MINING_LASER_MINE_SILKTOUCH_DEUTERIUM_MB = 0; public static int MINING_LASER_MINE_SILKTOUCH_DEUTERIUM_MB = 0;
public static double MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR = 1.5; public static double MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR = 1.5;
public static boolean MINING_LASER_PUMP_UPGRADE_HARVEST_FLUID = false;
// Laser tree farm // Laser tree farm
// oak tree height is 8 to 11 logs + 2 leaves // oak tree height is 8 to 11 logs + 2 leaves
@ -1258,6 +1259,7 @@ public class WarpDriveConfig {
MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR = Commons.clamp(0.01D, 1000.0D, MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR = Commons.clamp(0.01D, 1000.0D,
config.get("mining_laser", "fortune_energy_factor", MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR, "Energy cost multiplier per fortune level").getDouble(MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR)); config.get("mining_laser", "fortune_energy_factor", MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR, "Energy cost multiplier per fortune level").getDouble(MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR));
} }
MINING_LASER_PUMP_UPGRADE_HARVEST_FLUID = config.get("mining_laser", "pump_upgrade_harvest_fluid", MINING_LASER_PUMP_UPGRADE_HARVEST_FLUID, "Pump upgrade will actually pump fluid source if a tank is found, instead of just evaporating it").getBoolean(false);
// Tree Farm // Tree Farm
TREE_FARM_MAX_MEDIUMS_COUNT = Commons.clamp(1, 10, TREE_FARM_MAX_MEDIUMS_COUNT = Commons.clamp(1, 10,

View file

@ -7,12 +7,16 @@ import cr0s.warpdrive.config.InvalidXmlException;
import cr0s.warpdrive.config.Loot; import cr0s.warpdrive.config.Loot;
import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.config.XmlFileManager; import cr0s.warpdrive.config.XmlFileManager;
import cr0s.warpdrive.data.JumpBlock;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.state.IBlockState; import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -21,7 +25,7 @@ import org.w3c.dom.Element;
public class Schematic extends AbstractStructure { public class Schematic extends AbstractStructure {
protected String filename; protected HashMap<String, Integer> filenames;
protected Replacement[] replacements; protected Replacement[] replacements;
protected Insertion[] insertions; protected Insertion[] insertions;
@ -29,10 +33,57 @@ public class Schematic extends AbstractStructure {
super(group, name); super(group, name);
} }
public String getRandomFileName(final Random random) {
// In loadFromXmlElement, it's already checked that there must be at least 1 "schematic" xml node
// therefore, this should not be possible
assert(!filenames.isEmpty());
int totalWeight = 0;
for (final int weight : filenames.values()) {
totalWeight += weight;
}
int result = random.nextInt(totalWeight);
for (final Map.Entry<String, Integer> entry : filenames.entrySet()) {
result -= entry.getValue();
if (result <= 0) {
return entry.getKey();
}
}
return filenames.keySet().iterator().next();
}
@Override
public boolean generate(@Nonnull final World world, @Nonnull final Random random, @Nonnull final BlockPos blockPos) {
return instantiate(random).generate(world, random, blockPos);
}
@Override
public AbstractStructureInstance instantiate(final Random random) {
return new SchematicInstance(this, random);
}
@Override @Override
public boolean loadFromXmlElement(final Element element) throws InvalidXmlException { public boolean loadFromXmlElement(final Element element) throws InvalidXmlException {
super.loadFromXmlElement(element); super.loadFromXmlElement(element);
final List<Element> fileNameList = XmlFileManager.getChildrenElementByTagName(element, "schematic");
if (fileNameList.isEmpty()) {
throw new InvalidXmlException("Must have one schematic node with file name!");
}
this.filenames = new HashMap<>(fileNameList.size());
for (final Element entry : fileNameList) {
final String filename = entry.getAttribute("filename");
int weight = 1;
try {
weight = Integer.parseInt(entry.getAttribute("weight"));
} catch (final NumberFormatException numberFormatException) {
throw new InvalidXmlException(String.format("Invalid weight in schematic %s of structure %s:%s",
filename, group, name));
}
this.filenames.put(filename, weight);
}
// load all replacement elements // load all replacement elements
final List<Element> listReplacements = XmlFileManager.getChildrenElementByTagName(element, "replacement"); final List<Element> listReplacements = XmlFileManager.getChildrenElementByTagName(element, "replacement");
replacements = new Replacement[listReplacements.size()]; replacements = new Replacement[listReplacements.size()];
@ -72,21 +123,143 @@ public class Schematic extends AbstractStructure {
return true; return true;
} }
@Override static class BlockMatcher {
public boolean generate(@Nonnull final World world, @Nonnull final Random random, @Nonnull final BlockPos blockPos) {
return instantiate(random).generate(world, random, blockPos); IBlockState blockState;
public static BlockMatcher fromXmlElement(final Element element, final GenericSet<?> caller) throws InvalidXmlException{
final String blockStateString = element.getAttribute("blockState");
final String blockNameString = element.getAttribute("block");
final String metaString = element.getAttribute("metadata");
final BlockMatcher blockMatcher;
if(blockNameString.isEmpty()){
blockMatcher = BlockMatcher.fromBlockStateString(blockStateString);
}else {
blockMatcher = BlockMatcher.fromBlockAndMeta(blockNameString, metaString);
}
if (blockMatcher == null){
WarpDrive.logger.warn(String.format("Invalid matching scheme %s found for %s",
blockStateString.isEmpty() ? blockNameString + "@" + metaString : blockStateString,
caller.getFullName()));
}
return blockMatcher;
}
public static BlockMatcher fromBlockStateString(final String blockStateString) {
// TODO: allow different data input type for meta: range (e.g. 2-13), comma separated list (e.g. 1,2,3..), multiple property (e.g. variant=oak,half=bottom)
final BlockMatcher result = new BlockMatcher();
String blockNameString = "";
String metaString = "*";
if (blockStateString.contains("@")) {// (with metadata)
final String[] blockStateParts = blockStateString.split("@");
blockNameString = blockStateParts[0].trim();
metaString = blockStateParts[1].trim();
} else {// (without metadata)
blockNameString = blockStateString;
}
final Block block = Block.getBlockFromName(blockNameString);
if (block == null) {
WarpDrive.logger.warn(String.format("Ignoring invalid block with name %s.", blockNameString));
return null;
}
if (metaString.equals("*")) {// (no metadata or explicit wildcard)
result.blockState = block.getDefaultState();
} else if (metaString.contains("=")) {// (in string format (e.g. "color=red"))
final String[] metaParts = metaString.split("=");
final String propertyKey = metaParts[0].trim();
final String propertyValue = metaParts[1].trim();
final IProperty<? extends Comparable<?>> property = block.getBlockState().getProperty(propertyKey);
if (property == null) {
WarpDrive.logger.warn(String.format("Found invalid block property %s for block %s", propertyKey, blockNameString));
return null;
}
/*
Note: the below code was attempted but not succeeded.
IBlockState#WithProperty require (T extends Comparable<?> property , V extend T value).
It is impossible to ensure V extend T because value returned from parseValue is itself <? extends Comparable<?>>
Therefore, T may only be determined at runtime, and V extend T may not be enforced.
Optional<? extends Comparable<?>> parsedValue = property.parseValue(propertyValue);
if (result.isPresent()){
result.blockState = block.getDefaultState().withProperty(property, parsedValue.get());
}else{
WarpDrive.logger.warn(String.format("Value %s is not allowed for property %s for block %s", propertyValue, propertyKey, state));
}
*/
boolean found = false;
for (int i = 0; i < 16; i++) {// not efficient, but this would work (and since it's load time, it should not be a problem)
final IBlockState tmpState = block.getStateFromMeta(i);
if (tmpState.getProperties().get(property).equals(property.parseValue(propertyValue).orNull())) {
result.blockState = tmpState;
found = true;
break;
}
}
if (!found) {
WarpDrive.logger.warn(String.format("Failed to find metadata value that represent block property %s for block %s", propertyKey, blockNameString));
return null;
}
} else {// (metadata)
final int metadata;
try {
metadata = Integer.parseInt(metaString);
} catch (final NumberFormatException numberFormatException) {
WarpDrive.logger.warn(String.format("%s is not a valid number for metadata of block %s", metaString, blockNameString));
return null;
}
result.blockState = block.getStateFromMeta(metadata);
}
return result;
}
public static BlockMatcher fromBlockAndMeta(final String blockName, final String metaString) {
final BlockMatcher result = new BlockMatcher();
final Block block = Block.getBlockFromName(blockName);
if (block == null) {
WarpDrive.logger.warn(String.format("Found invalid block %s", blockName));
return null;
}
if (!metaString.isEmpty()) {
try {
final int meta = Integer.parseInt(metaString);
result.blockState = block.getStateFromMeta(meta);
} catch (final NumberFormatException numberFormatException) {
WarpDrive.logger.warn(String.format("%s is not a valid number for meta of block %s", metaString, blockName));
return null;
}
}
return result;
}
public boolean isMatching(final IBlockState blockStateIn) {
return blockStateIn.equals(blockState);
}
public boolean isMatching(final JumpBlock jumpBlockIn) {
return blockState != null && jumpBlockIn != null && jumpBlockIn.blockMeta == blockState.getBlock().getMetaFromState(blockState);
} }
@Override @Override
public AbstractStructureInstance instantiate(final Random random) { public String toString() {
return new SchematicInstance(this, random); return "BlockMatcher{" + (blockState == null ? "null" : blockState.toString()) + "}";
}
} }
public class Replacement extends GenericSet<Filler> { public static class Replacement extends GenericSet<Filler> {
private final String parentFullName; private final String parentFullName;
protected Block block; protected BlockMatcher matcher;
protected IBlockState blockState;
public Replacement(final String parentFullName, final String name) { public Replacement(final String parentFullName, final String name) {
super(null, name, Filler.DEFAULT, "filler"); super(null, name, Filler.DEFAULT, "filler");
@ -95,13 +268,15 @@ public class Schematic extends AbstractStructure {
@Override @Override
public boolean loadFromXmlElement(final Element element) throws InvalidXmlException { public boolean loadFromXmlElement(final Element element) throws InvalidXmlException {
if (WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.info(String.format(" + found replacement %s",
element.getAttribute("name")));
}
super.loadFromXmlElement(element); super.loadFromXmlElement(element);
matcher = BlockMatcher.fromXmlElement(element, this);
if ( WarpDriveConfig.LOGGING_WORLD_GENERATION
&& matcher != null ) {
WarpDrive.logger.info(String.format(" + found replacement for block %s", matcher));
}
// resolve static imports // resolve static imports
for (final String importGroupName : getImportGroupNames()) { for (final String importGroupName : getImportGroupNames()) {
final GenericSet<Filler> fillerSet = WarpDriveConfig.FillerManager.getGenericSet(importGroupName); final GenericSet<Filler> fillerSet = WarpDriveConfig.FillerManager.getGenericSet(importGroupName);
@ -126,8 +301,7 @@ public class Schematic extends AbstractStructure {
public Replacement instantiate(final Random random) { public Replacement instantiate(final Random random) {
final Replacement replacement = new Replacement(parentFullName, name); final Replacement replacement = new Replacement(parentFullName, name);
replacement.block = block; replacement.matcher = this.matcher;
replacement.blockState = blockState;
try { try {
replacement.loadFrom(this); replacement.loadFrom(this);
for (final String importGroup : getImportGroups()) { for (final String importGroup : getImportGroups()) {
@ -159,18 +333,21 @@ public class Schematic extends AbstractStructure {
} }
public boolean isMatching(final IBlockState blockStateIn) { public boolean isMatching(final IBlockState blockStateIn) {
return (block != null && block == blockStateIn.getBlock()) return matcher != null && matcher.isMatching(blockStateIn);
|| blockState.equals(blockStateIn); }
public boolean isMatching(final JumpBlock jumpBlockIn) {
return matcher != null && matcher.isMatching(jumpBlockIn);
} }
} }
public class Insertion extends GenericSet<Loot> { public static class Insertion extends GenericSet<Loot> {
private final String parentFullName; private final String parentFullName;
protected BlockMatcher matcher;
private int minQuantity; private int minQuantity;
private int maxQuantity; private int maxQuantity;
protected Block block; private int maxRetries;
protected IBlockState blockState;
public Insertion(final String parentFullName, final String name) { public Insertion(final String parentFullName, final String name) {
super(null, name, Loot.DEFAULT, "loot"); super(null, name, Loot.DEFAULT, "loot");
@ -179,13 +356,15 @@ public class Schematic extends AbstractStructure {
@Override @Override
public boolean loadFromXmlElement(final Element element) throws InvalidXmlException { public boolean loadFromXmlElement(final Element element) throws InvalidXmlException {
if (WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.info(String.format(" + found insertion %s",
element.getAttribute("name")));
}
super.loadFromXmlElement(element); super.loadFromXmlElement(element);
matcher = BlockMatcher.fromXmlElement(element, this);
if ( WarpDriveConfig.LOGGING_WORLD_GENERATION
&& matcher != null ) {
WarpDrive.logger.info(String.format(" + found insertion for block %s", matcher));
}
// get optional minQuantity attribute, defaulting to 0 // get optional minQuantity attribute, defaulting to 0
minQuantity = 0; minQuantity = 0;
final String stringMinQuantity = element.getAttribute("minQuantity"); final String stringMinQuantity = element.getAttribute("minQuantity");
@ -200,6 +379,13 @@ public class Schematic extends AbstractStructure {
maxQuantity = Integer.parseInt(stringMaxQuantity); maxQuantity = Integer.parseInt(stringMaxQuantity);
} }
// get optional maxTries attribute, defaulting to 3 according to WorldGenStructure#fillInventoryWithLoot
maxRetries = 3;
final String stringMaxTries = element.getAttribute("maxRetries");
if (!stringMaxTries.isEmpty()) {
maxRetries = Integer.parseInt(stringMaxTries);
}
// resolve static imports // resolve static imports
for (final String importGroupName : getImportGroupNames()) { for (final String importGroupName : getImportGroupNames()) {
final GenericSet<Loot> lootSet = WarpDriveConfig.LootManager.getGenericSet(importGroupName); final GenericSet<Loot> lootSet = WarpDriveConfig.LootManager.getGenericSet(importGroupName);
@ -226,8 +412,8 @@ public class Schematic extends AbstractStructure {
final Insertion insertion = new Insertion(parentFullName, name); final Insertion insertion = new Insertion(parentFullName, name);
insertion.minQuantity = minQuantity; insertion.minQuantity = minQuantity;
insertion.maxQuantity = maxQuantity; insertion.maxQuantity = maxQuantity;
insertion.block = block; insertion.maxRetries = maxRetries;
insertion.blockState = blockState; insertion.matcher = matcher;
try { try {
insertion.loadFrom(this); insertion.loadFrom(this);
for (final String importGroup : getImportGroups()) { for (final String importGroup : getImportGroups()) {
@ -238,7 +424,7 @@ public class Schematic extends AbstractStructure {
continue; continue;
} }
if (WarpDriveConfig.LOGGING_WORLD_GENERATION) { if (WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.info(String.format("Filling %s:%s with %s:%s", WarpDrive.logger.info(String.format("Inserting %s:%s with %s:%s",
parentFullName, name, importGroup, lootSet.getName())); parentFullName, name, importGroup, lootSet.getName()));
} }
insertion.loadFrom(lootSet); insertion.loadFrom(lootSet);
@ -258,9 +444,24 @@ public class Schematic extends AbstractStructure {
return insertion; return insertion;
} }
public int getMinQuantity() {
return minQuantity;
}
public int getMaxQuantity() {
return maxQuantity;
}
public int getMaxRetries() {
return maxRetries;
}
public boolean isMatching(final IBlockState blockStateIn) { public boolean isMatching(final IBlockState blockStateIn) {
return (block != null && block == blockStateIn.getBlock()) return matcher != null && matcher.isMatching(blockStateIn);
|| blockState.equals(blockStateIn); }
public boolean isMatching(final JumpBlock jumpBlockIn) {
return matcher != null && matcher.isMatching(jumpBlockIn);
} }
} }
} }

View file

@ -3,9 +3,11 @@ package cr0s.warpdrive.config.structures;
import cr0s.warpdrive.Commons; import cr0s.warpdrive.Commons;
import cr0s.warpdrive.WarpDrive; import cr0s.warpdrive.WarpDrive;
import cr0s.warpdrive.api.WarpDriveText; import cr0s.warpdrive.api.WarpDriveText;
import cr0s.warpdrive.config.Filler;
import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.config.structures.Schematic.Insertion; import cr0s.warpdrive.config.structures.Schematic.Insertion;
import cr0s.warpdrive.config.structures.Schematic.Replacement; import cr0s.warpdrive.config.structures.Schematic.Replacement;
import cr0s.warpdrive.data.JumpBlock;
import cr0s.warpdrive.data.JumpShip; import cr0s.warpdrive.data.JumpShip;
import cr0s.warpdrive.world.WorldGenStructure; import cr0s.warpdrive.world.WorldGenStructure;
@ -26,7 +28,7 @@ public class SchematicInstance extends AbstractStructureInstance {
super(schematic, random); super(schematic, random);
final WarpDriveText reason = new WarpDriveText(); final WarpDriveText reason = new WarpDriveText();
jumpShip = JumpShip.createFromFile(schematic.filename, reason); jumpShip = JumpShip.createFromFile(schematic.getRandomFileName(random), reason);
if (jumpShip == null) { if (jumpShip == null) {
WarpDrive.logger.error(String.format("Failed to instantiate schematic structure %s due to %s", WarpDrive.logger.error(String.format("Failed to instantiate schematic structure %s due to %s",
schematic.getFullName(), reason)); schematic.getFullName(), reason));
@ -45,7 +47,7 @@ public class SchematicInstance extends AbstractStructureInstance {
insertions = new Insertion[schematic.insertions.length]; insertions = new Insertion[schematic.insertions.length];
int insertionIndexOut = 0; int insertionIndexOut = 0;
for (int insertionIndexIn = 0; insertionIndexIn < schematic.replacements.length; insertionIndexIn++) { for (int insertionIndexIn = 0; insertionIndexIn < schematic.insertions.length; insertionIndexIn++) {
final Insertion insertion = schematic.insertions[insertionIndexIn].instantiate(random); final Insertion insertion = schematic.insertions[insertionIndexIn].instantiate(random);
if (insertion != null) { if (insertion != null) {
insertions[insertionIndexOut] = insertion; insertions[insertionIndexOut] = insertion;
@ -72,11 +74,26 @@ public class SchematicInstance extends AbstractStructureInstance {
return false; return false;
} }
for (final Replacement replacement : this.replacements) {
// Pick a common replacement block to get an homogenous result
final Filler filler = replacement.getRandomUnit(random);
// loop through the structure and see if a block need to be replaced
for (int i = 0; i < jumpShip.jumpBlocks.length; i++) {
if (replacement.isMatching(jumpShip.jumpBlocks[i])) {
jumpShip.jumpBlocks[i] = new JumpBlock(filler,
jumpShip.jumpBlocks[i].x,
jumpShip.jumpBlocks[i].y,
jumpShip.jumpBlocks[i].z );
}
}
}
final int y2 = Commons.clamp( final int y2 = Commons.clamp(
WarpDriveConfig.SPACE_GENERATOR_Y_MIN_BORDER + (jumpShip.core.getY() - jumpShip.minY), WarpDriveConfig.SPACE_GENERATOR_Y_MIN_BORDER + (jumpShip.core.getY() - jumpShip.minY),
WarpDriveConfig.SPACE_GENERATOR_Y_MAX_BORDER - (jumpShip.maxY - jumpShip.core.getY()), WarpDriveConfig.SPACE_GENERATOR_Y_MAX_BORDER - (jumpShip.maxY - jumpShip.core.getY()),
blockPos.getY() ); blockPos.getY() );
new WorldGenStructure(random.nextFloat() < 0.2F, random).deployShip(world, jumpShip, blockPos.getX(), y2, blockPos.getZ(), (byte) 0); new WorldGenStructure(random.nextFloat() < 0.2F, random).deployShip(world, jumpShip, blockPos.getX(), y2, blockPos.getZ(), (byte) 0, insertions);
return true; return true;
} }
} }

View file

@ -127,8 +127,11 @@ public class InventoryWrapper {
} else if (inventory instanceof IItemHandler) { } else if (inventory instanceof IItemHandler) {
qtyLeft = addToInventory(itemStack, (IItemHandler) inventory); qtyLeft = addToInventory(itemStack, (IItemHandler) inventory);
} else { } else {
WarpDrive.logger.error(String.format("Invalid inventory type %s, please report to mod author: %s", if (Commons.throttleMe("addToInventories")){
Commons.format(world, blockPos), inventory )); WarpDrive.logger.error(String.format("Invalid inventory type %s of class %s at %s, please report to mod author",
inventory, inventory.getClass(), Commons.format(world, blockPos) ));
break;
}
} }
if (qtyLeft > 0) { if (qtyLeft > 0) {
if (itemStackLeft == itemStack) { if (itemStackLeft == itemStack) {
@ -141,7 +144,7 @@ public class InventoryWrapper {
} }
if (qtyLeft > 0) { if (qtyLeft > 0) {
if (WarpDriveConfig.LOGGING_COLLECTION) { if (WarpDriveConfig.LOGGING_COLLECTION) {
WarpDrive.logger.info(String.format("Overflow detected %s", WarpDrive.logger.info(String.format("Overflow detected at %s",
Commons.format(world, blockPos) )); Commons.format(world, blockPos) ));
} }
overflow = true; overflow = true;

View file

@ -0,0 +1,132 @@
package cr0s.warpdrive.data;
import cr0s.warpdrive.Commons;
import cr0s.warpdrive.WarpDrive;
import cr0s.warpdrive.config.WarpDriveConfig;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockPos.MutableBlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
// this is almost a copy of InventoryWrapper, but for fluids.
public class TankWrapper {
// WarpDrive methods
public static boolean isTank(final TileEntity tileEntity, final EnumFacing facing) {
boolean isTank = false;
if (tileEntity instanceof IFluidTank) {
isTank = true;
}
if (!isTank
&& tileEntity.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, facing)) {
isTank = true;
}
return isTank;
}
public static Object getTank(final TileEntity tileEntity, final EnumFacing facing) {
if (tileEntity instanceof IFluidTank) {
return tileEntity;
}
if (tileEntity != null) {
return tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, facing);
}
return null;
}
public static @Nonnull Collection<Object> getConnectedTanks(final World world, final BlockPos blockPos) {
final Collection<Object> result = new ArrayList<>(6);
final Collection<IFluidHandler> resultCapabilities = new ArrayList<>(6);
final MutableBlockPos mutableBlockPos = new MutableBlockPos();
for (final EnumFacing side : EnumFacing.VALUES) {
mutableBlockPos.setPos(blockPos.getX() + side.getXOffset(),
blockPos.getY() + side.getYOffset(),
blockPos.getZ() + side.getZOffset());
final TileEntity tileEntity = world.getTileEntity(mutableBlockPos);
if (tileEntity instanceof IFluidTank) {
result.add(tileEntity);
} else if (tileEntity != null) {
final IFluidHandler fluidHandler = tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side);
if (fluidHandler != null) {
resultCapabilities.add(fluidHandler);
}
}
}
result.addAll(resultCapabilities);
return result;
}
public static boolean addToConnectedTanks(final World world, final BlockPos blockPos, final FluidStack fluidStack) {
final List<FluidStack> fluidStacks = new ArrayList<>(1);
fluidStacks.add(fluidStack);
return addToConnectedTanks(world, blockPos, fluidStacks);
}
public static boolean addToConnectedTanks(final World world, final BlockPos blockPos, final List<FluidStack> fluidStacks) {
final Collection<Object> inventories = getConnectedTanks(world, blockPos);
return addToTanks(world, blockPos, inventories, fluidStacks);
}
public static boolean addToTanks(final World world, final BlockPos blockPos,
final Collection<Object> tanks, final FluidStack fluidStack) {
final List<FluidStack> fluidStacks = new ArrayList<>(1);
fluidStacks.add(fluidStack);
return addToTanks(world, blockPos, tanks, fluidStacks);
}
public static boolean addToTanks(final World world, final BlockPos blockPos,
final Collection<Object> tanks, final List<FluidStack> fluidStacks) {
boolean overflow = false;
if (fluidStacks != null) {
for (final FluidStack fluidStack : fluidStacks) {
int qtyFilled = 0;
for (final Object tank : tanks) {
if (tank instanceof IFluidTank) {
qtyFilled = ((IFluidTank) tank).fill(fluidStack, true);
} else if (tank instanceof IFluidHandler) {
qtyFilled = ((IFluidHandler) tank).fill(fluidStack, true);
} else {
if (Commons.throttleMe("addToTanks")){
WarpDrive.logger.error(String.format("Invalid fluid tank type %s of class %s at %s, please report to mod author",
tank, tank.getClass(), Commons.format(world, blockPos) ));
break;
}
}
if (fluidStack.amount > qtyFilled) {
fluidStack.amount -= qtyFilled;
} else {
break;
}
}
if (fluidStack.amount > qtyFilled) {
if (WarpDriveConfig.LOGGING_COLLECTION) {
WarpDrive.logger.info(String.format("Tank overflow detected at %s",
Commons.format(world, blockPos)));
}
overflow = true;
}
}
}
return overflow;
}
}

View file

@ -8,6 +8,7 @@ import cr0s.warpdrive.config.Filler;
import cr0s.warpdrive.config.GenericSet; import cr0s.warpdrive.config.GenericSet;
import cr0s.warpdrive.config.Loot; import cr0s.warpdrive.config.Loot;
import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.config.structures.Schematic.Insertion;
import cr0s.warpdrive.data.InventoryWrapper; import cr0s.warpdrive.data.InventoryWrapper;
import cr0s.warpdrive.data.JumpBlock; import cr0s.warpdrive.data.JumpBlock;
import cr0s.warpdrive.data.JumpShip; import cr0s.warpdrive.data.JumpShip;
@ -18,6 +19,8 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.Random; import java.util.Random;
import javax.annotation.Nullable;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.init.Blocks; import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -200,29 +203,6 @@ public class WorldGenStructure {
public void fillInventoryWithLoot(final World world, final Random rand, final BlockPos blockPos, final String group, public void fillInventoryWithLoot(final World world, final Random rand, final BlockPos blockPos, final String group,
final int quantityMin, final int quantityRandom1, final int quantityRandom2, final int quantityMin, final int quantityRandom1, final int quantityRandom2,
final int maxRetries) { final int maxRetries) {
// validate context
final TileEntity tileEntity = world.getTileEntity(blockPos);
final Object inventory = InventoryWrapper.getInventory(tileEntity, null);
if (inventory == null) {
WarpDrive.logger.warn(String.format("Unable to fill inventory with LootSet %s %s: %s has no inventory",
group,
Commons.format(world, blockPos),
tileEntity ));
return;
}
if (tileEntity.isInvalid()) {
WarpDrive.logger.warn(String.format("Unable to fill inventory with LootSet %s %s: %s is Invalid",
group,
Commons.format(world, blockPos),
tileEntity ));
return;
}
// evaluate parameters: quantity of loot, actual loot set
final int size = InventoryWrapper.getSize(inventory);
final int countLoots = Math.min(quantityMin + rand.nextInt(quantityRandom1) + rand.nextInt(quantityRandom2), size);
final GenericSet<Loot> lootSet = WarpDriveConfig.LootManager.getRandomSetFromGroup(rand, group); final GenericSet<Loot> lootSet = WarpDriveConfig.LootManager.getRandomSetFromGroup(rand, group);
if (lootSet == null) { if (lootSet == null) {
@ -233,6 +213,36 @@ public class WorldGenStructure {
return; return;
} }
fillInventoryWithLoot(world, rand, blockPos, lootSet, quantityMin, quantityRandom1, quantityRandom2, maxRetries);
}
public void fillInventoryWithLoot(final World world, final Random rand, final BlockPos blockPos, final GenericSet<Loot> lootSet,
final int quantityMin, final int quantityRandom1, final int quantityRandom2,
final int maxRetries) {
// validate context
final TileEntity tileEntity = world.getTileEntity(blockPos);
final Object inventory = InventoryWrapper.getInventory(tileEntity, null);
if (inventory == null) {
WarpDrive.logger.warn(String.format("Unable to fill inventory with LootSet %s %s: %s has no inventory",
lootSet.getFullName(),
Commons.format(world, blockPos),
tileEntity ));
return;
}
if (tileEntity.isInvalid()) {
WarpDrive.logger.warn(String.format("Unable to fill inventory with LootSet %s %s: %s is Invalid",
lootSet.getFullName(),
Commons.format(world, blockPos),
tileEntity ));
return;
}
// evaluate parameters: quantity of loot, actual loot set
final int size = InventoryWrapper.getSize(inventory);
final int countLoots = Math.min(quantityMin + rand.nextInt(quantityRandom1) + rand.nextInt(quantityRandom2), size);
// shuffle slot indexes to reduce random calls and loops later on // shuffle slot indexes to reduce random calls and loops later on
final ArrayList<Integer> indexSlots = new ArrayList<>(size); final ArrayList<Integer> indexSlots = new ArrayList<>(size);
for (int indexSlot = 0; indexSlot < size; indexSlot++) { for (int indexSlot = 0; indexSlot < size; indexSlot++) {
@ -243,6 +253,14 @@ public class WorldGenStructure {
} }
Collections.shuffle(indexSlots); Collections.shuffle(indexSlots);
if (WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.debug(String.format("About to add %d loots from set %s into inventory %s at %s with max retries %d for each",
countLoots,
lootSet.getFullName(),
inventory,
Commons.format(world, blockPos),
maxRetries));
}
// for all loots to add // for all loots to add
ItemStack itemStackLoot; ItemStack itemStackLoot;
boolean isAdded; boolean isAdded;
@ -267,23 +285,15 @@ public class WorldGenStructure {
try { try {
InventoryWrapper.insertItem(inventory, indexSlot, itemStackLoot); InventoryWrapper.insertItem(inventory, indexSlot, itemStackLoot);
if (WarpDriveConfig.LOGGING_WORLD_GENERATION) { if (WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.debug(String.format("Filling inventory with LootSet %s %s: loot %s from %s in slot %d of inventory %s", WarpDrive.logger.debug(String.format(" + placed %s into slot %d",
group,
Commons.format(world, blockPos),
Commons.format(itemStackLoot), Commons.format(itemStackLoot),
lootSet.getFullName(), indexSlot));
indexSlot,
inventory ));
} }
} catch (final Exception exception) { } catch (final Exception exception) {
exception.printStackTrace(WarpDrive.printStreamError); exception.printStackTrace(WarpDrive.printStreamError);
WarpDrive.logger.error(String.format("Exception while filling inventory with LootSet %s %s: loot %s from %s in slot %d of inventory %s reported %s", WarpDrive.logger.error(String.format(" ! Exception while placing %s into slot %d: %s",
group,
Commons.format(world, blockPos),
Commons.format(itemStackLoot), Commons.format(itemStackLoot),
lootSet.getFullName(),
indexSlot, indexSlot,
inventory,
exception.getMessage() )); exception.getMessage() ));
} }
break; break;
@ -313,6 +323,10 @@ public class WorldGenStructure {
} }
public void deployShip(final World world, final JumpShip jumpShip, final int targetX, final int targetY, final int targetZ, final byte rotationSteps) { public void deployShip(final World world, final JumpShip jumpShip, final int targetX, final int targetY, final int targetZ, final byte rotationSteps) {
deployShip(world, jumpShip, targetX, targetY, targetZ, rotationSteps, null);
}
public void deployShip(final World world, final JumpShip jumpShip, final int targetX, final int targetY, final int targetZ, final byte rotationSteps, @Nullable final Insertion[] insertions) {
final Transformation transformation = new Transformation(jumpShip, world, final Transformation transformation = new Transformation(jumpShip, world,
targetX - jumpShip.core.getX(), targetX - jumpShip.core.getX(),
@ -336,7 +350,6 @@ public class WorldGenStructure {
WarpDrive.logger.info(String.format("At index %d, skipping anchor block %s", index, jumpBlock.block)); WarpDrive.logger.info(String.format("At index %d, skipping anchor block %s", index, jumpBlock.block));
} }
} else { } else {
index++;
if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) { if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.info(String.format("At index %d, deploying %s ", WarpDrive.logger.info(String.format("At index %d, deploying %s ",
index, jumpBlock)); index, jumpBlock));
@ -345,6 +358,23 @@ public class WorldGenStructure {
final Block blockAtTarget = world.getBlockState(targetLocation).getBlock(); final Block blockAtTarget = world.getBlockState(targetLocation).getBlock();
if (blockAtTarget == Blocks.AIR || Dictionary.BLOCKS_EXPANDABLE.contains(blockAtTarget)) { if (blockAtTarget == Blocks.AIR || Dictionary.BLOCKS_EXPANDABLE.contains(blockAtTarget)) {
jumpBlock.deploy(null, world, transformation); jumpBlock.deploy(null, world, transformation);
// Apply insertions as defined
if (insertions != null) {
for(final Insertion insertion : insertions){
if (insertion.isMatching(jumpBlock)){
final BlockPos deployedLocation = transformation.apply(jumpBlock.x, jumpBlock.y, jumpBlock.z);
fillInventoryWithLoot(world, rand, deployedLocation,
insertion,
insertion.getMinQuantity(),
insertion.getMaxQuantity(),
0,
insertion.getMaxRetries() );
}
}
}
} else { } else {
if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) { if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) {
WarpDrive.logger.info(String.format("Deployment collision detected %s with %s during world generation, skipping this block...", WarpDrive.logger.info(String.format("Deployment collision detected %s with %s during world generation, skipping this block...",

View file

@ -107,6 +107,7 @@
<xs:attribute name="blockState" type="xs:string" use="required" /> <xs:attribute name="blockState" type="xs:string" use="required" />
<xs:attribute name="minQuantity" type="xs:string" use="optional" /> <xs:attribute name="minQuantity" type="xs:string" use="optional" />
<xs:attribute name="maxQuantity" type="xs:string" use="optional" /> <xs:attribute name="maxQuantity" type="xs:string" use="optional" />
<xs:attribute name="maxRetries" type="xs:string" use="optional" />
<xs:attribute name="mods" type="xs:string" use="optional" /> <xs:attribute name="mods" type="xs:string" use="optional" />
</xs:complexType> </xs:complexType>