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
This commit is contained in:
parent
1a8fca038d
commit
c682247894
21 changed files with 587 additions and 205 deletions
|
@ -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<LinkedBehaviour> TYPE = new IBehaviourType<LinkedBehaviour>() {
|
||||
};
|
||||
|
||||
public LinkedBehaviour(SmartTileEntity te) {
|
||||
super(te);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBehaviourType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
}
|
|
@ -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,6 +28,10 @@ public abstract class TileEntityBehaviour {
|
|||
}
|
||||
|
||||
public void tick() {
|
||||
if (lazyTickCounter-- <= 0) {
|
||||
lazyTickCounter = lazyTickRate;
|
||||
lazyTick();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -56,6 +63,15 @@ 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;
|
||||
}
|
||||
|
|
|
@ -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<AutoExtractingBehaviour> TYPE = new IBehaviourType<AutoExtractingBehaviour>() {
|
||||
};
|
||||
|
||||
private int delay;
|
||||
private int timer;
|
||||
Supplier<Boolean> shouldExtract;
|
||||
Supplier<Boolean> shouldPause;
|
||||
|
||||
public AutoExtractingBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> attachments,
|
||||
Consumer<ItemStack> onExtract, int delay) {
|
||||
super(te, attachments, onExtract);
|
||||
shouldPause = () -> false;
|
||||
shouldExtract = () -> true;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public AutoExtractingBehaviour pauseWhen(Supplier<Boolean> condition) {
|
||||
shouldPause = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtractingBehaviour waitUntil(Supplier<Boolean> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ExtractingBehaviour> TYPE = new IBehaviourType<ExtractingBehaviour>() {
|
||||
};
|
||||
|
||||
private Predicate<ItemStack> extractionFilter;
|
||||
private Consumer<ItemStack> callback;
|
||||
|
||||
public ExtractingBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> attachments,
|
||||
Consumer<ItemStack> onExtract) {
|
||||
super(te, attachments);
|
||||
extractionFilter = stack -> true;
|
||||
callback = onExtract;
|
||||
}
|
||||
|
||||
public ExtractingBehaviour withSpecialFilter(Predicate<ItemStack> filter) {
|
||||
this.extractionFilter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean extract() {
|
||||
if (getWorld().isRemote)
|
||||
return false;
|
||||
|
||||
int amount = -1;
|
||||
Predicate<ItemStack> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<InsertingBehaviour> TYPE = new IBehaviourType<InsertingBehaviour>() {
|
||||
};
|
||||
|
||||
public InsertingBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> attachments) {
|
||||
super(te, attachments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBehaviourType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Pair<BlockPos, Direction>, LazyOptional<IItemHandler>> inventories;
|
||||
private Supplier<List<Pair<BlockPos, Direction>>> attachments;
|
||||
private List<IItemHandler> activeHandlers;
|
||||
|
||||
public static IBehaviourType<InventoryManagementBehaviour> TYPE = new IBehaviourType<InventoryManagementBehaviour>() {
|
||||
};
|
||||
|
||||
public InventoryManagementBehaviour(SmartTileEntity te, Supplier<List<Pair<BlockPos, Direction>>> 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<BlockPos, Direction> pair : inventories.keySet()) {
|
||||
LazyOptional<IItemHandler> 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<IItemHandler> getInventories() {
|
||||
return activeHandlers;
|
||||
}
|
||||
|
||||
public IItemHandler getInventory() {
|
||||
if (activeHandlers.isEmpty())
|
||||
return null;
|
||||
return activeHandlers.get(0);
|
||||
}
|
||||
|
||||
protected LazyOptional<IItemHandler> findInventory(Pair<BlockPos, Direction> 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<IItemHandler> 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<List<Pair<BlockPos, Direction>>> toward(Supplier<Direction> facing) {
|
||||
return () -> ImmutableList.of(Pair.of(new BlockPos(facing.get().getDirectionVec()), facing.get()));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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<SingleTargetAutoExtractingBehaviour> TYPE = new IBehaviourType<SingleTargetAutoExtractingBehaviour>() {
|
||||
};
|
||||
|
||||
private Supplier<Direction> attachmentDirection;
|
||||
boolean synced;
|
||||
boolean advantageOnNextSync;
|
||||
|
||||
public SingleTargetAutoExtractingBehaviour(SmartTileEntity te, Supplier<Direction> attachmentDirection,
|
||||
Consumer<ItemStack> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<SingleTargetAutoExtractingBehaviour> 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<SingleTargetAutoExtractingBehaviour> getAllSyncedExtractors(IEnviromentBlockReader reader,
|
||||
BlockPos inventoryPos) {
|
||||
List<SingleTargetAutoExtractingBehaviour> list = new ArrayList<>();
|
||||
List<BlockPos> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ItemStack> test, boolean simulate) {
|
||||
return extract(inv, test, -1, simulate);
|
||||
}
|
||||
|
||||
public static ItemStack extract(IItemHandler inv, Predicate<ItemStack> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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<FunnelTileEntity> {
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
@ -126,15 +94,15 @@ public class ExtractorBlock extends AttachedLogisiticalBlock {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<IItemHandler> 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<TileEntityBehaviour> 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<IItemHandler> getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInventory(LazyOptional<IItemHandler> inventory) {
|
||||
this.inventory = inventory;
|
||||
return world.getEntitiesWithinAABBExcludingEntity(null, new AxisAlignedBB(getPos())).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,7 +19,7 @@ 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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 ] }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 468 B |
Binary file not shown.
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 580 B |
Loading…
Reference in a new issue