package speedytools.clientside.tools; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.*; import net.minecraft.world.World; import speedytools.clientside.SpeedyToolsOptionsClient; import speedytools.clientside.UndoManagerClient; import speedytools.clientside.network.CloneToolsNetworkClient; import speedytools.clientside.network.PacketSenderClient; import speedytools.clientside.rendering.*; import speedytools.clientside.selections.BlockMultiSelector; import speedytools.clientside.selections.ClientVoxelSelection; import speedytools.clientside.sound.*; import speedytools.clientside.userinput.PowerUpEffect; import speedytools.clientside.userinput.UserInput; import speedytools.common.blocks.BlockWithMetadata; import speedytools.common.items.ItemSpeedyTool; import speedytools.common.network.ClientStatus; import speedytools.common.network.ServerStatus; import speedytools.common.selections.FillAlgorithmSettings; import speedytools.common.selections.FillMatcher; import speedytools.common.utilities.*; import java.util.LinkedList; import java.util.List; /** * User: The Grey Ghost * Date: 18/04/2014 * * The various renders for this tool are: * 1) Wireframe highlight of the block(s) under the cursor when no selection made yet * 2) The boundary field (if any) * 3) The solid selection, when made * 4) The various status indicators on the cursor: * a) When generating a selection - the "busy" cursor with progress * b) When a selection is made, the tool continually shows the server "ready" status * c) When a selection is made: the power up animation for the action or the undo * d) When an action is in progress: the progress meter * e) When an undo is in progress: the progress meter * */ public abstract class SpeedyToolComplex extends SpeedyToolComplexBase { public SpeedyToolComplex(ItemSpeedyTool i_parentItem, SpeedyToolRenderers i_renderers, SoundController i_speedyToolSounds, UndoManagerClient i_undoManagerClient, CloneToolsNetworkClient i_cloneToolsNetworkClient, SpeedyToolBoundary i_speedyToolBoundary, ClientVoxelSelection i_clientVoxelSelection, CommonSelectionState i_commonSelectionState, SelectionPacketSender i_selectionPacketSender, PacketSenderClient i_packetSenderClient) { super(i_parentItem, i_renderers, i_speedyToolSounds, i_undoManagerClient, i_packetSenderClient); // itemComplexBase = i_parentItem; speedyToolBoundary = i_speedyToolBoundary; boundaryFieldRendererUpdateLink = this.new BoundaryFieldRendererUpdateLink(); wireframeRendererUpdateLink = this.new CopyToolWireframeRendererLink(); solidSelectionRendererUpdateLink = this.new SolidSelectionRendererLink(); cursorRenderInfoUpdateLink = this.new CursorRenderInfoLink(); statusMessageRenderInfoUpdateLink = this.new StatusMessageRenderInfoLink(); hotbarRenderInfoUpdateLink = this.new HotbarRenderInfoUpdateLink(); cloneToolsNetworkClient = i_cloneToolsNetworkClient; selectionPacketSender = i_selectionPacketSender; clientVoxelSelection = i_clientVoxelSelection; commonSelectionState = i_commonSelectionState; fillAlgorithmSettings.setAutomaticLowerBound(true); fillAlgorithmSettings.setPropagation(FillAlgorithmSettings.Propagation.FLOODFILL); } @Override public boolean activateTool(ItemStack newToolItemStack) { currentToolItemStack = newToolItemStack; LinkedList<RendererElement> rendererElements = new LinkedList<RendererElement>(); rendererElements.add(new RendererWireframeSelection(wireframeRendererUpdateLink)); rendererElements.add(new RendererBoundaryField(boundaryFieldRendererUpdateLink)); rendererElements.add(new RendererSolidSelection(solidSelectionRendererUpdateLink)); rendererElements.add(new RendererHotbarCurrentItem(hotbarRenderInfoUpdateLink)); renderCursorStatus = new RenderCursorStatus(cursorRenderInfoUpdateLink); rendererElements.add(renderCursorStatus); rendererElements.add(new RendererStatusMessage(statusMessageRenderInfoUpdateLink)); speedyToolRenderers.setRenderers(rendererElements); // selectionPacketSender.reset(); if (soundEffectBoundaryHum == null) { BoundaryHumLink boundaryHumLink = this.new BoundaryHumLink(); soundEffectBoundaryHum = new SoundEffectBoundaryHum(SoundEffectNames.BOUNDARY_HUM, soundController, boundaryHumLink); } soundEffectBoundaryHum.startPlayingLoop(); if (soundEffectComplexTool == null) { RingSoundLink ringSoundLink = this.new RingSoundLink(); soundEffectComplexTool = new SoundEffectComplexTool(soundController, ringSoundLink); } if (soundEffectComplexSelectionGeneration == null) { soundEffectComplexSelectionGeneration = new SoundEffectComplexSelectionGeneration(soundController); } iAmActive = true; cloneToolsNetworkClient.changeClientStatus(ClientStatus.MONITORING_STATUS); toolState = ToolState.IDLE; return true; } /** The user has unequipped this tool: * (1) if generating a selection, stop * (2) ungrab if dragging a selection * (3) if currently performing any actions or communications with the server, terminate them * The tool will not deactivate itself until the server has acknowledged abort. * @return true if deactivation complete, false if not yet ready to deactivate */ @Override public boolean deactivateTool() { clientVoxelSelection.abortGenerationIfUnderway(); if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING && cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.COMPLETED ) { undoAction(); } if (this.iAmBusy()) return false; speedyToolRenderers.setRenderers(null); renderCursorStatus = null; if (soundEffectBoundaryHum != null) { soundEffectBoundaryHum.stopPlaying(); } if (soundEffectComplexTool != null) { soundEffectComplexTool.stopPlaying(); } if (soundEffectComplexSelectionGeneration != null) { soundEffectComplexSelectionGeneration.stopPlaying(); } iAmActive = false; cloneToolsNetworkClient.changeClientStatus(ClientStatus.IDLE); return true; } /** * the various valid user inputs are: * While performing an action (eg selection creation, or selection copy): * (1) any left click = abort action. * Otherwise: * If there is no selection: * (1) long left hold = undo previous copy (if any) * (2) short right click = create selection * If there is a selection: * (1) long left hold = undo previous copy (if any) * (2) short left click = uncreate selection * (3) short right click with CTRL = mirror selection left-right * (4) short right click without CTRL = grab / ungrab selection to move it around * (5) long right hold = copy the selection to the current position * (6) long left hold = undo the previous copy * (7) CTRL + mousewheel = rotate selection * @param player * @param partialTick * @param userInput * @return */ @Override public boolean processUserInput(EntityPlayerSP player, float partialTick, UserInput userInput) { if (!iAmActive) return false; final long MIN_UNDO_HOLD_DURATION_NS = SpeedyToolsOptionsClient.getLongClickMinDurationNS(); // length of time to hold for undo final long MIN_PLACE_HOLD_DURATION_NS = SpeedyToolsOptionsClient.getLongClickMinDurationNS(); // length of time to hold for action (place) final long MAX_SHORT_CLICK_DURATION_NS = SpeedyToolsOptionsClient.getShortClickMaxDurationNS(); // maximum length of time for a "short" click checkInvariants(); fillAlgorithmSettings.setDiagonalPropagationAllowed(isDiagonalPropagationAllowed(userInput.isControlKeyDown())); UserInput.InputEvent nextEvent; while (null != (nextEvent = userInput.poll())) { // if we are busy with an action - a short left click will abort current action if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING) { if (nextEvent.eventType == UserInput.InputEventType.LEFT_CLICK_DOWN) { if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING && cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.COMPLETED) { undoAction(); } } // if we are busy with an undo - we can't interrupt } else if (cloneToolsNetworkClient.peekCurrentUndoStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING) { // do nothing // otherwise - is this an undo? } else if (nextEvent.eventType == UserInput.InputEventType.LEFT_CLICK_UP && nextEvent.eventDuration >= MIN_UNDO_HOLD_DURATION_NS ) { undoAction(); // otherwise - split up according to whether we have a selection or not } else { if (nextEvent.eventType == UserInput.InputEventType.WHEEL_MOVE && mouseWheelChangesCount()) { if (currentToolItemStack != null) { int newCount = parentItem.getPlacementCount(currentToolItemStack) + nextEvent.count; parentItem.setPlacementCount(currentToolItemStack, newCount); } } switch (clientVoxelSelection.getReadinessForDisplaying()) { case NO_SELECTION: { // System.out.println("TEST:" + nextEvent.eventType + " : " + nextEvent.eventDuration); if (nextEvent.eventType == UserInput.InputEventType.RIGHT_CLICK_UP && nextEvent.eventDuration <= MAX_SHORT_CLICK_DURATION_NS) { initiateSelectionCreation(player); } break; } case GENERATING: { if (nextEvent.eventType == UserInput.InputEventType.LEFT_CLICK_DOWN) { undoSelectionCreation(); } return false; } case READY_FOR_DISPLAY: { processSelectionUserInput(player, partialTick, nextEvent); break; } default: assert false : "Illegal clientVoxelSelection state: " + clientVoxelSelection.getReadinessForDisplaying(); } } } // update the powerup for action or undo action // used for rendering display only; CLICK_UP is used for reacting to the event // undo action check long timeNow = System.nanoTime(); // if (this.iAmBusy()) { // if (!leftClickPowerup.isIdle()) { // leftClickPowerup.abort(); // } // } else { long leftButtonHoldTime = userInput.leftButtonHoldTimeNS(timeNow); if (leftButtonHoldTime >= MAX_SHORT_CLICK_DURATION_NS) { if (leftClickPowerup.isIdle()) { leftClickPowerup.initiate(timeNow, timeNow + MIN_UNDO_HOLD_DURATION_NS, leftButtonHoldTime); } leftClickPowerup.updateHolddownTime(leftButtonHoldTime); } else if (leftButtonHoldTime < 0) { // released button if (!leftClickPowerup.isIdle()) { long releaseTime = timeNow + leftButtonHoldTime; leftClickPowerup.release(releaseTime); } } // } if (clientVoxelSelection.getReadinessForDisplaying() != ClientVoxelSelection.VoxelSelectionState.READY_FOR_DISPLAY) { if (!rightClickPowerup.isIdle()) { rightClickPowerup.abort(); } } else { long rightButtonHoldTime = userInput.rightButtonHoldTimeNS(timeNow); // System.out.print("SpeedyToolCopy rightButtonHoldTime= " + rightButtonHoldTime); if (rightButtonHoldTime >= MAX_SHORT_CLICK_DURATION_NS) { // System.out.print("hold time greater than MAX_SHORT_CLICK_DURATION_NS"); if (rightClickPowerup.isIdle()) { rightClickPowerup.initiate(timeNow, timeNow + MIN_PLACE_HOLD_DURATION_NS, rightButtonHoldTime); // System.out.print("initiate rightClickPowerUp"); } rightClickPowerup.updateHolddownTime(rightButtonHoldTime); } else if (rightButtonHoldTime < 0) { // released button // System.out.print("released "); if (!rightClickPowerup.isIdle()) { long releaseTime = timeNow + rightButtonHoldTime; rightClickPowerup.release(releaseTime); // System.out.print("Powerup release"); } } // System.out.println(); } return true; } protected abstract RenderCursorStatus.CursorRenderInfo.CursorType getCursorType(); protected abstract Colour getSelectionRenderColour(); /** if true, the current selection is cancelled after an action is performed with it * @return */ protected abstract boolean cancelSelectionAfterAction(); /** * if true, selections made using this tool can be dragged around * * @return */ protected abstract boolean selectionIsMoveable(); /** Is the tool currently busy with something? * @return true if busy, false if not */ private boolean iAmBusy() { if (clientVoxelSelection.getReadinessForDisplaying() == ClientVoxelSelection.VoxelSelectionState.GENERATING) { return true; } if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING && cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.COMPLETED) { return true; } if (cloneToolsNetworkClient.peekCurrentUndoStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING && cloneToolsNetworkClient.peekCurrentUndoStatus() != CloneToolsNetworkClient.ActionStatus.COMPLETED) { return true; } return false; } /** processes user input received while there is a solid selection * (1) short left click = uncreate selection * (2) short right click with CTRL = mirror selection left-right * (3) short right click without CTRL = grab / ungrab selection to move it around * (4) long right hold = copy the selection to the current position * (5) long left hold = undo the previous copy * (6) CTRL + mousewheel = rotate selection * * @param player * @param partialTick * @param inputEvent * @return */ private boolean processSelectionUserInput(EntityPlayerSP player, float partialTick, UserInput.InputEvent inputEvent) { final long MIN_UNDO_HOLD_DURATION_NS = SpeedyToolsOptionsClient.getLongClickMinDurationNS(); // length of time to hold for undo final long MIN_PLACE_HOLD_DURATION_NS = SpeedyToolsOptionsClient.getLongClickMinDurationNS(); // length of time to hold for action (place) final long MAX_SHORT_CLICK_DURATION_NS = SpeedyToolsOptionsClient.getShortClickMaxDurationNS(); // maximum length of time for a "short" click switch (inputEvent.eventType) { case LEFT_CLICK_UP: { if (inputEvent.eventDuration <= MAX_SHORT_CLICK_DURATION_NS) { undoSelectionCreation(); } else if (inputEvent.eventDuration >= MIN_UNDO_HOLD_DURATION_NS) { undoAction(); } break; } case RIGHT_CLICK_UP: { if (inputEvent.eventDuration <= MAX_SHORT_CLICK_DURATION_NS && selectionIsMoveable()) { // only if this tool type allows selection moving / flipping if (inputEvent.controlKeyDown) { flipSelection(player); } else { // toggle selection grabbing commonSelectionState.hasBeenMoved = true; Vec3 playerPosition = player.getPositionEyes(partialTick); // beware, Vec3 is short-lived commonSelectionState.selectionGrabActivated = !commonSelectionState.selectionGrabActivated; if (commonSelectionState.selectionGrabActivated) { commonSelectionState.selectionMovedFastYet = false; commonSelectionState.selectionGrabPoint = new Vec3(playerPosition.xCoord, playerPosition.yCoord, playerPosition.zCoord); SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_GRAB, soundController); soundEffectSimple.startPlaying(); } else { Vec3 distanceMoved = playerPosition.subtract(commonSelectionState.selectionGrabPoint); commonSelectionState.selectionOrigin = commonSelectionState.selectionOrigin.add( (int)Math.round(distanceMoved.xCoord), (int)Math.round(distanceMoved.yCoord), (int)Math.round(distanceMoved.zCoord)); SoundEffectSimple soundEffectSimple = new SoundEffectSimple(SoundEffectNames.BOUNDARY_UNGRAB, soundController); soundEffectSimple.startPlaying(); } } } else if (inputEvent.eventDuration >= MIN_PLACE_HOLD_DURATION_NS) { placeSelection(player, partialTick); } break; } case WHEEL_MOVE: { if (selectionIsMoveable()) { rotateSelection(-inputEvent.count); // wheel down rotates clockwise // } else if (mouseWheelChangesCount()) { // if (currentToolItemStack != null) { // int newCount = parentItem.getPlacementCount(currentToolItemStack) + inputEvent.count; // parentItem.setPlacementCount(currentToolItemStack, newCount); // } } break; } } return true; } /** * if true, CTRL + mousewheel changes the item count * @return */ protected abstract boolean mouseWheelChangesCount(); /** * Change the object count (simple tools) * @param player * @param inputEvent */ // protected void changeObjectCount(EntityPlayerSP player, UserInput.InputEvent inputEvent) { // ItemStack currentItem = player.inventory.getCurrentItem(); // int currentcount = currentItem.stackSize; // int maxStackSize = currentItem.getMaxStackSize(); // if (currentcount >= 1 && currentcount <= maxStackSize) { // currentcount += inputEvent.count; // sdfs currentcount = ((currentcount - 1) % maxStackSize); // currentcount = ((currentcount + maxStackSize) % maxStackSize) + 1; // take care of negative // currentItem.stackSize = currentcount; // } // } /** * (1) Selects the first Block that will be affected by the tool when the player presses right-click, * (2) Determines which algorithm will be used to select blocks (see below), and * (3) creates a wireframe highlight showing up to MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS of the blocks that will be selected * 1) no boundary field: cursor pointing at a block - floodfill (all non-air blocks, including diagonal fill) from block clicked up * 2) boundary field: standing outside, cursor pointing at one of the boundaries - all solid blocks in the field * 3) boundary field: standing inside - cursor pointing at a block - floodfill to boundary field * So the selection algorithm is: * a) if the player is pointing at a block, specify that, and also check if inside a boundary field; else * b) check the player is pointing at a side of the boundary field (from outside) * @param player the player * @param partialTick partial tick time. */ // @Override // public void highlightBlocks(MovingObjectPosition target, EntityPlayer player, ItemStack currentItem, float partialTick) @Override public boolean updateForThisFrame(World world, EntityPlayerSP player, float partialTick) { checkInvariants(); if (clientVoxelSelection.getReadinessForDisplaying() != ClientVoxelSelection.VoxelSelectionState.NO_SELECTION) return false; updateBoundaryCornersFromToolBoundary(); final int MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS = 64; blockUnderCursor = null; highlightedBlocks = null; currentHighlighting = SelectionType.NONE; MovingObjectPosition target = selectBlockUnderCursor(player, null, partialTick); if (target != null && target.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { blockUnderCursor = target.getBlockPos(); blockUnderCursorSideHit = target.sideHit; fillAlgorithmSettings.setStartPosition(blockUnderCursor); fillAlgorithmSettings.setNormalDirection(blockUnderCursorSideHit); boolean selectedBlockIsInsideBoundaryField = false; if (boundaryCorner1 != null && boundaryCorner2 != null) { if ( blockUnderCursor.getX() >= boundaryCorner1.getX() && blockUnderCursor.getX() <= boundaryCorner2.getX() && blockUnderCursor.getY() >= boundaryCorner1.getY() && blockUnderCursor.getY() <= boundaryCorner2.getY() && blockUnderCursor.getZ() >= boundaryCorner1.getZ() && blockUnderCursor.getZ() <= boundaryCorner2.getZ() ) { selectedBlockIsInsideBoundaryField = true; } } FillMatcher fillMatcher = getFillMatcherForSelectionCreation(world, blockUnderCursor); fillAlgorithmSettings.setFillMatcher(fillMatcher); if (selectedBlockIsInsideBoundaryField) { currentHighlighting = SelectionType.BOUND_FILL; if (fillAlgorithmSettings.getPropagation() == FillAlgorithmSettings.Propagation.FLOODFILL) { highlightedBlocks = BlockMultiSelector.selectFillBounded(blockUnderCursor, world, MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS, fillAlgorithmSettings.isDiagonalPropagationAllowed(), fillAlgorithmSettings.getFillMatcher(), boundaryCorner1.getX(), boundaryCorner2.getX(), boundaryCorner1.getY(), boundaryCorner2.getY(), boundaryCorner1.getZ(), boundaryCorner2.getZ()); } else if (fillAlgorithmSettings.getPropagation() == FillAlgorithmSettings.Propagation.CONTOUR) { highlightedBlocks = BlockMultiSelector.selectContourBounded(blockUnderCursor, world, MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS, fillAlgorithmSettings.isDiagonalPropagationAllowed(), fillAlgorithmSettings.getFillMatcher(), blockUnderCursorSideHit, boundaryCorner1.getX(), boundaryCorner2.getX(), boundaryCorner1.getY(), boundaryCorner2.getY(), boundaryCorner1.getZ(), boundaryCorner2.getZ()); } } else { final int MAXIMUM_Y = 255; final int MINIMUM_Y = 0; currentHighlighting = SelectionType.UNBOUND_FILL; if (fillAlgorithmSettings.getPropagation() == FillAlgorithmSettings.Propagation.FLOODFILL) { highlightedBlocks = BlockMultiSelector.selectFillBounded(blockUnderCursor, world, MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS, fillAlgorithmSettings.isDiagonalPropagationAllowed(), fillAlgorithmSettings.getFillMatcher(), Integer.MIN_VALUE, Integer.MAX_VALUE, (fillAlgorithmSettings.isAutomaticLowerBound() ? blockUnderCursor.getY() : MINIMUM_Y), MAXIMUM_Y, Integer.MIN_VALUE, Integer.MAX_VALUE); } else if (fillAlgorithmSettings.getPropagation() == FillAlgorithmSettings.Propagation.CONTOUR) { highlightedBlocks = BlockMultiSelector.selectContourUnbounded(blockUnderCursor, world, MAX_NUMBER_OF_HIGHLIGHTED_BLOCKS, fillAlgorithmSettings.isDiagonalPropagationAllowed(), fillAlgorithmSettings.getFillMatcher(), blockUnderCursorSideHit); } } checkInvariants(); return true; } // if there is no block selected, check whether the player is pointing at the boundary field from outside. if (boundaryCorner1 == null || boundaryCorner2 == null) return false; Vec3 playerPosition = player.getPositionEyes(1.0F); if ( playerPosition.xCoord >= boundaryCorner1.getX() && playerPosition.xCoord <= boundaryCorner2.getX() +1 && playerPosition.yCoord >= boundaryCorner1.getY() && playerPosition.yCoord <= boundaryCorner2.getY() +1 && playerPosition.zCoord >= boundaryCorner1.getZ() && playerPosition.zCoord <= boundaryCorner2.getZ() +1) { return false; } MovingObjectPosition highlightedFace = boundaryFieldFaceSelection(player); if (highlightedFace == null) return false; currentHighlighting = SelectionType.FULL_BOX; checkInvariants(); return true; } // protected MovingObjectPosition getBlockUnderCursor(EntityPlayerSP player, float partialTick) // { // MovingObjectPosition target = parentItem.rayTraceLineOfSight(player.worldObj, player); // if (target != null && target.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) { // return target; // } else { // return null; // } // } @Override public void resetTool() { super.resetTool(); clientVoxelSelection.reset(); } /** * If there is a current selection, destroy it. No effect if waiting for the server to do something. */ private void undoSelectionCreation() { if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING || cloneToolsNetworkClient.peekCurrentUndoStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING ) { return; } clientVoxelSelection.reset(); } /** * if the player has previously performed an action, undo it. */ private void undoAction() { checkInvariants(); if (cloneToolsNetworkClient.getServerStatus() != ServerStatus.IDLE && cloneToolsNetworkClient.getServerStatus() != ServerStatus.PERFORMING_YOUR_ACTION) { return; } if (toolState == ToolState.PERFORMING_ACTION) { toolState = ToolState.PERFORMING_UNDO_FROM_PARTIAL; } else { toolState = ToolState.PERFORMING_UNDO_FROM_FULL; } ResultWithReason result = cloneToolsNetworkClient.performComplexToolUndo(); if (result.succeeded()) { cloneToolsNetworkClient.changeClientStatus(ClientStatus.WAITING_FOR_ACTION_COMPLETE); } else { displayNewErrorMessage(result.getReason()); } // playSound(CustomSoundsHandler.BOUNDARY_UNPLACE, thePlayer); } /** * don't call if an action is already pending */ protected void placeSelection(EntityPlayerSP player, float partialTick) { checkInvariants(); if (!clientVoxelSelection.isSelectionCompleteOnServer()) { displayNewErrorMessage("Must wait for spell preparation to finish ..."); return; } Vec3 selectionPosition = getSelectionPosition(player, partialTick, false); ResultWithReason result = performComplexToolAction(selectionPosition); if (result.succeeded()) { cloneToolsNetworkClient.changeClientStatus(ClientStatus.WAITING_FOR_ACTION_COMPLETE); toolState = ToolState.PERFORMING_ACTION; } else { displayNewErrorMessage(result.getReason()); } } protected ResultWithReason performComplexToolAction(Vec3 selectionPosition) { return cloneToolsNetworkClient.performComplexToolAction(Item.getIdFromItem(parentItem), Math.round((float) selectionPosition.xCoord), Math.round((float) selectionPosition.yCoord), Math.round((float) selectionPosition.zCoord), commonSelectionState.selectionOrientation, commonSelectionState.initialSelectionOrigin); } private void flipSelection(EntityPlayerSP EntityPlayerSP) { float modulusYaw = MathHelper.wrapAngleTo180_float(EntityPlayerSP.rotationYaw); // System.out.println("Flip modulusYaw:" + modulusYaw); // System.out.println("Clockwise Rotation Count:" + commonSelectionState.selectionOrientation.getClockwiseRotationCount()); if (Math.abs(modulusYaw) < 45 || Math.abs(modulusYaw) > 135) { // looking mostly north-south commonSelectionState.selectionOrientation.flipWX(); // System.out.println("FlipWX"); } else { commonSelectionState.selectionOrientation.flipWZ(); // System.out.println("FlipWZ"); } } private void rotateSelection(int rotationCountAndDirection) { commonSelectionState.selectionOrientation.rotateClockwise(rotationCountAndDirection); } private void initiateSelectionCreation(EntityPlayerSP thePlayer) { commonSelectionState.selectionGrabActivated = false; switch (currentHighlighting) { case NONE: { if (updateBoundaryCornersFromToolBoundary()) { displayNewErrorMessage("First point your cursor at a nearby block, or at the boundary field ..."); } else { displayNewErrorMessage("First point your cursor at a nearby block..."); } return; } case FULL_BOX: { clientVoxelSelection.createFullBoxSelection(thePlayer, boundaryCorner1, boundaryCorner2); break; } case UNBOUND_FILL: { clientVoxelSelection.createUnboundFillSelection(thePlayer, fillAlgorithmSettings, getOverrideTexture()); break; } case BOUND_FILL: { clientVoxelSelection.createBoundFillSelection(thePlayer, fillAlgorithmSettings, getOverrideTexture(), boundaryCorner1, boundaryCorner2); break; } default: { ErrorLog.defaultLog().severe("Invalid currentHighlighting in initiateSelectionCreation:" + currentHighlighting); break; } } cloneToolsNetworkClient.informSelectionMade(); soundEffectComplexSelectionGeneration.startPlaying(); } protected FillMatcher getFillMatcherForSelectionCreation(World world, BlockPos blockUnderCursor) { FillMatcher fillMatcher = new FillMatcher.AnyNonAir(); return fillMatcher; } protected boolean isDiagonalPropagationAllowed(boolean userRequested) { return userRequested; } protected FillAlgorithmSettings fillAlgorithmSettings = new FillAlgorithmSettings(); protected IBlockState getOverrideTexture() { return null; } /** 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) { checkInvariants(); super.performTick(world); updateGrabRenderTick(commonSelectionState.selectionGrabActivated && clientVoxelSelection.getReadinessForDisplaying() == ClientVoxelSelection.VoxelSelectionState.READY_FOR_DISPLAY); final long MAX_TIME_IN_NS = SpeedyToolsOptionsClient.getMaxClientBusyTimeMS() * 1000L * 1000L; ClientVoxelSelection.VoxelSelectionState oldState = clientVoxelSelection.getReadinessForDisplaying(); clientVoxelSelection.performTick(world, MAX_TIME_IN_NS); if (clientVoxelSelection.hasSelectionBeenUpdated()) { // update the origin and orientation if the selection has been updated if (oldState != ClientVoxelSelection.VoxelSelectionState.READY_FOR_DISPLAY) { commonSelectionState.initialSelectionOrigin = clientVoxelSelection.getSourceWorldOrigin(); commonSelectionState.initialSelectionOrientation = clientVoxelSelection.getSourceQuadOrientation(); commonSelectionState.selectionOrigin = commonSelectionState.initialSelectionOrigin; commonSelectionState.selectionOrientation = commonSelectionState.initialSelectionOrientation; } else { // apply any user translations and orientation changes to the new values int dx = commonSelectionState.selectionOrigin.getX() - commonSelectionState.initialSelectionOrigin.getX(); int dy = commonSelectionState.selectionOrigin.getY() - commonSelectionState.initialSelectionOrigin.getY(); int dz = commonSelectionState.selectionOrigin.getZ() - commonSelectionState.initialSelectionOrigin.getZ(); BlockPos newOrigin = clientVoxelSelection.getSourceWorldOrigin(); commonSelectionState.selectionOrigin = new BlockPos(newOrigin.getX() + dx, newOrigin.getY() + dy, newOrigin.getZ() + dz); commonSelectionState.initialSelectionOrigin = new BlockPos(commonSelectionState.selectionOrigin); QuadOrientation newOrientation = clientVoxelSelection.getSourceQuadOrientation(); if (commonSelectionState.initialSelectionOrientation.isFlippedX()) newOrientation.flipX(); newOrientation.rotateClockwise(commonSelectionState.initialSelectionOrientation.getClockwiseRotationCount()); commonSelectionState.selectionOrientation = newOrientation; } } CloneToolsNetworkClient.ActionStatus actionStatus = cloneToolsNetworkClient.getCurrentActionStatus(); CloneToolsNetworkClient.ActionStatus undoStatus = cloneToolsNetworkClient.getCurrentUndoStatus(); if (undoStatus == CloneToolsNetworkClient.ActionStatus.COMPLETED) { lastActionWasRejected = false; toolState = ToolState.UNDO_SUCCEEDED; cloneToolsNetworkClient.changeClientStatus(ClientStatus.MONITORING_STATUS); } if (undoStatus == CloneToolsNetworkClient.ActionStatus.REJECTED) { lastActionWasRejected = true; toolState = ToolState.UNDO_FAILED; displayNewErrorMessage(cloneToolsNetworkClient.getLastRejectionReason()); cloneToolsNetworkClient.changeClientStatus(ClientStatus.MONITORING_STATUS); } if (undoStatus == CloneToolsNetworkClient.ActionStatus.NONE_PENDING) { // ignore action statuses if undo status is not idle, since we are undoing the current action if (actionStatus == CloneToolsNetworkClient.ActionStatus.COMPLETED) { lastActionWasRejected = false; toolState = ToolState.ACTION_SUCCEEDED; cloneToolsNetworkClient.changeClientStatus(ClientStatus.MONITORING_STATUS); commonSelectionState.hasBeenMoved = false; if (cancelSelectionAfterAction()) { undoSelectionCreation(); } } if (actionStatus == CloneToolsNetworkClient.ActionStatus.REJECTED) { lastActionWasRejected = true; toolState = ToolState.ACTION_FAILED; displayNewErrorMessage(cloneToolsNetworkClient.getLastRejectionReason()); cloneToolsNetworkClient.changeClientStatus(ClientStatus.MONITORING_STATUS); } } if (!leftClickPowerup.isIdle() || !rightClickPowerup.isIdle() && soundEffectComplexTool != null) { soundEffectComplexTool.startPlayingIfNotAlreadyPlaying(); } if (soundEffectComplexTool != null) { soundEffectComplexTool.performTick(); } if (soundEffectComplexSelectionGeneration != null) { boolean generationInProgress = clientVoxelSelection.isGenerationInProgress(); soundEffectComplexSelectionGeneration.performTick(generationInProgress); } checkInvariants(); } /** * start displaying a new error message * @param newErrorMessage */ private void displayNewErrorMessage(String newErrorMessage) { if (newErrorMessage.isEmpty()) return; errorMessageDisplayTimeStartNS = System.nanoTime(); errorMessageBeingDisplayed = newErrorMessage; } private String errorMessageBeingDisplayed = ""; private long errorMessageDisplayTimeStartNS = 0; /** * copies the boundary field coordinates from the boundary tool, if a boundary is defined * @return true if a boundary field is defined, false otherwise */ protected boolean updateBoundaryCornersFromToolBoundary() { boundaryCorner1 = null; boundaryCorner2 = null; if (speedyToolBoundary == null) return false; Pair<BlockPos, BlockPos> boundaryCorners = speedyToolBoundary.getBoundaryCorners(); boolean hasABoundaryField = boundaryCorners != null; if (hasABoundaryField) { boundaryCorner1 = boundaryCorners.getFirst(); boundaryCorner2 = boundaryCorners.getSecond(); } return hasABoundaryField; } /** * This class is used to provide information to the Boundary Field Renderer when it needs it. * The information is taken from the reference to the SpeedyToolBoundary. */ public class BoundaryFieldRendererUpdateLink implements RendererBoundaryField.BoundaryFieldRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RendererBoundaryField.BoundaryFieldRenderInfo infoToUpdate, Vec3 playerPosition) { ToolSelectionStates currentToolSelectionState = ToolSelectionStates.getState(clientVoxelSelection.getReadinessForDisplaying()); if (!currentToolSelectionState.displayBoundaryField) return false; if (speedyToolBoundary == null) return false; AxisAlignedBB boundaryFieldAABB = speedyToolBoundary.getBoundaryField(); if (boundaryFieldAABB == null) return false; infoToUpdate.boundaryCursorSide = (currentHighlighting == SelectionType.FULL_BOX) ? UsefulConstants.FACE_ALL : UsefulConstants.FACE_NONE; infoToUpdate.boundaryGrabActivated = false; infoToUpdate.boundaryGrabSide = -1; infoToUpdate.boundaryFieldAABB = boundaryFieldAABB; return true; } } /** * This class is used to provide information to the WireFrame Renderer for rendering the wireframe * of highlighted blocks. * 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 CopyToolWireframeRendererLink implements RendererWireframeSelection.WireframeRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RendererWireframeSelection.WireframeRenderInfo infoToUpdate) { ToolSelectionStates currentToolSelectionState = ToolSelectionStates.getState(clientVoxelSelection.getReadinessForDisplaying()); if ( !currentToolSelectionState.displayWireframeHighlight || highlightedBlocks == null) { return false; } infoToUpdate.currentlySelectedBlocks = highlightedBlocks; return true; } } /** * This class is used to provide information to the Boundary Field Renderer when it needs it. * The information is taken from the reference to the SpeedyToolBoundary. */ public class HotbarRenderInfoUpdateLink implements RendererHotbarCurrentItem.HotbarRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RendererHotbarCurrentItem.HotbarRenderInfo infoToUpdate, ItemStack currentlyHeldItem) { if (currentlyHeldItem == null || !(currentlyHeldItem.getItem() instanceof ItemSpeedyTool)) { return false; } ItemSpeedyTool itemSpeedyTool = (ItemSpeedyTool) currentlyHeldItem.getItem(); return itemSpeedyTool.usesAdjacentBlockInHotbar(); } } /** * This class passes the needed information for the rendering of the solid selection: * - the voxelManager * - the coordinates of the selectionOrigin after it has been dragged from its starting point */ public class SolidSelectionRendererLink implements RendererSolidSelection.SolidSelectionRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RendererSolidSelection.SolidSelectionRenderInfo infoToUpdate, EntityPlayer player, float partialTick) { ToolSelectionStates currentToolSelectionState = ToolSelectionStates.getState(clientVoxelSelection.getReadinessForDisplaying()); if (!currentToolSelectionState.displaySolidSelection) return false; final double THRESHOLD_SPEED_SQUARED_FOR_SNAP_GRID = 0.01; checkInvariants(); double currentSpeedSquared = player.motionX * player.motionX + player.motionY * player.motionY + player.motionZ * player.motionZ; if (currentSpeedSquared >= THRESHOLD_SPEED_SQUARED_FOR_SNAP_GRID) { commonSelectionState.selectionMovedFastYet = true; } final boolean snapToGridWhileMoving = commonSelectionState.selectionMovedFastYet && currentSpeedSquared <= THRESHOLD_SPEED_SQUARED_FOR_SNAP_GRID; Vec3 selectionPosition = getSelectionPosition(player, partialTick, snapToGridWhileMoving); infoToUpdate.selectorRenderer = clientVoxelSelection.getVoxelSelectionRenderer(); infoToUpdate.draggedSelectionOriginX = selectionPosition.xCoord; infoToUpdate.draggedSelectionOriginY = selectionPosition.yCoord; infoToUpdate.draggedSelectionOriginZ = selectionPosition.zCoord; infoToUpdate.opaque = commonSelectionState.hasBeenMoved; infoToUpdate.renderColour = SpeedyToolComplex.this.getSelectionRenderColour(); infoToUpdate.selectionOrientation = commonSelectionState.selectionOrientation; 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; ToolSelectionStates currentToolSelectionState = ToolSelectionStates.getState(clientVoxelSelection.getReadinessForDisplaying()); if (!currentToolSelectionState.displayBoundaryField) return false; if (speedyToolBoundary == null) return false; AxisAlignedBB boundaryFieldAABB = speedyToolBoundary.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; } } private class RingSoundLink implements SoundEffectComplexTool.RingSoundUpdateLink { @Override public boolean refreshRingSoundInfo(SoundEffectComplexTool.RingSoundInfo infoToUpdate) { switch (renderCursorStatus.getAnimationState()) { case IDLE: { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.IDLE; break; } case SPIN_UP_CW: case SPIN_UP_CCW: { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_UP; break; } case SPINNING_CW: case SPINNING_CCW_FROM_FULL: case SPINNING_CCW_FROM_PARTIAL: { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.PERFORMING_ACTION; break; } case SPIN_DOWN_CW_CANCELLED: case SPIN_DOWN_CCW_CANCELLED: { if (toolState == ToolState.UNDO_FAILED || toolState == ToolState.ACTION_FAILED) { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.FAILURE; } else { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_UP_ABORT; } break; } case SPIN_DOWN_CW_SUCCESS: case SPIN_DOWN_CCW_SUCCESS: { infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_DOWN; break; } // case ACTION_FAILED: // case ACTION_SUCCEEDED: // case UNDO_FAILED: // case UNDO_SUCCEEDED: // case IDLE: { // if (!rightClickPowerup.isIdle()) { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_UP; // } else if (!leftClickPowerup.isIdle()) { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_UP; // } else { // if (lastPowerupStarted != null && lastPowerupStarted.isIdle()) { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_UP_ABORT; // } else { // switch (toolState) { // case UNDO_FAILED: // case ACTION_FAILED: { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.FAILURE; // break; // } // case UNDO_SUCCEEDED: // case ACTION_SUCCEEDED: { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.SPIN_DOWN; // break; // } // } // } // } // break; // } // case PERFORMING_UNDO_FROM_PARTIAL: // case PERFORMING_UNDO_FROM_FULL: // case PERFORMING_ACTION: { // infoToUpdate.ringState = SoundEffectComplexTool.RingSoundInfo.State.PERFORMING_ACTION; // break; // } default: { assert false : "Invalid toolstate = " + toolState + " in refreshRenderInfo()"; } } return true; } } /** * returns the current position of the selection, i.e. the corner where it will be placed if the user performs an action * @param snapToGrid if true, snap to the nearest integer coordinates * @return */ private Vec3 getSelectionPosition(EntityPlayer player, float partialTick, boolean snapToGrid) { Vec3 playerOrigin = player.getPositionEyes(partialTick); double draggedSelectionOriginX = commonSelectionState.selectionOrigin.getX(); double draggedSelectionOriginY = commonSelectionState.selectionOrigin.getY(); double draggedSelectionOriginZ = commonSelectionState.selectionOrigin.getZ(); if (commonSelectionState.selectionGrabActivated) { Vec3 distanceMoved = playerOrigin.subtract(commonSelectionState.selectionGrabPoint); draggedSelectionOriginX += distanceMoved.xCoord; draggedSelectionOriginY += distanceMoved.yCoord; draggedSelectionOriginZ += distanceMoved.zCoord; if (snapToGrid) { draggedSelectionOriginX = Math.round(draggedSelectionOriginX); draggedSelectionOriginY = Math.round(draggedSelectionOriginY); draggedSelectionOriginZ = Math.round(draggedSelectionOriginZ); } } return new Vec3(draggedSelectionOriginX, draggedSelectionOriginY, draggedSelectionOriginZ); } /** * This class is used to provide information to the Cursor Status indicator when it needs it: * The Renderer calls refreshRenderInfo, which copies the relevant information from the tool. */ public class CursorRenderInfoLink implements RenderCursorStatus.CursorRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RenderCursorStatus.CursorRenderInfo infoToUpdate) { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.IDLE; if (clientVoxelSelection.getReadinessForDisplaying() == ClientVoxelSelection.VoxelSelectionState.GENERATING) { infoToUpdate.vanillaCursorSpin = true; infoToUpdate.cursorSpinProgress = 100.0F * clientVoxelSelection.getLocalGenerationFractionComplete(); } else { infoToUpdate.vanillaCursorSpin = false; } infoToUpdate.aSelectionIsDefined = (clientVoxelSelection.getReadinessForDisplaying() == ClientVoxelSelection.VoxelSelectionState.READY_FOR_DISPLAY); PowerUpEffect activePowerUp; if (!leftClickPowerup.isIdle()) { activePowerUp = leftClickPowerup; } else { activePowerUp = rightClickPowerup; } // if (cloneToolsNetworkClient.peekCurrentActionStatus() == CloneToolsNetworkClient.ActionStatus.NONE_PENDING // && cloneToolsNetworkClient.peekCurrentUndoStatus() == CloneToolsNetworkClient.ActionStatus.NONE_PENDING ) { // toolState = ToolState.IDLE; // } if (!activePowerUp.isIdle()) { lastPowerupStarted = activePowerUp; } switch (toolState) { case ACTION_FAILED: case ACTION_SUCCEEDED: case UNDO_FAILED: case UNDO_SUCCEEDED: case IDLE: { if (!rightClickPowerup.isIdle()) { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_UP_CW; } else if (!leftClickPowerup.isIdle()) { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_UP_CCW; } else { if (lastPowerupStarted != null && lastPowerupStarted.isIdle()) { infoToUpdate.animationState = lastPowerupStarted == rightClickPowerup ? RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CW_CANCELLED : RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CCW_CANCELLED; } else { switch (toolState) { case ACTION_FAILED: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CW_CANCELLED; break; } case ACTION_SUCCEEDED: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CW_SUCCESS; break; } case UNDO_FAILED: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CCW_CANCELLED; break; } case UNDO_SUCCEEDED: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPIN_DOWN_CCW_SUCCESS; break; } } } } break; } case PERFORMING_ACTION: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPINNING_CW; lastPowerupStarted = null; break; } case PERFORMING_UNDO_FROM_FULL: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPINNING_CCW_FROM_FULL; lastPowerupStarted = null; break; } case PERFORMING_UNDO_FROM_PARTIAL: { infoToUpdate.animationState = RenderCursorStatus.CursorRenderInfo.AnimationState.SPINNING_CCW_FROM_PARTIAL; lastPowerupStarted = null; break; } default: { assert false : "Invalid toolstate = " + toolState + " in refreshRenderInfo()"; } } // if (cloneToolsNetworkClient.peekCurrentActionStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING) { // performingTask = true; // infoToUpdate. // } else if (cloneToolsNetworkClient.peekCurrentUndoStatus() != CloneToolsNetworkClient.ActionStatus.NONE_PENDING) { // infoToUpdate.performingTask = true; // infoToUpdate.clockwise = false; // } else { // infoToUpdate.performingTask = false; // infoToUpdate.idle = activePowerUp.isIdle(); // } // System.out.println("ServerStatus:" + cloneToolsNetworkClient.getServerStatus() + ", " + cloneToolsNetworkClient.getServerPercentComplete()); boolean serverIsIdle = cloneToolsNetworkClient.getServerStatus() == ServerStatus.IDLE; boolean selectionReadyOnServer = clientVoxelSelection.isSelectionCompleteOnServer(); if (!leftClickPowerup.isIdle()) selectionReadyOnServer = true; // undo doesn't need server selection float serverReadiness = serverIsIdle ? 100 : cloneToolsNetworkClient.getServerPercentComplete(); float serverSelectionReadiness = selectionReadyOnServer ? 100.0F : clientVoxelSelection.getServerSelectionFractionComplete(); infoToUpdate.fullyChargedAndReady = (!activePowerUp.isIdle() && activePowerUp.getPercentCompleted() >= 99.999 && serverIsIdle && selectionReadyOnServer ); infoToUpdate.chargePercent = (float)activePowerUp.getPercentCompleted(); infoToUpdate.readinessPercent = Math.min(serverReadiness, serverSelectionReadiness); infoToUpdate.cursorType = SpeedyToolComplex.this.getCursorType(); infoToUpdate.taskCompletionPercent = cloneToolsNetworkClient.getServerPercentComplete(); // System.out.println("CurserRenderInfoLink - refresh. readinessPercent=" + infoToUpdate.readinessPercent + // "; taskCompletionPercent=" + infoToUpdate.taskCompletionPercent + // "; chargePercent= " + infoToUpdate.chargePercent + // "; chargedAndReady=" + infoToUpdate.fullyChargedAndReady // ); if (infoToUpdate.animationState != lastState) { // System.out.println("State:" + infoToUpdate.animationState); lastState = infoToUpdate.animationState; } return true; } private RenderCursorStatus.CursorRenderInfo.AnimationState lastState; } public class StatusMessageRenderInfoLink implements RendererStatusMessage.StatusMessageRenderInfoUpdateLink { @Override public boolean refreshRenderInfo(RendererStatusMessage.StatusMessageRenderInfo infoToUpdate) { long timeMessageHasBeenDisplayed = System.nanoTime() - errorMessageDisplayTimeStartNS; if (timeMessageHasBeenDisplayed <= SpeedyToolsOptionsClient.getErrorMessageDisplayDurationNS()) { infoToUpdate.messageToDisplay = errorMessageBeingDisplayed; } else { infoToUpdate.messageToDisplay = ""; } return true; } } // protected ItemSpeedyTool itemComplexBase; private void checkInvariants() { // assert ( currentToolSelectionState != ToolSelectionStates.GENERATING_SELECTION || voxelSelectionManager != null); // assert ( currentToolSelectionState != ToolSelectionStates.DISPLAYING_SELECTION // || (voxelSelectionManager != null && selectionOrigin != null) ); assert ( commonSelectionState.selectionGrabActivated == false || commonSelectionState.selectionGrabPoint != null); } // the Tool Selection can be in several states as given by currentToolState: // 1) NO_SELECTION // highlightBlocks() is used to update some variables, based on what the player is looking at // a) highlightedBlocks (block wire outline) // b) currentHighlighting (what type of highlighting depending on whether there is a boundary field, whether // the player is looking at a block or at a side of the boundary field // c) blockUnderCursor (if looking at a block) // d) boundaryCursorSide (if looking at the boundary field) // voxelSelectionManager is not valid // 2) GENERATING_SELECTION - user has clicked to generate a selection // a) actionInProgress gives the action being performed // b) voxelSelectionManager has been created and initialised // c) every tick, the voxelSelectionManager is updated further until complete // 3) DISPLAYING_SELECTION - selection is being displayed and/or moved // voxelSelectionManager is valid and has a renderlist // combined with this are the various other actions that the tool can perform: // 1) transmitting selection to server // 2) performing an action / waiting for server // 3) performing an undo / waiting for server private SelectionType currentHighlighting = SelectionType.NONE; private List<BlockPos> highlightedBlocks; // private float selectionGenerationPercentComplete; private boolean lastActionWasRejected; // private ToolSelectionStates currentToolSelectionState = ToolSelectionStates.NO_SELECTION; // private BlockVoxelMultiSelector voxelSelectionManager; // private BlockVoxelMultiSelectorRenderer voxelSelectionRenderer; // private BlockPos selectionOrigin; // private boolean selectionGrabActivated = false; // private Vec3 selectionGrabPoint = null; // private boolean selectionMovedFastYet; // private boolean hasBeenMoved; // used to change the appearance when freshly created or placed. // private QuadOrientation selectionOrientation; // BlockPos initialSelectionOrigin; // QuadOrientation initialSelectionOrientation; protected CommonSelectionState commonSelectionState; private ClientVoxelSelection clientVoxelSelection; protected CloneToolsNetworkClient cloneToolsNetworkClient; private SelectionPacketSender selectionPacketSender; private SpeedyToolBoundary speedyToolBoundary; // used to retrieve the boundary field coordinates, if selected private enum SelectionType { NONE, FULL_BOX, BOUND_FILL, UNBOUND_FILL } // logic table used to determine which renderer parts to display private enum ToolSelectionStates { NO_SELECTION(ClientVoxelSelection.VoxelSelectionState.NO_SELECTION, true, false, true, false), GENERATING_SELECTION(ClientVoxelSelection.VoxelSelectionState.GENERATING, false, false, true, true), DISPLAYING_SELECTION(ClientVoxelSelection.VoxelSelectionState.READY_FOR_DISPLAY, false, true, false, false), ; public final boolean displayWireframeHighlight; public final boolean displaySolidSelection; public final boolean displayBoundaryField; public final boolean performingAction; public final ClientVoxelSelection.VoxelSelectionState voxelSelectionState; // returns the ToolSelectionState corresponding to a ClientVoxelSelection state public static ToolSelectionStates getState(ClientVoxelSelection.VoxelSelectionState stateToMatch) { for (ToolSelectionStates toolSelectionStates : ToolSelectionStates.values()) { if (toolSelectionStates.voxelSelectionState == stateToMatch) return toolSelectionStates; } assert false : "No matching ToolSelectionStates for VoxelSelectionState:" + stateToMatch; return null; } private ToolSelectionStates(ClientVoxelSelection.VoxelSelectionState i_voxelSelectionState, boolean init_displayHighlight, boolean init_displaySelection, boolean init_displayBoundaryField, boolean init_performingAction) { voxelSelectionState = i_voxelSelectionState; displayWireframeHighlight = init_displayHighlight; displaySolidSelection = init_displaySelection; displayBoundaryField = init_displayBoundaryField; performingAction = init_performingAction; } } // private enum SelectionGenerationState {IDLE, VOXELS, RENDERLISTS}; // SelectionGenerationState selectionGenerationState = SelectionGenerationState.IDLE; // private float voxelCompletionReached; private RendererSolidSelection.SolidSelectionRenderInfoUpdateLink solidSelectionRendererUpdateLink; private RenderCursorStatus.CursorRenderInfoUpdateLink cursorRenderInfoUpdateLink; private RendererStatusMessage.StatusMessageRenderInfoUpdateLink statusMessageRenderInfoUpdateLink; private RendererHotbarCurrentItem.HotbarRenderInfoUpdateLink hotbarRenderInfoUpdateLink; private enum ToolState { IDLE, PERFORMING_ACTION, PERFORMING_UNDO_FROM_FULL, PERFORMING_UNDO_FROM_PARTIAL, ACTION_SUCCEEDED, ACTION_FAILED, UNDO_SUCCEEDED, UNDO_FAILED } private ToolState toolState; private PowerUpEffect leftClickPowerup = new PowerUpEffect(); private PowerUpEffect rightClickPowerup = new PowerUpEffect(); private PowerUpEffect lastPowerupStarted = null; // points to the last powerup which was started (to detect when it has been released) private SoundEffectBoundaryHum soundEffectBoundaryHum; private SoundEffectComplexTool soundEffectComplexTool; private SoundEffectComplexSelectionGeneration soundEffectComplexSelectionGeneration; private RenderCursorStatus renderCursorStatus; }