/** * Checks whether a character should change movement mode (from being underwater or in a ladder). * A higher and lower point of the character is tested for being in water, only if both points are * in water does the character count as swimming. <br> * <br> * Sends the OnEnterLiquidEvent and OnLeaveLiquidEvent events. * * @param movementComp The movement component of the character. * @param state The current state of the character. */ private void checkMode( final CharacterMovementComponent movementComp, final CharacterStateEvent state, final CharacterStateEvent oldState, EntityRef entity, boolean firstRun) { // If we are ghosting or we can't move, the mode cannot be changed. if (!state.getMode().respondToEnvironment) { return; } Vector3f worldPos = state.getPosition(); Vector3f top = new Vector3f(worldPos); Vector3f bottom = new Vector3f(worldPos); top.y += 0.5f * movementComp.height; bottom.y -= 0.5f * movementComp.height; final boolean topUnderwater = worldProvider.getBlock(top).isLiquid(); final boolean bottomUnderwater = worldProvider.getBlock(bottom).isLiquid(); final boolean newSwimming = !topUnderwater && bottomUnderwater; final boolean newDiving = topUnderwater && bottomUnderwater; boolean newClimbing = false; // TODO: refactor this knot of if-else statements into something easy to read. Some sub-methods // and switch statements would be nice. if (!newSwimming && !newDiving) { // TODO: generalize to isClimbingAllowed() or similar Vector3f[] sides = { new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos) }; float factor = 1.0f; sides[0].x += factor * movementComp.radius; sides[1].x -= factor * movementComp.radius; sides[2].z += factor * movementComp.radius; sides[3].z -= factor * movementComp.radius; sides[4].y -= movementComp.height; float distance = 100f; for (Vector3f side : sides) { Block block = worldProvider.getBlock(side); if (block.isClimbable()) { // If any of our sides are near a climbable block, check if we are near to the side Vector3i myPos = new Vector3i(worldPos, 0.5f); Vector3i climbBlockPos = new Vector3i(side, 0.5f); Vector3i dir = new Vector3i(block.getDirection().getVector3i()); float currentDistance = 10f; if (dir.x != 0 && Math.abs(worldPos.x - (float) climbBlockPos.x + (float) dir.x * .5f) < movementComp.radius + 0.1f) { newClimbing = true; if (myPos.x < climbBlockPos.x) { dir.x = -dir.x; } currentDistance = Math.abs(climbBlockPos.z - worldPos.z); } else if (dir.z != 0 && Math.abs(worldPos.z - (float) climbBlockPos.z + (float) dir.z * .5f) < movementComp.radius + 0.1f) { newClimbing = true; if (myPos.z < climbBlockPos.z) { dir.z = -dir.z; } currentDistance = Math.abs(climbBlockPos.z - worldPos.z); } // if there are multiple climb blocks, choose the nearest one. This can happen when there // are two // adjacent ledges around a corner. if (currentDistance < distance) { distance = currentDistance; state.setClimbDirection(dir); } } } } if (newDiving) { if (state.getMode() != MovementMode.DIVING) { state.setMode(MovementMode.DIVING); } } else if (newSwimming) { if (state.getMode() != MovementMode.SWIMMING) { state.setMode(MovementMode.SWIMMING); } state.getVelocity().y += 0.02; } else if (state.getMode() == MovementMode.SWIMMING) { if (newClimbing) { state.setMode(MovementMode.CLIMBING); state.getVelocity().y = 0; } else { if (state.getVelocity().y > 0) { state.getVelocity().y += 4; } state.setMode(MovementMode.WALKING); } } else if (newClimbing != (state.getMode() == MovementMode.CLIMBING)) { // We need to toggle the climbing mode state.getVelocity().y = 0; state.setMode((newClimbing) ? MovementMode.CLIMBING : MovementMode.WALKING); } }