From b691306a71abca73f66f8591235d600a436ea8e6 Mon Sep 17 00:00:00 2001 From: n507 <123906343+n-507@users.noreply.github.com> Date: Tue, 31 Jan 2023 13:22:41 -0500 Subject: [PATCH] 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. --- .../collection/TileEntityAbstractMiner.java | 38 ++- .../warpdrive/config/WarpDriveConfig.java | 2 + .../config/structures/Schematic.java | 269 +++++++++++++++--- .../config/structures/SchematicInstance.java | 23 +- .../cr0s/warpdrive/data/InventoryWrapper.java | 9 +- .../java/cr0s/warpdrive/data/TankWrapper.java | 132 +++++++++ .../warpdrive/world/WorldGenStructure.java | 100 ++++--- src/main/resources/config/WarpDrive.xsd | 1 + 8 files changed, 491 insertions(+), 83 deletions(-) create mode 100644 src/main/java/cr0s/warpdrive/data/TankWrapper.java diff --git a/src/main/java/cr0s/warpdrive/block/collection/TileEntityAbstractMiner.java b/src/main/java/cr0s/warpdrive/block/collection/TileEntityAbstractMiner.java index a22a1d2c..aaef4d0c 100644 --- a/src/main/java/cr0s/warpdrive/block/collection/TileEntityAbstractMiner.java +++ b/src/main/java/cr0s/warpdrive/block/collection/TileEntityAbstractMiner.java @@ -7,9 +7,11 @@ import cr0s.warpdrive.block.TileEntityAbstractLaser; import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.data.FluidWrapper; import cr0s.warpdrive.data.InventoryWrapper; +import cr0s.warpdrive.data.TankWrapper; import cr0s.warpdrive.data.Vector3; import java.lang.reflect.InvocationTargetException; +import java.util.Collection; import java.util.List; import javax.annotation.Nonnull; @@ -27,8 +29,9 @@ import net.minecraft.util.NonNullList; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; import net.minecraft.world.WorldServer; - import net.minecraftforge.common.IPlantable; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser { @@ -55,11 +58,30 @@ public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser { if (blockState.getBlock().isAir(blockState, world, blockPos)) { return; } - if (FluidWrapper.isFluid(blockState)) { - // Evaporate fluid - 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); - + + 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 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 + 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); + } // remove without updating neighbours world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 2); @@ -78,14 +100,14 @@ public abstract class TileEntityAbstractMiner extends TileEntityAbstractLaser { // try to replant the crop if ( itemStackDrops != null - && blockState.getBlock() instanceof IGrowable) { + && blockState.getBlock() instanceof IGrowable ) { for (final ItemStack itemStackPlant : itemStackDrops) { if (itemStackPlant.getItem() instanceof IPlantable) { final IPlantable plantable = (IPlantable) itemStackPlant.getItem(); final IBlockState blockStatePlant = plantable.getPlant(world, blockPos); if (WarpDriveConfig.LOGGING_COLLECTION) { 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 IBlockState blockStateSoil = getWorld().getBlockState(blockPosSoil); diff --git a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java index d1539a0b..0985449a 100644 --- a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java +++ b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java @@ -430,6 +430,7 @@ public class WarpDriveConfig { public static double MINING_LASER_MINE_SILKTOUCH_ENERGY_FACTOR = 1.5; public static int MINING_LASER_MINE_SILKTOUCH_DEUTERIUM_MB = 0; public static double MINING_LASER_MINE_FORTUNE_ENERGY_FACTOR = 1.5; + public static boolean MINING_LASER_PUMP_UPGRADE_HARVEST_FLUID = false; // Laser tree farm // 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, 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_MAX_MEDIUMS_COUNT = Commons.clamp(1, 10, diff --git a/src/main/java/cr0s/warpdrive/config/structures/Schematic.java b/src/main/java/cr0s/warpdrive/config/structures/Schematic.java index c5aabc02..b41b0951 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/Schematic.java +++ b/src/main/java/cr0s/warpdrive/config/structures/Schematic.java @@ -7,12 +7,16 @@ import cr0s.warpdrive.config.InvalidXmlException; import cr0s.warpdrive.config.Loot; import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.XmlFileManager; +import cr0s.warpdrive.data.JumpBlock; import javax.annotation.Nonnull; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Random; import net.minecraft.block.Block; +import net.minecraft.block.properties.IProperty; import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -21,7 +25,7 @@ import org.w3c.dom.Element; public class Schematic extends AbstractStructure { - protected String filename; + protected HashMap filenames; protected Replacement[] replacements; protected Insertion[] insertions; @@ -29,10 +33,57 @@ public class Schematic extends AbstractStructure { 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 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 public boolean loadFromXmlElement(final Element element) throws InvalidXmlException { super.loadFromXmlElement(element); + final List 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 final List listReplacements = XmlFileManager.getChildrenElementByTagName(element, "replacement"); replacements = new Replacement[listReplacements.size()]; @@ -72,21 +123,143 @@ public class Schematic extends AbstractStructure { return true; } - @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); + static class BlockMatcher { + + 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> 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 > + Therefore, T may only be determined at runtime, and V extend T may not be enforced. + + Optional> 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 + public String toString() { + return "BlockMatcher{" + (blockState == null ? "null" : blockState.toString()) + "}"; + } } - public class Replacement extends GenericSet { + public static class Replacement extends GenericSet { private final String parentFullName; - protected Block block; - protected IBlockState blockState; + protected BlockMatcher matcher; public Replacement(final String parentFullName, final String name) { super(null, name, Filler.DEFAULT, "filler"); @@ -95,13 +268,15 @@ public class Schematic extends AbstractStructure { @Override 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); + 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 for (final String importGroupName : getImportGroupNames()) { final GenericSet fillerSet = WarpDriveConfig.FillerManager.getGenericSet(importGroupName); @@ -126,8 +301,7 @@ public class Schematic extends AbstractStructure { public Replacement instantiate(final Random random) { final Replacement replacement = new Replacement(parentFullName, name); - replacement.block = block; - replacement.blockState = blockState; + replacement.matcher = this.matcher; try { replacement.loadFrom(this); for (final String importGroup : getImportGroups()) { @@ -159,18 +333,21 @@ public class Schematic extends AbstractStructure { } public boolean isMatching(final IBlockState blockStateIn) { - return (block != null && block == blockStateIn.getBlock()) - || blockState.equals(blockStateIn); + return matcher != null && matcher.isMatching(blockStateIn); + } + + public boolean isMatching(final JumpBlock jumpBlockIn) { + return matcher != null && matcher.isMatching(jumpBlockIn); } } - public class Insertion extends GenericSet { + public static class Insertion extends GenericSet { private final String parentFullName; + protected BlockMatcher matcher; private int minQuantity; private int maxQuantity; - protected Block block; - protected IBlockState blockState; + private int maxRetries; public Insertion(final String parentFullName, final String name) { super(null, name, Loot.DEFAULT, "loot"); @@ -179,13 +356,15 @@ public class Schematic extends AbstractStructure { @Override 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); + 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 minQuantity = 0; final String stringMinQuantity = element.getAttribute("minQuantity"); @@ -200,6 +379,13 @@ public class Schematic extends AbstractStructure { 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 for (final String importGroupName : getImportGroupNames()) { final GenericSet lootSet = WarpDriveConfig.LootManager.getGenericSet(importGroupName); @@ -226,8 +412,8 @@ public class Schematic extends AbstractStructure { final Insertion insertion = new Insertion(parentFullName, name); insertion.minQuantity = minQuantity; insertion.maxQuantity = maxQuantity; - insertion.block = block; - insertion.blockState = blockState; + insertion.maxRetries = maxRetries; + insertion.matcher = matcher; try { insertion.loadFrom(this); for (final String importGroup : getImportGroups()) { @@ -238,7 +424,7 @@ public class Schematic extends AbstractStructure { continue; } 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())); } insertion.loadFrom(lootSet); @@ -258,9 +444,24 @@ public class Schematic extends AbstractStructure { return insertion; } + public int getMinQuantity() { + return minQuantity; + } + + public int getMaxQuantity() { + return maxQuantity; + } + + public int getMaxRetries() { + return maxRetries; + } + public boolean isMatching(final IBlockState blockStateIn) { - return (block != null && block == blockStateIn.getBlock()) - || blockState.equals(blockStateIn); + return matcher != null && matcher.isMatching(blockStateIn); + } + + public boolean isMatching(final JumpBlock jumpBlockIn) { + return matcher != null && matcher.isMatching(jumpBlockIn); } } } diff --git a/src/main/java/cr0s/warpdrive/config/structures/SchematicInstance.java b/src/main/java/cr0s/warpdrive/config/structures/SchematicInstance.java index 10c49bee..93111812 100644 --- a/src/main/java/cr0s/warpdrive/config/structures/SchematicInstance.java +++ b/src/main/java/cr0s/warpdrive/config/structures/SchematicInstance.java @@ -3,9 +3,11 @@ package cr0s.warpdrive.config.structures; import cr0s.warpdrive.Commons; import cr0s.warpdrive.WarpDrive; import cr0s.warpdrive.api.WarpDriveText; +import cr0s.warpdrive.config.Filler; import cr0s.warpdrive.config.WarpDriveConfig; import cr0s.warpdrive.config.structures.Schematic.Insertion; import cr0s.warpdrive.config.structures.Schematic.Replacement; +import cr0s.warpdrive.data.JumpBlock; import cr0s.warpdrive.data.JumpShip; import cr0s.warpdrive.world.WorldGenStructure; @@ -26,7 +28,7 @@ public class SchematicInstance extends AbstractStructureInstance { super(schematic, random); final WarpDriveText reason = new WarpDriveText(); - jumpShip = JumpShip.createFromFile(schematic.filename, reason); + jumpShip = JumpShip.createFromFile(schematic.getRandomFileName(random), reason); if (jumpShip == null) { WarpDrive.logger.error(String.format("Failed to instantiate schematic structure %s due to %s", schematic.getFullName(), reason)); @@ -45,7 +47,7 @@ public class SchematicInstance extends AbstractStructureInstance { insertions = new Insertion[schematic.insertions.length]; 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); if (insertion != null) { insertions[insertionIndexOut] = insertion; @@ -72,11 +74,26 @@ public class SchematicInstance extends AbstractStructureInstance { 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( WarpDriveConfig.SPACE_GENERATOR_Y_MIN_BORDER + (jumpShip.core.getY() - jumpShip.minY), WarpDriveConfig.SPACE_GENERATOR_Y_MAX_BORDER - (jumpShip.maxY - jumpShip.core.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; } } diff --git a/src/main/java/cr0s/warpdrive/data/InventoryWrapper.java b/src/main/java/cr0s/warpdrive/data/InventoryWrapper.java index 44c4d1fa..4f305675 100644 --- a/src/main/java/cr0s/warpdrive/data/InventoryWrapper.java +++ b/src/main/java/cr0s/warpdrive/data/InventoryWrapper.java @@ -127,8 +127,11 @@ public class InventoryWrapper { } else if (inventory instanceof IItemHandler) { qtyLeft = addToInventory(itemStack, (IItemHandler) inventory); } else { - WarpDrive.logger.error(String.format("Invalid inventory type %s, please report to mod author: %s", - Commons.format(world, blockPos), inventory )); + if (Commons.throttleMe("addToInventories")){ + 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 (itemStackLeft == itemStack) { @@ -141,7 +144,7 @@ public class InventoryWrapper { } if (qtyLeft > 0) { 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) )); } overflow = true; diff --git a/src/main/java/cr0s/warpdrive/data/TankWrapper.java b/src/main/java/cr0s/warpdrive/data/TankWrapper.java new file mode 100644 index 00000000..48b6021f --- /dev/null +++ b/src/main/java/cr0s/warpdrive/data/TankWrapper.java @@ -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 getConnectedTanks(final World world, final BlockPos blockPos) { + final Collection result = new ArrayList<>(6); + final Collection 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 fluidStacks = new ArrayList<>(1); + fluidStacks.add(fluidStack); + return addToConnectedTanks(world, blockPos, fluidStacks); + } + + public static boolean addToConnectedTanks(final World world, final BlockPos blockPos, final List fluidStacks) { + final Collection inventories = getConnectedTanks(world, blockPos); + return addToTanks(world, blockPos, inventories, fluidStacks); + } + + public static boolean addToTanks(final World world, final BlockPos blockPos, + final Collection tanks, final FluidStack fluidStack) { + final List 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 tanks, final List 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; + } + +} diff --git a/src/main/java/cr0s/warpdrive/world/WorldGenStructure.java b/src/main/java/cr0s/warpdrive/world/WorldGenStructure.java index c502eb86..722579d9 100644 --- a/src/main/java/cr0s/warpdrive/world/WorldGenStructure.java +++ b/src/main/java/cr0s/warpdrive/world/WorldGenStructure.java @@ -8,6 +8,7 @@ import cr0s.warpdrive.config.Filler; import cr0s.warpdrive.config.GenericSet; import cr0s.warpdrive.config.Loot; import cr0s.warpdrive.config.WarpDriveConfig; +import cr0s.warpdrive.config.structures.Schematic.Insertion; import cr0s.warpdrive.data.InventoryWrapper; import cr0s.warpdrive.data.JumpBlock; import cr0s.warpdrive.data.JumpShip; @@ -18,6 +19,8 @@ import java.util.Collections; import java.util.Iterator; import java.util.Random; +import javax.annotation.Nullable; + import net.minecraft.block.Block; import net.minecraft.init.Blocks; 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, 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", - 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 lootSet = WarpDriveConfig.LootManager.getRandomSetFromGroup(rand, group); if (lootSet == null) { @@ -233,6 +213,36 @@ public class WorldGenStructure { return; } + fillInventoryWithLoot(world, rand, blockPos, lootSet, quantityMin, quantityRandom1, quantityRandom2, maxRetries); + } + + public void fillInventoryWithLoot(final World world, final Random rand, final BlockPos blockPos, final GenericSet 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 final ArrayList indexSlots = new ArrayList<>(size); for (int indexSlot = 0; indexSlot < size; indexSlot++) { @@ -243,6 +253,14 @@ public class WorldGenStructure { } 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 ItemStack itemStackLoot; boolean isAdded; @@ -267,23 +285,15 @@ public class WorldGenStructure { try { InventoryWrapper.insertItem(inventory, indexSlot, itemStackLoot); 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", - group, - Commons.format(world, blockPos), + WarpDrive.logger.debug(String.format(" + placed %s into slot %d", Commons.format(itemStackLoot), - lootSet.getFullName(), - indexSlot, - inventory )); + indexSlot)); } } catch (final Exception exception) { 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", - group, - Commons.format(world, blockPos), + WarpDrive.logger.error(String.format(" ! Exception while placing %s into slot %d: %s", Commons.format(itemStackLoot), - lootSet.getFullName(), indexSlot, - inventory, exception.getMessage() )); } 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) { + 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, 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)); } } else { - index++; if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) { WarpDrive.logger.info(String.format("At index %d, deploying %s ", index, jumpBlock)); @@ -345,6 +358,23 @@ public class WorldGenStructure { final Block blockAtTarget = world.getBlockState(targetLocation).getBlock(); if (blockAtTarget == Blocks.AIR || Dictionary.BLOCKS_EXPANDABLE.contains(blockAtTarget)) { 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 { if (WarpDrive.isDev && WarpDriveConfig.LOGGING_WORLD_GENERATION) { WarpDrive.logger.info(String.format("Deployment collision detected %s with %s during world generation, skipping this block...", diff --git a/src/main/resources/config/WarpDrive.xsd b/src/main/resources/config/WarpDrive.xsd index ec8eec5b..f5f00de8 100644 --- a/src/main/resources/config/WarpDrive.xsd +++ b/src/main/resources/config/WarpDrive.xsd @@ -107,6 +107,7 @@ +