Add part placement preview, render all part boxes on hover (#349)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user