package speedytools.clientside.tools;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.util.*;
import net.minecraft.world.World;
import speedytools.clientside.UndoManagerClient;
import speedytools.clientside.network.PacketSenderClient;
import speedytools.clientside.rendering.*;
import speedytools.clientside.selections.BlockMultiSelector;
import speedytools.clientside.sound.SoundController;
import speedytools.clientside.sound.SoundEffectBoundaryHum;
import speedytools.clientside.sound.SoundEffectNames;
import speedytools.clientside.sound.SoundEffectSimple;
import speedytools.clientside.userinput.UserInput;
import speedytools.common.items.ItemSpeedyBoundary;
import speedytools.common.items.ItemSpeedyTool;
import speedytools.common.utilities.Pair;
import speedytools.common.utilities.UsefulConstants;
import speedytools.common.utilities.UsefulFunctions;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* User: The Grey Ghost
* Date: 18/04/2014
*/
public class SpeedyToolBoundary extends SpeedyToolComplexBase
{
public SpeedyToolBoundary(ItemSpeedyBoundary i_parentItem, SpeedyToolRenderers i_renderers, SoundController i_speedyToolSounds,
UndoManagerClient i_undoManagerClient, PacketSenderClient i_packetSenderClient) {
super(i_parentItem, i_renderers, i_speedyToolSounds, i_undoManagerClient, i_packetSenderClient);
itemSpeedyBoundary = i_parentItem;
wireframeRendererUpdateLink = this.new BoundaryToolWireframeRendererLink();
boundaryFieldRendererUpdateLink = this.new BoundaryFieldRendererUpdateLink();
}
@Override
public boolean activateTool(ItemStack newToolItemStack) {
currentToolItemStack = newToolItemStack;
if (soundEffectBoundaryHum == null) {
BoundaryHumLink boundaryHumLink = this.new BoundaryHumLink();
soundEffectBoundaryHum = new SoundEffectBoundaryHum(SoundEffectNames.BOUNDARY_HUM, soundController, boundaryHumLink);
}
soundEffectBoundaryHum.startPlayingLoop();
LinkedList<RendererElement> rendererElements = new LinkedList<RendererElement>();
rendererElements.add(new RendererWireframeSelection(wireframeRendererUpdateLink));
rendererElements.add(new RendererBoundaryField(boundaryFieldRendererUpdateLink));
speedyToolRenderers.setRenderers(rendererElements);
iAmActive = true;
return true;
}
/** The user has unequipped this tool, deactivate it, stop any effects, etc
* @return
*/
@Override
public boolean deactivateTool() {
speedyToolRenderers.setRenderers(null);
if (soundEffectBoundaryHum != null) {
soundEffectBoundaryHum.stopPlaying();
}
iAmActive = false;
return true;
}
/** called once per tick on the client side while the user is holding an ItemCloneTool
* used to:
* (1) background generation of a selection, if it has been initiated
* (2) start transmission of the selection, if it has just been completed
* (3) acknowledge (get) the action and undo statuses
*/
@Override
public void performTick(World world) {
updateGrabRenderTick(boundaryGrabActivated);
}
/**
* when selecting the first block in a selection, how should it be done?
*
* @return
*/
@Override
protected BlockMultiSelector.BlockSelectionBehaviour getBlockSelectionBehaviour() {
return BlockMultiSelector.BlockSelectionBehaviour.BOUNDARY_STYLE;
}
@Override
public boolean processUserInput(EntityPlayerSP player, float partialTick, UserInput userInput) {
if (!iAmActive) return false;
controlKeyIsDown = userInput.isControlKeyDown();
UserInput.InputEvent nextEvent;
while (null != (nextEvent = userInput.poll())) {
switch (nextEvent.eventType) {
case LEFT_CLICK_DOWN: {
boundaryCorner1 = null;
boundaryCorner2 = null;
SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_UNPLACE, soundController);
soundEffectSimple.startPlaying();
break;
}
case RIGHT_CLICK_DOWN: {
doRightClick(player, partialTick);
break;
}
}
}
return true;
}
/**
* place corner blocks; or if already both placed - grab / ungrab one of the faces.
* @param player
* @param partialTick
*/
protected void doRightClick(EntityPlayerSP player, float partialTick)
{
if (boundaryCorner1 == null) {
if (blockUnderCursor == null) return;
boundaryCorner1 = new BlockPos(blockUnderCursor);
SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_PLACE_1ST, soundController);
soundEffectSimple.startPlaying();
} else if (boundaryCorner2 == null) {
if (blockUnderCursor == null) return;
addCornerPointWithMaxSize(blockUnderCursor);
SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_PLACE_2ND, soundController);
soundEffectSimple.startPlaying();
} else {
if (boundaryGrabActivated) { // ungrab
Vec3 playerPositionEyes = player.getPositionEyes(partialTick);
AxisAlignedBB newBoundaryField = getGrabDraggedBoundaryField(playerPositionEyes);
boundaryCorner1 = new BlockPos((int)Math.round(newBoundaryField.minX),
(int)Math.round(newBoundaryField.minY),
(int)Math.round(newBoundaryField.minZ));
boundaryCorner2 = new BlockPos((int)Math.round(newBoundaryField.maxX - 1),
(int)Math.round(newBoundaryField.maxY - 1),
(int)Math.round(newBoundaryField.maxZ - 1));
boundaryGrabActivated = false;
SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_UNGRAB, soundController);
soundEffectSimple.startPlaying();
} else {
MovingObjectPosition highlightedFace = boundaryFieldFaceSelection(player);
if (highlightedFace == null) return;
boundaryGrabActivated = true;
boundaryGrabSide = highlightedFace.sideHit.getIndex();
Vec3 playerPosition = player.getPositionEyes(1.0F);
boundaryGrabPoint = new Vec3(playerPosition.xCoord, playerPosition.yCoord, playerPosition.zCoord);
SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_GRAB, soundController);
soundEffectSimple.startPlaying();
}
}
}
/**
* Update the selection or boundary field based on where the player is looking
* @param world
* @param player
* @param partialTick
* @return
*/
@Override
public boolean updateForThisFrame(World world, EntityPlayerSP player, float partialTick) {
// update icon renderer
ItemSpeedyBoundary.IconNames itemIcon = ItemSpeedyBoundary.IconNames.BLANK;
if (boundaryCorner1 == null && boundaryCorner2 == null) {
itemIcon = ItemSpeedyBoundary.IconNames.NONE_PLACED;
} else if (boundaryCorner1 == null || boundaryCorner2 == null) {
itemIcon = ItemSpeedyBoundary.IconNames.ONE_PLACED;
} else {
if (boundaryGrabActivated) {
itemIcon = ItemSpeedyBoundary.IconNames.GRABBING;
} else {
itemIcon = ItemSpeedyBoundary.IconNames.TWO_PLACED;
}
}
itemSpeedyBoundary.setCurrentIcon(itemIcon);
if (boundaryCorner1 == null || boundaryCorner2 == null) {
boundaryGrabActivated = false;
}
// if boundary field active: calculate the face where the cursor is
if (boundaryCorner1 != null && boundaryCorner2 != null) {
MovingObjectPosition highlightedFace = boundaryFieldFaceSelection(player);
boundaryCursorSide = (highlightedFace != null) ? highlightedFace.sideHit.getIndex() : UsefulConstants.FACE_NONE;
return true;
}
// choose a starting block
blockUnderCursor = null;
BlockMultiSelector.BlockSelectionBehaviour blockSelectionBehaviour = getBlockSelectionBehaviour();
MovingObjectPosition airSelectionIgnoringBlocks = BlockMultiSelector.selectStartingBlock(null, blockSelectionBehaviour, player, partialTick);
if (airSelectionIgnoringBlocks == null) return false;
ItemSpeedyTool.CollideWithLiquids collideWithLiquids= blockSelectionBehaviour.isWaterCollision() ? ItemSpeedyTool.CollideWithLiquids.COLLIDE_WITH_LIQUIDS : ItemSpeedyTool.CollideWithLiquids.DO_NOT_COLLIDE_WITH_LIQUIDS;
MovingObjectPosition target = itemSpeedyBoundary.rayTraceLineOfSight(player.worldObj, player, collideWithLiquids);
// we want to make sure that we only select a block at very short range. So if we have hit a block beyond this range, shorten the target to eliminate it
if (target == null) {
target = airSelectionIgnoringBlocks;
} else if (target.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
if (target.hitVec.dotProduct(target.hitVec) > airSelectionIgnoringBlocks.hitVec.dotProduct(airSelectionIgnoringBlocks.hitVec)) {
target = airSelectionIgnoringBlocks;
}
}
blockUnderCursor = target.getBlockPos();
return true;
}
/**
* This class is used to provide information to the Boundary Field Renderer when it needs it:
* The Renderer calls refreshRenderInfo, which copies the relevant information from the tool.
*/
public class BoundaryFieldRendererUpdateLink implements RendererBoundaryField.BoundaryFieldRenderInfoUpdateLink
{
@Override
public boolean refreshRenderInfo(RendererBoundaryField.BoundaryFieldRenderInfo infoToUpdate, Vec3 playerPositionEyes)
{
if (boundaryCorner1 == null && boundaryCorner2 == null) return false;
infoToUpdate.boundaryCursorSide = boundaryCursorSide;
infoToUpdate.boundaryGrabActivated = boundaryGrabActivated;
infoToUpdate.boundaryGrabSide = boundaryGrabSide;
infoToUpdate.boundaryFieldAABB = getGrabDraggedBoundaryField(playerPositionEyes);
return true;
}
}
/**
* This class is used to provide information to the WireFrame Renderer when it needs it:
* The Renderer calls refreshRenderInfo, which copies the relevant information from the tool.
* Draws a wireframe selection, provided that not both of the corners have been placed yet
*/
public class BoundaryToolWireframeRendererLink implements RendererWireframeSelection.WireframeRenderInfoUpdateLink
{
@Override
public boolean refreshRenderInfo(RendererWireframeSelection.WireframeRenderInfo infoToUpdate)
{
if (boundaryCorner1 != null && boundaryCorner2 != null) return false;
infoToUpdate.currentlySelectedBlocks = new ArrayList<BlockPos>(1);
infoToUpdate.currentlySelectedBlocks.add(blockUnderCursor);
return true;
}
}
/** find the closest part of the boundary field (the epicentre), calculate the distance to it.
*/
private class BoundaryHumLink implements SoundEffectBoundaryHum.BoundaryHumUpdateLink
{
@Override
public boolean refreshHumInfo(SoundEffectBoundaryHum.BoundaryHumInfo infoToUpdate) {
EntityPlayerSP entityPlayerSP = Minecraft.getMinecraft().thePlayer;
AxisAlignedBB boundaryFieldAABB = getBoundaryField();
if (boundaryFieldAABB == null) return false;
Vec3 playerPosition = entityPlayerSP.getPositionEyes(0);
Vec3 epicentre = new Vec3((boundaryFieldAABB.minX + boundaryFieldAABB.maxX) / 2.0,
(boundaryFieldAABB.minY + boundaryFieldAABB.maxY) / 2.0,
(boundaryFieldAABB.minZ + boundaryFieldAABB.maxZ) / 2.0);
MovingObjectPosition mop = boundaryFieldAABB.calculateIntercept(playerPosition, epicentre);
if (mop == null) { // full volume when inside field
infoToUpdate.soundEpicentre = playerPosition;
infoToUpdate.distanceToEpicentre = 0;
return true;
}
infoToUpdate.soundEpicentre = mop.hitVec;
infoToUpdate.distanceToEpicentre = (float)playerPosition.distanceTo(infoToUpdate.soundEpicentre);
return true;
}
}
/**
* Calculate the current boundary field
* @return the new boundary field, null if a problem occurred. The AABB must be used for only one tick because it
* is allocated from a pool that is reused.
*/
public AxisAlignedBB getBoundaryField()
{
sortBoundaryFieldCorners();
if (boundaryCorner1 == null) return null;
BlockPos cnrMin = boundaryCorner1;
BlockPos cnrMax = (boundaryCorner2 != null) ? boundaryCorner2 : boundaryCorner1;
double wXmin = cnrMin.getX();
double wYmin = cnrMin.getY();
double wZmin = cnrMin.getZ();
double wXmax = cnrMax.getX() + 1;
double wYmax = cnrMax.getY() + 1;
double wZmax = cnrMax.getZ() + 1;
return new AxisAlignedBB(wXmin, wYmin, wZmin, wXmax, wYmax, wZmax);
}
/**
* Copies the boundary field coordinates into the supplied min and max BlockPos
* @param minCoord the chunk coordinate to be filled with the minimum x, y, z corner
* @param maxCoord the chunk coordinate to be filled with the maximum x, y, z corner
* @return true if the boundary field is valid; false if there is no boundary field
*/
@Deprecated
public boolean copyBoundaryCorners(BlockPos minCoord, BlockPos maxCoord)
{
sortBoundaryFieldCorners();
if (boundaryCorner1 == null) {
return false;
}
BlockPos cnrMin = boundaryCorner1;
BlockPos cnrMax = (boundaryCorner2 != null) ? boundaryCorner2 : boundaryCorner1;
// minCoord.posX = cnrMin.posX;
// minCoord.posY = cnrMin.posY;
// minCoord.posZ = cnrMin.posZ;
// maxCoord.posX = cnrMax.posX;
// maxCoord.posY = cnrMax.posY;
// maxCoord.posZ = cnrMax.posZ;
BlockPos nullPos = null;
nullPos.add(1,2,3); // cause a crash
return true;
}
/**
* Gets the boundary field extend (integer positions)
* @return Pair<[minx, miny, minz], [maxx, maxy, maxz]> or null if there is no boundary field
*/
public Pair<BlockPos, BlockPos> getBoundaryCorners()
{
sortBoundaryFieldCorners();
if (boundaryCorner1 == null) {
return null;
}
BlockPos cnrMin = boundaryCorner1;
BlockPos cnrMax = (boundaryCorner2 != null) ? boundaryCorner2 : boundaryCorner1;
return new Pair<BlockPos, BlockPos>(cnrMin, cnrMax);
}
/**
* Calculate the new boundary field after being dragged to the current player position
* Drags one side depending on which one the player grabbed, and how far they have moved
* Won't drag the boundary field smaller than one block
* @param playerPositionEyes
* @return the new boundary field, null if a problem occurred
*/
private AxisAlignedBB getGrabDraggedBoundaryField(Vec3 playerPositionEyes)
{
sortBoundaryFieldCorners();
if (boundaryCorner1 == null) return null;
BlockPos cnrMin = boundaryCorner1;
BlockPos cnrMax = (boundaryCorner2 != null) ? boundaryCorner2 : boundaryCorner1;
double wXmin = cnrMin.getX();
double wYmin = cnrMin.getY();
double wZmin = cnrMin.getZ();
double wXmax = cnrMax.getX() + 1;
double wYmax = cnrMax.getY() + 1;
double wZmax = cnrMax.getZ() + 1;
if (boundaryGrabActivated) {
switch (boundaryGrabSide) {
case UsefulConstants.FACE_YNEG: {
wYmin += playerPositionEyes.yCoord - boundaryGrabPoint.yCoord;
wYmin = UsefulFunctions.clipToRange(wYmin, wYmax - SELECTION_MAX_YSIZE, wYmax - 1.0);
break;
}
case UsefulConstants.FACE_YPOS: {
wYmax += playerPositionEyes.yCoord - boundaryGrabPoint.yCoord;
wYmax = UsefulFunctions.clipToRange(wYmax, wYmin + SELECTION_MAX_YSIZE, wYmin + 1.0);
break;
}
case UsefulConstants.FACE_ZNEG: {
wZmin += playerPositionEyes.zCoord - boundaryGrabPoint.zCoord;
wZmin = UsefulFunctions.clipToRange(wZmin, wZmax - SELECTION_MAX_ZSIZE, wZmax - 1.0);
break;
}
case UsefulConstants.FACE_ZPOS: {
wZmax += playerPositionEyes.zCoord - boundaryGrabPoint.zCoord;
wZmax = UsefulFunctions.clipToRange(wZmax, wZmin + SELECTION_MAX_ZSIZE, wZmin + 1.0);
break;
}
case UsefulConstants.FACE_XNEG: {
wXmin += playerPositionEyes.xCoord - boundaryGrabPoint.xCoord;
wXmin = UsefulFunctions.clipToRange(wXmin, wXmax - SELECTION_MAX_XSIZE, wXmax - 1.0);
break;
}
case UsefulConstants.FACE_XPOS: {
wXmax += playerPositionEyes.xCoord - boundaryGrabPoint.xCoord;
wXmax = UsefulFunctions.clipToRange(wXmax, wXmin + SELECTION_MAX_XSIZE, wXmin + 1.0);
break;
}
default: {
FMLLog.warning("Invalid boundaryGrabSide (%d", boundaryGrabSide);
}
}
}
return new AxisAlignedBB(wXmin, wYmin, wZmin, wXmax, wYmax, wZmax);
}
/**
* add a new corner to the boundary (replace boundaryCorner2). If the selection is too big, move boundaryCorner1.
* @param newCorner
*/
private void addCornerPointWithMaxSize(BlockPos newCorner)
{
boundaryCorner2 = new BlockPos(newCorner);
int posX = UsefulFunctions.clipToRange(boundaryCorner1.getX(), newCorner.getX() - SELECTION_MAX_XSIZE + 1, newCorner.getX() + SELECTION_MAX_XSIZE - 1);
int posY = UsefulFunctions.clipToRange(boundaryCorner1.getY(), newCorner.getY() - SELECTION_MAX_YSIZE + 1, newCorner.getY() + SELECTION_MAX_YSIZE - 1);
int posZ = UsefulFunctions.clipToRange(boundaryCorner1.getZ(), newCorner.getZ() - SELECTION_MAX_ZSIZE + 1, newCorner.getZ() + SELECTION_MAX_ZSIZE - 1);
boundaryCorner1 = new BlockPos(posX, posY, posZ);
sortBoundaryFieldCorners();
}
private boolean boundaryGrabActivated = false;
private int boundaryGrabSide = UsefulConstants.FACE_NONE;
private Vec3 boundaryGrabPoint = null;
protected int boundaryCursorSide = UsefulConstants.FACE_NONE;
private SoundEffectBoundaryHum soundEffectBoundaryHum;
private ItemSpeedyBoundary itemSpeedyBoundary;
}