Tree cutter fixes and compat improvements (issue #54, issue #59). Experimental Fluid Collection Funnel implementation.

This commit is contained in:
stfwi 2019-11-03 13:01:03 +01:00
parent 8746491095
commit 7592c9d494
50 changed files with 1718 additions and 96 deletions

View file

@ -4,4 +4,4 @@ org.gradle.jvmargs=-Xmx8G
version_minecraft=1.12.2
version_forge=14.23.5.2768
version_jei=4.10.0.198
version_engineersdecor=1.0.15-b2
version_engineersdecor=1.0.15

View file

@ -1,6 +1,7 @@
{
"homepage": "https://www.curseforge.com/minecraft/mc-mods/engineers-decor/",
"1.12.2": {
"1.0.15": "[R] Release based on v1.0.15-b2. Release-to-release changes: * Added Small Block Breaker * Small Tree Cutter fixes and compatability improved. * Crafting table compat fixes.\n[M] Small Tree Cutter log detection bug fixed (issue #59).\n[M] Small Tree Cutter supports Menril chopping (issue #54).",
"1.0.15-b2": "[A] Added Small Block Breaker\n[M] Crafting Table: Allowing NBT \"Damage\" mismatch only items that are declared damagable (issue #56).\n[M] Tree Cutter: Loosened the strict mod namespace requirement for Dynamic Trees log detection (issue #52) to enable checking DT compat mod log blocks.",
"1.0.15-b1": "[A] Added Floor Edge Light.\n[A] Added Factory Block Placer and Planter.",
"1.0.14": "[R] Release based on v1.0.14-b1. Release-to-release changes: * Factory Hopper added. * Small Waste Incinerator improved. * Lang updates. * Recipe fixes.",
@ -64,7 +65,7 @@
"1.0.0-b1": "[A] Initial structure.\n[A] Added clinker bricks and clinker brick stairs.\n[A] Added slag bricks and slag brick stairs.\n[A] Added metal rung ladder.\n[A] Added staggered metal steps ladder.\n[A] Added treated wood ladder.\n[A] Added treated wood pole.\n[A] Added treated wood table."
},
"promos": {
"1.12.2-recommended": "1.0.14",
"1.12.2-latest": "1.0.15-b2"
"1.12.2-recommended": "1.0.15",
"1.12.2-latest": "1.0.15"
}
}

View file

@ -10,6 +10,15 @@ Mod sources for Minecraft version 1.12.2.
----
## Version history
-------------------------------------------------------------------
- v1.0.15 [R] Release based on v1.0.15-b2. Release-to-release changes:
* Added Small Block Breaker
* Small Tree Cutter fixes and compatability improved.
* Crafting table compat fixes.
-------------------------------------------------------------------
[M] Small Tree Cutter log detection bug fixed (issue #59).
[M] Small Tree Cutter supports Menril chopping (issue #54).
- v1.0.15-b2 [A] Added Small Block Breaker
[M] Crafting Table: Allowing NBT "Damage" mismatch only
items that are declared damagable (issue #56).

View file

@ -193,6 +193,13 @@ public class ModContent
ModAuxiliaries.getPixeledAABB(0,0,0, 16,16,16)
);
public static final BlockDecorFluidFunnel SMALL_FLUID_FUNNEL = new BlockDecorFluidFunnel(
"small_fluid_funnel",
BlockDecor.CFG_CUTOUT|BlockDecor.CFG_ELECTRICAL|BlockDecor.CFG_REDSTONE_CONTROLLED,
Material.IRON, 1f, 15f, SoundType.METAL,
ModAuxiliaries.getPixeledAABB(0,0,0, 16,16,16)
);
//--------------------------------------------------------------------------------------------------------------------
public static final BlockDecorStraightPole TREATED_WOOD_POLE = new BlockDecorStraightPole(
@ -468,6 +475,9 @@ public class ModContent
private static final TileEntityRegistrationData PASSIVE_FLUID_ACCUMULATOR_TEI = new TileEntityRegistrationData(
BlockDecorPassiveFluidAccumulator.BTileEntity.class, "te_passive_fluid_accumulator"
);
private static final TileEntityRegistrationData SMALL_FLUID_FUNNEL_TEI = new TileEntityRegistrationData(
BlockDecorFluidFunnel.BTileEntity.class, "te_small_fluid_funnel"
);
private static final TileEntityRegistrationData WASTE_INCINERATOR_TEI = new TileEntityRegistrationData(
BlockDecorWasteIncinerator.BTileEntity.class, "te_small_waste_incinerator"
);
@ -568,6 +578,7 @@ public class ModContent
PANZERGLASS_SLAB, // @todo: check if another class is needed due to is_side_visible
TREATED_WOOD_FLOOR, // @todo: check if textures need improvement
TEST_BLOCK,TEST_BLOCK_TEI,
SMALL_FLUID_FUNNEL,SMALL_FLUID_FUNNEL_TEI
};
//--------------------------------------------------------------------------------------------------------------------

View file

@ -116,6 +116,7 @@ public class ModEngineersDecor
if(RecipeCondModSpecific.num_skipped > 0) logger.info("Excluded " + RecipeCondModSpecific.num_skipped + " recipes due to config opt-out.");
if(ModConfig.zmisc.with_experimental) logger.info("Included experimental features due to mod config.");
ExtItems.onPostInit();
BlockCategories.reload();
TreeCutting.reload();
}

View file

@ -0,0 +1,387 @@
/*
* @file BlockDecorFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2019 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.*;
import net.minecraft.world.World;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ITickable;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class BlockDecorFluidFunnel extends BlockDecor
{
public static final int FILL_LEVEL_MAX = 3;
public static final PropertyInteger FILL_LEVEL = PropertyInteger.create("level", 0, FILL_LEVEL_MAX);
public BlockDecorFluidFunnel(@Nonnull String registryName, long config, @Nullable Material material, float hardness, float resistance, @Nullable SoundType sound, @Nonnull AxisAlignedBB unrotatedAABB)
{ super(registryName, config, material, hardness, resistance, sound, unrotatedAABB); }
@Override
protected BlockStateContainer createBlockState()
{ return new BlockStateContainer(this, FILL_LEVEL); }
@Override
public IBlockState getStateFromMeta(int meta)
{ return super.getStateFromMeta(meta).withProperty(FILL_LEVEL, meta & 0x3); }
@Override
public int getMetaFromState(IBlockState state)
{ return super.getMetaFromState(state) | (state.getValue(FILL_LEVEL)); }
@Override
public IBlockState getStateForPlacement(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer, EnumHand hand)
{ return super.getStateForPlacement(world, pos, facing, hitX, hitY, hitZ, meta, placer, hand).withProperty(FILL_LEVEL, 0); }
@Override
@SuppressWarnings("deprecation")
public int getComparatorInputOverride(IBlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.getValue(FILL_LEVEL)*5), 0, 15); }
@Override
public boolean hasTileEntity(IBlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(World world, IBlockState state)
{ return new BTileEntity(); }
@Override
public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack)
{
if(world.isRemote) return;
if((!stack.hasTagCompound()) || (!stack.getTagCompound().hasKey("tedata"))) return;
NBTTagCompound te_nbt = stack.getTagCompound().getCompoundTag("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return;
((BTileEntity)te).readnbt(te_nbt, false);
((BTileEntity)te).markDirty();
}
@Override
public boolean removedByPlayer(IBlockState state, World world, BlockPos pos, EntityPlayer player, boolean willHarvest)
{
if(world.isRemote) return true;
TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return super.removedByPlayer(state, world, pos, player, willHarvest);
ItemStack stack = new ItemStack(this, 1);
NBTTagCompound te_nbt = new NBTTagCompound();
((BTileEntity) te).writenbt(te_nbt, false);
if(!te_nbt.isEmpty()) {
NBTTagCompound nbt = new NBTTagCompound();
nbt.setTag("tedata", te_nbt);
stack.setTagCompound(nbt);
}
world.spawnEntity(new EntityItem(world, pos.getX()+0.5, pos.getY()+0.5, pos.getZ()+0.5, stack));
world.setBlockToAir(pos);
world.removeTileEntity(pos);
return false;
}
@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
{
if(world.isRemote) return true;
TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return false;
return FluidUtil.interactWithFluidHandler(player, hand, world, pos, facing);
}
@Override
public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos fromPos)
{ TileEntity te = world.getTileEntity(pos); if(te instanceof BTileEntity) ((BTileEntity)te).block_changed(); }
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BTileEntity extends TileEntity implements IFluidHandler, IFluidTankProperties, ICapabilityProvider, ITickable
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private final IFluidTankProperties[] fluid_props_ = {this};
private FluidStack tank_ = null;
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ORIGIN;
private ArrayList<Vec3i> search_offsets_ = null;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
public BTileEntity()
{}
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; } // collect after flowing fluid has a stable state, otherwise it looks odd.
public void readnbt(NBTTagCompound nbt, boolean update_packet)
{
tank_ = (!nbt.hasKey("tank")) ? (null) : (FluidStack.loadFluidStackFromNBT(nbt.getCompoundTag("tank")));
}
protected void writenbt(NBTTagCompound nbt, boolean update_packet)
{
if(tank_ != null) nbt.setTag("tank", tank_.writeToNBT(new NBTTagCompound()));
}
// TileEntity ------------------------------------------------------------------------------
@Override
public boolean shouldRefresh(World world, BlockPos pos, IBlockState os, IBlockState ns)
{
block_changed();
return (os.getBlock() != ns.getBlock()) || (!(ns.getBlock() instanceof BlockDecorFluidFunnel));
}
@Override
public void readFromNBT(NBTTagCompound nbt)
{ super.readFromNBT(nbt); readnbt(nbt, false); }
@Override
public NBTTagCompound writeToNBT(NBTTagCompound nbt)
{ super.writeToNBT(nbt); writenbt(nbt, false); return nbt; }
// ICapabilityProvider --------------------------------------------------------------------
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{ return ((capability==CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY)) || super.hasCapability(capability, facing); }
@Override
@Nullable
@SuppressWarnings("unchecked")
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
if(capability != CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return super.getCapability(capability, facing);
return ((T)this);
}
// IFluidHandler of the output port --------------------------------------------------------
@Override
public IFluidTankProperties[] getTankProperties()
{ return fluid_props_; }
@Override
public int fill(FluidStack resource, boolean doFill)
{ return 0; }
@Override
@Nullable
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if((resource==null) || (tank_==null)) return null;
return (!(tank_.isFluidEqual(resource))) ? (null) : drain(resource.amount, doDrain);
}
@Override
@Nullable
public FluidStack drain(int maxDrain, boolean doDrain)
{
if(tank_==null) return null;
maxDrain = MathHelper.clamp(maxDrain ,0 , tank_.amount);
FluidStack res = tank_.copy();
res.amount = maxDrain;
if(doDrain) tank_.amount -= maxDrain;
if(tank_.amount <= 0) tank_= null;
return res;
}
// IFluidTankProperties --------------------------------------------------------------------
@Override @Nullable public FluidStack getContents() { return (tank_==null) ? (null) : (tank_.copy()); }
@Override public int getCapacity() { return TANK_CAPACITY; }
@Override public boolean canFill() { return false; }
@Override public boolean canDrain() { return true; }
@Override public boolean canFillFluidType(FluidStack fluidStack) { return false; }
@Override public boolean canDrainFluidType(FluidStack fluidStack) { return true; }
// ITickable--------------------------------------------------------------------------------
private Fluid get_fluid(BlockPos pos)
{ return FluidRegistry.lookupFluidForBlock(world.getBlockState(pos).getBlock()); }
private boolean try_pick(BlockPos pos)
{
IFluidHandler hnd = FluidUtil.getFluidHandler(world, pos, null);
if(hnd == null) return false;
FluidStack fs = hnd.drain((tank_==null) ? (TANK_CAPACITY) : (TANK_CAPACITY-tank_.amount), true);
if(fs == null) return false;
if(tank_ == null) {
tank_ = fs.copy();
} else if(tank_.isFluidEqual(fs)) {
tank_.amount = MathHelper.clamp(tank_.amount+fs.amount, 0, TANK_CAPACITY);
} else {
return false;
}
world.setBlockToAir(pos);
world.notifyNeighborsOfStateChange(pos, world.getBlockState(pos).getBlock(), true); // explicitly update neighbours to allow these start flowing
return true;
}
private boolean can_pick(BlockPos pos, Fluid fluid)
{
IFluidHandler hnd = FluidUtil.getFluidHandler(world, pos, null);
if(hnd == null) return false;
FluidStack fs = hnd.drain((tank_==null) ? (TANK_CAPACITY) : (TANK_CAPACITY-tank_.amount), false);
return (fs != null) && (fs.getFluid().equals(fluid));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vec3i(0, 1, 0)); // up first
{
ArrayList<Vec3i> ofs = new ArrayList<Vec3i>(Arrays.asList(new Vec3i(-1, 0, 0), new Vec3i( 1, 0, 0), new Vec3i( 0, 0,-1), new Vec3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vec3i> ofs = new ArrayList<Vec3i>(Arrays.asList(new Vec3i(-1, 1, 0), new Vec3i( 1, 1, 0), new Vec3i( 0, 1,-1), new Vec3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(BlockPos collection_pos)
{
final Block collection_block = world.getBlockState(collection_pos).getBlock();
if((!(collection_block instanceof IFluidBlock)) && (!(collection_block instanceof BlockLiquid))) return false;
final Fluid fluid_to_collect = FluidRegistry.lookupFluidForBlock(collection_block);
if(fluid_to_collect == null) return false; // not sure if that can return null
if((tank_!=null) && (!tank_.getFluid().equals(fluid_to_collect))) return false;
if(try_pick(collection_pos)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
// Not picked, not a source block -> search highest allowed source block to pick
// ---------------------------------------------------------------------------------------------------------------
// Plan is to pick preferably surface blocks whilst not wasting much mem and cpu. Blocks will dynamically change due to flowing.
// Basic assumptions: fluid flows straight (not diagonal) and not up the hill. Only falling fluid streams are long, lateral streams are about max 16 blocks (water 8).
// Problem resolving: Threre are fluids going all over the place, and do not sometimes continue flowing without a source block.
// - Stack based trail tracking is used
// - Turtle motion with reset on fail, preferrs up, remaining motion order is shuffled to pick not in the same direction.
// - Calculation time capping, reset on fail, extended search for far blocks (e.g. stream from a high position) on many fails.
// - Preferrs fluid surface blocks if possible and anough calculation time left.
// - On fail, replace last flowing block with air and cause a block update, in case previous block updates or strange fluid block behaviour does not prevent advancing.
// - Assumption is: The search can go much further than fluids can flow, except top-bottom falling streams. so
if((last_pick_pos_==null) || (last_pick_pos_.distanceSq(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<BlockPos>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.add(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
if(fluid_to_collect.equals(get_fluid(p))) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
if(!can_pick(pos.up(), fluid_to_collect)) break;
pos = pos.up();
trail.push(pos);
}
}
if(try_pick(pos)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && world.rand.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos))); String s = new String(); for(BlockPos p:checked) s += "\n" + p; System.out.println(s);
if(intensive_search_counter_ > 2) world.setBlockToAir(pos);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void update()
{
if((world.isRemote) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.amount <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!world.isBlockPowered(pos)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = pos.up();
if(try_collect(pos.up())) dirty = true;
}
}
// Gravity fluid transfer
if((tank_!=null) && (tank_.amount >= 1000)) {
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos.down(), EnumFacing.UP);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid(), 1000);
int nfilled = MathHelper.clamp(fh.fill(fs, true), 0, 1000);
tank_.amount -= nfilled;
if(tank_.amount <= 0) tank_ = null;
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (MathHelper.clamp(tank_.amount/1000,0,FILL_LEVEL_MAX));
final IBlockState funnel_state = world.getBlockState(pos);
if(funnel_state.getValue(FILL_LEVEL) != fill_level) world.setBlockState(pos, funnel_state.withProperty(FILL_LEVEL, fill_level), 2|16);
if(dirty) markDirty();
}
}
}

View file

@ -124,7 +124,7 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
public boolean shouldRefresh(World world, BlockPos pos, IBlockState os, IBlockState ns)
{
block_changed();
return (os.getBlock() != ns.getBlock()) || (!(ns.getBlock() instanceof BlockDecorPipeValve));
return (os.getBlock() != ns.getBlock()) || (!(ns.getBlock() instanceof BlockDecorPassiveFluidAccumulator));
}
@Override

View file

@ -26,6 +26,7 @@ public class BlockCategories
{
private static Set<Block> logs_ = new HashSet<Block>();
private static Set<Block> leaves_ = new HashSet<Block>();
private static Set<Block> variant_logs_ = new HashSet<Block>(); // logs that are not checked for log equivalence
public static final Set<Block> logs()
{ return logs_; } // wrap in case immutable needed one time.
@ -33,7 +34,6 @@ public class BlockCategories
public static final Set<Block> leaves()
{ return leaves_; }
public static boolean isLog(IBlockState state)
{
final Block block = state.getBlock();
@ -48,23 +48,27 @@ public class BlockCategories
}
public static final boolean isSameLeaves(IBlockState a, IBlockState b)
{ return (a.getBlock() == b.getBlock()); }
{
if(!isLeaves(a)) return false;
final Block block = a.getBlock();
if(block != b.getBlock()) return false;
if(block instanceof BlockNewLeaf) return a.getValue(BlockNewLeaf.VARIANT) == b.getValue(BlockNewLeaf.VARIANT);
if(block instanceof BlockOldLeaf) return a.getValue(BlockOldLeaf.VARIANT) == b.getValue(BlockOldLeaf.VARIANT);
return true;
}
public static final boolean isSameLog(IBlockState a, IBlockState b)
{
// very strange ...
if(a.getBlock()!=b.getBlock()) {
return false;
} else if(a.getBlock() instanceof BlockNewLog) {
return a.getValue(BlockNewLog.VARIANT) == b.getValue(BlockNewLog.VARIANT);
} else if(a.getBlock() instanceof BlockOldLog) {
return a.getValue(BlockOldLog.VARIANT) == b.getValue(BlockOldLog.VARIANT);
} else {
// Uagh, that hurts the heart of performance ...
final IProperty<?> prop = a.getPropertyKeys().stream().filter( (IProperty<?> p) -> (p.getName().contains("variant") || p.getName().contains("type"))).findFirst().orElse(null);
if(prop==null) return false;
return a.getValue(prop).equals(b.getValue(prop));
}
if((!isLog(a)) || (!isLog(b))) return false;
if(variant_logs_.contains(a.getBlock()) || (variant_logs_.contains(b.getBlock()))) return true;
if(a.getBlock()!=b.getBlock()) return false;
if(a.getBlock() instanceof BlockNewLog) return a.getValue(BlockNewLog.VARIANT) == b.getValue(BlockNewLog.VARIANT);
if(a.getBlock() instanceof BlockOldLog) return a.getValue(BlockOldLog.VARIANT)==b.getValue(BlockOldLog.VARIANT);
// Uagh, that hurts the heart of performance ...
final IProperty<?> prop = a.getPropertyKeys().stream().filter( (IProperty<?> p) -> (p.getName().contains("variant") || p.getName().contains("type"))).findFirst().orElse(null);
if(prop!=null) return a.getValue(prop).equals(b.getValue(prop));
// All other: We have to assume that there are no variants for this block, and the block type denotes the log type unambiguously.
return true;
}
public static final void reload()
@ -77,11 +81,17 @@ public class BlockCategories
for(ItemStack stack : stacks) {
final Item item = stack.getItem();
if(!(item instanceof ItemBlock)) continue;
logs.add(((ItemBlock)item).getBlock());
Block block = ((ItemBlock)item).getBlock();
logs.add(block);
// @todo: make this configurable
if(block.getRegistryName().getPath().contains("menril")) variant_logs_.add(block);
}
}
logs_ = logs;
ModEngineersDecor.logger.info("Found "+logs.size()+" types of 'choppable' log.");
if(ModConfig.zmisc.with_experimental) {
for(Block b:logs_) ModEngineersDecor.logger.info(" - choppable log: " + b.getRegistryName());
}
}
{
HashSet<Block> leaves = new HashSet<Block>();
@ -96,6 +106,9 @@ public class BlockCategories
}
leaves_ = leaves;
ModEngineersDecor.logger.info("Found "+leaves.size()+" types of leaves.");
if(ModConfig.zmisc.with_experimental) {
for(Block b:leaves_) ModEngineersDecor.logger.info(" - choppable leaf: " + b.getRegistryName());
}
}
}
}

View file

@ -26,6 +26,9 @@ import java.util.*;
public class TreeCutting
{
private static org.apache.logging.log4j.Logger LOGGER = ModEngineersDecor.logger;
private static int max_log_tracing_steps = 128;
private static int max_cutting_height = 128;
private static int max_cutting_radius = 12;
private static class Compat
{
@ -39,27 +42,28 @@ public class TreeCutting
choppable_states.clear();
if(ModAuxiliaries.isModLoaded("dynamictrees")) {
ForgeRegistries.BLOCKS.getKeys().forEach((regname)->{
//if("dynamictrees".equals(regname.getNamespace())) { ... let's see if that also work with dyntrees compat mods
if(!regname.getPath().contains("branch")) return;
try {
Block block = ForgeRegistries.BLOCKS.getValue(regname);
IBlockState state = block.getDefaultState();
for(IProperty<?> vaprop: state.getProperties().keySet()) {
if(!("radius".equals(vaprop.getName())) || (vaprop.getValueClass() != Integer.class)) continue;
@SuppressWarnings("unchecked")
IProperty<Integer> prop = (IProperty<Integer>)vaprop;
Integer max = ((Collection<Integer>)prop.getAllowedValues()).stream().max(Integer::compare).orElse(0);
if(max<7) continue;
for(int r=7; r<=max; ++r) choppable_states.put(state.withProperty(prop, r), ChoppingMethod.RootBlockBreaking);
}
} catch(Throwable e) {
LOGGER.warn("Failed to register chopping for " + regname.toString());
return;
if(!regname.getPath().contains("branch")) return;
try {
Block block = ForgeRegistries.BLOCKS.getValue(regname);
IBlockState state = block.getDefaultState();
for(IProperty<?> vaprop: state.getProperties().keySet()) {
if(!("radius".equals(vaprop.getName())) || (vaprop.getValueClass() != Integer.class)) continue;
@SuppressWarnings("unchecked")
IProperty<Integer> prop = (IProperty<Integer>)vaprop;
Integer max = ((Collection<Integer>)prop.getAllowedValues()).stream().max(Integer::compare).orElse(0);
if(max<7) continue;
for(int r=7; r<=max; ++r) choppable_states.put(state.withProperty(prop, r), ChoppingMethod.RootBlockBreaking);
}
//}
} catch(Throwable e) {
LOGGER.warn("Failed to register chopping for " + regname.toString());
return;
}
});
}
LOGGER.info("Dynamic Trees chopping compat: " + choppable_states.size() + " choppable states found.");
if(ModConfig.zmisc.with_experimental) {
for(IBlockState b:choppable_states.keySet()) ModEngineersDecor.logger.info(" - dynamic tree state: " + b);
}
} catch(Throwable e) {
choppable_states.clear();
LOGGER.warn("Failed to determine choppings for dynamic trees compat, skipping that:" + e);
@ -100,7 +104,13 @@ public class TreeCutting
);
public static void reload()
{ Compat.reload(); }
{
Compat.reload();
// later config, now keep IJ from suggesting that a private variable should be used.
max_log_tracing_steps = 128;
max_cutting_height = 128;
max_cutting_radius = 12;
}
private static List<BlockPos> findBlocksAround(final World world, final BlockPos centerPos, final IBlockState leaf_type_state, final Set<BlockPos> checked, int recursion_left)
{
@ -121,6 +131,17 @@ public class TreeCutting
return to_decay;
}
private static boolean too_far(BlockPos start, BlockPos pos)
{
int dy = pos.getY()-start.getY();
if((dy < 0) || (dy>max_cutting_height)) return true;
final int dx = Math.abs(pos.getX()-start.getX());
final int dz = Math.abs(pos.getZ()-start.getZ());
if(dy > max_cutting_radius) dy = max_cutting_radius;
if((dx >= dy+3) || (dz >= dy+3)) return true;
return false;
}
public static boolean canChop(IBlockState state)
{ return BlockCategories.isLog(state) || Compat.canChop(state); }
@ -144,11 +165,13 @@ public class TreeCutting
LinkedList<BlockPos> upqueue = new LinkedList<BlockPos>();
queue.add(startPos);
int cutlevel = 0;
int steps_left = 64;
int steps_left = max_log_tracing_steps;
IBlockState tracked_leaves_state = null;
while(!queue.isEmpty() && (--steps_left >= 0)) {
final BlockPos pos = queue.removeFirst();
// Vertical search
final BlockPos uppos = pos.up();
if(too_far(startPos, uppos)) { checked.add(uppos); continue; }
final IBlockState upstate = world.getBlockState(uppos);
if(!checked.contains(uppos)) {
checked.add(uppos);
@ -156,11 +179,18 @@ public class TreeCutting
// Up is log
upqueue.add(uppos);
to_break.add(uppos);
steps_left = 64;
steps_left = max_log_tracing_steps;
} else {
boolean isleaf = BlockCategories.isLeaves(upstate);
if(isleaf || world.isAirBlock(uppos) || (upstate.getBlock() instanceof BlockVine)) {
if(isleaf) to_decay.add(uppos);
if(isleaf) {
if(tracked_leaves_state==null) {
tracked_leaves_state=upstate;
to_decay.add(uppos);
} else if(BlockCategories.isSameLeaves(upstate, tracked_leaves_state)) {
to_decay.add(uppos);
}
}
// Up is air, check adjacent for diagonal up (e.g. Accacia)
for(Vec3i v:hoffsets) {
final BlockPos p = uppos.add(v);
@ -172,7 +202,9 @@ public class TreeCutting
queue.add(p);
to_break.add(p);
} else if(BlockCategories.isLeaves(st)) {
to_decay.add(p);
if((tracked_leaves_state==null) || (BlockCategories.isSameLeaves(st, tracked_leaves_state))) {
to_decay.add(p);
}
}
}
}
@ -183,14 +215,15 @@ public class TreeCutting
final BlockPos p = pos.add(v);
if(checked.contains(p)) continue;
checked.add(p);
if(p.distanceSq(new BlockPos(startPos.getX(), p.getY(), startPos.getZ())) > (3+cutlevel*cutlevel)) continue;
final IBlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(BlockCategories.isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(BlockCategories.isLeaves(st)) {
to_decay.add(p);
if((tracked_leaves_state==null) || (BlockCategories.isSameLeaves(st, tracked_leaves_state))) {
to_decay.add(p);
}
}
}
if(queue.isEmpty() && (!upqueue.isEmpty())) {

View file

@ -0,0 +1,14 @@
{
"forge_marker": 1,
"defaults": { "model": "engineersdecor:device/small_fluid_funnel_model_s0" },
"variants": {
"normal": [{}],
"inventory": [{}],
"level": {
"0":{},
"1":{"model": "engineersdecor:device/small_fluid_funnel_model_s1"},
"2":{"model": "engineersdecor:device/small_fluid_funnel_model_s2"},
"3":{"model": "engineersdecor:device/small_fluid_funnel_model_s3"}
}
}
}

View file

@ -150,6 +150,11 @@ tile.engineersdecor.straight_pipe_valve_redstone_analog.help=§6Straight fluid p
tile.engineersdecor.passive_fluid_accumulator.name=Passive Fluid Accumulator
tile.engineersdecor.passive_fluid_accumulator.help=§6Vacuum suction based fluid collector.§r Has one output, all other sides are input. \
Drains fluids from adjacent tanks when being drained from the output port by a pump.
tile.engineersdecor.small_fluid_funnel.name=Small Fluid Collection Funnel
tile.engineersdecor.small_fluid_funnel.help=§6Collects fluids above it.§r Has an internal tank with three buckets capacity. Traces \
flowing fluids to nearby source blocks. The fluid can be obtained with fluid transfer systems \
or a bucket. Fills only tanks below (gravity transfer). Compatible with vanilla \
infinite-water-source creation.
#-----------------------------------------------------------------------------------------------------------
tile.engineersdecor.factory_dropper.name=Factory Dropper
tile.engineersdecor.factory_dropper.help=§6Dropper suitable for advanced factory automation.§r Has twelve round-robin selected slots. \

View file

@ -145,6 +145,11 @@ tile.engineersdecor.straight_pipe_valve_redstone_analog.help=§6Фрагмент
tile.engineersdecor.passive_fluid_accumulator.name=Пассивный жидкостный накопитель
tile.engineersdecor.passive_fluid_accumulator.help=§6Вакуумный всасывающий жидкостный коллектор.§r Имеет один выход, все остальные стороны входные. \
Сливает жидкости из соседних резервуаров при выкачивании жидкости из выходного порта.
tile.engineersdecor.small_fluid_funnel.name=Small Fluid Collection Funnel
#tile.engineersdecor.small_fluid_funnel.help=§6Collects fluids above it.§r Has an internal tank with three buckets capacity. Traces \
flowing fluids to nearby source blocks. The fluid can be obtained with fluid transfer systems \
or a bucket. Fills only tanks below (gravity transfer). Compatible with vanilla \
infinite-water-source creation.
#-----------------------------------------------------------------------------------------------------------
tile.engineersdecor.factory_dropper.name=Фабричный выбрасыватель
tile.engineersdecor.factory_dropper.help=§6Выбрасыватель подходит для продвинутой автоматизации производства.§r Имеет 12 выборочных слотов. \

View file

@ -149,6 +149,11 @@ tile.engineersdecor.straight_pipe_valve_redstone_analog.help=§6一段直输液
tile.engineersdecor.passive_fluid_accumulator.name=被动流体累积器。
tile.engineersdecor.passive_fluid_accumulator.help=§6基于真空吸力的流体收集器。§r有一个输出面其他面都是输入。\
当从输出面被泵抽取时,从输入面邻接储罐抽取液体。
tile.engineersdecor.small_fluid_funnel.name=Small Fluid Collection Funnel
#tile.engineersdecor.small_fluid_funnel.help=§6Collects fluids above it.§r Has an internal tank with three buckets capacity. Traces \
flowing fluids to nearby source blocks. The fluid can be obtained with fluid transfer systems \
or a bucket. Fills only tanks below (gravity transfer). Compatible with vanilla \
infinite-water-source creation.
#-----------------------------------------------------------------------------------------------------------
tile.engineersdecor.factory_dropper.name=工厂掉落器
tile.engineersdecor.factory_dropper.help=§6适用于高级工厂自动化的掉落器。§r有十二个轮询选择的储物格。\

View file

@ -0,0 +1,295 @@
{
"parent": "block/cube",
"textures": {
"top": "engineersdecor:blocks/device/small_fluid_funnel_top",
"bottom": "engineersdecor:blocks/device/small_fluid_funnel_bottom",
"side": "engineersdecor:blocks/device/small_fluid_funnel_side_s0",
"particle": "engineersdecor:blocks/device/small_fluid_funnel_side_s0"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 14, 16],
"faces": {
"north": {"uv": [0, 2, 16, 16], "texture": "#side"},
"east": {"uv": [0, 2, 16, 16], "texture": "#side"},
"south": {"uv": [0, 2, 16, 16], "texture": "#side"},
"west": {"uv": [0, 2, 16, 16], "texture": "#side"},
"up": {"uv": [0, 0, 16, 16], "texture": "#top"},
"down": {"uv": [0, 0, 16, 16], "texture": "#bottom"}
}
},
{
"from": [14, 15, 0],
"to": [16, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 22, 8]},
"faces": {
"north": {"uv": [0, 0, 2, 1], "texture": "#side"},
"east": {"uv": [0, 0, 16, 1], "texture": "#side"},
"south": {"uv": [14, 0, 16, 1], "texture": "#side"},
"west": {"uv": [0, 0, 16, 1], "texture": "#side"},
"up": {"uv": [14, 0, 16, 16], "texture": "#top"},
"down": {"uv": [14, 0, 16, 16], "texture": "#bottom"}
}
},
{
"from": [13, 14, 2],
"to": [15, 15, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 20, 8]},
"faces": {
"north": {"uv": [1, 1, 3, 2], "texture": "#side"},
"east": {"uv": [2, 1, 14, 2], "texture": "#side"},
"south": {"uv": [13, 1, 15, 2], "texture": "#side"},
"west": {"uv": [2, 1, 14, 2], "texture": "#side"},
"up": {"uv": [13, 2, 15, 14], "texture": "#top"},
"down": {"uv": [13, 2, 15, 14], "texture": "#bottom"}
}
},
{
"from": [2, 15, 0],
"to": [15, 16, 2],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 8]},
"faces": {
"north": {"uv": [1, 0, 14, 1], "texture": "#side"},
"east": {"uv": [14, 0, 16, 1], "texture": "#side"},
"south": {"uv": [2, 0, 15, 1], "texture": "#side"},
"west": {"uv": [0, 0, 2, 1], "texture": "#side"},
"up": {"uv": [2, 0, 15, 2], "texture": "#top"},
"down": {"uv": [2, 14, 15, 16], "texture": "#bottom"}
}
},
{
"from": [3, 14, 1],
"to": [13, 15, 3],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 20, 9]},
"faces": {
"north": {"uv": [3, 1, 13, 2], "texture": "#side"},
"east": {"uv": [13, 1, 15, 2], "texture": "#side"},
"south": {"uv": [3, 1, 13, 2], "texture": "#side"},
"west": {"uv": [1, 1, 3, 2], "texture": "#side"},
"up": {"uv": [3, 1, 13, 3], "texture": "#top"},
"down": {"uv": [3, 13, 13, 15], "texture": "#bottom"}
}
},
{
"from": [2, 15, 14],
"to": [15, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 22]},
"faces": {
"north": {"uv": [1, 0, 14, 1], "texture": "#side"},
"east": {"uv": [0, 0, 2, 1], "texture": "#side"},
"south": {"uv": [2, 0, 15, 1], "texture": "#side"},
"west": {"uv": [14, 0, 16, 1], "texture": "#side"},
"up": {"uv": [2, 14, 15, 16], "texture": "#top"},
"down": {"uv": [2, 0, 15, 2], "texture": "#bottom"}
}
},
{
"from": [3, 14, 13],
"to": [13, 15, 15],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 20, 21]},
"faces": {
"north": {"uv": [3, 1, 13, 2], "texture": "#side"},
"east": {"uv": [1, 1, 3, 2], "texture": "#side"},
"south": {"uv": [3, 1, 13, 2], "texture": "#side"},
"west": {"uv": [13, 1, 15, 2], "texture": "#side"},
"up": {"uv": [3, 13, 13, 15], "texture": "#top"},
"down": {"uv": [3, 1, 13, 3], "texture": "#bottom"}
}
},
{
"from": [2, 14, 2],
"to": [2.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-4, 22, 8]},
"faces": {
"north": {"uv": [13.5, 0, 14, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [2, 0, 2.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [2, 2, 2.5, 14], "texture": "#top"},
"down": {"uv": [2, 2, 2.5, 14], "texture": "#bottom"}
}
},
{
"from": [3, 14, 2],
"to": [3.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-3, 22, 8]},
"faces": {
"north": {"uv": [12.5, 0, 13, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [3, 0, 3.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [3, 2, 3.5, 14], "texture": "#top"},
"down": {"uv": [3, 2, 3.5, 14], "texture": "#bottom"}
}
},
{
"from": [4, 14, 2],
"to": [4.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-1, 22, 8]},
"faces": {
"north": {"uv": [11.5, 0, 12, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [4, 0, 4.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [4, 2, 4.5, 14], "texture": "#top"},
"down": {"uv": [4, 2, 4.5, 14], "texture": "#bottom"}
}
},
{
"from": [5, 14, 2],
"to": [5.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [0, 22, 8]},
"faces": {
"north": {"uv": [10.5, 0, 11, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [5, 0, 5.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [5, 2, 5.5, 14], "texture": "#top"},
"down": {"uv": [5, 2, 5.5, 14], "texture": "#bottom"}
}
},
{
"from": [6, 14, 2],
"to": [6.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [1, 22, 8]},
"faces": {
"north": {"uv": [9.5, 0, 10, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [6, 0, 6.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [6, 2, 6.5, 14], "texture": "#top"},
"down": {"uv": [6, 2, 6.5, 14], "texture": "#bottom"}
}
},
{
"from": [7, 14, 2],
"to": [7.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [2, 22, 8]},
"faces": {
"north": {"uv": [8.5, 0, 9, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [7, 0, 7.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [7, 2, 7.5, 14], "texture": "#top"},
"down": {"uv": [7, 2, 7.5, 14], "texture": "#bottom"}
}
},
{
"from": [8, 14, 2],
"to": [8.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [3, 22, 8]},
"faces": {
"north": {"uv": [7.5, 0, 8, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [8, 0, 8.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [8, 2, 8.5, 14], "texture": "#top"},
"down": {"uv": [8, 2, 8.5, 14], "texture": "#bottom"}
}
},
{
"from": [9, 14, 2],
"to": [9.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [4, 22, 8]},
"faces": {
"north": {"uv": [6.5, 0, 7, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [9, 0, 9.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [9, 2, 9.5, 14], "texture": "#top"},
"down": {"uv": [9, 2, 9.5, 14], "texture": "#bottom"}
}
},
{
"from": [10, 14, 2],
"to": [10.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 8]},
"faces": {
"north": {"uv": [5.5, 0, 6, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [10, 0, 10.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [10, 2, 10.5, 14], "texture": "#top"},
"down": {"uv": [10, 2, 10.5, 14], "texture": "#bottom"}
}
},
{
"from": [11, 14, 2],
"to": [11.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [6, 22, 8]},
"faces": {
"north": {"uv": [4.5, 0, 5, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [11, 0, 11.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [11, 2, 11.5, 14], "texture": "#top"},
"down": {"uv": [11, 2, 11.5, 14], "texture": "#bottom"}
}
},
{
"from": [12, 14, 2],
"to": [12.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 22, 8]},
"faces": {
"north": {"uv": [3.5, 0, 4, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [12, 0, 12.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [12, 2, 12.5, 14], "texture": "#top"},
"down": {"uv": [12, 2, 12.5, 14], "texture": "#bottom"}
}
},
{
"from": [13, 14, 2],
"to": [13.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 22, 8]},
"faces": {
"north": {"uv": [2.5, 0, 3, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [13, 0, 13.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [13, 2, 13.5, 14], "texture": "#top"},
"down": {"uv": [13, 2, 13.5, 14], "texture": "#bottom"}
}
},
{
"from": [0, 15, 0],
"to": [2, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [-6, 22, 8]},
"faces": {
"north": {"uv": [14, 0, 16, 1], "texture": "#side"},
"east": {"uv": [0, 0, 16, 1], "texture": "#side"},
"south": {"uv": [0, 0, 2, 1], "texture": "#side"},
"west": {"uv": [0, 0, 16, 1], "texture": "#side"},
"up": {"uv": [0, 0, 2, 16], "texture": "#top"},
"down": {"uv": [0, 0, 2, 16], "texture": "#bottom"}
}
},
{
"from": [1, 14, 2],
"to": [3, 15, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-5, 20, 8]},
"faces": {
"north": {"uv": [13, 1, 15, 2], "texture": "#side"},
"east": {"uv": [2, 1, 14, 2], "texture": "#side"},
"south": {"uv": [1, 1, 3, 2], "texture": "#side"},
"west": {"uv": [2, 1, 14, 2], "texture": "#side"},
"up": {"uv": [1, 2, 3, 14], "texture": "#top"},
"down": {"uv": [1, 2, 3, 14], "texture": "#bottom"}
}
}
],
"display": {
"ground": {
"scale": [0.2, 0.2, 0.2]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:blocks/device/small_fluid_funnel_side_s1" }
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:blocks/device/small_fluid_funnel_side_s2" }
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:blocks/device/small_fluid_funnel_side_s3" }
}

View file

@ -0,0 +1,33 @@
{
"conditions": [
{
"type": "engineersdecor:grc",
"result": "engineersdecor:small_fluid_funnel",
"missing": ["immersiveengineering:metal_device1"]
}
],
"type": "minecraft:crafting_shaped",
"pattern": [
"HHH",
"IBI",
"III"
],
"key": {
"B": {
"item": "minecraft:bucket",
"data": 0
},
"I": {
"item": "#ingotIron",
"data": 0
},
"H": {
"item": "#anyHopper",
"data": 0
}
},
"result": {
"item": "engineersdecor:small_fluid_funnel",
"count": 1
}
}

View file

@ -0,0 +1,33 @@
{
"conditions": [
{
"type": "engineersdecor:grc",
"result": "engineersdecor:small_fluid_funnel",
"required": ["immersiveengineering:metal_device1"]
}
],
"type": "minecraft:crafting_shaped",
"pattern": [
"HHH",
"PBP",
"PPP"
],
"key": {
"B": {
"item": "#itemSteelBarrel",
"data": 0
},
"P": {
"item": "#plateAnyMetal",
"data": 0
},
"H": {
"item": "#anyHopper",
"data": 0
}
},
"result": {
"item": "engineersdecor:small_fluid_funnel",
"count": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

View file

@ -5,4 +5,4 @@ version_minecraft=1.14.4
version_forge_minecraft=1.14.4-28.1.68
version_fml_mappings=20190719-1.14.3
version_jei=1.14.4:6.0.0.10
version_engineersdecor=1.0.15-b3
version_engineersdecor=1.0.15-b4

View file

@ -11,6 +11,8 @@ Mod sources for Minecraft version 1.14.4.
## Version history
~ v1.0.15-b4 [A] Added Fluid Collection Funnel.
- v1.0.15-b3 [A] Added Small Block Breaker.
[M] Mineral Smelter fluid handler/transfer added.

View file

@ -434,6 +434,12 @@ public class ModContent
ModAuxiliaries.getPixeledAABB(0,0,0, 16,16,16)
)).setRegistryName(new ResourceLocation(ModEngineersDecor.MODID, "passive_fluid_accumulator"));
public static final BlockDecorFluidFunnel SMALL_FLUID_FUNNEL = (BlockDecorFluidFunnel)(new BlockDecorFluidFunnel(
BlockDecor.CFG_CUTOUT|BlockDecor.CFG_REDSTONE_CONTROLLED,
Block.Properties.create(Material.IRON, MaterialColor.IRON).hardnessAndResistance(2f, 15f).sound(SoundType.METAL),
ModAuxiliaries.getPixeledAABB(0,0,0, 16,16,16)
)).setRegistryName(new ResourceLocation(ModEngineersDecor.MODID, "small_fluid_funnel"));
// -------------------------------------------------------------------------------------------------------------------
public static final BlockDecorWall CONCRETE_WALL = (BlockDecorWall)(new BlockDecorWall(
@ -497,6 +503,11 @@ public class ModContent
SMALL_SOLAR_PANEL,
SMALL_WASTE_INCINERATOR,
SMALL_MINERAL_SMELTER,
STRAIGHT_CHECK_VALVE,
STRAIGHT_REDSTONE_VALVE,
STRAIGHT_REDSTONE_ANALOG_VALVE,
PASSIVE_FLUID_ACCUMULATOR,
SMALL_FLUID_FUNNEL,
CLINKER_BRICK_BLOCK,
CLINKER_BRICK_SLAB,
CLINKER_BRICK_STAIRS,
@ -558,10 +569,6 @@ public class ModContent
};
private static final Block devBlocks[] = {
STRAIGHT_CHECK_VALVE,
STRAIGHT_REDSTONE_VALVE,
STRAIGHT_REDSTONE_ANALOG_VALVE,
PASSIVE_FLUID_ACCUMULATOR,
};
//--------------------------------------------------------------------------------------------------------------------
@ -618,6 +625,11 @@ public class ModContent
.build(null)
.setRegistryName(ModEngineersDecor.MODID, "te_passive_fluid_accumulator");
public static final TileEntityType<?> TET_SMALL_FLUID_FUNNEL = TileEntityType.Builder
.create(BlockDecorFluidFunnel.BTileEntity::new, SMALL_FLUID_FUNNEL)
.build(null)
.setRegistryName(ModEngineersDecor.MODID, "te_small_fluid_funnel");
public static final TileEntityType<?> TET_MINERAL_SMELTER = TileEntityType.Builder
.create(BlockDecorMineralSmelter.BTileEntity::new, SMALL_MINERAL_SMELTER)
.build(null)
@ -648,6 +660,7 @@ public class ModContent
TET_SMALL_SOLAR_PANEL,
TET_STRAIGHT_PIPE_VALVE,
TET_PASSIVE_FLUID_ACCUMULATOR,
TET_SMALL_FLUID_FUNNEL,
};
//--------------------------------------------------------------------------------------------------------------------

View file

@ -40,6 +40,7 @@ public class ModEngineersDecor
public static final String MODID = "engineersdecor";
public static final int VERSION_DATAFIXER = 0;
private static final Logger LOGGER = LogManager.getLogger();
private static boolean config_loaded = false;
public ModEngineersDecor()
{
@ -63,6 +64,16 @@ public class ModEngineersDecor
LOGGER.info("Registering recipe condition processor ...");
CraftingHelper.register(Serializer.INSTANCE);
Networking.init();
if(config_loaded) {
try {
logger().info("Applying loaded config file.");
ModConfig.apply();
} catch(Throwable e) {
logger().error("Failed to apply config: " + e.getMessage());
}
} else {
logger().info("Cannot apply config, load event was not casted yet.");
}
}
private void onClientSetup(final FMLClientSetupEvent event)
@ -101,16 +112,9 @@ public class ModEngineersDecor
public static void onServerStarting(FMLServerStartingEvent event)
{}
// @SubscribeEvent
@SubscribeEvent
public static void onConfigLoad(net.minecraftforge.fml.config.ModConfig.Loading configEvent)
{
try {
ModEngineersDecor.logger().info("Loaded config file {}", configEvent.getConfig().getFileName());
ModConfig.apply();
} catch(Throwable e) {
ModEngineersDecor.logger().error("Failed to load config: " + e.getMessage());
}
}
{ config_loaded = true; }
@SubscribeEvent
public static void onConfigFileChange(net.minecraftforge.fml.config.ModConfig.ConfigReloading configEvent)
@ -128,7 +132,6 @@ public class ModEngineersDecor
{
event.getGenerator().addProvider(new ModLootTables(event.getGenerator()));
}
}
//

View file

@ -0,0 +1,392 @@
/*
* @file BlockDecorFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2019 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.ModContent;
import net.minecraft.block.*;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.*;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class BlockDecorFluidFunnel extends BlockDecor
{
public static final int FILL_LEVEL_MAX = 3;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public BlockDecorFluidFunnel(long config, Block.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder)
{ super.fillStateContainer(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).with(FILL_LEVEL, 0); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new BTileEntity(); }
@Override
@SuppressWarnings("deprecation")
public boolean hasComparatorInputOverride(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getComparatorInputOverride(BlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.get(FILL_LEVEL)*5), 0, 15); }
@Override
public void onBlockPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isRemote) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundNBT te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return;
((BTileEntity)te).readnbt(te_nbt);
((BTileEntity)te).markDirty();
world.setBlockState(pos, state.with(FILL_LEVEL, 0));
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, BlockPos pos, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isRemote) return stacks;
final TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return stacks;
if(!explosion) {
ItemStack stack = new ItemStack(this, 1);
CompoundNBT te_nbt = new CompoundNBT();
((BTileEntity)te).writenbt(te_nbt);
if(!te_nbt.isEmpty()) {
CompoundNBT nbt = new CompoundNBT();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
} else {
stacks.add(new ItemStack(this, 1));
}
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public boolean onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(world.isRemote) return true;
TileEntity te = world.getTileEntity(pos);
if(!(te instanceof BTileEntity)) return false;
return FluidUtil.interactWithFluidHandler(player, hand, world, pos, rayTraceResult.getFace());
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{ TileEntity te = world.getTileEntity(pos); if(te instanceof BTileEntity) ((BTileEntity)te).block_changed(); }
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private FluidStack tank_ = FluidStack.EMPTY.copy();
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ZERO;
private ArrayList<Vec3i> search_offsets_ = null;
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; } // collect after flowing fluid has a stable state, otherwise it looks odd.
public BTileEntity()
{ this(ModContent.TET_SMALL_FLUID_FUNNEL); }
public BTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public void readnbt(CompoundNBT nbt)
{
tank_ = (!nbt.contains("tank")) ? (FluidStack.EMPTY.copy()) : (FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
}
public void writenbt(CompoundNBT nbt)
{
if(!tank_.isEmpty()) nbt.put("tank", tank_.writeToNBT(new CompoundNBT()));
}
// TileEntity -----------------------------------------------------------------------------------------
@Override
public void read(CompoundNBT nbt)
{ super.read(nbt); readnbt(nbt); }
@Override
public CompoundNBT write(CompoundNBT nbt)
{ super.write(nbt); writenbt(nbt); return nbt; }
// ICapabilityProvider / Output flow handler ----------------------------------------------------------
private static class OutputFluidHandler implements IFluidHandler
{
private final BTileEntity te;
OutputFluidHandler(BTileEntity parent) { te = parent; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return te.tank_.copy(); }
@Override public int getTankCapacity(int tank) { return TANK_CAPACITY; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action)
{
if((resource==null) || (te.tank_.isEmpty())) return FluidStack.EMPTY.copy();
return (!(te.tank_.isFluidEqual(resource))) ? (FluidStack.EMPTY.copy()) : drain(resource.getAmount(), action);
}
@Override public FluidStack drain(int maxDrain, FluidAction action)
{
FluidStack res = te.tank_.copy();
if(res.isEmpty()) return res;
maxDrain = MathHelper.clamp(maxDrain ,0 , te.tank_.getAmount());
res.setAmount(maxDrain);
if(action != FluidAction.EXECUTE) return res;
te.tank_.setAmount(te.tank_.getAmount()-maxDrain);
if(te.tank_.getAmount() <= 0) te.tank_ = FluidStack.EMPTY.copy();
return res;
}
}
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new OutputFluidHandler(this));
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickableTileEntity --------------------------------------------------------------------------------
private IFluidState get_fluidstate(BlockPos pos)
{
// todo: check if getFluidState() is enough
final Block collection_block = world.getBlockState(pos).getBlock();
if((!(collection_block instanceof IFluidBlock)) && (!(collection_block instanceof FlowingFluidBlock)) && (!(collection_block instanceof IWaterLoggable))) {
return Fluids.EMPTY.getDefaultState();
}
return world.getFluidState(pos);
}
private boolean try_pick(BlockPos pos, IFluidState fluidstate)
{
if(!fluidstate.isSource()) return false;
IFluidHandler hnd = FluidUtil.getFluidHandler(world, pos, null).orElse(null);
FluidStack fs;
if(hnd != null) {
fs = hnd.drain(TANK_CAPACITY, FluidAction.EXECUTE); // IFluidBlock
} else {
fs = new FluidStack(fluidstate.getFluid(), 1000);
BlockState state = world.getBlockState(pos);
if(state instanceof IBucketPickupHandler) {
((IBucketPickupHandler)state).pickupFluid(world, pos, state);
} else {
world.setBlockState(pos, Blocks.AIR.getDefaultState(), 1|2); // ok we can't leave the block, that would be an infinite source of an unknown fluid.
}
}
if((fs==null) || (fs.isEmpty())) return false; // it's marked nonnull but I don't trust every modder - including meself ...
if(tank_.isEmpty()) {
tank_ = fs.copy();
} else if(tank_.isFluidEqual(fs)) {
tank_.setAmount(MathHelper.clamp(tank_.getAmount()+fs.getAmount(), 0, TANK_CAPACITY));
} else {
return false;
}
return true;
}
private boolean can_pick(BlockPos pos, IFluidState fluidstate)
{
if(fluidstate.isSource()) return true;
IFluidHandler hnd = FluidUtil.getFluidHandler(world, pos, null).orElse(null);
if(hnd == null) return false;
FluidStack fs = hnd.drain(TANK_CAPACITY, FluidAction.SIMULATE); // don't trust that everyone returns nonnull
return ((fs!=null) && (!fs.isEmpty())) && (fluidstate.getFluid().isEquivalentTo(fs.getFluid()));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vec3i(0, 1, 0)); // up first
{
ArrayList<Vec3i> ofs = new ArrayList<Vec3i>(Arrays.asList(new Vec3i(-1, 0, 0), new Vec3i( 1, 0, 0), new Vec3i( 0, 0,-1), new Vec3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vec3i> ofs = new ArrayList<Vec3i>(Arrays.asList(new Vec3i(-1, 1, 0), new Vec3i( 1, 1, 0), new Vec3i( 0, 1,-1), new Vec3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(final BlockPos collection_pos)
{
IFluidState collection_fluidstate = get_fluidstate(collection_pos);
if(collection_fluidstate.isEmpty()) return false;
Fluid fluid_to_collect = collection_fluidstate.getFluid();
if((!tank_.isEmpty()) && (!tank_.getFluid().isEquivalentTo(fluid_to_collect))) return false;
if(try_pick(collection_pos, collection_fluidstate)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
if((last_pick_pos_==null) || (last_pick_pos_.distanceSq(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<BlockPos>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.add(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
IFluidState fluidstate = get_fluidstate(p);
// @todo: nice thing in 1.14: the fluid level is easily readable,
// so lateral motion can be restricted to higher fill levels.
if(fluidstate.getFluid().isEquivalentTo(fluid_to_collect)) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
IFluidState fs = get_fluidstate(pos.up());
if(!can_pick(pos.up(), fs)) break;
fluidstate = fs;
pos = pos.up();
trail.push(pos);
}
}
if(try_pick(pos, fluidstate)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && world.rand.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos)));
//String s = new String(); for(BlockPos p:checked) s += "\n" + p; println(s);
if(intensive_search_counter_ > 2) world.removeBlock(pos, false);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void tick()
{
if((world.isRemote) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.getAmount() <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!world.isBlockPowered(pos)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = pos.up();
if(try_collect(pos.up())) dirty = true;
}
}
// Gravity fluid transfer
if((tank_.getAmount() >= 1000)) {
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos.down(), Direction.UP).orElse(null);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid(), 1000);
int nfilled = MathHelper.clamp(fh.fill(fs, FluidAction.EXECUTE), 0, 1000);
tank_.shrink(nfilled);
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (MathHelper.clamp(tank_.getAmount()/1000,0,FILL_LEVEL_MAX));
final BlockState funnel_state = world.getBlockState(pos);
if(funnel_state.get(FILL_LEVEL) != fill_level) world.setBlockState(pos, funnel_state.with(FILL_LEVEL, fill_level), 2|16);
if(dirty) markDirty();
}
}
}

View file

@ -484,7 +484,7 @@ public class BlockDecorMineralSmelter extends BlockDecorDirectedHorizontal
@Override
public FluidStack drain(FluidStack resource, FluidAction action)
{
if(!resource.isFluidEqual(lava) || (te.fluid_level() <= 0)) return FluidStack.EMPTY;
if(!resource.isFluidEqual(lava) || (te.fluid_level() <= 0)) return FluidStack.EMPTY.copy();
FluidStack stack = new FluidStack(lava, te.fluid_level());
if(action == FluidAction.EXECUTE) te.fluid_level_drain(te.fluid_level());
return stack;
@ -493,7 +493,7 @@ public class BlockDecorMineralSmelter extends BlockDecorDirectedHorizontal
@Override
public FluidStack drain(int maxDrain, FluidAction action)
{
if(te.fluid_level() <= 0) return FluidStack.EMPTY;
if(te.fluid_level() <= 0) return FluidStack.EMPTY.copy();
maxDrain = (action==FluidAction.EXECUTE) ? (te.fluid_level_drain(maxDrain)) : (Math.min(maxDrain, te.fluid_level()));
return new FluidStack(lava, maxDrain);
}

View file

@ -15,21 +15,23 @@ package wile.engineersdecor.blocks;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.detail.ModAuxiliaries;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Hand;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.Hand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
@ -67,7 +69,6 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
// @todo double check if this is actually needed
TileEntity te = world.getTileEntity(pos);
if(te instanceof BlockDecorPipeValve.BTileEntity) ((BTileEntity)te).block_changed();
}
@ -76,7 +77,7 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BTileEntity extends TileEntity // implements ITickableTileEntity, IFluidHandler, IFluidTankProperties, ICapabilityProvider
public static class BTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider
{
protected static int tick_idle_interval = 20; // ca 1000ms, simulates suction delay and saves CPU when not drained.
protected static int max_flowrate = 1000;
@ -99,7 +100,30 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
public void block_changed()
{ initialized_ = false; tick_timer_ = MathHelper.clamp(tick_timer_ , 0, tick_idle_interval); }
// Output flow handler ---------------------------------------------------------------------
// TileEntity ------------------------------------------------------------------------------
public BTileEntity()
{ this(ModContent.TET_PASSIVE_FLUID_ACCUMULATOR); }
public BTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
@Override
public void read(CompoundNBT nbt)
{
super.read(nbt);
tank_ = (!nbt.contains("tank")) ? (FluidStack.EMPTY.copy()) : (FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
}
@Override
public CompoundNBT write(CompoundNBT nbt)
{
super.write(nbt);
if(!tank_.isEmpty()) nbt.put("tank", tank_.writeToNBT(new CompoundNBT()));
return nbt;
}
// Input flow handler ---------------------------------------------------------------------
private static class InputFillHandler implements IFluidHandler
{
@ -114,7 +138,7 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY.copy(); }
}
// Input flow handler ---------------------------------------------------------------------
// Output flow handler ---------------------------------------------------------------------
private static class OutputFlowHandler implements IFluidHandler
{
@ -148,29 +172,6 @@ public class BlockDecorPassiveFluidAccumulator extends BlockDecorDirected
}
}
// TileEntity ------------------------------------------------------------------------------
public BTileEntity()
{ this(ModContent.TET_PASSIVE_FLUID_ACCUMULATOR); }
public BTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
@Override
public void read(CompoundNBT nbt)
{
super.read(nbt);
tank_ = (!nbt.contains("tank")) ? (FluidStack.EMPTY.copy()) : (FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
}
@Override
public CompoundNBT write(CompoundNBT nbt)
{
super.write(nbt);
if(!tank_.isEmpty()) nbt.put("tank", tank_.writeToNBT(new CompoundNBT()));
return nbt;
}
// ICapabilityProvider --------------------------------------------------------------------
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new OutputFlowHandler(this));

View file

@ -0,0 +1,12 @@
{
"forge_marker": 1,
"defaults": { "model": "engineersdecor:block/device/small_fluid_funnel_model_s0" },
"variants": {
"level": {
"0":{},
"1":{"model": "engineersdecor:block/device/small_fluid_funnel_model_s1"},
"2":{"model": "engineersdecor:block/device/small_fluid_funnel_model_s2"},
"3":{"model": "engineersdecor:block/device/small_fluid_funnel_model_s3"}
}
}
}

View file

@ -155,6 +155,8 @@
"block.engineersdecor.straight_pipe_valve_redstone_analog.help": "§6Straight fluid pipe fragment.§r Conducts fluids only in one direction. Does not connect to the sides. Sneak to place in reverse direction. Blocks if not redstone powered, reduces the flow rate linear from power 1 to 14, opens to maximum possible valve flow rate for power 15.",
"block.engineersdecor.passive_fluid_accumulator": "Passive Fluid Accumulator",
"block.engineersdecor.passive_fluid_accumulator.help": "§6Vacuum suction based fluid collector.§r Has one output, all other sides are input. Drains fluids from adjacent tanks when being drained from the output port by a pump.",
"block.engineersdecor.small_fluid_funnel": "Small Fluid Collection Funnel",
"block.engineersdecor.small_fluid_funnel.help": "§6Collects fluids above it.§r Has an internal tank with three buckets capacity. Traces flowing fluids to nearby source blocks. The fluid can be obtained with fluid transfer systems or a bucket. Fills only tanks below (gravity transfer). Compatible with vanilla infinite-water-source creation.",
"block.engineersdecor.factory_dropper": "Factory Dropper",
"block.engineersdecor.factory_dropper.help": "§6Dropper suitable for advanced factory automation.§r Has twelve round-robin selected slots. Drop force, angle, stack size, and cool-down delay adjustable using sliders in the GUI. Three stack compare slots (below the inventory slots) with logical AND or OR can be used as internal trigger source. The internal trigger can be AND'ed or OR'ed with the external redstone signal trigger. Trigger simulation buttons for testing. Pre-opens shutter door when internal trigger conditions are met. Drops all matching stacks simultaneously. Simply click on all elements in the GUI to see how it works.",
"block.engineersdecor.factory_hopper": "Factory Hopper",

View file

@ -151,6 +151,7 @@
"block.engineersdecor.straight_pipe_valve_redstone_analog.help": "§6Фрагмент прямой трубы.§r Проводит жидкости только в одном направлении. Не соединяется по бокам. SHIFT для размещения в обратном направлении. Не пропускает при отсутствии сигнала красного камня, уменьшает расход линейно с мощности 1 до 14, открывается максимально-возможно при уровне сигнала красного камня 15.",
"block.engineersdecor.passive_fluid_accumulator": "Пассивный жидкостный накопитель",
"block.engineersdecor.passive_fluid_accumulator.help": "§6Вакуумный всасывающий жидкостный коллектор.§r Имеет один выход, все остальные стороны входные. Сливает жидкости из соседних резервуаров при выкачивании жидкости из выходного порта.",
"block.engineersdecor.small_fluid_funnel": "Small Fluid Collection Funnel",
"block.engineersdecor.factory_dropper": "Фабричный выбрасыватель",
"block.engineersdecor.factory_dropper.help": "§6Выбрасыватель подходит для продвинутой автоматизации производства.§r Имеет 12 выборочных слотов. Сила броска, угол, размер стопки и задержка настраиваются в GUI. 3 слота сравнения стека с логическим И или ИЛИ могут использоваться в качестве внутреннего источника запуска. Внутренний триггер может быть И или ИЛИ с внешним триггерным сигналом красного камня. Триггерные кнопки симуляции для тестирования. Предварительно открывает дверцу затвора, когда выполняются условия внутреннего запуска. Сбрасывает все соответствующие стеки одновременно. Нажмите на все элементы в GUI, чтобы увидеть, как это работает.",
"block.engineersdecor.factory_hopper": "Factory Hopper",

View file

@ -154,6 +154,7 @@
"block.engineersdecor.straight_pipe_valve_redstone_analog.help": "§6一段直输液管。§r单向传递流体。 侧面不会与管道连接。会减少流速。潜行能反方向放置。 没有红石信号时断流流速与红石信号强度从1到14线性增长 15时流速上限达到最大。",
"block.engineersdecor.passive_fluid_accumulator": "被动流体累积器。",
"block.engineersdecor.passive_fluid_accumulator.help": "§6基于真空吸力的流体收集器。§r有一个输出面其他面都是输入。 当从输出面被泵抽取时,从输入面邻接储罐抽取液体。",
"block.engineersdecor.small_fluid_funnel": "Small Fluid Collection Funnel",
"block.engineersdecor.factory_dropper": "工厂掉落器",
"block.engineersdecor.factory_dropper.help": "§6适用于高级工厂自动化的掉落器。§r有十二个轮询选择的储物格。 掉落的力度、角度、一叠数量和冷却延时可在GUI调节。三个 内部比较槽带有逻辑与或逻辑或功能,可用作内部触发源。内部触发 还能和外部红石信号触发再进行逻辑与或逻辑或。触发模拟按钮仅作测试用途。 当内部触发条件满足时,预先打开卷帘门。所有符合条件的物品 会同时掉落。点击GUI的各处来了解如何运作。",
"block.engineersdecor.factory_hopper": "Factory Hopper",

View file

@ -0,0 +1,295 @@
{
"parent": "block/cube",
"textures": {
"top": "engineersdecor:block/device/small_fluid_funnel_top",
"bottom": "engineersdecor:block/device/small_fluid_funnel_bottom",
"side": "engineersdecor:block/device/small_fluid_funnel_side_s0",
"particle": "engineersdecor:block/device/small_fluid_funnel_side_s0"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 14, 16],
"faces": {
"north": {"uv": [0, 2, 16, 16], "texture": "#side"},
"east": {"uv": [0, 2, 16, 16], "texture": "#side"},
"south": {"uv": [0, 2, 16, 16], "texture": "#side"},
"west": {"uv": [0, 2, 16, 16], "texture": "#side"},
"up": {"uv": [0, 0, 16, 16], "texture": "#top"},
"down": {"uv": [0, 0, 16, 16], "texture": "#bottom"}
}
},
{
"from": [14, 15, 0],
"to": [16, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 22, 8]},
"faces": {
"north": {"uv": [0, 0, 2, 1], "texture": "#side"},
"east": {"uv": [0, 0, 16, 1], "texture": "#side"},
"south": {"uv": [14, 0, 16, 1], "texture": "#side"},
"west": {"uv": [0, 0, 16, 1], "texture": "#side"},
"up": {"uv": [14, 0, 16, 16], "texture": "#top"},
"down": {"uv": [14, 0, 16, 16], "texture": "#bottom"}
}
},
{
"from": [13, 14, 2],
"to": [15, 15, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 20, 8]},
"faces": {
"north": {"uv": [1, 1, 3, 2], "texture": "#side"},
"east": {"uv": [2, 1, 14, 2], "texture": "#side"},
"south": {"uv": [13, 1, 15, 2], "texture": "#side"},
"west": {"uv": [2, 1, 14, 2], "texture": "#side"},
"up": {"uv": [13, 2, 15, 14], "texture": "#top"},
"down": {"uv": [13, 2, 15, 14], "texture": "#bottom"}
}
},
{
"from": [2, 15, 0],
"to": [15, 16, 2],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 8]},
"faces": {
"north": {"uv": [1, 0, 14, 1], "texture": "#side"},
"east": {"uv": [14, 0, 16, 1], "texture": "#side"},
"south": {"uv": [2, 0, 15, 1], "texture": "#side"},
"west": {"uv": [0, 0, 2, 1], "texture": "#side"},
"up": {"uv": [2, 0, 15, 2], "texture": "#top"},
"down": {"uv": [2, 14, 15, 16], "texture": "#bottom"}
}
},
{
"from": [3, 14, 1],
"to": [13, 15, 3],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 20, 9]},
"faces": {
"north": {"uv": [3, 1, 13, 2], "texture": "#side"},
"east": {"uv": [13, 1, 15, 2], "texture": "#side"},
"south": {"uv": [3, 1, 13, 2], "texture": "#side"},
"west": {"uv": [1, 1, 3, 2], "texture": "#side"},
"up": {"uv": [3, 1, 13, 3], "texture": "#top"},
"down": {"uv": [3, 13, 13, 15], "texture": "#bottom"}
}
},
{
"from": [2, 15, 14],
"to": [15, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 22]},
"faces": {
"north": {"uv": [1, 0, 14, 1], "texture": "#side"},
"east": {"uv": [0, 0, 2, 1], "texture": "#side"},
"south": {"uv": [2, 0, 15, 1], "texture": "#side"},
"west": {"uv": [14, 0, 16, 1], "texture": "#side"},
"up": {"uv": [2, 14, 15, 16], "texture": "#top"},
"down": {"uv": [2, 0, 15, 2], "texture": "#bottom"}
}
},
{
"from": [3, 14, 13],
"to": [13, 15, 15],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 20, 21]},
"faces": {
"north": {"uv": [3, 1, 13, 2], "texture": "#side"},
"east": {"uv": [1, 1, 3, 2], "texture": "#side"},
"south": {"uv": [3, 1, 13, 2], "texture": "#side"},
"west": {"uv": [13, 1, 15, 2], "texture": "#side"},
"up": {"uv": [3, 13, 13, 15], "texture": "#top"},
"down": {"uv": [3, 1, 13, 3], "texture": "#bottom"}
}
},
{
"from": [2, 14, 2],
"to": [2.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-4, 22, 8]},
"faces": {
"north": {"uv": [13.5, 0, 14, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [2, 0, 2.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [2, 2, 2.5, 14], "texture": "#top"},
"down": {"uv": [2, 2, 2.5, 14], "texture": "#bottom"}
}
},
{
"from": [3, 14, 2],
"to": [3.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-3, 22, 8]},
"faces": {
"north": {"uv": [12.5, 0, 13, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [3, 0, 3.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [3, 2, 3.5, 14], "texture": "#top"},
"down": {"uv": [3, 2, 3.5, 14], "texture": "#bottom"}
}
},
{
"from": [4, 14, 2],
"to": [4.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-1, 22, 8]},
"faces": {
"north": {"uv": [11.5, 0, 12, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [4, 0, 4.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [4, 2, 4.5, 14], "texture": "#top"},
"down": {"uv": [4, 2, 4.5, 14], "texture": "#bottom"}
}
},
{
"from": [5, 14, 2],
"to": [5.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [0, 22, 8]},
"faces": {
"north": {"uv": [10.5, 0, 11, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [5, 0, 5.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [5, 2, 5.5, 14], "texture": "#top"},
"down": {"uv": [5, 2, 5.5, 14], "texture": "#bottom"}
}
},
{
"from": [6, 14, 2],
"to": [6.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [1, 22, 8]},
"faces": {
"north": {"uv": [9.5, 0, 10, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [6, 0, 6.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [6, 2, 6.5, 14], "texture": "#top"},
"down": {"uv": [6, 2, 6.5, 14], "texture": "#bottom"}
}
},
{
"from": [7, 14, 2],
"to": [7.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [2, 22, 8]},
"faces": {
"north": {"uv": [8.5, 0, 9, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [7, 0, 7.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [7, 2, 7.5, 14], "texture": "#top"},
"down": {"uv": [7, 2, 7.5, 14], "texture": "#bottom"}
}
},
{
"from": [8, 14, 2],
"to": [8.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [3, 22, 8]},
"faces": {
"north": {"uv": [7.5, 0, 8, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [8, 0, 8.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [8, 2, 8.5, 14], "texture": "#top"},
"down": {"uv": [8, 2, 8.5, 14], "texture": "#bottom"}
}
},
{
"from": [9, 14, 2],
"to": [9.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [4, 22, 8]},
"faces": {
"north": {"uv": [6.5, 0, 7, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [9, 0, 9.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [9, 2, 9.5, 14], "texture": "#top"},
"down": {"uv": [9, 2, 9.5, 14], "texture": "#bottom"}
}
},
{
"from": [10, 14, 2],
"to": [10.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [5, 22, 8]},
"faces": {
"north": {"uv": [5.5, 0, 6, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [10, 0, 10.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [10, 2, 10.5, 14], "texture": "#top"},
"down": {"uv": [10, 2, 10.5, 14], "texture": "#bottom"}
}
},
{
"from": [11, 14, 2],
"to": [11.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [6, 22, 8]},
"faces": {
"north": {"uv": [4.5, 0, 5, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [11, 0, 11.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [11, 2, 11.5, 14], "texture": "#top"},
"down": {"uv": [11, 2, 11.5, 14], "texture": "#bottom"}
}
},
{
"from": [12, 14, 2],
"to": [12.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 22, 8]},
"faces": {
"north": {"uv": [3.5, 0, 4, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [12, 0, 12.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [12, 2, 12.5, 14], "texture": "#top"},
"down": {"uv": [12, 2, 12.5, 14], "texture": "#bottom"}
}
},
{
"from": [13, 14, 2],
"to": [13.5, 16, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 22, 8]},
"faces": {
"north": {"uv": [2.5, 0, 3, 2], "texture": "#side"},
"east": {"uv": [2, 0, 14, 2], "texture": "#side"},
"south": {"uv": [13, 0, 13.5, 2], "texture": "#side"},
"west": {"uv": [2, 0, 14, 2], "texture": "#side"},
"up": {"uv": [13, 2, 13.5, 14], "texture": "#top"},
"down": {"uv": [13, 2, 13.5, 14], "texture": "#bottom"}
}
},
{
"from": [0, 15, 0],
"to": [2, 16, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [-6, 22, 8]},
"faces": {
"north": {"uv": [14, 0, 16, 1], "texture": "#side"},
"east": {"uv": [0, 0, 16, 1], "texture": "#side"},
"south": {"uv": [0, 0, 2, 1], "texture": "#side"},
"west": {"uv": [0, 0, 16, 1], "texture": "#side"},
"up": {"uv": [0, 0, 2, 16], "texture": "#top"},
"down": {"uv": [0, 0, 2, 16], "texture": "#bottom"}
}
},
{
"from": [1, 14, 2],
"to": [3, 15, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [-5, 20, 8]},
"faces": {
"north": {"uv": [13, 1, 15, 2], "texture": "#side"},
"east": {"uv": [2, 1, 14, 2], "texture": "#side"},
"south": {"uv": [1, 1, 3, 2], "texture": "#side"},
"west": {"uv": [2, 1, 14, 2], "texture": "#side"},
"up": {"uv": [1, 2, 3, 14], "texture": "#top"},
"down": {"uv": [1, 2, 3, 14], "texture": "#bottom"}
}
}
],
"display": {
"ground": {
"scale": [0.2, 0.2, 0.2]
},
"gui": {
"rotation": [30, 225, 0],
"scale": [0.625, 0.625, 0.625]
},
"fixed": {
"scale": [0.5, 0.5, 0.5]
}
}
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:block/device/small_fluid_funnel_side_s1" }
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:block/device/small_fluid_funnel_side_s2" }
}

View file

@ -0,0 +1,4 @@
{
"parent": "engineersdecor:block/device/small_fluid_funnel_model_s0",
"textures": { "side": "engineersdecor:block/device/small_fluid_funnel_side_s3" }
}

View file

@ -0,0 +1 @@
{ "parent": "engineersdecor:block/device/small_fluid_funnel_model_s0" }

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

View file

@ -0,0 +1,24 @@
{
"conditions": [
{
"type": "engineersdecor:optional",
"result": "engineersdecor:small_fluid_funnel",
"missing": ["immersiveengineering:metal_device1"]
}
],
"type": "minecraft:crafting_shaped",
"pattern": [
"HHH",
"IBI",
"III"
],
"key": {
"B": { "item": "minecraft:bucket" },
"I": { "item": "minecraft:iron_ingot" },
"H": { "item": "minecraft:hopper" }
},
"result": {
"item": "engineersdecor:small_fluid_funnel",
"count": 1
}
}

View file

@ -1,12 +1,13 @@
{
"homepage": "https://www.curseforge.com/minecraft/mc-mods/engineers-decor/",
"promos": {
"1.12.2-recommended": "1.0.14",
"1.12.2-latest": "1.0.15-b2",
"1.12.2-recommended": "1.0.15",
"1.12.2-latest": "1.0.15",
"1.14.4-recommended": "",
"1.14.4-latest": "1.0.15-b3"
},
"1.12.2": {
"1.0.15": "[R] Release based on v1.0.15-b2. Release-to-release changes: * Added Small Block Breaker * Small Tree Cutter fixes and compatability improved. * Crafting table compat fixes.\n[M] Small Tree Cutter log detection bug fixed (issue #59).\n[M] Small Tree Cutter supports Menril chopping (issue #54).",
"1.0.15-b2": "[A] Added Small Block Breaker\n[M] Crafting Table: Allowing NBT \"Damage\" mismatch only items that are declared damagable (issue #56).\n[M] Tree Cutter: Loosened the strict mod namespace requirement for Dynamic Trees log detection (issue #52) to enable checking DT compat mod log blocks.",
"1.0.15-b1": "[A] Added Floor Edge Light.\n[A] Added Factory Block Placer and Planter.",
"1.0.14": "[R] Release based on v1.0.14-b1. Release-to-release changes: * Factory Hopper added. * Small Waste Incinerator improved. * Lang updates. * Recipe fixes.",

View file

@ -168,6 +168,11 @@ looking manufacturing contraptions. Current feature set:
finally to lava. Needs a lot of power. When the lava is cooled down in the smelter by removing
the RF power, obsidian is generated.
- *Fluid Collection Funnel*: Collects fluids above it. Has an internal tank with three buckets
capacity. Traces flowing fluids to nearby source blocks. The fluid can be obtained with fluid
transfer systems or a bucket. Fills only tanks below (gravity). Compatible with vanilla
infinite-water-source creation.
More to come slowly but steadily.
----