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