/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see . */ package appeng.parts.automation; import alexiil.mc.lib.attributes.item.FixedItemInv; import appeng.api.config.AccessRestriction; import appeng.api.config.Actionable; import appeng.api.config.FuzzyMode; import appeng.api.config.IncludeExclude; import appeng.api.config.Settings; import appeng.api.config.Upgrades; import appeng.api.config.YesNo; import appeng.api.networking.events.MENetworkCellArrayUpdate; import appeng.api.networking.events.MENetworkChannelsChanged; import appeng.api.networking.events.MENetworkEventSubscribe; import appeng.api.networking.events.MENetworkPowerStatusChange; import appeng.api.networking.security.IActionSource; import appeng.api.parts.IPartModel; import appeng.api.storage.IMEInventoryHandler; import appeng.api.storage.IStorageChannel; import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; import appeng.api.util.AEPartLocation; import appeng.container.ContainerLocator; import appeng.container.ContainerOpener; import appeng.container.implementations.FormationPlaneContainer; import appeng.core.AEConfig; import appeng.core.Api; import appeng.hooks.AECustomEntityItem; import appeng.items.parts.PartModels; import appeng.me.GridAccessException; import appeng.me.storage.MEInventoryHandler; import appeng.tile.inventory.AppEngInternalAEInventory; import appeng.util.FakePlayer; import appeng.util.Platform; import appeng.util.inv.InvOperation; import appeng.util.prioritylist.FuzzyPriorityList; import appeng.util.prioritylist.PrecisePriorityList; import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.AutomaticItemPlacementContext; import net.minecraft.item.Item; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.screen.ScreenHandlerType; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; public class FormationPlanePart extends AbstractFormationPlanePart { private static final PlaneModels MODELS = new PlaneModels("part/formation_plane", "part/formation_plane_on"); private static final Random RANDOM_OFFSET = new Random(); @PartModels public static List getModels() { return MODELS.getModels(); } private final MEInventoryHandler myHandler = new MEInventoryHandler<>(this, Api.instance().storage().getStorageChannel(IItemStorageChannel.class)); private final AppEngInternalAEInventory Config = new AppEngInternalAEInventory(this, 63); public FormationPlanePart(final ItemStack is) { super(is); this.getConfigManager().registerSetting(Settings.FUZZY_MODE, FuzzyMode.IGNORE_ALL); this.getConfigManager().registerSetting(Settings.PLACE_BLOCK, YesNo.YES); this.updateHandler(); } @Override protected void updateHandler() { this.myHandler.setBaseAccess(AccessRestriction.WRITE); this.myHandler.setWhitelist( this.getInstalledUpgrades(Upgrades.INVERTER) > 0 ? IncludeExclude.BLACKLIST : IncludeExclude.WHITELIST); this.myHandler.setPriority(this.getPriority()); final IItemList priorityList = Api.instance().storage() .getStorageChannel(IItemStorageChannel.class).createList(); final int slotsToUse = 18 + this.getInstalledUpgrades(Upgrades.CAPACITY) * 9; for (int x = 0; x < this.Config.getSlotCount() && x < slotsToUse; x++) { final IAEItemStack is = this.Config.getAEStackInSlot(x); if (is != null) { priorityList.add(is); } } if (this.getInstalledUpgrades(Upgrades.FUZZY) > 0) { this.myHandler.setPartitionList(new FuzzyPriorityList(priorityList, (FuzzyMode) this.getConfigManager().getSetting(Settings.FUZZY_MODE))); } else { this.myHandler.setPartitionList(new PrecisePriorityList(priorityList)); } try { this.getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate()); } catch (final GridAccessException e) { // :P } } @Override public void onChangeInventory(final FixedItemInv inv, final int slot, final InvOperation mc, final ItemStack removedStack, final ItemStack newStack) { super.onChangeInventory(inv, slot, mc, removedStack, newStack); if (inv == this.Config) { this.updateHandler(); } } @Override public void readFromNBT(final CompoundTag data) { super.readFromNBT(data); this.Config.readFromNBT(data, "config"); this.updateHandler(); } @Override public void writeToNBT(final CompoundTag data) { super.writeToNBT(data); this.Config.writeToNBT(data, "config"); } @Override public FixedItemInv getInventoryByName(final String name) { if (name.equals("config")) { return this.Config; } return super.getInventoryByName(name); } @Override @MENetworkEventSubscribe public void powerRender(final MENetworkPowerStatusChange c) { this.stateChanged(); } @MENetworkEventSubscribe public void updateChannels(final MENetworkChannelsChanged changedChannels) { this.stateChanged(); } @Override public boolean onPartActivate(final PlayerEntity player, final Hand hand, final Vec3d pos) { if (Platform.isServer()) { ContainerOpener.openContainer(FormationPlaneContainer.TYPE, player, ContainerLocator.forPart(this)); } return true; } @Override public List getCellArray(final IStorageChannel channel) { if (this.getProxy().isActive() && channel == Api.instance().storage().getStorageChannel(IItemStorageChannel.class)) { final List handler = new ArrayList<>(1); handler.add(this.myHandler); return handler; } return Collections.emptyList(); } @Override public IAEItemStack injectItems(final IAEItemStack input, final Actionable type, final IActionSource src) { if (this.blocked || input == null || input.getStackSize() <= 0) { return input; } final YesNo placeBlock = (YesNo) this.getConfigManager().getSetting(Settings.PLACE_BLOCK); final ItemStack is = input.createItemStack(); final Item i = is.getItem(); long maxStorage = Math.min(input.getStackSize(), is.getMaxCount()); boolean worked = false; final BlockEntity te = this.getHost().getTile(); final World w = te.getWorld(); if (!(w instanceof ServerWorld)) { return input; } ServerWorld serverWorld = (ServerWorld) w; final AEPartLocation side = this.getSide(); final BlockPos placePos = te.getPos().offset(side.getFacing()); if (w.getBlockState(placePos).getMaterial().isReplaceable()) { if (placeBlock == YesNo.YES) { final PlayerEntity player = FakePlayer.getOrCreate(serverWorld); Platform.configurePlayer(player, side, this.getTile()); // Seems to work without... // Hand hand = player.getActiveHand(); // player.setStackInHand(hand, is); maxStorage = is.getCount(); worked = true; if (type == Actionable.MODULATE) { // The side the plane is attached to will be considered the look direction // in terms of placing an item Direction lookDirection = side.getFacing(); PlaneDirectionalPlaceContext context = new PlaneDirectionalPlaceContext(w, player, placePos, lookDirection, is, lookDirection.getOpposite()); i.useOnBlock(context); maxStorage -= is.getCount(); } else { maxStorage = 1; } // Seems to work without... Safe keeping // player.setStackInHand(hand, ItemStack.EMPTY); } else { final int sum = this.countEntitesAround(w, placePos); // Disable spawning once there is a certain amount of entities in an area. if (sum < AEConfig.instance().getFormationPlaneEntityLimit()) { worked = true; if (type == Actionable.MODULATE) { is.setCount((int) maxStorage); if (!spawnItemEntity(serverWorld, te, side, is)) { // revert in case something prevents spawning. worked = false; } } } } } this.blocked = !w.getBlockState(placePos).getMaterial().isReplaceable(); if (worked) { final IAEItemStack out = input.copy(); out.decStackSize(maxStorage); if (out.getStackSize() == 0) { return null; } return out; } return input; } private static boolean spawnItemEntity(ServerWorld w, BlockEntity te, AEPartLocation side, ItemStack is) { // the item offset based on the entity height plus some offset final double itemOffset = .55 + EntityType.ITEM.getHeight(); // The center of the block the plane is located in final double centerX = te.getPos().getX() + .5; final double centerY = te.getPos().getY() + .5; final double centerZ = te.getPos().getZ() + .5; // When spawning downwards, we have to take the item height of 0.25 into account // Otherwise it will get stuck and be spit out in a random direction as // minecraft spawns it at its feet position and not center final double additionalYOffset = side.yOffset == -1 ? -.3 : 0; // Calculate the offsets to spawn it into the adjacent block, taking the sign // into account. // Spawn it 0.8 blocks away from the center pos when facing in this direction // Every other direction will select a position in a .5 block area around the // block center. final double offsetX = (side.xOffset == 0) ? ((RANDOM_OFFSET.nextFloat() / 2) - .25) : (side.xOffset * itemOffset); final double offsetY = (side.yOffset == 0) ? ((RANDOM_OFFSET.nextFloat() / 2) - .25) : ((side.yOffset * itemOffset) + additionalYOffset); final double offsetZ = (side.zOffset == 0) ? ((RANDOM_OFFSET.nextFloat() / 2) - .25) : (side.zOffset * itemOffset); final double absoluteX = centerX + offsetX; final double absoluteY = centerY + offsetY; final double absoluteZ = centerZ + offsetZ; final ItemEntity ei = new ItemEntity(w, absoluteX, absoluteY, absoluteZ, is.copy()); ei.setVelocity(side.xOffset * .1, side.yOffset * 0.1, side.zOffset * 0.1); Entity result; if (is.getItem() instanceof AECustomEntityItem) { result = ((AECustomEntityItem) is.getItem()).replaceItemEntity(w, ei, is); if (result != ei) { ei.remove(); } } else { result = ei; } if (!w.spawnEntity(result)) { result.remove(); return false; } return true; } @Override public IStorageChannel getChannel() { return Api.instance().storage().getStorageChannel(IItemStorageChannel.class); } @Override public IPartModel getStaticModels() { return MODELS.getModel(this.isPowered(), this.isActive()); } @Override public Object getModelData() { return getConnections(); } @Override public ItemStack getItemStackRepresentation() { return Api.instance().definitions().parts().formationPlane().maybeStack(1).orElse(ItemStack.EMPTY); } @Override public ScreenHandlerType getContainerType() { return FormationPlaneContainer.TYPE; } private int countEntitesAround(World world, BlockPos pos) { final Box t = new Box(pos).expand(8); final List list = world.getEntitiesByClass(Entity.class, t, null); return list.size(); } /** * A custom {@link AutomaticItemPlacementContext} which also accepts a player needed * various blocks like seeds. *

* Also removed {@link AutomaticItemPlacementContext#canReplaceExisting} as this * can cause a {@link StackOverflowError} for certain replaceable blocks. */ private static class PlaneDirectionalPlaceContext extends ItemPlacementContext { private final Direction lookDirection; public PlaneDirectionalPlaceContext(World world, PlayerEntity player, BlockPos pos, Direction lookDirection, ItemStack itemStack, Direction facing) { super(world, player, Hand.MAIN_HAND, itemStack, new BlockHitResult(Vec3d.ofBottomCenter(pos), facing, pos, false)); this.lookDirection = lookDirection; } @Override public BlockPos getBlockPos() { return this.method_30344().getBlockPos(); } @Override public boolean canPlace() { return this.getWorld().getBlockState(this.method_30344().getBlockPos()).canReplace(this); } @Override public Direction getPlayerLookDirection() { return Direction.DOWN; } @Override public Direction[] getPlacementDirections() { switch (this.lookDirection) { case DOWN: default: return new Direction[]{Direction.DOWN, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP}; case UP: return new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; case NORTH: return new Direction[]{Direction.DOWN, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.UP, Direction.SOUTH }; case SOUTH: return new Direction[] { Direction.DOWN, Direction.SOUTH, Direction.EAST, Direction.WEST, Direction.UP, Direction.NORTH }; case WEST: return new Direction[] { Direction.DOWN, Direction.WEST, Direction.SOUTH, Direction.UP, Direction.NORTH, Direction.EAST }; case EAST: return new Direction[] { Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.UP, Direction.NORTH, Direction.WEST }; } } @Override public Direction getPlayerFacing() { return this.lookDirection.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.lookDirection; } @Override public boolean shouldCancelInteraction() { return false; } @Override public float getPlayerYaw() { return (float) (this.lookDirection.getHorizontal() * 90); } } }