From f7651fff2926b0184fbceaed326e5fe97d1145a1 Mon Sep 17 00:00:00 2001 From: Serenibyss <10861407+serenibyss@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:53:52 -0600 Subject: [PATCH] Add part placement preview, render all part boxes on hover (#349) --- .../appeng/api/parts/IFacadeContainer.java | 7 + .../java/appeng/api/parts/IPartHelper.java | 50 +++ src/api/java/appeng/api/parts/IPartHost.java | 11 + src/main/java/appeng/core/AEConfig.java | 6 + src/main/java/appeng/core/Registration.java | 5 +- src/main/java/appeng/core/api/ApiPart.java | 65 ++- .../core/sync/AppEngPacketHandlerBase.java | 2 - .../appeng/core/sync/packets/PacketClick.java | 27 -- .../sync/packets/PacketPartPlacement.java | 79 ---- .../java/appeng/facade/FacadeContainer.java | 7 +- .../appeng/hooks/RenderBlockOutlineHook.java | 208 ++++++++++ .../java/appeng/hooks/WrenchClickHook.java | 86 ++++ .../java/appeng/items/parts/ItemFacade.java | 61 ++- .../tools/powered/ToolColorApplicator.java | 19 +- .../java/appeng/parts/CableBusContainer.java | 3 +- src/main/java/appeng/parts/PartPlacement.java | 392 +++--------------- src/main/java/appeng/util/Platform.java | 4 + 17 files changed, 585 insertions(+), 447 deletions(-) delete mode 100644 src/main/java/appeng/core/sync/packets/PacketPartPlacement.java create mode 100644 src/main/java/appeng/hooks/RenderBlockOutlineHook.java create mode 100644 src/main/java/appeng/hooks/WrenchClickHook.java diff --git a/src/api/java/appeng/api/parts/IFacadeContainer.java b/src/api/java/appeng/api/parts/IFacadeContainer.java index 16b0221c3..675eaa76f 100644 --- a/src/api/java/appeng/api/parts/IFacadeContainer.java +++ b/src/api/java/appeng/api/parts/IFacadeContainer.java @@ -41,6 +41,13 @@ import appeng.api.util.AEPartLocation; public interface IFacadeContainer { + /** + * Checks if the {@link IFacadePart} can be added to the given side. + * + * @return true if the facade can be successfully added + */ + boolean canAddFacade( IFacadePart a ); + /** * Attempts to add the {@link IFacadePart} to the given side. * diff --git a/src/api/java/appeng/api/parts/IPartHelper.java b/src/api/java/appeng/api/parts/IPartHelper.java index d38272be5..2cd290cab 100644 --- a/src/api/java/appeng/api/parts/IPartHelper.java +++ b/src/api/java/appeng/api/parts/IPartHelper.java @@ -24,6 +24,7 @@ package appeng.api.parts; +import appeng.api.util.AEPartLocation; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumActionResult; @@ -32,6 +33,8 @@ import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import javax.annotation.Nullable; + public interface IPartHelper { @@ -53,4 +56,51 @@ public interface IPartHelper * @return the render mode */ CableRenderMode getCableRenderMode(); + + /** + * Try to get a part at a specific place in the world. + * + * @param w world the part is in + * @param pos pos of the part host + * @param side side to test for a part on. + * + * @return the part if it exists, null otherwise + */ + @Nullable + IPart getPart( World w, BlockPos pos, AEPartLocation side ); + + /** + * Try to get a part host at a specific place in the world. + * + * @param w world the part host is in + * @param pos pos of the part host + * + * @return the part host if it exists, null otherwise + */ + @Nullable + IPartHost getPartHost( World w, BlockPos pos ); + + /** + * Get a part host if it exists, or place one if it doesn't. + * + * @param w world the part host is in + * @param pos pos to get or place the part host + * @param force whether to skip permission and existing block checks and forcibly place the part host here. + * @param p the placing player, or null if none + * + * @return the existing or created part host, or null if it didn't already exist and part host is unable to be placed. + */ + @Nullable + IPartHost getOrPlacePartHost( World w, BlockPos pos, boolean force, @Nullable EntityPlayer p ); + + /** + * Test if a part host can be successfully placed at a given position by the provided player. + * + * @param w world the part host is in + * @param pos pos to test for part host placement + * @param p the placing player, or null if none + * + * @return if the part can be placed at the provided world and position + */ + boolean canPlacePartHost( World w, BlockPos pos, @Nullable EntityPlayer p ); } diff --git a/src/api/java/appeng/api/parts/IPartHost.java b/src/api/java/appeng/api/parts/IPartHost.java index f9c05fcf9..57e797ec4 100644 --- a/src/api/java/appeng/api/parts/IPartHost.java +++ b/src/api/java/appeng/api/parts/IPartHost.java @@ -145,6 +145,17 @@ public interface IPartHost extends ICustomCableConnection */ SelectedPart selectPart( Vec3d pos ); + /** + * Same as {@link #selectPart(Vec3d)}, but with global instead of local coordinates. + */ + default SelectedPart selectPartGlobal( Vec3d pos ) { + DimensionalCoord globalPos = getLocation(); + return selectPart(pos.subtract( + globalPos.getPos().getX(), + globalPos.getPos().getY(), + globalPos.getPos().getZ())); + } + /** * can be used by parts to trigger the tile or part to save. */ diff --git a/src/main/java/appeng/core/AEConfig.java b/src/main/java/appeng/core/AEConfig.java index b285c6bbe..ace9c3913 100644 --- a/src/main/java/appeng/core/AEConfig.java +++ b/src/main/java/appeng/core/AEConfig.java @@ -83,6 +83,7 @@ public final class AEConfig extends Configuration implements IConfigurableObject private int craftingCalculationTimePerTick = 5; private PowerUnits selectedPowerUnit = PowerUnits.AE; private boolean showCraftableTooltip = true; + private boolean showPlacementPreview = true; // Spatial IO/Dimension private int storageProviderID = -1; @@ -257,6 +258,7 @@ public final class AEConfig extends Configuration implements IConfigurableObject this.useLargeFonts = this.get("Client", "useTerminalUseLargeFont", false).getBoolean(false); this.useColoredCraftingStatus = this.get("Client", "useColoredCraftingStatus", true).getBoolean(true); this.showCraftableTooltip = this.get("Client", "showCraftableTooltip", true, "Whether to add \"Craftable\" to item tooltips when they can be crafted automatically.").getBoolean(true); + this.showPlacementPreview = this.get("Client", "showPlacementPreview", true, "Whether to show a preview of part and facade placement.").getBoolean(true); // load buttons.. for (int btnNum = 0; btnNum < 4; btnNum++) { @@ -514,6 +516,10 @@ public final class AEConfig extends Configuration implements IConfigurableObject return this.showCraftableTooltip; } + public boolean showPlacementPreview() { + return this.showPlacementPreview; + } + public boolean isDisableColoredCableRecipesInJEI() { return this.disableColoredCableRecipesInJEI; } diff --git a/src/main/java/appeng/core/Registration.java b/src/main/java/appeng/core/Registration.java index a783a7ea3..107b816d0 100644 --- a/src/main/java/appeng/core/Registration.java +++ b/src/main/java/appeng/core/Registration.java @@ -54,13 +54,13 @@ import appeng.core.stats.PartItemPredicate; import appeng.core.stats.Stats; import appeng.core.worlddata.SpatialDimensionManager; import appeng.fluids.registries.BasicFluidCellGuiHandler; +import appeng.hooks.WrenchClickHook; import appeng.hooks.TickHandler; import appeng.items.materials.ItemMaterial; import appeng.items.parts.ItemFacade; import appeng.items.parts.ItemPart; import appeng.loot.ChestLoot; import appeng.me.cache.*; -import appeng.parts.PartPlacement; import appeng.recipes.AEItemResolver; import appeng.recipes.AERecipeLoader; import appeng.recipes.game.DisassembleRecipe; @@ -109,7 +109,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Set; @@ -193,7 +192,7 @@ final class Registration { MinecraftForge.EVENT_BUS.register(TickHandler.INSTANCE); - MinecraftForge.EVENT_BUS.register(new PartPlacement()); + MinecraftForge.EVENT_BUS.register(new WrenchClickHook()); if (AEConfig.instance().isFeatureEnabled(AEFeature.CHEST_LOOT)) { MinecraftForge.EVENT_BUS.register(new ChestLoot()); diff --git a/src/main/java/appeng/core/api/ApiPart.java b/src/main/java/appeng/core/api/ApiPart.java index c55390e86..f06bf6360 100644 --- a/src/main/java/appeng/core/api/ApiPart.java +++ b/src/main/java/appeng/core/api/ApiPart.java @@ -19,28 +19,91 @@ package appeng.core.api; +import appeng.api.AEApi; +import appeng.api.definitions.ITileDefinition; import appeng.api.parts.CableRenderMode; +import appeng.api.parts.IPart; import appeng.api.parts.IPartHelper; import appeng.core.AppEng; import appeng.parts.PartPlacement; +import appeng.api.parts.IPartHost; +import appeng.api.util.AEPartLocation; +import appeng.util.Platform; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + public class ApiPart implements IPartHelper { @Override public EnumActionResult placeBus(final ItemStack is, final BlockPos pos, final EnumFacing side, final EntityPlayer player, final EnumHand hand, final World w) { - return PartPlacement.place(is, pos, side, player, hand, w, PartPlacement.PlaceType.PLACE_ITEM, 0); + return PartPlacement.place(is, pos, side, player, hand, w); } @Override public CableRenderMode getCableRenderMode() { return AppEng.proxy.getRenderMode(); } + + @Nullable + @Override + public IPart getPart(World w, BlockPos pos, AEPartLocation side) { + final TileEntity tile = w.getTileEntity(pos); + if (tile instanceof IPartHost partHost) { + return partHost.getPart(side); + } + return null; + } + + @Nullable + @Override + public IPartHost getPartHost(World w, BlockPos pos) { + final TileEntity tile = w.getTileEntity(pos); + if (tile instanceof IPartHost partHost) { + return partHost; + } + return null; + } + + @Nullable + @Override + public IPartHost getOrPlacePartHost(World w, BlockPos pos, boolean force, @Nullable EntityPlayer p) { + final TileEntity tile = w.getTileEntity(pos); + if (tile instanceof IPartHost partHost) { + return partHost; + } else { + if (!force && !canPlacePartHost(w, pos, p)) { + return null; + } + + final ITileDefinition multiPart = AEApi.instance().definitions().blocks().multiPart(); + if (!multiPart.isEnabled()) return null; + Block blk = multiPart.maybeBlock().orElse(null); + if (blk == null) return null; + + final IBlockState state = blk.getDefaultState(); + w.setBlockState(pos, state, 3); + return w.getTileEntity(pos) instanceof IPartHost host ? host : null; + } + } + + @Override + public boolean canPlacePartHost(World w, BlockPos pos, @Nullable EntityPlayer p) { + if (p != null && !Platform.hasPermissions(w, pos, p)) { + return false; + } + + final Block blk = w.getBlockState(pos).getBlock(); + return blk == null || blk.isReplaceable(w, pos); + } } diff --git a/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java b/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java index 78f6f0574..8ab792bfa 100644 --- a/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java +++ b/src/main/java/appeng/core/sync/AppEngPacketHandlerBase.java @@ -44,8 +44,6 @@ public class AppEngPacketHandlerBase { PACKET_CONFIG_BUTTON(PacketConfigButton.class), - PACKET_PART_PLACEMENT(PacketPartPlacement.class), - PACKET_LIGHTNING(PacketLightning.class), PACKET_MATTER_CANNON(PacketMatterCannon.class), diff --git a/src/main/java/appeng/core/sync/packets/PacketClick.java b/src/main/java/appeng/core/sync/packets/PacketClick.java index 893cb39e7..87a8e987e 100644 --- a/src/main/java/appeng/core/sync/packets/PacketClick.java +++ b/src/main/java/appeng/core/sync/packets/PacketClick.java @@ -19,21 +19,13 @@ package appeng.core.sync.packets; -import appeng.api.AEApi; -import appeng.api.definitions.IComparableDefinition; -import appeng.api.definitions.IItems; -import appeng.api.implementations.items.IMemoryCard; -import appeng.api.implementations.items.MemoryCardMessages; import appeng.block.networking.BlockCableBus; import appeng.core.sync.AppEngPacket; import appeng.core.sync.network.INetworkInfo; -import appeng.items.tools.ToolNetworkTool; -import appeng.items.tools.powered.ToolColorApplicator; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; @@ -99,31 +91,12 @@ public class PacketClick extends AppEngPacket { @Override public void serverPacketData(final INetworkInfo manager, final AppEngPacket packet, final EntityPlayer player) { - final ItemStack is = player.inventory.getCurrentItem(); - final IItems items = AEApi.instance().definitions().items(); - final IComparableDefinition maybeMemoryCard = items.memoryCard(); - final IComparableDefinition maybeColorApplicator = items.colorApplicator(); final BlockPos pos = new BlockPos(this.x, this.y, this.z); if (this.leftClick) { final Block block = player.world.getBlockState(pos).getBlock(); if (block instanceof BlockCableBus) { ((BlockCableBus) block).onBlockClickPacket(player.world, pos, player, this.hand, new Vec3d(this.hitX, this.hitY, this.hitZ)); } - } else { - if (!is.isEmpty()) { - if (is.getItem() instanceof ToolNetworkTool) { - final ToolNetworkTool tnt = (ToolNetworkTool) is.getItem(); - tnt.serverSideToolLogic(is, player, this.hand, player.world, pos, this.side, this.hitX, this.hitY, - this.hitZ); - } else if (maybeMemoryCard.isSameAs(is)) { - final IMemoryCard mem = (IMemoryCard) is.getItem(); - mem.notifyUser(player, MemoryCardMessages.SETTINGS_CLEARED); - is.setTagCompound(null); - } else if (maybeColorApplicator.isSameAs(is)) { - final ToolColorApplicator mem = (ToolColorApplicator) is.getItem(); - mem.cycleColors(is, mem.getColor(is), 1); - } - } } } } diff --git a/src/main/java/appeng/core/sync/packets/PacketPartPlacement.java b/src/main/java/appeng/core/sync/packets/PacketPartPlacement.java deleted file mode 100644 index ccd46df12..000000000 --- a/src/main/java/appeng/core/sync/packets/PacketPartPlacement.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.core.sync.packets; - - -import appeng.core.AppEng; -import appeng.core.sync.AppEngPacket; -import appeng.core.sync.network.INetworkInfo; -import appeng.parts.PartPlacement; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import net.minecraft.util.math.BlockPos; - - -public class PacketPartPlacement extends AppEngPacket { - - private int x; - private int y; - private int z; - private int face; - private float eyeHeight; - private EnumHand hand; - - // automatic. - public PacketPartPlacement(final ByteBuf stream) { - this.x = stream.readInt(); - this.y = stream.readInt(); - this.z = stream.readInt(); - this.face = stream.readByte(); - this.eyeHeight = stream.readFloat(); - this.hand = EnumHand.values()[stream.readByte()]; - } - - // api - public PacketPartPlacement(final BlockPos pos, final EnumFacing face, final float eyeHeight, final EnumHand hand) { - final ByteBuf data = Unpooled.buffer(); - - data.writeInt(this.getPacketID()); - data.writeInt(pos.getX()); - data.writeInt(pos.getY()); - data.writeInt(pos.getZ()); - data.writeByte(face.ordinal()); - data.writeFloat(eyeHeight); - data.writeByte(hand.ordinal()); - - this.configureWrite(data); - } - - @Override - public void serverPacketData(final INetworkInfo manager, final AppEngPacket packet, final EntityPlayer player) { - final EntityPlayerMP sender = (EntityPlayerMP) player; - AppEng.proxy.updateRenderMode(sender); - PartPlacement.setEyeHeight(this.eyeHeight); - PartPlacement.place(sender.getHeldItem(this.hand), new BlockPos(this.x, this.y, this.z), EnumFacing.VALUES[this.face], sender, this.hand, - sender.world, - PartPlacement.PlaceType.INTERACT_FIRST_PASS, 0); - AppEng.proxy.updateRenderMode(null); - } -} diff --git a/src/main/java/appeng/facade/FacadeContainer.java b/src/main/java/appeng/facade/FacadeContainer.java index 02c958bdb..bb71286f3 100644 --- a/src/main/java/appeng/facade/FacadeContainer.java +++ b/src/main/java/appeng/facade/FacadeContainer.java @@ -44,9 +44,14 @@ public class FacadeContainer implements IFacadeContainer { this.storage = cbs; } + @Override + public boolean canAddFacade(IFacadePart a) { + return this.getFacade(a.getSide()) == null; + } + @Override public boolean addFacade(final IFacadePart a) { - if (this.getFacade(a.getSide()) == null) { + if (canAddFacade(a)) { this.storage.setFacade(a.getSide().ordinal(), a); return true; } diff --git a/src/main/java/appeng/hooks/RenderBlockOutlineHook.java b/src/main/java/appeng/hooks/RenderBlockOutlineHook.java new file mode 100644 index 000000000..4ad77356a --- /dev/null +++ b/src/main/java/appeng/hooks/RenderBlockOutlineHook.java @@ -0,0 +1,208 @@ +package appeng.hooks; + +import appeng.api.AEApi; +import appeng.api.parts.IFacadePart; +import appeng.api.parts.IPart; +import appeng.api.parts.IPartCollisionHelper; +import appeng.api.parts.IPartHost; +import appeng.api.parts.IPartItem; +import appeng.api.parts.SelectedPart; +import appeng.api.util.AEPartLocation; +import appeng.core.AEConfig; +import appeng.facade.FacadePart; +import appeng.facade.IFacadeItem; +import appeng.items.parts.ItemFacade; +import appeng.parts.BusCollisionHelper; +import appeng.parts.PartPlacement; +import appeng.parts.PartPlacement.Placement; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraftforge.client.event.DrawBlockHighlightEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.lwjgl.opengl.GL11; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +@SideOnly(Side.CLIENT) +@Mod.EventBusSubscriber(Side.CLIENT) +public class RenderBlockOutlineHook { + + @SubscribeEvent + public static void onDrawHighlightEvent(DrawBlockHighlightEvent event) { + if (event.getTarget() == null) return; + // noinspection ConstantConditions + if (event.getTarget().getBlockPos() == null) return; + if (event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK) return; + + EntityPlayer player = event.getPlayer(); + ItemStack stack = player.getHeldItemMainhand(); + RayTraceResult hitResult = event.getTarget(); + + if (player.world.getBlockState(hitResult.getBlockPos()).getBlock() == Blocks.AIR) { + return; + } + + if (replaceBlockOutline(player, stack, hitResult, event.getPartialTicks())) { + event.setCanceled(true); + } + } + + private static boolean replaceBlockOutline(EntityPlayer player, ItemStack stack, RayTraceResult hitResult, float partialTicks) { + BlockPos pos = hitResult.getBlockPos(); + + // Render the placement preview + if (AEConfig.instance().showPlacementPreview()) { + renderPartPlacementPreview(player, hitResult, stack, partialTicks); + } + + IPartHost host = AEApi.instance().partHelper().getPartHost(player.world, pos); + if (host != null) { + + // Try to render facade placement preview here, since it's a + // convenient time to do it due to having the Part Host already. + if (AEConfig.instance().showPlacementPreview()) { + renderFacadePlacementPreview(host, player, hitResult, stack, partialTicks); + } + + // Render the Part Host block outline, which is done differently from default behavior + SelectedPart selectedPart = host.selectPartGlobal(hitResult.hitVec); + if (selectedPart.facade != null) { + renderFacade(selectedPart.facade, host, pos, selectedPart.side.getFacing(), player, partialTicks, false, false); + return true; + } + if (selectedPart.part != null) { + renderPart(selectedPart.part, pos, selectedPart.side.getFacing(), player, partialTicks, false, false); + return true; + } + } + + return false; + } + + /** Render a placement preview for a part item, if possible. */ + private static void renderPartPlacementPreview(EntityPlayer player, RayTraceResult hitResult, ItemStack stack, float partialTicks) { + if (!(stack.getItem() instanceof IPartItem partItem)) return; + + Placement placement = PartPlacement.getPartPlacement(player, player.world, stack, hitResult.getBlockPos(), hitResult.sideHit); + if (placement == null) return; + if (!player.world.getWorldBorder().contains(placement.pos())) return; + + IPart part = partItem.createPartFromItemStack(stack); + if (part == null) return; + + // Render with two depth passes to render behind blocks + renderPart(part, placement.pos(), placement.side(), player, partialTicks, true, true); + renderPart(part, placement.pos(), placement.side(), player, partialTicks, true, false); + } + + /** Render a placement preview for a facade item, if possible. */ + private static void renderFacadePlacementPreview(@Nonnull IPartHost host, EntityPlayer player, RayTraceResult hitResult, ItemStack stack, float partialTicks) { + if (!(stack.getItem() instanceof IFacadeItem facadeItem)) return; + + Placement placement = PartPlacement.getPartPlacement(player, player.world, stack, hitResult.getBlockPos(), hitResult.sideHit); + if (placement == null) return; + + FacadePart part = facadeItem.createPartFromItemStack(stack, AEPartLocation.fromFacing(placement.side())); + if (part == null) return; + if (!ItemFacade.canPlaceFacade(host, part)) return; + + // Render with two depth passes to render behind blocks + renderFacade(part, host, placement.pos(), placement.side(), player, partialTicks, true, true); + renderFacade(part, host, placement.pos(), placement.side(), player, partialTicks, true, false); + } + + /** Render a part block outline. */ + private static void renderPart(IPart part, BlockPos pos, EnumFacing side, EntityPlayer player, float partialTicks, boolean preview, boolean insideBlock) { + List boxes = new ArrayList<>(); + IPartCollisionHelper helper = new BusCollisionHelper(boxes, AEPartLocation.fromFacing(side), player, true); + part.getBoxes(helper); + offsetBoxes(boxes, pos, player, partialTicks); + renderBoxes(boxes, preview, insideBlock); + } + + /** Render a facade block outline. */ + private static void renderFacade(IFacadePart facade, IPartHost host, BlockPos pos, EnumFacing side, EntityPlayer player, float partialTicks, boolean preview, boolean insideBlock) { + List boxes = new ArrayList<>(); + IPartCollisionHelper helper = new BusCollisionHelper(boxes, AEPartLocation.fromFacing(side), player, true); + facade.getBoxes(helper, player); + + // Render a cable anchor part box as well if there is no part + // attachment on this side, and if we are in a preview render pass. + if (host.getPart(side) == null && preview) { + addAnchorBox(helper); + } + + offsetBoxes(boxes, pos, player, partialTicks); + renderBoxes(boxes, preview, insideBlock); + } + + /** + * Render the provided list of AABB boxes as a block outline. + * + * @param preview Whether this is a preview placement or a normal block outline. Determines coloration of the outline. + * @param insideBlock Whether to disable depth test and darken the outline. Will draw behind other blocks. + */ + private static void renderBoxes(List boxes, boolean preview, boolean insideBlock) { + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate( + GlStateManager.SourceFactor.SRC_ALPHA, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, + GlStateManager.SourceFactor.ONE, + GlStateManager.DestFactor.ZERO); + GlStateManager.glLineWidth(2.0F); + GlStateManager.disableTexture2D(); + GlStateManager.depthMask(false); + + if (insideBlock) { + GL11.glDisable(GL11.GL_DEPTH_TEST); + } + + for (AxisAlignedBB box : boxes) { + RenderGlobal.drawSelectionBoundingBox( + box, + preview ? 1 : 0, + preview ? 1 : 0, + preview ? 1 : 0, + insideBlock ? 0.2F : preview ? 0.6F : 0.4F); + } + + if (insideBlock) { + GL11.glEnable(GL11.GL_DEPTH_TEST); + } + + GlStateManager.depthMask(true); + GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + } + + /** Offset each box in the list to the appropriate render position. */ + private static void offsetBoxes(List boxes, BlockPos pos, EntityPlayer player, float partialTicks) { + double dX = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialTicks; + double dY = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialTicks; + double dZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialTicks; + boxes.replaceAll(box -> box.offset(pos.getX() - dX, pos.getY() - dY, pos.getZ() - dZ).grow(0.002D)); + } + + /** Adds a cable anchor box to the collision helper. This does NOT offset the box! */ + private static void addAnchorBox(IPartCollisionHelper helper) { + ItemStack anchorStack = AEApi.instance().definitions().parts().cableAnchor().maybeStack(1).orElse(null); + if (anchorStack != null && anchorStack.getItem() instanceof IPartItem anchorPartItem) { + IPart anchorPart = anchorPartItem.createPartFromItemStack(anchorStack); + if (anchorPart != null) { + anchorPart.getBoxes(helper); + } + } + } +} diff --git a/src/main/java/appeng/hooks/WrenchClickHook.java b/src/main/java/appeng/hooks/WrenchClickHook.java new file mode 100644 index 000000000..0140cd2d0 --- /dev/null +++ b/src/main/java/appeng/hooks/WrenchClickHook.java @@ -0,0 +1,86 @@ +package appeng.hooks; + +import appeng.api.parts.IPartHost; +import appeng.api.parts.PartItemStack; +import appeng.api.parts.SelectedPart; +import appeng.api.util.DimensionalCoord; +import appeng.util.LookDirection; +import appeng.util.Platform; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.World; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Wrench action, handled in event rather than item to support implementors of our wrench api + */ +public class WrenchClickHook { + + @SubscribeEvent + public void playerInteract(final PlayerInteractEvent event) { + // Only handle the main hand event + if (event.getHand() != EnumHand.MAIN_HAND) return; + + if (event instanceof PlayerInteractEvent.RightClickBlock && !event.getEntityPlayer().world.isRemote) { + EntityPlayer player = event.getEntityPlayer(); + EnumHand hand = event.getHand(); + BlockPos pos = event.getPos(); + World world = event.getWorld(); + ItemStack held = event.getItemStack(); + + if (!Platform.hasPermissions(new DimensionalCoord(world, pos), player)) { + return; + } + + if (player.isSneaking() && Platform.isWrench(player, held, pos)) { + Block block = world.getBlockState(pos).getBlock(); + TileEntity tile = world.getTileEntity(pos); + if (!(tile instanceof IPartHost host)) { + return; + } + + final LookDirection dir = Platform.getPlayerRay(player, player.getEyeHeight()); + final RayTraceResult mop = block.collisionRayTrace(world.getBlockState(pos), world, pos, dir.getA(), dir.getB()); + if (mop != null) { + final SelectedPart sp = host.selectPartGlobal(mop.hitVec); + if (sp == null) { + return; + } + + final List is = new ArrayList<>(); + + if (sp.part != null) { + is.add(sp.part.getItemStack(PartItemStack.WRENCH)); + sp.part.getDrops(is, true); + host.removePart(sp.side, false); + } + + if (sp.facade != null) { + is.add(sp.facade.getItemStack()); + host.getFacadeContainer().removeFacade(host, sp.side); + Platform.notifyBlocksOfNeighbors(world, pos); + } + + if (host.isEmpty()) { + host.cleanup(); + } + + if (!is.isEmpty()) { + Platform.spawnDrops(world, pos, is); + } + } else { + player.swingArm(hand); + } + } + } + } +} diff --git a/src/main/java/appeng/items/parts/ItemFacade.java b/src/main/java/appeng/items/parts/ItemFacade.java index 747882023..e504b3c58 100644 --- a/src/main/java/appeng/items/parts/ItemFacade.java +++ b/src/main/java/appeng/items/parts/ItemFacade.java @@ -22,6 +22,8 @@ package appeng.items.parts; import appeng.api.AEApi; import appeng.api.exceptions.MissingDefinitionException; import appeng.api.parts.IAlphaPassItem; +import appeng.api.parts.IFacadePart; +import appeng.api.parts.IPartHost; import appeng.api.util.AEPartLocation; import appeng.core.AELog; import appeng.core.FacadeConfig; @@ -40,7 +42,9 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.*; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent; import java.util.ArrayList; import java.util.List; @@ -59,7 +63,62 @@ public class ItemFacade extends AEBaseItem implements IFacadeItem, IAlphaPassIte @Override public EnumActionResult onItemUseFirst(final EntityPlayer player, final World world, final BlockPos pos, final EnumFacing side, final float hitX, final float hitY, final float hitZ, final EnumHand hand) { - return AEApi.instance().partHelper().placeBus(player.getHeldItem(hand), pos, side, player, hand, world); + ItemStack stack = player.getHeldItem(hand); + if (stack.getItem() != this) { + return EnumActionResult.PASS; + } + + FacadePart facade = createPartFromItemStack(stack, AEPartLocation.fromFacing(side)); + if (!placeFacade(facade, world, pos)) { + return EnumActionResult.FAIL; + } + + if (!world.isRemote) { + if (!player.isCreative()) { + stack.grow(-1); + if (stack.isEmpty()) { + player.setHeldItem(hand, ItemStack.EMPTY); + MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, stack, hand)); + } + } + return EnumActionResult.SUCCESS; + } else { + player.swingArm(hand); + return EnumActionResult.PASS; + } + } + + private static boolean placeFacade(FacadePart facade, World world, BlockPos pos) { + IPartHost host = AEApi.instance().partHelper().getPartHost(world, pos); + if (host == null) { + return false; + } + + if (!canPlaceFacade(host, facade)) { + return false; + } + + if (!host.getFacadeContainer().addFacade(facade)) { + return false; + } + + host.markForSave(); + host.markForUpdate(); + return true; + } + + public static boolean canPlaceFacade(IPartHost host, FacadePart facade) { + if (host.getPart(AEPartLocation.INTERNAL) == null) { + return false; + } + return host.getFacadeContainer().canAddFacade(facade); + } + + public static IFacadePart createFacade(ItemStack held, AEPartLocation side) { + if (held.getItem() instanceof IFacadeItem facadeItem) { + return facadeItem.createPartFromItemStack(held, side); + } + return null; } @Override diff --git a/src/main/java/appeng/items/tools/powered/ToolColorApplicator.java b/src/main/java/appeng/items/tools/powered/ToolColorApplicator.java index 2b6dd198d..a99adab99 100644 --- a/src/main/java/appeng/items/tools/powered/ToolColorApplicator.java +++ b/src/main/java/appeng/items/tools/powered/ToolColorApplicator.java @@ -60,6 +60,7 @@ import net.minecraft.item.ItemSnowball; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ActionResult; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; @@ -97,8 +98,22 @@ public class ToolColorApplicator extends AEBasePoweredItem implements IStorageCe return this.onItemUse(p.getHeldItem(hand), p, w, pos, hand, side, hitX, hitY, hitZ); } + @Override + public ActionResult onItemRightClick(World w, EntityPlayer p, EnumHand hand) { + ItemStack stack = p.getHeldItem(hand); + if (p.isSneaking()) { + if (!w.isRemote) { + cycleColors(stack, getColor(stack), 1); + } + return ActionResult.newResult(EnumActionResult.SUCCESS, stack); + } + return ActionResult.newResult(EnumActionResult.PASS, stack); + } + @Override public EnumActionResult onItemUse(ItemStack is, EntityPlayer p, World w, BlockPos pos, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) { + if (p.isSneaking()) return EnumActionResult.PASS; + final Block blk = w.getBlockState(pos).getBlock(); ItemStack paintBall = this.getColor(is); @@ -158,10 +173,6 @@ public class ToolColorApplicator extends AEBasePoweredItem implements IStorageCe } } - if (p.isSneaking()) { - this.cycleColors(is, paintBall, 1); - } - return EnumActionResult.FAIL; } diff --git a/src/main/java/appeng/parts/CableBusContainer.java b/src/main/java/appeng/parts/CableBusContainer.java index 9a382ba99..e9f86f1bf 100644 --- a/src/main/java/appeng/parts/CableBusContainer.java +++ b/src/main/java/appeng/parts/CableBusContainer.java @@ -37,6 +37,7 @@ import appeng.core.AELog; import appeng.core.AEConfig; import appeng.facade.FacadeContainer; import appeng.helpers.AEMultiTile; +import appeng.items.parts.ItemFacade; import appeng.me.GridConnection; import appeng.parts.networking.PartCable; import appeng.util.Platform; @@ -110,7 +111,7 @@ public class CableBusContainer extends CableBusStorage implements AEMultiTile, I @Override public boolean canAddPart(ItemStack is, final AEPartLocation side) { - if (PartPlacement.isFacade(is, side) != null) { + if (ItemFacade.createFacade(is, side) != null) { return true; } diff --git a/src/main/java/appeng/parts/PartPlacement.java b/src/main/java/appeng/parts/PartPlacement.java index f23c7fb38..4113d79dd 100644 --- a/src/main/java/appeng/parts/PartPlacement.java +++ b/src/main/java/appeng/parts/PartPlacement.java @@ -18,377 +18,113 @@ package appeng.parts; - import appeng.api.AEApi; -import appeng.api.definitions.IBlockDefinition; -import appeng.api.definitions.IItems; import appeng.api.parts.*; import appeng.api.util.AEPartLocation; -import appeng.api.util.DimensionalCoord; -import appeng.core.AppEng; -import appeng.core.sync.network.NetworkHandler; -import appeng.core.sync.packets.PacketClick; -import appeng.core.sync.packets.PacketPartPlacement; -import appeng.facade.IFacadeItem; -import appeng.util.LookDirection; -import appeng.util.Platform; -import net.minecraft.block.Block; +import com.github.bsideup.jabel.Desugar; import net.minecraft.block.SoundType; import net.minecraft.block.state.IBlockState; -import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumActionResult; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent; -import net.minecraftforge.event.entity.player.PlayerInteractEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import javax.annotation.Nullable; public class PartPlacement { - private static float eyeHeight = 0.0f; - private final ThreadLocal placing = new ThreadLocal<>(); - private boolean wasCanceled = false; + public static EnumActionResult place(final ItemStack held, final BlockPos pos, EnumFacing side, final EntityPlayer player, final EnumHand hand, final World world) { + if (!(held.getItem() instanceof IPartItem)) { + return EnumActionResult.PASS; + } - public static EnumActionResult place(final ItemStack held, final BlockPos pos, EnumFacing side, final EntityPlayer player, final EnumHand hand, final World world, PlaceType pass, final int depth) { - if (depth > 3) { + // determine where the part would be placed + Placement placement = getPartPlacement(player, world, held, pos, side); + if (placement == null) { return EnumActionResult.FAIL; } - if (!held.isEmpty() && Platform.isWrench(player, held, pos) && player.isSneaking()) { - if (!Platform.hasPermissions(new DimensionalCoord(world, pos), player)) { - return EnumActionResult.FAIL; - } - - final Block block = world.getBlockState(pos).getBlock(); - final TileEntity tile = world.getTileEntity(pos); - IPartHost host = null; - - if (tile instanceof IPartHost) { - host = (IPartHost) tile; - } - - if (host != null) { - if (!world.isRemote) { - final LookDirection dir = Platform.getPlayerRay(player, getEyeOffset(player)); - final RayTraceResult mop = block.collisionRayTrace(world.getBlockState(pos), world, pos, dir.getA(), dir.getB()); - - if (mop != null) { - final List is = new ArrayList<>(); - final SelectedPart sp = selectPart(player, host, - mop.hitVec.add(-mop.getBlockPos().getX(), -mop.getBlockPos().getY(), -mop.getBlockPos().getZ())); - - if (sp.part != null) { - is.add(sp.part.getItemStack(PartItemStack.WRENCH)); - sp.part.getDrops(is, true); - host.removePart(sp.side, false); - } - - if (sp.facade != null) { - is.add(sp.facade.getItemStack()); - host.getFacadeContainer().removeFacade(host, sp.side); - Platform.notifyBlocksOfNeighbors(world, pos); - } - - if (host.isEmpty()) { - host.cleanup(); - } - - if (!is.isEmpty()) { - Platform.spawnDrops(world, pos, is); - } - } - } else { - player.swingArm(hand); - NetworkHandler.instance().sendToServer(new PacketPartPlacement(pos, side, getEyeOffset(player), hand)); - } - return EnumActionResult.SUCCESS; - } - - return EnumActionResult.PASS; - } - - TileEntity tile = world.getTileEntity(pos); - IPartHost host = null; - - if (tile instanceof IPartHost) { - host = (IPartHost) tile; - } - - if (!held.isEmpty()) { - final IFacadePart fp = isFacade(held, AEPartLocation.fromFacing(side)); - if (fp != null) { - if (host != null) { - if (!world.isRemote) { - if (host.getPart(AEPartLocation.INTERNAL) == null) { - return EnumActionResult.FAIL; - } - - if (host.canAddPart(held, AEPartLocation.fromFacing(side))) { - if (host.getFacadeContainer().addFacade(fp)) { - host.markForSave(); - host.markForUpdate(); - if (!player.capabilities.isCreativeMode) { - held.grow(-1); - if (held.getCount() == 0) { - player.setHeldItem(hand, ItemStack.EMPTY); - MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, held, hand)); - } - } - return EnumActionResult.SUCCESS; - } - } - } else { - player.swingArm(hand); - NetworkHandler.instance().sendToServer(new PacketPartPlacement(pos, side, getEyeOffset(player), hand)); - return EnumActionResult.SUCCESS; - } - } - return EnumActionResult.FAIL; - } - } - - if (held.isEmpty()) { - final Block block = world.getBlockState(pos).getBlock(); - if (host != null && player.isSneaking() && block != null) { - final LookDirection dir = Platform.getPlayerRay(player, getEyeOffset(player)); - final RayTraceResult mop = block.collisionRayTrace(world.getBlockState(pos), world, pos, dir.getA(), dir.getB()); - - if (mop != null) { - mop.hitVec = mop.hitVec.add(-mop.getBlockPos().getX(), -mop.getBlockPos().getY(), -mop.getBlockPos().getZ()); - final SelectedPart sPart = selectPart(player, host, mop.hitVec); - if (sPart != null && sPart.part != null) { - if (sPart.part.onShiftActivate(player, hand, mop.hitVec)) { - if (world.isRemote) { - NetworkHandler.instance().sendToServer(new PacketPartPlacement(pos, side, getEyeOffset(player), hand)); - } - return EnumActionResult.SUCCESS; - } - } - } - } - } - - if (held.isEmpty() || !(held.getItem() instanceof IPartItem)) { - return EnumActionResult.PASS; - } - - BlockPos te_pos = pos; - - final IBlockDefinition multiPart = AEApi.instance().definitions().blocks().multiPart(); - if (host == null && pass == PlaceType.PLACE_ITEM) { - EnumFacing offset = null; - - final Block blkID = world.getBlockState(pos).getBlock(); - if (blkID != null && !blkID.isReplaceable(world, pos)) { - offset = side; - if (Platform.isServer()) { - side = side.getOpposite(); - } - } - - te_pos = offset == null ? pos : pos.offset(offset); - - tile = world.getTileEntity(te_pos); - if (tile instanceof IPartHost) { - host = (IPartHost) tile; - } - - final Optional maybeMultiPartStack = multiPart.maybeStack(1); - final Optional maybeMultiPartBlock = multiPart.maybeBlock(); - final Optional maybeMultiPartItemBlock = multiPart.maybeItemBlock(); - - final boolean hostIsNotPresent = host == null; - final boolean multiPartPresent = maybeMultiPartBlock.isPresent() && maybeMultiPartStack.isPresent() && maybeMultiPartItemBlock.isPresent(); - final boolean canMultiPartBePlaced = maybeMultiPartBlock.get().canPlaceBlockAt(world, te_pos); - - if (hostIsNotPresent && multiPartPresent && canMultiPartBePlaced && maybeMultiPartItemBlock.get() - .placeBlockAt(maybeMultiPartStack.get(), player, - world, te_pos, side, 0.5f, 0.5f, 0.5f, maybeMultiPartBlock.get().getDefaultState())) { - if (!world.isRemote) { - tile = world.getTileEntity(te_pos); - - if (tile instanceof IPartHost) { - host = (IPartHost) tile; - } - - pass = PlaceType.INTERACT_SECOND_PASS; - } else { - player.swingArm(hand); - NetworkHandler.instance().sendToServer(new PacketPartPlacement(pos, side, getEyeOffset(player), hand)); - return EnumActionResult.SUCCESS; - } - } else if (host != null && !host.canAddPart(held, AEPartLocation.fromFacing(side))) { - return EnumActionResult.FAIL; - } - } - - if (host == null) { - return EnumActionResult.PASS; - } - - if (!host.canAddPart(held, AEPartLocation.fromFacing(side))) { - if (pass == PlaceType.INTERACT_FIRST_PASS || pass == PlaceType.PLACE_ITEM) { - te_pos = pos.offset(side); - - final Block blkID = world.getBlockState(te_pos).getBlock(); - - if (blkID == null || blkID.isReplaceable(world, te_pos) || host != null) { - return place(held, te_pos, side.getOpposite(), player, hand, world, - pass == PlaceType.INTERACT_FIRST_PASS ? PlaceType.INTERACT_SECOND_PASS : PlaceType.PLACE_ITEM, depth + 1); - } - } - return EnumActionResult.PASS; + // then try to place it + IPart part = placePart(player, world, held, placement.pos(), placement.side(), hand); + if (part == null) { + return EnumActionResult.FAIL; } + // handle placement logic with the stack if (!world.isRemote) { - final IBlockState state = world.getBlockState(pos); - final LookDirection dir = Platform.getPlayerRay(player, getEyeOffset(player)); - final RayTraceResult mop = state.getBlock().collisionRayTrace(state, world, pos, dir.getA(), dir.getB()); - - if (mop != null) { - final SelectedPart sp = selectPart(player, host, - mop.hitVec.add(-mop.getBlockPos().getX(), -mop.getBlockPos().getY(), -mop.getBlockPos().getZ())); - - if (sp.part != null) { - if (!player.isSneaking() && sp.part.onActivate(player, hand, mop.hitVec)) { - return EnumActionResult.FAIL; - } - } - } - - final DimensionalCoord dc = host.getLocation(); - if (!Platform.hasPermissions(dc, player)) { - return EnumActionResult.FAIL; - } - - final AEPartLocation mySide = host.addPart(held, AEPartLocation.fromFacing(side), player, hand); - if (mySide != null) { - multiPart.maybeBlock().ifPresent(multiPartBlock -> - { - final SoundType ss = multiPartBlock.getSoundType(state, world, pos, player); - - world.playSound(null, pos, ss.getPlaceSound(), SoundCategory.BLOCKS, (ss.getVolume() + 1.0F) / 2.0F, ss.getPitch() * 0.8F); - }); - - if (!player.capabilities.isCreativeMode) { - held.grow(-1); - if (held.getCount() == 0) { - player.setHeldItem(hand, ItemStack.EMPTY); - MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, held, hand)); - } + if (player != null && !player.isCreative()) { + held.shrink(1); + if (held.getCount() == 0) { + player.setHeldItem(hand, ItemStack.EMPTY); + MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, held, hand)); } } + return EnumActionResult.SUCCESS; } else { player.swingArm(hand); + return EnumActionResult.PASS; } - return EnumActionResult.SUCCESS; } - private static float getEyeOffset(final EntityPlayer p) { - if (p.world.isRemote) { - return Platform.getEyeOffset(p); + public static IPart placePart(@Nullable EntityPlayer player, World world, ItemStack partItem, BlockPos pos, EnumFacing side, EnumHand hand) { + IPartHost host = AEApi.instance().partHelper().getOrPlacePartHost(world, pos, false, player); + if (host == null) { + return null; } - return getEyeHeight(); - } - - private static SelectedPart selectPart(final EntityPlayer player, final IPartHost host, final Vec3d pos) { - AppEng.proxy.updateRenderMode(player); - final SelectedPart sp = host.selectPart(pos); - AppEng.proxy.updateRenderMode(null); - - return sp; - } - - public static IFacadePart isFacade(final ItemStack held, final AEPartLocation side) { - if (held.getItem() instanceof IFacadeItem) { - return ((IFacadeItem) held.getItem()).createPartFromItemStack(held, side); + AEPartLocation location = host.addPart(partItem, AEPartLocation.fromFacing(side), player, hand); + IPart part = host.getPart(location); + if (part == null) { + if (host.isEmpty()) { + host.cleanup(); + } + return null; } + IBlockState multiPartState = AEApi.instance().definitions().blocks().multiPart().maybeBlock().get().getDefaultState(); + SoundType soundType = multiPartState.getBlock().getSoundType(multiPartState, world, pos, null); + world.playSound(null, pos, soundType.getPlaceSound(), SoundCategory.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); + return part; + } + + @Nullable + public static Placement getPartPlacement(@Nullable EntityPlayer player, World world, ItemStack partStack, BlockPos pos, EnumFacing side) { + if (canPlacePartOnBlock(player, world, partStack, pos, side)) { + return new Placement(pos, side); + } + + // If the part cannot be placed directly in the block, try the opposite side of + // the adjacent block. This is somewhat similar to how torches are placed. + pos = pos.offset(side); + side = side.getOpposite(); + if (canPlacePartOnBlock(player, world, partStack, pos, side)) { + return new Placement(pos, side); + } + + // can't place the part return null; } - @SubscribeEvent - public void playerInteract(final TickEvent.ClientTickEvent event) { - this.wasCanceled = false; - } + public static boolean canPlacePartOnBlock(@Nullable EntityPlayer player, World world, ItemStack partStack, BlockPos pos, EnumFacing side) { + IPartHost host = AEApi.instance().partHelper().getPartHost(world, pos); - @SubscribeEvent - public void playerInteract(final PlayerInteractEvent event) { - // Only handle the main hand event - if (event.getHand() != EnumHand.MAIN_HAND) { - return; + // There is no host at the location, we also cannot place one + if (host == null && !AEApi.instance().partHelper().canPlacePartHost(world, pos, player)) { + return false; } - if (event instanceof PlayerInteractEvent.RightClickEmpty && event.getEntityPlayer().world.isRemote) { - // re-check to see if this event was already channeled, cause these two events are really stupid... - final RayTraceResult mop = Platform.rayTrace(event.getEntityPlayer(), true, false); - final Minecraft mc = Minecraft.getMinecraft(); - - final float f = 1.0F; - final double d0 = mc.playerController.getBlockReachDistance(); - final Vec3d vec3 = mc.getRenderViewEntity().getPositionEyes(f); - - if (mop != null && mop.hitVec.distanceTo(vec3) < d0) { - final World w = event.getEntity().world; - final TileEntity te = w.getTileEntity(mop.getBlockPos()); - if (te instanceof IPartHost && this.wasCanceled) { - event.setCanceled(true); - } - } else { - final ItemStack held = event.getEntityPlayer().getHeldItem(event.getHand()); - final IItems items = AEApi.instance().definitions().items(); - - boolean supportedItem = items.memoryCard().isSameAs(held); - supportedItem |= items.colorApplicator().isSameAs(held); - - if (event.getEntityPlayer().isSneaking() && !held.isEmpty() && supportedItem) { - NetworkHandler.instance().sendToServer(new PacketClick(event.getPos(), event.getFace(), 0, 0, 0, event.getHand())); - } - } - } else if (event instanceof PlayerInteractEvent.RightClickBlock && !event.getEntityPlayer().world.isRemote) { - if (this.placing.get() != null) { - return; - } - - this.placing.set(event); - - final ItemStack held = event.getEntityPlayer().getHeldItem(event.getHand()); - if (place(held, event.getPos(), event.getFace(), event.getEntityPlayer(), event.getHand(), event.getEntityPlayer().world, - PlaceType.INTERACT_FIRST_PASS, 0) == EnumActionResult.SUCCESS) { - event.setCanceled(true); - this.wasCanceled = true; - } - - this.placing.set(null); - } + // Either there is no host, then we assume a freshly placed host will always accept our part, + // or there is a host, and it has a free side. + return host == null || host.canAddPart(partStack, AEPartLocation.fromFacing(side)); } - private static float getEyeHeight() { - return eyeHeight; + @Desugar + public record Placement(BlockPos pos, EnumFacing side) { } - - public static void setEyeHeight(final float eyeHeight) { - PartPlacement.eyeHeight = eyeHeight; - } - - public enum PlaceType { - PLACE_ITEM, INTERACT_FIRST_PASS, INTERACT_SECOND_PASS - } -} \ No newline at end of file +} diff --git a/src/main/java/appeng/util/Platform.java b/src/main/java/appeng/util/Platform.java index 396154c24..1be39c3f0 100644 --- a/src/main/java/appeng/util/Platform.java +++ b/src/main/java/appeng/util/Platform.java @@ -384,6 +384,10 @@ public class Platform { return dc.getWorld().canMineBlockBody(player, dc.getPos()); } + public static boolean hasPermissions(final World world, final BlockPos pos, final EntityPlayer player) { + return world.canMineBlockBody(player, pos); + } + /* * Checks to see if a block is air? */