509 lines
17 KiB
Java
509 lines
17 KiB
Java
package com.simibubi.create.content.kinetics.saw;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.Random;
|
|
import java.util.function.Predicate;
|
|
import java.util.function.Supplier;
|
|
import java.util.stream.Collectors;
|
|
|
|
import javax.annotation.ParametersAreNonnullByDefault;
|
|
|
|
import com.google.common.base.Suppliers;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.simibubi.create.AllRecipeTypes;
|
|
import com.simibubi.create.AllSoundEvents;
|
|
import com.simibubi.create.content.kinetics.base.BlockBreakingKineticBlockEntity;
|
|
import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour;
|
|
import com.simibubi.create.content.processing.recipe.ProcessingInventory;
|
|
import com.simibubi.create.content.processing.sequenced.SequencedAssemblyRecipe;
|
|
import com.simibubi.create.foundation.advancement.AllAdvancements;
|
|
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
|
|
import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour;
|
|
import com.simibubi.create.foundation.item.ItemHelper;
|
|
import com.simibubi.create.foundation.recipe.RecipeConditions;
|
|
import com.simibubi.create.foundation.recipe.RecipeFinder;
|
|
import com.simibubi.create.foundation.utility.AbstractBlockBreakQueue;
|
|
import com.simibubi.create.foundation.utility.TreeCutter;
|
|
import com.simibubi.create.foundation.utility.VecHelper;
|
|
import com.simibubi.create.infrastructure.config.AllConfigs;
|
|
|
|
import net.minecraft.MethodsReturnNonnullByDefault;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Registry;
|
|
import net.minecraft.core.particles.BlockParticleOption;
|
|
import net.minecraft.core.particles.ItemParticleOption;
|
|
import net.minecraft.core.particles.ParticleOptions;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.item.BlockItem;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.crafting.Recipe;
|
|
import net.minecraft.world.item.crafting.RecipeType;
|
|
import net.minecraft.world.item.crafting.StonecutterRecipe;
|
|
import net.minecraft.world.level.block.BambooBlock;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.CactusBlock;
|
|
import net.minecraft.world.level.block.ChorusPlantBlock;
|
|
import net.minecraft.world.level.block.KelpBlock;
|
|
import net.minecraft.world.level.block.KelpPlantBlock;
|
|
import net.minecraft.world.level.block.SoundType;
|
|
import net.minecraft.world.level.block.StemGrownBlock;
|
|
import net.minecraft.world.level.block.SugarCaneBlock;
|
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
import net.minecraftforge.common.capabilities.Capability;
|
|
import net.minecraftforge.common.util.LazyOptional;
|
|
import net.minecraftforge.items.CapabilityItemHandler;
|
|
import net.minecraftforge.items.IItemHandler;
|
|
|
|
@ParametersAreNonnullByDefault
|
|
@MethodsReturnNonnullByDefault
|
|
public class SawBlockEntity extends BlockBreakingKineticBlockEntity {
|
|
|
|
private static final Object cuttingRecipesKey = new Object();
|
|
public static final Supplier<RecipeType<?>> woodcuttingRecipeType =
|
|
Suppliers.memoize(() -> Registry.RECIPE_TYPE.get(new ResourceLocation("druidcraft", "woodcutting")));
|
|
|
|
public ProcessingInventory inventory;
|
|
private int recipeIndex;
|
|
private final LazyOptional<IItemHandler> invProvider;
|
|
private FilteringBehaviour filtering;
|
|
|
|
private ItemStack playEvent;
|
|
|
|
public SawBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
|
super(type, pos, state);
|
|
inventory = new ProcessingInventory(this::start).withSlotLimit(!AllConfigs.server().recipes.bulkCutting.get());
|
|
inventory.remainingTime = -1;
|
|
recipeIndex = 0;
|
|
invProvider = LazyOptional.of(() -> inventory);
|
|
playEvent = ItemStack.EMPTY;
|
|
}
|
|
|
|
@Override
|
|
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
|
|
super.addBehaviours(behaviours);
|
|
filtering = new FilteringBehaviour(this, new SawFilterSlot()).forRecipes();
|
|
behaviours.add(filtering);
|
|
behaviours.add(new DirectBeltInputBehaviour(this).allowingBeltFunnelsWhen(this::canProcess));
|
|
registerAwardables(behaviours, AllAdvancements.SAW_PROCESSING);
|
|
}
|
|
|
|
@Override
|
|
public void write(CompoundTag compound, boolean clientPacket) {
|
|
compound.put("Inventory", inventory.serializeNBT());
|
|
compound.putInt("RecipeIndex", recipeIndex);
|
|
super.write(compound, clientPacket);
|
|
|
|
if (!clientPacket || playEvent.isEmpty())
|
|
return;
|
|
compound.put("PlayEvent", playEvent.serializeNBT());
|
|
playEvent = ItemStack.EMPTY;
|
|
}
|
|
|
|
@Override
|
|
protected void read(CompoundTag compound, boolean clientPacket) {
|
|
super.read(compound, clientPacket);
|
|
inventory.deserializeNBT(compound.getCompound("Inventory"));
|
|
recipeIndex = compound.getInt("RecipeIndex");
|
|
if (compound.contains("PlayEvent"))
|
|
playEvent = ItemStack.of(compound.getCompound("PlayEvent"));
|
|
}
|
|
|
|
@Override
|
|
protected AABB createRenderBoundingBox() {
|
|
return new AABB(worldPosition).inflate(.125f);
|
|
}
|
|
|
|
@Override
|
|
@OnlyIn(Dist.CLIENT)
|
|
public void tickAudio() {
|
|
super.tickAudio();
|
|
if (getSpeed() == 0)
|
|
return;
|
|
|
|
if (!playEvent.isEmpty()) {
|
|
boolean isWood = false;
|
|
Item item = playEvent.getItem();
|
|
if (item instanceof BlockItem) {
|
|
Block block = ((BlockItem) item).getBlock();
|
|
isWood = block.getSoundType(block.defaultBlockState(), level, worldPosition, null) == SoundType.WOOD;
|
|
}
|
|
spawnEventParticles(playEvent);
|
|
playEvent = ItemStack.EMPTY;
|
|
if (!isWood)
|
|
AllSoundEvents.SAW_ACTIVATE_STONE.playAt(level, worldPosition, 3, 1, true);
|
|
else
|
|
AllSoundEvents.SAW_ACTIVATE_WOOD.playAt(level, worldPosition, 3, 1, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (shouldRun() && ticksUntilNextProgress < 0)
|
|
destroyNextTick();
|
|
super.tick();
|
|
|
|
if (!canProcess())
|
|
return;
|
|
if (getSpeed() == 0)
|
|
return;
|
|
if (inventory.remainingTime == -1) {
|
|
if (!inventory.isEmpty() && !inventory.appliedRecipe)
|
|
start(inventory.getStackInSlot(0));
|
|
return;
|
|
}
|
|
|
|
float processingSpeed = Mth.clamp(Math.abs(getSpeed()) / 24, 1, 128);
|
|
inventory.remainingTime -= processingSpeed;
|
|
|
|
if (inventory.remainingTime > 0)
|
|
spawnParticles(inventory.getStackInSlot(0));
|
|
|
|
if (inventory.remainingTime < 5 && !inventory.appliedRecipe) {
|
|
if (level.isClientSide && !isVirtual())
|
|
return;
|
|
playEvent = inventory.getStackInSlot(0);
|
|
applyRecipe();
|
|
inventory.appliedRecipe = true;
|
|
inventory.recipeDuration = 20;
|
|
inventory.remainingTime = 20;
|
|
sendData();
|
|
return;
|
|
}
|
|
|
|
Vec3 itemMovement = getItemMovementVec();
|
|
Direction itemMovementFacing = Direction.getNearest(itemMovement.x, itemMovement.y, itemMovement.z);
|
|
if (inventory.remainingTime > 0)
|
|
return;
|
|
inventory.remainingTime = 0;
|
|
|
|
for (int slot = 0; slot < inventory.getSlots(); slot++) {
|
|
ItemStack stack = inventory.getStackInSlot(slot);
|
|
if (stack.isEmpty())
|
|
continue;
|
|
ItemStack tryExportingToBeltFunnel = getBehaviour(DirectBeltInputBehaviour.TYPE)
|
|
.tryExportingToBeltFunnel(stack, itemMovementFacing.getOpposite(), false);
|
|
if (tryExportingToBeltFunnel != null) {
|
|
if (tryExportingToBeltFunnel.getCount() != stack.getCount()) {
|
|
inventory.setStackInSlot(slot, tryExportingToBeltFunnel);
|
|
notifyUpdate();
|
|
return;
|
|
}
|
|
if (!tryExportingToBeltFunnel.isEmpty())
|
|
return;
|
|
}
|
|
}
|
|
|
|
BlockPos nextPos = worldPosition.offset(itemMovement.x, itemMovement.y, itemMovement.z);
|
|
DirectBeltInputBehaviour behaviour = BlockEntityBehaviour.get(level, nextPos, DirectBeltInputBehaviour.TYPE);
|
|
if (behaviour != null) {
|
|
boolean changed = false;
|
|
if (!behaviour.canInsertFromSide(itemMovementFacing))
|
|
return;
|
|
if (level.isClientSide && !isVirtual())
|
|
return;
|
|
for (int slot = 0; slot < inventory.getSlots(); slot++) {
|
|
ItemStack stack = inventory.getStackInSlot(slot);
|
|
if (stack.isEmpty())
|
|
continue;
|
|
ItemStack remainder = behaviour.handleInsertion(stack, itemMovementFacing, false);
|
|
if (remainder.equals(stack, false))
|
|
continue;
|
|
inventory.setStackInSlot(slot, remainder);
|
|
changed = true;
|
|
}
|
|
if (changed) {
|
|
setChanged();
|
|
sendData();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Eject Items
|
|
Vec3 outPos = VecHelper.getCenterOf(worldPosition)
|
|
.add(itemMovement.scale(.5f)
|
|
.add(0, .5, 0));
|
|
Vec3 outMotion = itemMovement.scale(.0625)
|
|
.add(0, .125, 0);
|
|
for (int slot = 0; slot < inventory.getSlots(); slot++) {
|
|
ItemStack stack = inventory.getStackInSlot(slot);
|
|
if (stack.isEmpty())
|
|
continue;
|
|
ItemEntity entityIn = new ItemEntity(level, outPos.x, outPos.y, outPos.z, stack);
|
|
entityIn.setDeltaMovement(outMotion);
|
|
level.addFreshEntity(entityIn);
|
|
}
|
|
inventory.clear();
|
|
level.updateNeighbourForOutputSignal(worldPosition, getBlockState().getBlock());
|
|
inventory.remainingTime = -1;
|
|
sendData();
|
|
}
|
|
|
|
@Override
|
|
public void invalidate() {
|
|
super.invalidate();
|
|
invProvider.invalidate();
|
|
}
|
|
|
|
@Override
|
|
public void destroy() {
|
|
super.destroy();
|
|
ItemHelper.dropContents(level, worldPosition, inventory);
|
|
}
|
|
|
|
@Override
|
|
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
|
|
if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY && side != Direction.DOWN)
|
|
return invProvider.cast();
|
|
return super.getCapability(cap, side);
|
|
}
|
|
|
|
protected void spawnEventParticles(ItemStack stack) {
|
|
if (stack == null || stack.isEmpty())
|
|
return;
|
|
|
|
ParticleOptions particleData = null;
|
|
if (stack.getItem() instanceof BlockItem)
|
|
particleData = new BlockParticleOption(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock()
|
|
.defaultBlockState());
|
|
else
|
|
particleData = new ItemParticleOption(ParticleTypes.ITEM, stack);
|
|
|
|
Random r = level.random;
|
|
Vec3 v = VecHelper.getCenterOf(this.worldPosition)
|
|
.add(0, 5 / 16f, 0);
|
|
for (int i = 0; i < 10; i++) {
|
|
Vec3 m = VecHelper.offsetRandomly(new Vec3(0, 0.25f, 0), r, .125f);
|
|
level.addParticle(particleData, v.x, v.y, v.z, m.x, m.y, m.y);
|
|
}
|
|
}
|
|
|
|
protected void spawnParticles(ItemStack stack) {
|
|
if (stack == null || stack.isEmpty())
|
|
return;
|
|
|
|
ParticleOptions particleData = null;
|
|
float speed = 1;
|
|
if (stack.getItem() instanceof BlockItem)
|
|
particleData = new BlockParticleOption(ParticleTypes.BLOCK, ((BlockItem) stack.getItem()).getBlock()
|
|
.defaultBlockState());
|
|
else {
|
|
particleData = new ItemParticleOption(ParticleTypes.ITEM, stack);
|
|
speed = .125f;
|
|
}
|
|
|
|
Random r = level.random;
|
|
Vec3 vec = getItemMovementVec();
|
|
Vec3 pos = VecHelper.getCenterOf(this.worldPosition);
|
|
float offset = inventory.recipeDuration != 0 ? (float) (inventory.remainingTime) / inventory.recipeDuration : 0;
|
|
offset /= 2;
|
|
if (inventory.appliedRecipe)
|
|
offset -= .5f;
|
|
level.addParticle(particleData, pos.x() + -vec.x * offset, pos.y() + .45f, pos.z() + -vec.z * offset,
|
|
-vec.x * speed, r.nextFloat() * speed, -vec.z * speed);
|
|
}
|
|
|
|
public Vec3 getItemMovementVec() {
|
|
boolean alongX = !getBlockState().getValue(SawBlock.AXIS_ALONG_FIRST_COORDINATE);
|
|
int offset = getSpeed() < 0 ? -1 : 1;
|
|
return new Vec3(offset * (alongX ? 1 : 0), 0, offset * (alongX ? 0 : -1));
|
|
}
|
|
|
|
private void applyRecipe() {
|
|
List<? extends Recipe<?>> recipes = getRecipes();
|
|
if (recipes.isEmpty())
|
|
return;
|
|
if (recipeIndex >= recipes.size())
|
|
recipeIndex = 0;
|
|
|
|
Recipe<?> recipe = recipes.get(recipeIndex);
|
|
|
|
int rolls = inventory.getStackInSlot(0)
|
|
.getCount();
|
|
inventory.clear();
|
|
|
|
List<ItemStack> list = new ArrayList<>();
|
|
for (int roll = 0; roll < rolls; roll++) {
|
|
List<ItemStack> results = new LinkedList<ItemStack>();
|
|
if (recipe instanceof CuttingRecipe)
|
|
results = ((CuttingRecipe) recipe).rollResults();
|
|
else if (recipe instanceof StonecutterRecipe || recipe.getType() == woodcuttingRecipeType.get())
|
|
results.add(recipe.getResultItem()
|
|
.copy());
|
|
|
|
for (int i = 0; i < results.size(); i++) {
|
|
ItemStack stack = results.get(i);
|
|
ItemHelper.addToList(stack, list);
|
|
}
|
|
}
|
|
|
|
for (int slot = 0; slot < list.size() && slot + 1 < inventory.getSlots(); slot++)
|
|
inventory.setStackInSlot(slot + 1, list.get(slot));
|
|
|
|
award(AllAdvancements.SAW_PROCESSING);
|
|
}
|
|
|
|
private List<? extends Recipe<?>> getRecipes() {
|
|
Optional<CuttingRecipe> assemblyRecipe = SequencedAssemblyRecipe.getRecipe(level, inventory.getStackInSlot(0),
|
|
AllRecipeTypes.CUTTING.getType(), CuttingRecipe.class);
|
|
if (assemblyRecipe.isPresent() && filtering.test(assemblyRecipe.get()
|
|
.getResultItem()))
|
|
return ImmutableList.of(assemblyRecipe.get());
|
|
|
|
Predicate<Recipe<?>> types = RecipeConditions.isOfType(AllRecipeTypes.CUTTING.getType(),
|
|
AllConfigs.server().recipes.allowStonecuttingOnSaw.get() ? RecipeType.STONECUTTING : null,
|
|
AllConfigs.server().recipes.allowWoodcuttingOnSaw.get() ? woodcuttingRecipeType.get() : null);
|
|
|
|
List<Recipe<?>> startedSearch = RecipeFinder.get(cuttingRecipesKey, level, types);
|
|
return startedSearch.stream()
|
|
.filter(RecipeConditions.outputMatchesFilter(filtering))
|
|
.filter(RecipeConditions.firstIngredientMatches(inventory.getStackInSlot(0)))
|
|
.filter(r -> !AllRecipeTypes.shouldIgnoreInAutomation(r))
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
public void insertItem(ItemEntity entity) {
|
|
if (!canProcess())
|
|
return;
|
|
if (!inventory.isEmpty())
|
|
return;
|
|
if (!entity.isAlive())
|
|
return;
|
|
if (level.isClientSide)
|
|
return;
|
|
|
|
inventory.clear();
|
|
ItemStack remainder = inventory.insertItem(0, entity.getItem()
|
|
.copy(), false);
|
|
if (remainder.isEmpty())
|
|
entity.discard();
|
|
else
|
|
entity.setItem(remainder);
|
|
}
|
|
|
|
public void start(ItemStack inserted) {
|
|
if (!canProcess())
|
|
return;
|
|
if (inventory.isEmpty())
|
|
return;
|
|
if (level.isClientSide && !isVirtual())
|
|
return;
|
|
|
|
List<? extends Recipe<?>> recipes = getRecipes();
|
|
boolean valid = !recipes.isEmpty();
|
|
int time = 50;
|
|
|
|
if (recipes.isEmpty()) {
|
|
inventory.remainingTime = inventory.recipeDuration = 10;
|
|
inventory.appliedRecipe = false;
|
|
sendData();
|
|
return;
|
|
}
|
|
|
|
if (valid) {
|
|
recipeIndex++;
|
|
if (recipeIndex >= recipes.size())
|
|
recipeIndex = 0;
|
|
}
|
|
|
|
Recipe<?> recipe = recipes.get(recipeIndex);
|
|
if (recipe instanceof CuttingRecipe) {
|
|
time = ((CuttingRecipe) recipe).getProcessingDuration();
|
|
}
|
|
|
|
inventory.remainingTime = time * Math.max(1, (inserted.getCount() / 5));
|
|
inventory.recipeDuration = inventory.remainingTime;
|
|
inventory.appliedRecipe = false;
|
|
sendData();
|
|
}
|
|
|
|
protected boolean canProcess() {
|
|
return getBlockState().getValue(SawBlock.FACING) == Direction.UP;
|
|
}
|
|
|
|
// Block Breaker
|
|
|
|
@Override
|
|
protected boolean shouldRun() {
|
|
return getBlockState().getValue(SawBlock.FACING)
|
|
.getAxis()
|
|
.isHorizontal();
|
|
}
|
|
|
|
@Override
|
|
protected BlockPos getBreakingPos() {
|
|
return getBlockPos().relative(getBlockState().getValue(SawBlock.FACING));
|
|
}
|
|
|
|
@Override
|
|
public void onBlockBroken(BlockState stateToBreak) {
|
|
Optional<AbstractBlockBreakQueue> dynamicTree =
|
|
TreeCutter.findDynamicTree(stateToBreak.getBlock(), breakingPos);
|
|
if (dynamicTree.isPresent()) {
|
|
dynamicTree.get()
|
|
.destroyBlocks(level, null, this::dropItemFromCutTree);
|
|
return;
|
|
}
|
|
|
|
super.onBlockBroken(stateToBreak);
|
|
TreeCutter.findTree(level, breakingPos)
|
|
.destroyBlocks(level, null, this::dropItemFromCutTree);
|
|
}
|
|
|
|
public void dropItemFromCutTree(BlockPos pos, ItemStack stack) {
|
|
float distance = (float) Math.sqrt(pos.distSqr(breakingPos));
|
|
Vec3 dropPos = VecHelper.getCenterOf(pos);
|
|
ItemEntity entity = new ItemEntity(level, dropPos.x, dropPos.y, dropPos.z, stack);
|
|
entity.setDeltaMovement(Vec3.atLowerCornerOf(breakingPos.subtract(this.worldPosition))
|
|
.scale(distance / 20f));
|
|
level.addFreshEntity(entity);
|
|
}
|
|
|
|
@Override
|
|
public boolean canBreak(BlockState stateToBreak, float blockHardness) {
|
|
boolean sawable = isSawable(stateToBreak);
|
|
return super.canBreak(stateToBreak, blockHardness) && sawable;
|
|
}
|
|
|
|
public static boolean isSawable(BlockState stateToBreak) {
|
|
if (stateToBreak.is(BlockTags.SAPLINGS))
|
|
return false;
|
|
if (TreeCutter.isLog(stateToBreak) || (stateToBreak.is(BlockTags.LEAVES)))
|
|
return true;
|
|
Block block = stateToBreak.getBlock();
|
|
if (block instanceof BambooBlock)
|
|
return true;
|
|
if (block instanceof StemGrownBlock)
|
|
return true;
|
|
if (block instanceof CactusBlock)
|
|
return true;
|
|
if (block instanceof SugarCaneBlock)
|
|
return true;
|
|
if (block instanceof KelpPlantBlock)
|
|
return true;
|
|
if (block instanceof KelpBlock)
|
|
return true;
|
|
if (block instanceof ChorusPlantBlock)
|
|
return true;
|
|
if (TreeCutter.canDynamicTreeCutFrom(block))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
}
|