Add part placement preview, render all part boxes on hover (#349)

This commit is contained in:
Serenibyss
2024-01-08 22:53:52 -06:00
committed by GitHub
parent 51f2a42367
commit f7651fff29
17 changed files with 585 additions and 447 deletions
@@ -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.
*
@@ -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 );
}
@@ -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.
*/
+6
View File
@@ -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;
}
+2 -3
View File
@@ -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());
+64 -1
View File
@@ -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);
}
}
@@ -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),
@@ -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);
}
}
}
}
}
@@ -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 <http://www.gnu.org/licenses/lgpl>.
*/
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);
}
}
@@ -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;
}
@@ -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<AxisAlignedBB> 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<AxisAlignedBB> 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<AxisAlignedBB> 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<AxisAlignedBB> 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);
}
}
}
}
@@ -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<ItemStack> 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);
}
}
}
}
}
@@ -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
@@ -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<ItemStack> 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;
}
@@ -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;
}
+64 -328
View File
@@ -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<Object> 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<ItemStack> 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<ItemStack> maybeMultiPartStack = multiPart.maybeStack(1);
final Optional<Block> maybeMultiPartBlock = multiPart.maybeBlock();
final Optional<ItemBlock> 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
}
}
}
+4
View File
@@ -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?
*/