Mechanical Crafting

- Crafters now apply crafting recipes to held items and play an animation
- Connected textures now also apply to the sides
- Fixed inventory manipulating behaviours not initializing in time
This commit is contained in:
simibubi 2020-01-17 13:30:32 +01:00
parent cea90d50e4
commit d40fd52043
12 changed files with 708 additions and 33 deletions

View file

@ -22,6 +22,7 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItem;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@ -96,6 +97,8 @@ public class ValueBoxRenderer {
return NUDGE; return NUDGE;
if (block instanceof FenceBlock) if (block instanceof FenceBlock)
return NUDGE; return NUDGE;
if (block.isIn(BlockTags.BUTTONS))
return NUDGE;
if (block == Blocks.END_ROD) if (block == Blocks.END_ROD)
return NUDGE; return NUDGE;
} }

View file

@ -17,12 +17,15 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
private Map<IBehaviourType<?>, TileEntityBehaviour> behaviours; private Map<IBehaviourType<?>, TileEntityBehaviour> behaviours;
private boolean initialized; private boolean initialized;
private boolean firstNbtRead; private boolean firstNbtRead;
private int lazyTickRate;
private int lazyTickCounter;
public SmartTileEntity(TileEntityType<?> tileEntityTypeIn) { public SmartTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn); super(tileEntityTypeIn);
behaviours = new HashMap<>(); behaviours = new HashMap<>();
initialized = false; initialized = false;
firstNbtRead = true; firstNbtRead = true;
setLazyTickRate(10);
ArrayList<TileEntityBehaviour> list = new ArrayList<>(); ArrayList<TileEntityBehaviour> list = new ArrayList<>();
addBehaviours(list); addBehaviours(list);
@ -45,6 +48,11 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
initialized = true; initialized = true;
} }
if (lazyTickCounter-- <= 0) {
lazyTickCounter = lazyTickRate;
lazyTick();
}
behaviours.values().forEach(TileEntityBehaviour::tick); behaviours.values().forEach(TileEntityBehaviour::tick);
} }
@ -83,6 +91,15 @@ public abstract class SmartTileEntity extends SyncedTileEntity implements ITicka
super.remove(); super.remove();
} }
public void setLazyTickRate(int slowTickRate) {
this.lazyTickRate = slowTickRate;
this.lazyTickCounter = slowTickRate;
}
public void lazyTick() {
}
protected void forEachBehaviour(Consumer<TileEntityBehaviour> action) { protected void forEachBehaviour(Consumer<TileEntityBehaviour> action) {
behaviours.values().forEach(tb -> { behaviours.values().forEach(tb -> {
if (!tb.isPaused()) if (!tb.isPaused())

View file

@ -43,6 +43,7 @@ public class InventoryManagementBehaviour extends TileEntityBehaviour {
public void initialize() { public void initialize() {
super.initialize(); super.initialize();
attachments.get().forEach(offset -> inventories.put(offset, findInventory(offset))); attachments.get().forEach(offset -> inventories.put(offset, findInventory(offset)));
lazyTick();
} }
@Override @Override

View file

@ -272,6 +272,8 @@ public abstract class KineticTileEntity extends SmartTileEntity implements ITick
@Override @Override
public void tick() { public void tick() {
super.tick();
if (world.isRemote) if (world.isRemote)
return; return;
if (speedChangeCounter > 0) if (speedChangeCounter > 0)

View file

@ -36,7 +36,6 @@ import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT; import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent;
@ -126,8 +125,8 @@ public class ConnectedInputHandler {
} }
public static void toggleConnection(World world, BlockPos pos, BlockPos pos2) { public static void toggleConnection(World world, BlockPos pos, BlockPos pos2) {
MechanicalCrafterTileEntity crafter1 = getCrafter(world, pos); MechanicalCrafterTileEntity crafter1 = CrafterHelper.getCrafter(world, pos);
MechanicalCrafterTileEntity crafter2 = getCrafter(world, pos2); MechanicalCrafterTileEntity crafter2 = CrafterHelper.getCrafter(world, pos2);
if (crafter1 == null || crafter2 == null) if (crafter1 == null || crafter2 == null)
return; return;
@ -136,7 +135,7 @@ public class ConnectedInputHandler {
BlockPos controllerPos2 = crafter2.getPos().add(crafter2.input.data.get(0)); BlockPos controllerPos2 = crafter2.getPos().add(crafter2.input.data.get(0));
if (controllerPos1.equals(controllerPos2)) { if (controllerPos1.equals(controllerPos2)) {
MechanicalCrafterTileEntity controller = getCrafter(world, controllerPos1); MechanicalCrafterTileEntity controller = CrafterHelper.getCrafter(world, controllerPos1);
Set<BlockPos> positions = controller.input.data.stream().map(l -> controllerPos1.add(l)) Set<BlockPos> positions = controller.input.data.stream().map(l -> controllerPos1.add(l))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@ -168,9 +167,9 @@ public class ConnectedInputHandler {
} }
if (!crafter1.input.isController) if (!crafter1.input.isController)
crafter1 = getCrafter(world, controllerPos1); crafter1 = CrafterHelper.getCrafter(world, controllerPos1);
if (!crafter2.input.isController) if (!crafter2.input.isController)
crafter2 = getCrafter(world, controllerPos2); crafter2 = CrafterHelper.getCrafter(world, controllerPos2);
if (crafter1 == null || crafter2 == null) if (crafter1 == null || crafter2 == null)
return; return;
@ -217,18 +216,6 @@ public class ConnectedInputHandler {
crafter1.input.data.add(BlockPos.ZERO.subtract(crafter2.input.data.get(0))); crafter1.input.data.add(BlockPos.ZERO.subtract(crafter2.input.data.get(0)));
} }
public static MechanicalCrafterTileEntity getCrafter(IEnviromentBlockReader reader, BlockPos pos) {
TileEntity te = reader.getTileEntity(pos);
if (!(te instanceof MechanicalCrafterTileEntity))
return null;
return (MechanicalCrafterTileEntity) te;
}
public static ConnectedInput getInput(IEnviromentBlockReader reader, BlockPos pos) {
MechanicalCrafterTileEntity crafter = getCrafter(reader, pos);
return crafter == null ? null : crafter.input;
}
private static void modifyAndUpdate(World world, BlockPos pos, Consumer<ConnectedInput> callback) { private static void modifyAndUpdate(World world, BlockPos pos, Consumer<ConnectedInput> callback) {
TileEntity te = world.getTileEntity(pos); TileEntity te = world.getTileEntity(pos);
if (!(te instanceof MechanicalCrafterTileEntity)) if (!(te instanceof MechanicalCrafterTileEntity))
@ -258,13 +245,13 @@ public class ConnectedInputHandler {
public IItemHandler getItemHandler(World world, BlockPos pos) { public IItemHandler getItemHandler(World world, BlockPos pos) {
if (!isController) { if (!isController) {
BlockPos controllerPos = pos.add(data.get(0)); BlockPos controllerPos = pos.add(data.get(0));
ConnectedInput input = getInput(world, controllerPos); ConnectedInput input = CrafterHelper.getInput(world, controllerPos);
if (input == this || input == null || !input.isController) if (input == this || input == null || !input.isController)
return new ItemStackHandler(); return new ItemStackHandler();
return input.getItemHandler(world, controllerPos); return input.getItemHandler(world, controllerPos);
} }
List<IItemHandlerModifiable> list = data.stream().map(l -> getCrafter(world, pos.add(l))) List<IItemHandlerModifiable> list = data.stream().map(l -> CrafterHelper.getCrafter(world, pos.add(l)))
.filter(Predicates.notNull()).map(crafter -> crafter.inventory).collect(Collectors.toList()); .filter(Predicates.notNull()).map(crafter -> crafter.inventory).collect(Collectors.toList());
return new CombinedInvWrapper(Arrays.copyOf(list.toArray(), list.size(), IItemHandlerModifiable[].class)); return new CombinedInvWrapper(Arrays.copyOf(list.toArray(), list.size(), IItemHandlerModifiable[].class));
} }

View file

@ -0,0 +1,21 @@
package com.simibubi.create.modules.contraptions.components.crafter;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
public class CrafterHelper {
public static MechanicalCrafterTileEntity getCrafter(IEnviromentBlockReader reader, BlockPos pos) {
TileEntity te = reader.getTileEntity(pos);
if (!(te instanceof MechanicalCrafterTileEntity))
return null;
return (MechanicalCrafterTileEntity) te;
}
public static ConnectedInputHandler.ConnectedInput getInput(IEnviromentBlockReader reader, BlockPos pos) {
MechanicalCrafterTileEntity crafter = getCrafter(reader, pos);
return crafter == null ? null : crafter.input;
}
}

View file

@ -31,8 +31,8 @@ public class InputCTBehaviour extends ConnectedTextureBehaviour {
if (state.get(HORIZONTAL_FACING) != other.get(HORIZONTAL_FACING)) if (state.get(HORIZONTAL_FACING) != other.get(HORIZONTAL_FACING))
return false; return false;
ConnectedInput input1 = ConnectedInputHandler.getInput(reader, pos); ConnectedInput input1 = CrafterHelper.getInput(reader, pos);
ConnectedInput input2 = ConnectedInputHandler.getInput(reader, otherPos); ConnectedInput input2 = CrafterHelper.getInput(reader, otherPos);
if (input1 == null || input2 == null) if (input1 == null || input2 == null)
return false; return false;

View file

@ -1,6 +1,7 @@
package com.simibubi.create.modules.contraptions.components.crafter; package com.simibubi.create.modules.contraptions.components.crafter;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllItems;
import com.simibubi.create.foundation.block.IWithTileEntity; import com.simibubi.create.foundation.block.IWithTileEntity;
import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour; import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour;
import com.simibubi.create.foundation.block.connected.IHaveConnectedTextures; import com.simibubi.create.foundation.block.connected.IHaveConnectedTextures;
@ -9,6 +10,7 @@ import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.base.HorizontalKineticBlock; import com.simibubi.create.modules.contraptions.base.HorizontalKineticBlock;
import com.simibubi.create.modules.contraptions.components.crafter.ConnectedInputHandler.ConnectedInput; import com.simibubi.create.modules.contraptions.components.crafter.ConnectedInputHandler.ConnectedInput;
import com.simibubi.create.modules.contraptions.components.crafter.MechanicalCrafterTileEntity.Phase;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -90,14 +92,26 @@ public class MechanicalCrafterBlock extends HorizontalKineticBlock
@Override @Override
public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) { public void onReplaced(BlockState state, World worldIn, BlockPos pos, BlockState newState, boolean isMoving) {
if (state.getBlock() == newState.getBlock()) {
if (getTargetDirection(state) != getTargetDirection(newState)) {
MechanicalCrafterTileEntity crafter = CrafterHelper.getCrafter(worldIn, pos);
if (crafter != null)
crafter.blockChanged();
}
}
if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) { if (state.hasTileEntity() && state.getBlock() != newState.getBlock()) {
MechanicalCrafterTileEntity crafter = CrafterHelper.getCrafter(worldIn, pos);
if (crafter != null)
crafter.ejectWholeGrid();
for (Direction direction : Direction.values()) { for (Direction direction : Direction.values()) {
if (direction.getAxis() == state.get(HORIZONTAL_FACING).getAxis()) if (direction.getAxis() == state.get(HORIZONTAL_FACING).getAxis())
continue; continue;
BlockPos otherPos = pos.offset(direction); BlockPos otherPos = pos.offset(direction);
ConnectedInput thisInput = ConnectedInputHandler.getInput(worldIn, pos); ConnectedInput thisInput = CrafterHelper.getInput(worldIn, pos);
ConnectedInput otherInput = ConnectedInputHandler.getInput(worldIn, otherPos); ConnectedInput otherInput = CrafterHelper.getInput(worldIn, otherPos);
if (thisInput == null || otherInput == null) if (thisInput == null || otherInput == null)
continue; continue;
@ -130,6 +144,7 @@ public class MechanicalCrafterBlock extends HorizontalKineticBlock
public ActionResultType onWrenched(BlockState state, ItemUseContext context) { public ActionResultType onWrenched(BlockState state, ItemUseContext context) {
if (context.getFace() == state.get(HORIZONTAL_FACING)) { if (context.getFace() == state.get(HORIZONTAL_FACING)) {
context.getWorld().setBlockState(context.getPos(), state.cycle(POINTING)); context.getWorld().setBlockState(context.getPos(), state.cycle(POINTING));
withTileEntityDo(context.getWorld(), context.getPos(), TileEntity::markDirty);
return ActionResultType.SUCCESS; return ActionResultType.SUCCESS;
} }
@ -148,6 +163,12 @@ public class MechanicalCrafterBlock extends HorizontalKineticBlock
MechanicalCrafterTileEntity crafter = (MechanicalCrafterTileEntity) te; MechanicalCrafterTileEntity crafter = (MechanicalCrafterTileEntity) te;
if (hit.getFace() == state.get(HORIZONTAL_FACING)) { if (hit.getFace() == state.get(HORIZONTAL_FACING)) {
if (crafter.phase != Phase.IDLE && !AllItems.WRENCH.typeOf(heldItem)) {
crafter.ejectWholeGrid();
return true;
}
ItemStack inSlot = crafter.inventory.getStackInSlot(0); ItemStack inSlot = crafter.inventory.getStackInSlot(0);
if (inSlot.isEmpty()) if (inSlot.isEmpty())
return false; return false;

View file

@ -1,12 +1,34 @@
package com.simibubi.create.modules.contraptions.components.crafter; package com.simibubi.create.modules.contraptions.components.crafter;
import static com.simibubi.create.modules.contraptions.base.HorizontalKineticBlock.HORIZONTAL_FACING;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour;
import com.simibubi.create.foundation.behaviour.inventory.InsertingBehaviour;
import com.simibubi.create.foundation.behaviour.inventory.InventoryManagementBehaviour.Attachments;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.base.KineticTileEntity; import com.simibubi.create.modules.contraptions.base.KineticTileEntity;
import com.simibubi.create.modules.contraptions.components.crafter.ConnectedInputHandler.ConnectedInput; import com.simibubi.create.modules.contraptions.components.crafter.ConnectedInputHandler.ConnectedInput;
import com.simibubi.create.modules.contraptions.components.crafter.MechanicalCrafterBlock.Pointing;
import com.simibubi.create.modules.contraptions.components.crafter.RecipeGridHandler.GroupedItems;
import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.particles.ItemParticleData;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.items.CapabilityItemHandler;
@ -16,7 +38,7 @@ import net.minecraftforge.items.ItemStackHandler;
public class MechanicalCrafterTileEntity extends KineticTileEntity { public class MechanicalCrafterTileEntity extends KineticTileEntity {
enum Phase { enum Phase {
IDLE, ACCEPTING, ASSEMBLING, EXPORTING IDLE, ACCEPTING, ASSEMBLING, EXPORTING, WAITING, CRAFTING, INSERTING;
} }
protected ItemStackHandler inventory = new ItemStackHandler(1) { protected ItemStackHandler inventory = new ItemStackHandler(1) {
@ -30,19 +52,53 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
return ItemStack.EMPTY; return ItemStack.EMPTY;
}; };
public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
if (phase != Phase.IDLE)
return stack;
return super.insertItem(slot, stack, simulate);
};
protected void onContentsChanged(int slot) { protected void onContentsChanged(int slot) {
if (!getStackInSlot(slot).isEmpty() && phase == Phase.IDLE)
checkCompletedRecipe();
markDirty(); markDirty();
sendData(); sendData();
}; };
}; };
protected GroupedItems groupedItems = new GroupedItems();
protected ConnectedInput input = new ConnectedInput(); protected ConnectedInput input = new ConnectedInput();
protected LazyOptional<IItemHandler> invSupplier = LazyOptional.of(() -> input.getItemHandler(world, pos)); protected LazyOptional<IItemHandler> invSupplier = LazyOptional.of(() -> input.getItemHandler(world, pos));
protected boolean reRender; protected boolean reRender;
protected Phase phase;
protected int countDown;
protected GroupedItems groupedItemsBeforeCraft; // for rendering on client
private InsertingBehaviour inserting;
public MechanicalCrafterTileEntity() { public MechanicalCrafterTileEntity() {
super(AllTileEntities.MECHANICAL_CRAFTER.type); super(AllTileEntities.MECHANICAL_CRAFTER.type);
setLazyTickRate(20);
phase = Phase.IDLE;
groupedItemsBeforeCraft = new GroupedItems();
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {
super.addBehaviours(behaviours);
inserting = new InsertingBehaviour(this, Attachments.toward(this::getTargetFacing));
behaviours.add(inserting);
}
public void blockChanged() {
removeBehaviour(InsertingBehaviour.TYPE);
inserting = new InsertingBehaviour(this, Attachments.toward(this::getTargetFacing));
putBehaviour(inserting);
}
public Direction getTargetFacing() {
return MechanicalCrafterBlock.getTargetDirection(world.getBlockState(pos));
} }
@Override @Override
@ -53,9 +109,18 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
@Override @Override
public CompoundNBT write(CompoundNBT compound) { public CompoundNBT write(CompoundNBT compound) {
compound.put("Inventory", inventory.serializeNBT()); compound.put("Inventory", inventory.serializeNBT());
CompoundNBT inputNBT = new CompoundNBT(); CompoundNBT inputNBT = new CompoundNBT();
input.write(inputNBT); input.write(inputNBT);
compound.put("ConnectedInput", inputNBT); compound.put("ConnectedInput", inputNBT);
CompoundNBT groupedItemsNBT = new CompoundNBT();
groupedItems.write(groupedItemsNBT);
compound.put("GroupedItems", groupedItemsNBT);
compound.putString("Phase", phase.name());
compound.putInt("CountDown", countDown);
return super.write(compound); return super.write(compound);
} }
@ -72,13 +137,35 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
public void readClientUpdate(CompoundNBT tag) { public void readClientUpdate(CompoundNBT tag) {
if (tag.contains("Redraw")) if (tag.contains("Redraw"))
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16); world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
Phase phaseBefore = phase;
GroupedItems before = this.groupedItems;
super.readClientUpdate(tag); super.readClientUpdate(tag);
if (phaseBefore != phase && phase == Phase.CRAFTING)
groupedItemsBeforeCraft = before;
if (phaseBefore == Phase.EXPORTING && phase == Phase.WAITING) {
Direction facing = getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING);
Vec3d vec = new Vec3d(facing.getDirectionVec()).scale(.75).add(VecHelper.getCenterOf(pos));
Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(getBlockState());
vec = vec.add(new Vec3d(targetDirection.getDirectionVec()).scale(1));
world.addParticle(ParticleTypes.CRIT, vec.x, vec.y, vec.z, 0, 0, 0);
}
} }
@Override @Override
public void read(CompoundNBT compound) { public void read(CompoundNBT compound) {
inventory.deserializeNBT(compound.getCompound("Inventory")); inventory.deserializeNBT(compound.getCompound("Inventory"));
input.read(compound.getCompound("ConnectedInput")); input.read(compound.getCompound("ConnectedInput"));
groupedItems = GroupedItems.read(compound.getCompound("GroupedItems"));
phase = Phase.IDLE;
String name = compound.getString("Phase");
for (Phase phase : Phase.values())
if (phase.name().equals(name))
this.phase = phase;
countDown = compound.getInt("CountDown");
super.read(compound); super.read(compound);
} }
@ -88,10 +175,244 @@ public class MechanicalCrafterTileEntity extends KineticTileEntity {
super.remove(); super.remove();
} }
public int getCountDownSpeed() {
if (getSpeed() == 0)
return 0;
return MathHelper.clamp((int) Math.abs(getSpeed() / 2), 1, 250);
}
@Override
public void tick() {
super.tick();
if (phase == Phase.ACCEPTING)
return;
if (phase == Phase.ASSEMBLING) {
countDown -= getCountDownSpeed();
if (countDown < 0) {
countDown = 0;
if (world.isRemote)
return;
if (RecipeGridHandler.getTargetingCrafter(this) != null) {
phase = Phase.EXPORTING;
countDown = 1000;
sendData();
return;
}
ItemStack result = RecipeGridHandler.tryToApplyRecipe(world, groupedItems);
if (result != null) {
groupedItems = new GroupedItems(result);
phase = Phase.CRAFTING;
countDown = 2000;
sendData();
return;
}
ejectWholeGrid();
return;
}
}
if (phase == Phase.EXPORTING) {
countDown -= getCountDownSpeed();
if (countDown < 0) {
countDown = 0;
if (world.isRemote)
return;
MechanicalCrafterTileEntity targetingCrafter = RecipeGridHandler.getTargetingCrafter(this);
if (targetingCrafter == null) {
ejectWholeGrid();
return;
}
Pointing pointing = getBlockState().get(MechanicalCrafterBlock.POINTING);
groupedItems.mergeOnto(targetingCrafter.groupedItems, pointing);
groupedItems = new GroupedItems();
phase = Phase.WAITING;
countDown = 0;
sendData();
targetingCrafter.continueIfAllPrecedingFinished();
targetingCrafter.sendData();
return;
}
}
if (phase == Phase.CRAFTING) {
if (world.isRemote) {
Direction facing = getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING);
float progress = countDown / 2000f;
Vec3d facingVec = new Vec3d(facing.getDirectionVec());
Vec3d vec = facingVec.scale(.65).add(VecHelper.getCenterOf(pos));
Vec3d offset = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f)
.mul(VecHelper.planeByNormal(facingVec)).normalize().scale(progress * .5f).add(vec);
if (progress > .5f)
world.addParticle(ParticleTypes.CRIT, offset.x, offset.y, offset.z, 0, 0, 0);
if (!groupedItemsBeforeCraft.grid.isEmpty() && progress < .5f) {
if (groupedItems.grid.containsKey(Pair.of(0, 0))) {
ItemStack stack = groupedItems.grid.get(Pair.of(0, 0));
groupedItemsBeforeCraft = new GroupedItems();
for (int i = 0; i < 10; i++) {
Vec3d randVec = VecHelper.offsetRandomly(Vec3d.ZERO, world.rand, .125f)
.mul(VecHelper.planeByNormal(facingVec)).normalize().scale(.25f);
Vec3d offset2 = randVec.add(vec);
randVec = randVec.scale(.35f);
world.addParticle(new ItemParticleData(ParticleTypes.ITEM, stack), offset2.x, offset2.y,
offset2.z, randVec.x, randVec.y, randVec.z);
}
}
}
}
countDown -= getCountDownSpeed();
if (countDown < 0) {
countDown = 0;
if (world.isRemote)
return;
tryInsert();
return;
}
}
if (phase == Phase.INSERTING) {
if (!world.isRemote && isTargetingBelt())
tryInsert();
return;
}
}
protected boolean isTargetingBelt() {
BlockPos targetPos = pos.offset(getTargetFacing());
if (!AllBlocks.BELT.typeOf(world.getBlockState(targetPos)))
return false;
TileEntity te = world.getTileEntity(targetPos);
if (te == null || !(te instanceof BeltTileEntity))
return false;
return ((KineticTileEntity) te).getSpeed() != 0;
}
public void tryInsert() {
if (inserting.getInventory() == null && !isTargetingBelt()) {
ejectWholeGrid();
return;
}
boolean chagedPhase = phase != Phase.INSERTING;
final List<Pair<Integer, Integer>> inserted = new LinkedList<>();
groupedItems.grid.forEach((pair, stack) -> {
if (isTargetingBelt()) {
Direction facing = getTargetFacing();
BlockPos targetPos = pos.offset(facing);
BeltTileEntity te = (BeltTileEntity) world.getTileEntity(targetPos);
if (te.tryInsertingFromSide(facing, stack, false))
inserted.add(pair);
return;
}
ItemStack remainder = inserting.insert(stack.copy(), false);
if (!remainder.isEmpty())
stack.setCount(remainder.getCount());
else
inserted.add(pair);
});
inserted.forEach(groupedItems.grid::remove);
if (groupedItems.grid.isEmpty())
ejectWholeGrid();
else
phase = Phase.INSERTING;
if (!inserted.isEmpty() || chagedPhase)
sendData();
}
public void ejectWholeGrid() {
List<MechanicalCrafterTileEntity> chain = RecipeGridHandler.getAllCraftersOfChain(this);
if (chain == null)
return;
chain.forEach(MechanicalCrafterTileEntity::eject);
}
public void eject() {
Vec3d ejectPos = VecHelper.getCenterOf(pos)
.add(new Vec3d(getBlockState().get(HORIZONTAL_FACING).getDirectionVec()).scale(.75f));
groupedItems.grid.forEach((pair, stack) -> dropItem(ejectPos, stack));
if (!inventory.getStackInSlot(0).isEmpty())
dropItem(ejectPos, inventory.getStackInSlot(0));
phase = Phase.IDLE;
groupedItems = new GroupedItems();
inventory.setStackInSlot(0, ItemStack.EMPTY);
sendData();
}
public void dropItem(Vec3d ejectPos, ItemStack stack) {
ItemEntity itemEntity = new ItemEntity(world, ejectPos.x, ejectPos.y, ejectPos.z, stack);
itemEntity.setDefaultPickupDelay();
world.addEntity(itemEntity);
}
@Override
public void lazyTick() {
super.lazyTick();
if (world.isRemote)
return;
if (phase == Phase.IDLE && craftingItemPresent())
checkCompletedRecipe();
if (phase == Phase.INSERTING)
tryInsert();
}
public boolean craftingItemPresent() {
return !inventory.getStackInSlot(0).isEmpty();
}
protected void checkCompletedRecipe() {
if (getSpeed() == 0)
return;
if (world.isRemote)
return;
List<MechanicalCrafterTileEntity> chain = RecipeGridHandler.getAllCraftersOfChainIf(this,
MechanicalCrafterTileEntity::craftingItemPresent);
if (chain == null)
return;
chain.forEach(MechanicalCrafterTileEntity::begin);
}
protected void begin() {
phase = Phase.ACCEPTING;
groupedItems = new GroupedItems(inventory.getStackInSlot(0));
inventory.setStackInSlot(0, ItemStack.EMPTY);
if (RecipeGridHandler.getPrecedingCrafters(this).isEmpty()) {
phase = Phase.ASSEMBLING;
countDown = 500;
}
sendData();
}
protected void continueIfAllPrecedingFinished() {
List<MechanicalCrafterTileEntity> preceding = RecipeGridHandler.getPrecedingCrafters(this);
if (preceding == null) {
ejectWholeGrid();
return;
}
for (MechanicalCrafterTileEntity mechanicalCrafterTileEntity : preceding)
if (mechanicalCrafterTileEntity.phase != Phase.WAITING)
return;
phase = Phase.ASSEMBLING;
countDown = Math.max(100, getCountDownSpeed() + 1);
}
@Override @Override
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) { public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
if (getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING) == side) if (getBlockState().get(HORIZONTAL_FACING) == side)
return LazyOptional.empty(); return LazyOptional.empty();
return invSupplier.cast(); return invSupplier.cast();
} }

View file

@ -3,10 +3,15 @@ package com.simibubi.create.modules.contraptions.components.crafter;
import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.GlStateManager;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.block.render.SpriteShiftEntry;
import com.simibubi.create.foundation.block.render.SpriteShifter;
import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.SuperByteBuffer; import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.TessellatorHelper; import com.simibubi.create.foundation.utility.TessellatorHelper;
import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer; import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer;
import com.simibubi.create.modules.contraptions.components.crafter.MechanicalCrafterTileEntity.Phase;
import com.simibubi.create.modules.contraptions.components.crafter.RecipeGridHandler.GroupedItems;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -21,11 +26,15 @@ import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class MechanicalCrafterTileEntityRenderer extends TileEntityRenderer<MechanicalCrafterTileEntity> { public class MechanicalCrafterTileEntityRenderer extends TileEntityRenderer<MechanicalCrafterTileEntity> {
public static SpriteShiftEntry animatedTexture = SpriteShifter.get("block/crafter_thingies",
"block/crafter_thingies");
@Override @Override
public void render(MechanicalCrafterTileEntity te, double x, double y, double z, float partialTicks, public void render(MechanicalCrafterTileEntity te, double x, double y, double z, float partialTicks,
int destroyStage) { int destroyStage) {
@ -36,11 +45,19 @@ public class MechanicalCrafterTileEntityRenderer extends TileEntityRenderer<Mech
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
Direction facing = te.getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING); Direction facing = te.getBlockState().get(MechanicalCrafterBlock.HORIZONTAL_FACING);
Vec3d vec = new Vec3d(facing.getDirectionVec()).scale(.58).add(.5, .5, .5); Vec3d vec = new Vec3d(facing.getDirectionVec()).scale(.58).add(.5, .5, .5);
if (te.phase == Phase.EXPORTING) {
Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(te.getBlockState());
float progress = MathHelper.clamp((1000 - te.countDown + te.getCountDownSpeed() * partialTicks) / 1000f, 0,
1);
vec = vec.add(new Vec3d(targetDirection.getDirectionVec()).scale(progress * .75f));
}
GlStateManager.translated(x + vec.x, y + vec.y, z + vec.z); GlStateManager.translated(x + vec.x, y + vec.y, z + vec.z);
GlStateManager.scalef(1 / 2f, 1 / 2f, 1 / 2f); GlStateManager.scalef(1 / 2f, 1 / 2f, 1 / 2f);
float yRot = AngleHelper.horizontalAngle(facing); float yRot = AngleHelper.horizontalAngle(facing);
GlStateManager.rotated(yRot, 0, 1, 0); GlStateManager.rotated(yRot, 0, 1, 0);
renderItems(te); renderItems(te, partialTicks);
GlStateManager.popMatrix(); GlStateManager.popMatrix();
TessellatorHelper.prepareFastRender(); TessellatorHelper.prepareFastRender();
@ -49,12 +66,72 @@ public class MechanicalCrafterTileEntityRenderer extends TileEntityRenderer<Mech
TessellatorHelper.draw(); TessellatorHelper.draw();
} }
public void renderItems(MechanicalCrafterTileEntity te) { public void renderItems(MechanicalCrafterTileEntity te, float partialTicks) {
RenderHelper.enableStandardItemLighting(); RenderHelper.enableStandardItemLighting();
if (te.phase == Phase.IDLE) {
ItemStack stack = te.inventory.getStackInSlot(0); ItemStack stack = te.inventory.getStackInSlot(0);
if (!stack.isEmpty()) if (!stack.isEmpty()) {
GlStateManager.pushMatrix();
GlStateManager.translatef(0, 0, -1 / 256f);
Minecraft.getInstance().getItemRenderer().renderItem(stack, TransformType.FIXED); Minecraft.getInstance().getItemRenderer().renderItem(stack, TransformType.FIXED);
GlStateManager.popMatrix();
}
} else {
// render grouped items
GroupedItems items = te.groupedItems;
float distance = .5f;
GlStateManager.pushMatrix();
if (te.phase == Phase.CRAFTING) {
items = te.groupedItemsBeforeCraft;
items.calcStats();
float progress = MathHelper.clamp((2000 - te.countDown + te.getCountDownSpeed() * partialTicks) / 1000f,
0, 1);
float earlyProgress = MathHelper.clamp(progress * 2, 0, 1);
float lateProgress = MathHelper.clamp(progress * 2 - 1, 0, 1);
// GlStateManager.rotated(lateProgress * 360, 0, 0, 1);
GlStateManager.scaled(1 - lateProgress, 1 - lateProgress, 1 - lateProgress);
Vec3d centering = new Vec3d(-items.minX + (-items.width + 1) / 2f,
-items.minY + (-items.height + 1) / 2f, 0).scale(earlyProgress);
GlStateManager.translated(centering.x * .5f, centering.y * .5f, 0);
distance += (-4 * (progress - .5f) * (progress - .5f) + 1) * .25f;
}
final float spacing = distance;
items.grid.forEach((pair, stack) -> {
GlStateManager.pushMatrix();
GlStateManager.translatef(pair.getKey() * spacing, pair.getValue() * spacing, 0);
TessellatorHelper.fightZFighting(pair.hashCode() + te.getPos().hashCode());
Minecraft.getInstance().getItemRenderer().renderItem(stack, TransformType.FIXED);
GlStateManager.popMatrix();
});
GlStateManager.popMatrix();
if (te.phase == Phase.CRAFTING) {
items = te.groupedItems;
float progress = MathHelper.clamp((1000 - te.countDown + te.getCountDownSpeed() * partialTicks) / 1000f,
0, 1);
float earlyProgress = MathHelper.clamp(progress * 2, 0, 1);
float lateProgress = MathHelper.clamp(progress * 2 - 1, 0, 1);
GlStateManager.rotated(earlyProgress * 2 * 360, 0, 0, 1);
float upScaling = earlyProgress * 1.125f;
float downScaling = 1 + (1 - lateProgress) * .125f;
GlStateManager.scaled(upScaling, upScaling, upScaling);
GlStateManager.scaled(downScaling, downScaling, downScaling);
items.grid.forEach((pair, stack) -> {
Minecraft.getInstance().getItemRenderer().renderItem(stack, TransformType.FIXED);
});
}
}
RenderHelper.disableStandardItemLighting(); RenderHelper.disableStandardItemLighting();
} }
@ -70,13 +147,24 @@ public class MechanicalCrafterTileEntityRenderer extends TileEntityRenderer<Mech
Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(blockState); Direction targetDirection = MechanicalCrafterBlock.getTargetDirection(blockState);
BlockPos pos = te.getPos(); BlockPos pos = te.getPos();
// SuperByteBuffer lidBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_LID, blockState, pos); if (te.phase != Phase.IDLE && te.phase != Phase.CRAFTING && te.phase != Phase.INSERTING) {
// lidBuffer.translate(x, y, z).renderInto(buffer); SuperByteBuffer lidBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_LID, blockState, pos);
lidBuffer.translate(x, y, z).renderInto(buffer);
}
if (MechanicalCrafterBlock.isValidTarget(getWorld(), pos.offset(targetDirection), blockState)) { if (MechanicalCrafterBlock.isValidTarget(getWorld(), pos.offset(targetDirection), blockState)) {
SuperByteBuffer beltBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_BELT, blockState, pos); SuperByteBuffer beltBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_BELT, blockState, pos);
SuperByteBuffer beltFrameBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_BELT_FRAME, blockState, SuperByteBuffer beltFrameBuffer = renderAndTransform(AllBlocks.MECHANICAL_CRAFTER_BELT_FRAME, blockState,
pos); pos);
if (te.phase == Phase.EXPORTING) {
int textureIndex = (int) ((te.getCountDownSpeed() / 128f * AnimationTickHolder.ticks));
beltBuffer.shiftUVtoSheet(animatedTexture.getOriginal(), animatedTexture.getTarget(),
(textureIndex % 4) * 4, 0);
} else {
beltBuffer.shiftUVtoSheet(animatedTexture.getOriginal(), animatedTexture.getTarget(), 0, 0);
}
beltBuffer.translate(x, y, z).renderInto(buffer); beltBuffer.translate(x, y, z).renderInto(buffer);
beltFrameBuffer.translate(x, y, z).renderInto(buffer); beltFrameBuffer.translate(x, y, z).renderInto(buffer);

View file

@ -0,0 +1,214 @@
package com.simibubi.create.modules.contraptions.components.crafter;
import static com.simibubi.create.modules.contraptions.base.HorizontalKineticBlock.HORIZONTAL_FACING;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicates;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.modules.contraptions.components.crafter.MechanicalCrafterBlock.Pointing;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.CraftingInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipeType;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
public class RecipeGridHandler {
public static List<MechanicalCrafterTileEntity> getAllCraftersOfChain(MechanicalCrafterTileEntity root) {
return getAllCraftersOfChainIf(root, Predicates.alwaysTrue());
}
public static List<MechanicalCrafterTileEntity> getAllCraftersOfChainIf(MechanicalCrafterTileEntity root,
Predicate<MechanicalCrafterTileEntity> test) {
List<MechanicalCrafterTileEntity> crafters = new ArrayList<>();
List<Pair<MechanicalCrafterTileEntity, MechanicalCrafterTileEntity>> frontier = new ArrayList<>();
Set<MechanicalCrafterTileEntity> visited = new HashSet<>();
frontier.add(Pair.of(root, null));
while (!frontier.isEmpty()) {
Pair<MechanicalCrafterTileEntity, MechanicalCrafterTileEntity> pair = frontier.remove(0);
MechanicalCrafterTileEntity current = pair.getKey();
MechanicalCrafterTileEntity last = pair.getValue();
if (visited.contains(current) || !test.test(current))
return null;
crafters.add(current);
visited.add(current);
MechanicalCrafterTileEntity target = getTargetingCrafter(current);
if (target != last && target != null)
frontier.add(Pair.of(target, current));
for (MechanicalCrafterTileEntity preceding : getPrecedingCrafters(current))
if (preceding != last)
frontier.add(Pair.of(preceding, current));
}
return crafters;
}
public static MechanicalCrafterTileEntity getTargetingCrafter(MechanicalCrafterTileEntity crafter) {
BlockState state = crafter.getBlockState();
if (!AllBlocks.MECHANICAL_CRAFTER.typeOf(state))
return null;
BlockPos targetPos = crafter.getPos().offset(MechanicalCrafterBlock.getTargetDirection(state));
MechanicalCrafterTileEntity targetTE = CrafterHelper.getCrafter(crafter.getWorld(), targetPos);
if (targetTE == null)
return null;
BlockState targetState = targetTE.getBlockState();
if (!AllBlocks.MECHANICAL_CRAFTER.typeOf(targetState))
return null;
if (state.get(HORIZONTAL_FACING) != targetState.get(HORIZONTAL_FACING))
return null;
return targetTE;
}
public static List<MechanicalCrafterTileEntity> getPrecedingCrafters(MechanicalCrafterTileEntity crafter) {
BlockPos pos = crafter.getPos();
World world = crafter.getWorld();
List<MechanicalCrafterTileEntity> crafters = new ArrayList<>();
BlockState blockState = crafter.getBlockState();
if (!AllBlocks.MECHANICAL_CRAFTER.typeOf(blockState))
return crafters;
Direction blockFacing = blockState.get(HORIZONTAL_FACING);
Direction blockPointing = MechanicalCrafterBlock.getTargetDirection(blockState);
for (Direction facing : Direction.values()) {
if (blockFacing.getAxis() == facing.getAxis())
continue;
if (blockPointing == facing)
continue;
BlockPos neighbourPos = pos.offset(facing);
BlockState neighbourState = world.getBlockState(neighbourPos);
if (!AllBlocks.MECHANICAL_CRAFTER.typeOf(neighbourState))
continue;
if (MechanicalCrafterBlock.getTargetDirection(neighbourState) != facing.getOpposite())
continue;
if (blockFacing != neighbourState.get(HORIZONTAL_FACING))
continue;
MechanicalCrafterTileEntity te = CrafterHelper.getCrafter(world, neighbourPos);
if (te == null)
continue;
crafters.add(te);
}
return crafters;
}
public static ItemStack tryToApplyRecipe(World world, GroupedItems items) {
CraftingInventory craftinginventory = getCraftingInventory(items);
ItemStack result = world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, craftinginventory, world)
.map(r -> r.getCraftingResult(craftinginventory)).orElse(null);
return result;
}
private static CraftingInventory getCraftingInventory(GroupedItems items) {
items.calcStats();
CraftingInventory craftinginventory = new CraftingInventory(new Container(null, -1) {
public boolean canInteractWith(PlayerEntity playerIn) {
return false;
}
}, items.width, items.height);
for (int y = 0; y < items.height; y++) {
for (int x = 0; x < items.width; x++) {
ItemStack stack = items.grid.get(Pair.of(x + items.minX, y + items.minY));
craftinginventory.setInventorySlotContents(x + (items.height - y - 1) * items.width,
stack == null ? ItemStack.EMPTY : stack.copy());
}
}
return craftinginventory;
}
public static class GroupedItems {
Map<Pair<Integer, Integer>, ItemStack> grid = new HashMap<>();
int minX, minY, maxX, maxY, width, height;
boolean statsReady;
public GroupedItems() {
}
public GroupedItems(ItemStack stack) {
grid.put(Pair.of(0, 0), stack);
}
public void mergeOnto(GroupedItems other, Pointing pointing) {
int xOffset = pointing == Pointing.LEFT ? 1 : pointing == Pointing.RIGHT ? -1 : 0;
int yOffset = pointing == Pointing.DOWN ? 1 : pointing == Pointing.UP ? -1 : 0;
grid.forEach((pair, stack) -> other.grid.put(Pair.of(pair.getKey() + xOffset, pair.getValue() + yOffset),
stack));
other.statsReady = false;
}
public void write(CompoundNBT nbt) {
ListNBT gridNBT = new ListNBT();
grid.forEach((pair, stack) -> {
CompoundNBT entry = new CompoundNBT();
entry.putInt("x", pair.getKey());
entry.putInt("y", pair.getValue());
entry.put("item", stack.serializeNBT());
gridNBT.add(entry);
});
nbt.put("Grid", gridNBT);
}
public static GroupedItems read(CompoundNBT nbt) {
GroupedItems items = new GroupedItems();
ListNBT gridNBT = nbt.getList("Grid", NBT.TAG_COMPOUND);
gridNBT.forEach(inbt -> {
CompoundNBT entry = (CompoundNBT) inbt;
int x = entry.getInt("x");
int y = entry.getInt("y");
ItemStack stack = ItemStack.read(entry.getCompound("item"));
items.grid.put(Pair.of(x, y), stack);
});
return items;
}
public void calcStats() {
if (statsReady)
return;
statsReady = true;
minX = 0;
minY = 0;
maxX = 0;
maxY = 0;
for (Pair<Integer, Integer> pair : grid.keySet()) {
int x = pair.getKey();
int y = pair.getValue();
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
width = maxX - minX + 1;
height = maxY - minY + 1;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 429 B