From c6822478944bce383c96e650328f56d08758ee4e Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Tue, 7 Jan 2020 17:49:07 +0100 Subject: [PATCH] ExtractionBehaviour and Synchronized Extractors - Added Inventory Manipulation and Extraction Behaviours - Extractors are now aware of other extractors on the same inventory and will take turns --- .../foundation/behaviour/LinkedBehaviour.java | 21 --- .../behaviour/base/TileEntityBehaviour.java | 22 ++- .../inventory/AutoExtractingBehaviour.java | 80 +++++++++++ .../inventory/ExtractingBehaviour.java | 70 +++++++++ .../inventory/InsertingBehaviour.java | 28 ++++ .../InventoryManagementBehaviour.java | 110 ++++++++++++++ .../SingleTargetAutoExtractingBehaviour.java | 53 +++++++ .../inventory/SynchronizedExtraction.java | 67 +++++++++ .../create/foundation/item/ItemHelper.java | 56 ++++++++ .../modules/logistics/block/IExtractor.java | 2 +- ...lock.java => AttachedLogisticalBlock.java} | 8 +- .../logistics/block/belts/FunnelBlock.java | 14 +- .../block/belts/FunnelTileEntity.java | 8 +- .../block/extractor/ExtractorBlock.java | 46 +----- .../block/extractor/ExtractorTileEntity.java | 136 ++++++++---------- .../block/extractor/LinkedExtractorBlock.java | 8 +- .../extractor/LinkedExtractorTileEntity.java | 29 +--- .../block/transposer/TransposerBlock.java | 19 ++- .../create/models/block/flex_crate.json | 15 +- .../create/textures/block/brass_casing_14.png | Bin 0 -> 468 bytes .../create/textures/block/flex_crate.png | Bin 528 -> 580 bytes 21 files changed, 587 insertions(+), 205 deletions(-) delete mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/LinkedBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/AutoExtractingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/ExtractingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/InsertingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/InventoryManagementBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java create mode 100644 src/main/java/com/simibubi/create/foundation/behaviour/inventory/SynchronizedExtraction.java rename src/main/java/com/simibubi/create/modules/logistics/block/belts/{AttachedLogisiticalBlock.java => AttachedLogisticalBlock.java} (92%) create mode 100644 src/main/resources/assets/create/textures/block/brass_casing_14.png diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/LinkedBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/LinkedBehaviour.java deleted file mode 100644 index d03631156..000000000 --- a/src/main/java/com/simibubi/create/foundation/behaviour/LinkedBehaviour.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.simibubi.create.foundation.behaviour; - -import com.simibubi.create.foundation.behaviour.base.IBehaviourType; -import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; -import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour; - -public class LinkedBehaviour extends TileEntityBehaviour { - - public static IBehaviourType TYPE = new IBehaviourType() { - }; - - public LinkedBehaviour(SmartTileEntity te) { - super(te); - } - - @Override - public IBehaviourType getType() { - return TYPE; - } - -} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/base/TileEntityBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/base/TileEntityBehaviour.java index faa9764f2..1451339db 100644 --- a/src/main/java/com/simibubi/create/foundation/behaviour/base/TileEntityBehaviour.java +++ b/src/main/java/com/simibubi/create/foundation/behaviour/base/TileEntityBehaviour.java @@ -12,10 +12,13 @@ public abstract class TileEntityBehaviour { public SmartTileEntity tileEntity; private boolean paused; + private int lazyTickRate; + private int lazyTickCounter; public TileEntityBehaviour(SmartTileEntity te) { tileEntity = te; paused = false; + setLazyTickRate(10); } public abstract IBehaviourType getType(); @@ -25,7 +28,11 @@ public abstract class TileEntityBehaviour { } public void tick() { - + if (lazyTickCounter-- <= 0) { + lazyTickCounter = lazyTickRate; + lazyTick(); + } + } public void readNBT(CompoundNBT nbt) { @@ -56,14 +63,23 @@ public abstract class TileEntityBehaviour { return paused; } + public void setLazyTickRate(int slowTickRate) { + this.lazyTickRate = slowTickRate; + this.lazyTickCounter = slowTickRate; + } + + public void lazyTick() { + + } + public void setPaused(boolean paused) { this.paused = paused; } - + public BlockPos getPos() { return tileEntity.getPos(); } - + public World getWorld() { return tileEntity.getWorld(); } diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/AutoExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/AutoExtractingBehaviour.java new file mode 100644 index 000000000..cb9d0acd2 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/AutoExtractingBehaviour.java @@ -0,0 +1,80 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.behaviour.base.IBehaviourType; +import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; + +public class AutoExtractingBehaviour extends ExtractingBehaviour { + + public static IBehaviourType TYPE = new IBehaviourType() { + }; + + private int delay; + private int timer; + Supplier shouldExtract; + Supplier shouldPause; + + public AutoExtractingBehaviour(SmartTileEntity te, Supplier>> attachments, + Consumer onExtract, int delay) { + super(te, attachments, onExtract); + shouldPause = () -> false; + shouldExtract = () -> true; + this.delay = delay; + } + + public AutoExtractingBehaviour pauseWhen(Supplier condition) { + shouldPause = condition; + return this; + } + + public ExtractingBehaviour waitUntil(Supplier condition) { + this.shouldExtract = condition; + return this; + } + + public void setDelay(int delay) { + this.delay = delay; + this.timer = delay; + } + + @Override + public boolean extract() { + timer = delay; + return super.extract(); + } + + @Override + public void tick() { + super.tick(); + + if (shouldPause.get()) { + timer = 0; + return; + } + + if (timer > 0) { + timer--; + return; + } + + if (!shouldExtract.get()) + return; + + extract(); + } + + @Override + public IBehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/ExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/ExtractingBehaviour.java new file mode 100644 index 000000000..4309d2ede --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/ExtractingBehaviour.java @@ -0,0 +1,70 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.behaviour.base.IBehaviourType; +import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; +import com.simibubi.create.foundation.behaviour.filtering.FilteringBehaviour; +import com.simibubi.create.foundation.item.ItemHelper; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.items.IItemHandler; + +public class ExtractingBehaviour extends InventoryManagementBehaviour { + + public static IBehaviourType TYPE = new IBehaviourType() { + }; + + private Predicate extractionFilter; + private Consumer callback; + + public ExtractingBehaviour(SmartTileEntity te, Supplier>> attachments, + Consumer onExtract) { + super(te, attachments); + extractionFilter = stack -> true; + callback = onExtract; + } + + public ExtractingBehaviour withSpecialFilter(Predicate filter) { + this.extractionFilter = filter; + return this; + } + + public boolean extract() { + if (getWorld().isRemote) + return false; + + int amount = -1; + Predicate test = extractionFilter; + + FilteringBehaviour filter = get(tileEntity, FilteringBehaviour.TYPE); + if (filter != null) { + ItemStack filterItem = filter.getFilter(); + amount = filterItem.isEmpty() ? -1 : filterItem.getCount(); + test = extractionFilter.and(filter::test); + } + + for (IItemHandler inv : getInventories()) { + ItemStack extract = ItemHelper.extract(inv, test, amount, false); + if (!extract.isEmpty()) { + callback.accept(extract); + return true; + } + } + + return false; + } + + @Override + public IBehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InsertingBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InsertingBehaviour.java new file mode 100644 index 000000000..3c38225de --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InsertingBehaviour.java @@ -0,0 +1,28 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.List; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; + +import com.simibubi.create.foundation.behaviour.base.IBehaviourType; +import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; + +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; + +public class InsertingBehaviour extends InventoryManagementBehaviour { + + public static IBehaviourType TYPE = new IBehaviourType() { + }; + + public InsertingBehaviour(SmartTileEntity te, Supplier>> attachments) { + super(te, attachments); + } + + @Override + public IBehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InventoryManagementBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InventoryManagementBehaviour.java new file mode 100644 index 000000000..22fa74527 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/InventoryManagementBehaviour.java @@ -0,0 +1,110 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.collect.ImmutableList; +import com.simibubi.create.foundation.behaviour.base.IBehaviourType; +import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; +import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour; + +import net.minecraft.block.BlockState; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; + +public class InventoryManagementBehaviour extends TileEntityBehaviour { + + Map, LazyOptional> inventories; + private Supplier>> attachments; + private List activeHandlers; + + public static IBehaviourType TYPE = new IBehaviourType() { + }; + + public InventoryManagementBehaviour(SmartTileEntity te, Supplier>> attachments) { + super(te); + this.attachments = attachments; + setLazyTickRate(20); + activeHandlers = new ArrayList<>(); + inventories = new HashMap<>(); + } + + @Override + public void initialize() { + super.initialize(); + attachments.get().forEach(offset -> inventories.put(offset, findInventory(offset))); + } + + @Override + public void lazyTick() { + super.lazyTick(); + activeHandlers.clear(); + for (Pair pair : inventories.keySet()) { + LazyOptional lazyOptional = inventories.get(pair); + if (lazyOptional.isPresent()) { + activeHandlers.add(lazyOptional.orElse(null)); + continue; + } + + lazyOptional = findInventory(pair); + if (lazyOptional.isPresent()) + activeHandlers.add(lazyOptional.orElse(null)); + inventories.put(pair, lazyOptional); + } + } + + public List getInventories() { + return activeHandlers; + } + + public IItemHandler getInventory() { + if (activeHandlers.isEmpty()) + return null; + return activeHandlers.get(0); + } + + protected LazyOptional findInventory(Pair offset) { + BlockPos invPos = tileEntity.getPos().add(offset.getKey()); + World world = getWorld(); + + if (!world.isBlockPresent(invPos)) + return LazyOptional.empty(); + BlockState invState = world.getBlockState(invPos); + + if (!invState.hasTileEntity()) + return LazyOptional.empty(); + TileEntity invTE = world.getTileEntity(invPos); + if (invTE == null) + return LazyOptional.empty(); + + LazyOptional inventory = invTE.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, + offset.getValue()); + if (inventory == null) { + return LazyOptional.empty(); + } + + return inventory; + } + + @Override + public IBehaviourType getType() { + return TYPE; + } + + public static class Attachments { + public static final Supplier>> toward(Supplier facing) { + return () -> ImmutableList.of(Pair.of(new BlockPos(facing.get().getDirectionVec()), facing.get())); + }; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java new file mode 100644 index 000000000..d4a999eaa --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SingleTargetAutoExtractingBehaviour.java @@ -0,0 +1,53 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import com.simibubi.create.foundation.behaviour.base.IBehaviourType; +import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; + +public class SingleTargetAutoExtractingBehaviour extends AutoExtractingBehaviour { + + public static IBehaviourType TYPE = new IBehaviourType() { + }; + + private Supplier attachmentDirection; + boolean synced; + boolean advantageOnNextSync; + + public SingleTargetAutoExtractingBehaviour(SmartTileEntity te, Supplier attachmentDirection, + Consumer onExtract, int delay) { + super(te, Attachments.toward(attachmentDirection), onExtract, delay); + this.attachmentDirection = attachmentDirection; + synced = true; + advantageOnNextSync = false; + } + + public SingleTargetAutoExtractingBehaviour dontSynchronize() { + synced = false; + return this; + } + + @Override + public boolean extract() { + if (synced) { + BlockPos invPos = tileEntity.getPos().offset(attachmentDirection.get()); + return SynchronizedExtraction.extractSynchronized(getWorld(), invPos); + } else + return extractFromInventory(); + } + + public boolean extractFromInventory() { + return super.extract(); + } + + @Override + public IBehaviourType getType() { + return TYPE; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SynchronizedExtraction.java b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SynchronizedExtraction.java new file mode 100644 index 000000000..aec9d4b00 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/behaviour/inventory/SynchronizedExtraction.java @@ -0,0 +1,67 @@ +package com.simibubi.create.foundation.behaviour.inventory; + +import java.util.ArrayList; +import java.util.List; + +import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour; + +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.state.properties.ChestType; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IEnviromentBlockReader; + +public class SynchronizedExtraction { + + static boolean extractSynchronized(IEnviromentBlockReader reader, BlockPos inventoryPos) { + List actors = getAllSyncedExtractors(reader, inventoryPos); + int startIndex = actors.size() - 1; + boolean success = false; + + for (; startIndex > 0; startIndex--) + if (actors.get(startIndex).advantageOnNextSync) + break; + for (int i = 0; i < actors.size(); i++) + success |= actors.get((startIndex + i) % actors.size()).extractFromInventory(); + + if (success) { + actors.get(startIndex).advantageOnNextSync = false; + actors.get((startIndex + 1) % actors.size()).advantageOnNextSync = true; + } + + return success; + } + + private static List getAllSyncedExtractors(IEnviromentBlockReader reader, + BlockPos inventoryPos) { + List list = new ArrayList<>(); + List inventoryPositions = new ArrayList<>(); + inventoryPositions.add(inventoryPos); + + // Sync across double chests + BlockState blockState = reader.getBlockState(inventoryPos); + if (blockState.getBlock() instanceof ChestBlock) + if (blockState.get(ChestBlock.TYPE) != ChestType.SINGLE) + inventoryPositions.add(inventoryPos.offset(ChestBlock.getDirectionToAttached(blockState))); + + for (BlockPos pos : inventoryPositions) { + for (Direction direction : Direction.values()) { + SingleTargetAutoExtractingBehaviour behaviour = TileEntityBehaviour.get(reader, pos.offset(direction), + SingleTargetAutoExtractingBehaviour.TYPE); + if (behaviour == null) + continue; + if (!behaviour.synced) + continue; + if (behaviour.shouldPause.get()) + continue; + if (!behaviour.shouldExtract.get()) + continue; + + list.add(behaviour); + } + } + return list; + } + +} diff --git a/src/main/java/com/simibubi/create/foundation/item/ItemHelper.java b/src/main/java/com/simibubi/create/foundation/item/ItemHelper.java index 74926ea3b..063891818 100644 --- a/src/main/java/com/simibubi/create/foundation/item/ItemHelper.java +++ b/src/main/java/com/simibubi/create/foundation/item/ItemHelper.java @@ -2,10 +2,13 @@ package com.simibubi.create.foundation.item; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import org.apache.commons.lang3.mutable.MutableInt; import org.apache.commons.lang3.tuple.Pair; +import com.simibubi.create.CreateConfig; + import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import net.minecraft.util.NonNullList; @@ -85,4 +88,57 @@ public class ItemHelper { return false; } + public static ItemStack extract(IItemHandler inv, Predicate test, boolean simulate) { + return extract(inv, test, -1, simulate); + } + + public static ItemStack extract(IItemHandler inv, Predicate test, int exactAmount, boolean simulate) { + ItemStack extracting = ItemStack.EMPTY; + boolean amountRequired = exactAmount != -1; + boolean checkHasEnoughItems = amountRequired; + boolean hasEnoughItems = !checkHasEnoughItems; + int maxExtractionCount = hasEnoughItems ? CreateConfig.parameters.extractorAmount.get() : exactAmount; + + Extraction: do { + extracting = ItemStack.EMPTY; + + for (int slot = 0; slot < inv.getSlots(); slot++) { + ItemStack stack = inv.extractItem(slot, maxExtractionCount - extracting.getCount(), true); + + if (!test.test(stack)) + continue; + if (!extracting.isEmpty() && !ItemHandlerHelper.canItemStacksStack(stack, extracting)) + continue; + + if (extracting.isEmpty()) + extracting = stack.copy(); + else + extracting.grow(stack.getCount()); + + if (!simulate && hasEnoughItems) + inv.extractItem(slot, stack.getCount(), false); + + if (extracting.getCount() >= maxExtractionCount) { + if (checkHasEnoughItems) { + hasEnoughItems = true; + checkHasEnoughItems = false; + continue Extraction; + } else { + break Extraction; + } + } + } + + if (checkHasEnoughItems) + checkHasEnoughItems = false; + else + break Extraction; + } while (true); + + if (amountRequired && extracting.getCount() < exactAmount) + return ItemStack.EMPTY; + + return extracting; + } + } diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java b/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java index 07133fa4b..845f1d12b 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/IExtractor.java @@ -25,7 +25,7 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.items.IItemHandler; -// Its like delegation but better! +// Its like delegation but worse! public interface IExtractor extends ITickableTileEntity, IInventoryManipulator { public enum State { diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisiticalBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisticalBlock.java similarity index 92% rename from src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisiticalBlock.java rename to src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisticalBlock.java index 40e9121a9..80a3ba6c5 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisiticalBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/AttachedLogisticalBlock.java @@ -18,11 +18,11 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.IWorldReader; import net.minecraft.world.World; -public abstract class AttachedLogisiticalBlock extends HorizontalBlock implements IHaveNoBlockItem { +public abstract class AttachedLogisticalBlock extends HorizontalBlock implements IHaveNoBlockItem { public static final BooleanProperty UPWARD = BooleanProperty.create("upward"); - public AttachedLogisiticalBlock() { + public AttachedLogisticalBlock() { super(Properties.from(Blocks.ANDESITE)); } @@ -97,8 +97,8 @@ public abstract class AttachedLogisiticalBlock extends HorizontalBlock implement public static boolean isVertical(BlockState state) { Block block = state.getBlock(); - return ((block instanceof AttachedLogisiticalBlock) - && (((AttachedLogisiticalBlock) state.getBlock())).isVertical()); + return ((block instanceof AttachedLogisticalBlock) + && (((AttachedLogisticalBlock) state.getBlock())).isVertical()); } @Override diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelBlock.java index 6bcb45f43..6959070f5 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelBlock.java @@ -29,7 +29,7 @@ import net.minecraft.world.IWorld; import net.minecraft.world.IWorldReader; import net.minecraft.world.World; -public class FunnelBlock extends AttachedLogisiticalBlock +public class FunnelBlock extends AttachedLogisticalBlock implements IBeltAttachment, IWithTileEntity { public static final BooleanProperty BELT = BooleanProperty.create("belt"); @@ -61,18 +61,6 @@ public class FunnelBlock extends AttachedLogisiticalBlock return AllBlocks.VERTICAL_FUNNEL.getDefault(); } - @Override - public void neighborChanged(BlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, - boolean isMoving) { - Direction blockFacing = state.get(HORIZONTAL_FACING); - if (fromPos.equals(pos.offset(blockFacing))) { - if (!isValidPosition(state, worldIn, pos)) { - worldIn.destroyBlock(pos, true); - return; - } - } - } - @Override public BlockState updatePostPlacement(BlockState stateIn, Direction facing, BlockState facingState, IWorld worldIn, BlockPos currentPos, BlockPos facingPos) { diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelTileEntity.java index c3d3460d4..dcaffaa09 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/belts/FunnelTileEntity.java @@ -165,16 +165,16 @@ public class FunnelTileEntity extends SmartTileEntity implements ITickableTileEn Vec3d vec = offsetForHorizontal; float yRot = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); - if (AttachedLogisiticalBlock.isVertical(state)) - vec = state.get(AttachedLogisiticalBlock.UPWARD) ? offsetForUpward : offsetForDownward; + if (AttachedLogisticalBlock.isVertical(state)) + vec = state.get(AttachedLogisticalBlock.UPWARD) ? offsetForUpward : offsetForDownward; else if (state.get(FunnelBlock.BELT)) vec = offsetForBelt; return VecHelper.rotateCentered(vec, yRot, Axis.Y); }, state -> { - Direction blockFacing = AttachedLogisiticalBlock.getBlockFacing(state); - boolean vertical = AttachedLogisiticalBlock.isVertical(state); + Direction blockFacing = AttachedLogisticalBlock.getBlockFacing(state); + boolean vertical = AttachedLogisticalBlock.isVertical(state); float horizontalAngle = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); float yRot = blockFacing == Direction.DOWN ? horizontalAngle + 180 : horizontalAngle; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorBlock.java index 3302de698..030aaa353 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorBlock.java @@ -4,8 +4,7 @@ import com.simibubi.create.AllBlocks; import com.simibubi.create.foundation.utility.AllShapes; import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.VecHelper; -import com.simibubi.create.modules.logistics.block.IExtractor; -import com.simibubi.create.modules.logistics.block.belts.AttachedLogisiticalBlock; +import com.simibubi.create.modules.logistics.block.belts.AttachedLogisticalBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -20,10 +19,9 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; import net.minecraft.world.IBlockReader; -import net.minecraft.world.IWorldReader; import net.minecraft.world.World; -public class ExtractorBlock extends AttachedLogisiticalBlock { +public class ExtractorBlock extends AttachedLogisticalBlock { public static BooleanProperty POWERED = BlockStateProperties.POWERED; @@ -63,31 +61,6 @@ public class ExtractorBlock extends AttachedLogisiticalBlock { reactsToRedstone() && context.getWorld().isBlockPowered(context.getPos())); } - @Override - public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving) { - updateObservedInventory(state, worldIn, pos); - } - - @Override - public void onNeighborChange(BlockState state, IWorldReader world, BlockPos pos, BlockPos neighbor) { - if (world.isRemote()) - return; - if (!isObserving(state, pos, neighbor)) - return; - updateObservedInventory(state, world, pos); - } - - private void updateObservedInventory(BlockState state, IWorldReader world, BlockPos pos) { - IExtractor extractor = (IExtractor) world.getTileEntity(pos); - if (extractor == null) - return; - extractor.neighborChanged(); - } - - private boolean isObserving(BlockState state, BlockPos pos, BlockPos observing) { - return observing.equals(pos.offset(getBlockFacing(state))); - } - @Override public void neighborChanged(BlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, boolean isMoving) { @@ -99,13 +72,8 @@ public class ExtractorBlock extends AttachedLogisiticalBlock { return; boolean previouslyPowered = state.get(POWERED); - if (previouslyPowered != worldIn.isBlockPowered(pos)) { + if (previouslyPowered != worldIn.isBlockPowered(pos)) worldIn.setBlockState(pos, state.cycle(POWERED), 2); - IExtractor extractor = (IExtractor) worldIn.getTileEntity(pos); - if (extractor == null) - return; - extractor.setLocked(!previouslyPowered); - } } protected boolean reactsToRedstone() { @@ -119,22 +87,22 @@ public class ExtractorBlock extends AttachedLogisiticalBlock { public static Vec3d getFilterSlotPosition(BlockState state) { float verticalOffset = (state.getBlock() instanceof ExtractorBlock) ? 10.5f : 12.5f; - + Vec3d offsetForHorizontal = VecHelper.voxelSpace(8f, verticalOffset, 14f); Vec3d offsetForUpward = VecHelper.voxelSpace(8f, 14.15f, 3.5f); Vec3d offsetForDownward = VecHelper.voxelSpace(8f, 1.85f, 3.5f); Vec3d vec = offsetForHorizontal; float yRot = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); - if (AttachedLogisiticalBlock.isVertical(state)) - vec = state.get(AttachedLogisiticalBlock.UPWARD) ? offsetForUpward : offsetForDownward; + if (AttachedLogisticalBlock.isVertical(state)) + vec = state.get(AttachedLogisticalBlock.UPWARD) ? offsetForUpward : offsetForDownward; return VecHelper.rotateCentered(vec, yRot, Axis.Y); } public static Vec3d getFilterSlotOrientation(BlockState state) { float yRot = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); - float zRot = (AttachedLogisiticalBlock.isVertical(state)) ? 0 : 90; + float zRot = (AttachedLogisticalBlock.isVertical(state)) ? 0 : 90; return new Vec3d(0, yRot, zRot); } diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorTileEntity.java index 62605a8a6..804f7b93a 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/ExtractorTileEntity.java @@ -2,32 +2,37 @@ package com.simibubi.create.modules.logistics.block.extractor; import java.util.List; +import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTileEntities; import com.simibubi.create.CreateConfig; import com.simibubi.create.foundation.behaviour.base.SmartTileEntity; import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour; import com.simibubi.create.foundation.behaviour.filtering.FilteringBehaviour; import com.simibubi.create.foundation.behaviour.filtering.FilteringBehaviour.SlotPositioning; -import com.simibubi.create.modules.logistics.block.IExtractor; -import com.simibubi.create.modules.logistics.block.belts.AttachedLogisiticalBlock; +import com.simibubi.create.foundation.behaviour.inventory.ExtractingBehaviour; +import com.simibubi.create.foundation.behaviour.inventory.SingleTargetAutoExtractingBehaviour; +import com.simibubi.create.foundation.utility.VecHelper; +import com.simibubi.create.modules.contraptions.relays.belt.BeltTileEntity; +import com.simibubi.create.modules.logistics.block.belts.AttachedLogisticalBlock; +import com.simibubi.create.modules.logistics.item.CardboardBoxItem; +import com.simibubi.create.modules.logistics.transport.CardboardBoxEntity; -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.ItemEntity; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundNBT; -import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; -import net.minecraft.util.math.BlockPos; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.items.IItemHandler; +import net.minecraft.util.Direction; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvents; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; -public class ExtractorTileEntity extends SmartTileEntity implements IExtractor, ITickableTileEntity { +public class ExtractorTileEntity extends SmartTileEntity { private static FilteringBehaviour.SlotPositioning slots; - private State state; - private int cooldown; - private LazyOptional inventory; + private ExtractingBehaviour extracting; private FilteringBehaviour filtering; public ExtractorTileEntity() { @@ -36,13 +41,16 @@ public class ExtractorTileEntity extends SmartTileEntity implements IExtractor, protected ExtractorTileEntity(TileEntityType tileEntityTypeIn) { super(tileEntityTypeIn); - state = State.ON_COOLDOWN; - cooldown = CreateConfig.parameters.extractorDelay.get(); - inventory = LazyOptional.empty(); } @Override public void addBehaviours(List behaviours) { + int delay = CreateConfig.parameters.extractorDelay.get(); + extracting = new SingleTargetAutoExtractingBehaviour(this, + () -> AttachedLogisticalBlock.getBlockFacing(getBlockState()), this::onExtract, delay) + .pauseWhen(this::isPowered).waitUntil(this::canExtract); + behaviours.add(extracting); + if (slots == null) slots = new SlotPositioning(ExtractorBlock::getFilterSlotPosition, ExtractorBlock::getFilterSlotOrientation) .scale(.4f); @@ -51,73 +59,49 @@ public class ExtractorTileEntity extends SmartTileEntity implements IExtractor, behaviours.add(filtering); } - public void filterChanged(ItemStack stack) { - neighborChanged(); + private void onExtract(ItemStack stack) { + Vec3d entityPos = VecHelper.getCenterOf(getPos()).add(0, -0.5f, 0); + Entity entityIn = null; + Direction facing = AttachedLogisticalBlock.getBlockFacing(getBlockState()); + if (facing == Direction.DOWN) + entityPos = entityPos.add(0, .5, 0); + + if (stack.getItem() instanceof CardboardBoxItem) { + entityIn = new CardboardBoxEntity(world, entityPos, stack, facing.getOpposite()); + world.playSound(null, getPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .25f, .05f); + + } else { + entityIn = new ItemEntity(world, entityPos.x, entityPos.y, entityPos.z, stack); + entityIn.setMotion(Vec3d.ZERO); + ((ItemEntity) entityIn).setPickupDelay(5); + world.playSound(null, getPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, .125f, .1f); + } + + world.addEntity(entityIn); } - @Override - public State getState() { - return state; + protected boolean isPowered() { + return getBlockState().get(ExtractorBlock.POWERED); } - @Override - public void read(CompoundNBT compound) { - if (compound.getBoolean("Locked")) - setState(State.LOCKED); - super.read(compound); + private void filterChanged(ItemStack stack) { + } - @Override - public CompoundNBT write(CompoundNBT compound) { - compound.putBoolean("Locked", getState() == State.LOCKED); - return super.write(compound); - } + private boolean canExtract() { + if (AllBlocks.BELT.typeOf(world.getBlockState(pos.down()))) { + TileEntity te = world.getTileEntity(pos.down()); + if (te != null && te instanceof BeltTileEntity) { + BeltTileEntity belt = (BeltTileEntity) te; + BeltTileEntity controller = belt.getControllerTE(); + if (controller != null) { + if (!controller.getInventory().canInsertFrom(belt.index, Direction.UP)) + return false; + } + } + } - @Override - public void initialize() { - super.initialize(); - if (world.isBlockPowered(pos)) - state = State.LOCKED; - neighborChanged(); - } - - @Override - public void tick() { - super.tick(); - IExtractor.super.tick(); - } - - @Override - public void setState(State state) { - if (state == State.ON_COOLDOWN) - cooldown = CreateConfig.parameters.extractorDelay.get(); - if (state == State.WAITING_FOR_INVENTORY) - cooldown = CreateConfig.parameters.extractorInventoryScanDelay.get(); - this.state = state; - } - - @Override - public int tickCooldown() { - return cooldown--; - } - - @Override - public BlockPos getInventoryPos() { - BlockState blockState = getBlockState(); - Block block = blockState.getBlock(); - if (!(block instanceof ExtractorBlock)) - return null; - return getPos().offset(AttachedLogisiticalBlock.getBlockFacing(blockState)); - } - - @Override - public LazyOptional getInventory() { - return inventory; - } - - @Override - public void setInventory(LazyOptional inventory) { - this.inventory = inventory; + return world.getEntitiesWithinAABBExcludingEntity(null, new AxisAlignedBB(getPos())).isEmpty(); } } diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorBlock.java index 72fd2c2d5..fba60ced7 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorBlock.java @@ -5,7 +5,7 @@ import org.apache.commons.lang3.tuple.Pair; import com.simibubi.create.AllBlocks; import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.VecHelper; -import com.simibubi.create.modules.logistics.block.belts.AttachedLogisiticalBlock; +import com.simibubi.create.modules.logistics.block.belts.AttachedLogisticalBlock; import net.minecraft.block.BlockState; import net.minecraft.tileentity.TileEntity; @@ -48,8 +48,8 @@ public class LinkedExtractorBlock extends ExtractorBlock { Vec3d secondDownward = VecHelper.voxelSpace(6f, 2f, 11.5f); float yRot = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); - if (AttachedLogisiticalBlock.isVertical(state)) { - Boolean up = state.get(AttachedLogisiticalBlock.UPWARD); + if (AttachedLogisticalBlock.isVertical(state)) { + Boolean up = state.get(AttachedLogisticalBlock.UPWARD); first = up ? firstUpward : firstDownward; second = up ? secondUpward : secondDownward; } @@ -60,7 +60,7 @@ public class LinkedExtractorBlock extends ExtractorBlock { } public static Vec3d getFrequencySlotOrientation(BlockState state) { - boolean vertical = AttachedLogisiticalBlock.isVertical(state); + boolean vertical = AttachedLogisticalBlock.isVertical(state); float horizontalAngle = AngleHelper.horizontalAngle(state.get(ExtractorBlock.HORIZONTAL_FACING)); float xRot = vertical ? (state.get(UPWARD) ? 90 : 270) : 0; diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorTileEntity.java b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorTileEntity.java index 74b842b3e..974e949d2 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorTileEntity.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/extractor/LinkedExtractorTileEntity.java @@ -8,11 +8,6 @@ import com.simibubi.create.AllTileEntities; import com.simibubi.create.foundation.behaviour.base.TileEntityBehaviour; import com.simibubi.create.foundation.behaviour.linked.LinkBehaviour; import com.simibubi.create.foundation.behaviour.linked.LinkBehaviour.SlotPositioning; -import com.simibubi.create.modules.logistics.block.belts.AttachedLogisiticalBlock; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.util.math.BlockPos; public class LinkedExtractorTileEntity extends ExtractorTileEntity { @@ -38,33 +33,13 @@ public class LinkedExtractorTileEntity extends ExtractorTileEntity { receivedSignal = powered; } - @Override - public void initialize() { - super.initialize(); - if (world.isBlockPowered(pos)) - setState(State.LOCKED); - neighborChanged(); - } - @Override public void tick() { super.tick(); if (world.isRemote) return; - if (receivedSignal != getBlockState().get(POWERED)) { - setLocked(receivedSignal); + if (receivedSignal != getBlockState().get(POWERED)) world.setBlockState(pos, getBlockState().cycle(POWERED)); - return; - } } - - @Override - public BlockPos getInventoryPos() { - BlockState blockState = getBlockState(); - Block block = blockState.getBlock(); - if (!(block instanceof ExtractorBlock)) - return null; - return getPos().offset(AttachedLogisiticalBlock.getBlockFacing(blockState)); - } - + } diff --git a/src/main/java/com/simibubi/create/modules/logistics/block/transposer/TransposerBlock.java b/src/main/java/com/simibubi/create/modules/logistics/block/transposer/TransposerBlock.java index 3ea860198..2a2c1b238 100644 --- a/src/main/java/com/simibubi/create/modules/logistics/block/transposer/TransposerBlock.java +++ b/src/main/java/com/simibubi/create/modules/logistics/block/transposer/TransposerBlock.java @@ -2,7 +2,7 @@ package com.simibubi.create.modules.logistics.block.transposer; import com.simibubi.create.AllBlocks; import com.simibubi.create.foundation.utility.AllShapes; -import com.simibubi.create.modules.logistics.block.belts.AttachedLogisiticalBlock; +import com.simibubi.create.modules.logistics.block.belts.AttachedLogisticalBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -19,19 +19,19 @@ import net.minecraft.world.IBlockReader; import net.minecraft.world.IWorldReader; import net.minecraft.world.World; -public class TransposerBlock extends AttachedLogisiticalBlock { +public class TransposerBlock extends AttachedLogisticalBlock { public static BooleanProperty POWERED = BlockStateProperties.POWERED; public TransposerBlock() { setDefaultState(getDefaultState().with(POWERED, false)); } - + @Override public boolean hasTileEntity(BlockState state) { return true; } - + @Override public TileEntity createTileEntity(BlockState state, IBlockReader world) { return new TransposerTileEntity(); @@ -66,10 +66,17 @@ public class TransposerBlock extends AttachedLogisiticalBlock { @Override public void neighborChanged(BlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos, boolean isMoving) { - super.neighborChanged(state, worldIn, pos, blockIn, fromPos, isMoving); - if (worldIn.isRemote) return; + + Direction blockFacing = getBlockFacing(state); + if (fromPos.equals(pos.offset(blockFacing)) || fromPos.equals(pos.offset(blockFacing.getOpposite()))) { + if (!isValidPosition(state, worldIn, pos)) { + worldIn.destroyBlock(pos, true); + return; + } + } + if (!reactsToRedstone()) return; diff --git a/src/main/resources/assets/create/models/block/flex_crate.json b/src/main/resources/assets/create/models/block/flex_crate.json index c3d00528a..c397bfad0 100644 --- a/src/main/resources/assets/create/models/block/flex_crate.json +++ b/src/main/resources/assets/create/models/block/flex_crate.json @@ -2,7 +2,8 @@ "parent": "block/block", "textures": { "particle": "create:block/flex_crate", - "flex_crate": "create:block/flex_crate" + "side": "create:block/flex_crate", + "top": "create:block/brass_casing_14" }, "elements": [ { @@ -10,12 +11,12 @@ "from": [ 1, 0, 1 ], "to": [ 15, 14, 15 ], "faces": { - "north": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] }, - "east": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] }, - "south": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] }, - "west": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] }, - "up": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] }, - "down": { "texture": "#flex_crate", "uv": [ 1, 1, 15, 15 ] } + "north": { "texture": "#side", "uv": [ 1, 1, 15, 15 ] }, + "east": { "texture": "#side", "uv": [ 1, 1, 15, 15 ] }, + "south": { "texture": "#side", "uv": [ 1, 1, 15, 15 ] }, + "west": { "texture": "#side", "uv": [ 1, 1, 15, 15 ] }, + "up": { "texture": "#top", "uv": [ 1, 1, 15, 15 ] }, + "down": { "texture": "#top", "uv": [ 1, 1, 15, 15 ] } } } ] diff --git a/src/main/resources/assets/create/textures/block/brass_casing_14.png b/src/main/resources/assets/create/textures/block/brass_casing_14.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5d6ab98d7e8a57a0ebb4920d80334fac77f7d7 GIT binary patch literal 468 zcmV;_0W1EAP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0clA@K~y+TrIWEv z!!Qtr&q zNvehcKI!ydZTY{;)n>*R_zz@odAj3WI1id^JxWzg25@ut$_AL^xrU>IeOi>6u2p6r z#0Dvn6(#9(b42>MSdfxI5HgTU1y|P(Afm9|;0#J-NZa7r{Opj%J7=`!t~?!!CTkhUtn9es(B4z+hw1431YQ073pg~4tt9LW6Tu>R zbEyzAP^G<|O>rFBXIk_IGD?Q^84JCIi<6nzo9)`m!R>|utqp|sra47KJS3IN946x! zo?f2e_Wq5L!TIqv)fX|{Z2LL(U|U2jCl^@V=SDy3k1g0000< KMNUMnLSTY>`oxO> literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/create/textures/block/flex_crate.png b/src/main/resources/assets/create/textures/block/flex_crate.png index 424a40ac7ad7e8f1e9aa89034fa13cf1a56197ff..44d648bd0e4725d690ac44886829365d3f4dff3e 100644 GIT binary patch delta 516 zcmV+f0{i`t1jGc883+ad001BJ|6!3KHGkPjL_t(IPo-1KY7{{fJ>Aof>7Je$M`tim zj6%>wHvWd-7r51h3wQYiKR|G)ApV2@5+xu)z!;1$WZvD?59^#xg_*4$sJgeh?m2bu ztuC)=8vG9=@b>uWjmjpk;rp#0g{@PV8g&hHV_*BkYF*;pr>~v_zI=Gq4EtT2{(rc{ z?OT}wj4`sCqC&@$YyDFDckiT_FLOM5`LPu^y5Db}>}5E;Sm42^jV_BC_Fy6D`dH)v zCW9D7)gZEpj6L}|_mpECw5if=>JXhEMVw~vqFCei+!Hy7d@Ktivg2q@qmCA|rD^bM zy2jPAkUv|NIJ}pMe0jC8qX4sY9DfO{Y@N>Tj}sASrz<00d+SPK?qtW&DlcnH*M%%k zVerY>3`Yk81@Rj*+8-r5u|zAkgS2Y0{>>4GE0WP(-HZPGYwmnV{(R z^Tkdqp_%oty98?|xMt0dCOwtHUT|#35;r~INe{=VK05NC7sz*gp>?(I+;IXi8Ah_M z+fI~&YnDb!@OpVeM8N6z&2#LHGhF0Wbc<1ds%f83+OZ005AYXf}}{HGic^L_t(IPjyl|Pr^_b?SrTgu%s8R}*&QUvqJDaV9a&x)}#!Fh)h7kyi@^+IlXZDUImK&F#5g&-c8SKdbL> ze_=3ki0E+@Jltb2)he}-OZT{%>{>$@$zo)gI-5!8am6qU z{jDkB{N8FJ5)|N2BM%7FOuGP#gON*IEFYvx+q?bd<3lTuyg*a=ufBtj&DV+T1mWWO zS#B#Q$s7w4_$+qS6ry?d_mY%kQGXaCqe-u0^%$cbR(+5sT38{I$7F?ECgsoA$f3ic z$wYm~4kF4Li~_QJSf5~&8tcE>WN9%{WY82HiYQiVBRe3L%(V?8)3|xUh@xXjDXcb{ zn=_uOsuF0v=XuMX!jeM%E6--Z1c{;Xq3z-y-4|dM>B|6)>xU0I@uoH`{5|PO{;*Pp z4=GaVb!rY6+kNcxE1@aa@%Vn8H$7&&UhLr!AWL}UIPM!civTPW*+HWK0000