private float moveUp(float riseAmount, CharacterCollider collider, Vector3f position) { Vector3f to = new Vector3f(position.x, position.y + riseAmount + VERTICAL_PENETRATION_LEEWAY, position.z); if (collider != null) { SweepCallback callback = collider.sweep(position, to, VERTICAL_PENETRATION_LEEWAY, -1f); if (callback.hasHit()) { float actualDist = Math.max( 0, ((riseAmount + VERTICAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction()) - VERTICAL_PENETRATION_LEEWAY); position.y += actualDist; return actualDist; } } position.y += riseAmount; return riseAmount; }
@ReceiveEvent(components = {HealthComponent.class}) public void onCrash(HorizontalCollisionEvent event, EntityRef entity) { HealthComponent health = entity.getComponent(HealthComponent.class); Vector3f vel = new Vector3f(event.getVelocity()); vel.y = 0; float speed = vel.length(); if (speed > health.horizontalDamageSpeedThreshold) { int damage = (int) ((speed - health.horizontalDamageSpeedThreshold) * health.excessSpeedDamageMultiplier); if (damage > 0) { checkDamage( entity, damage, EngineDamageTypes.PHYSICAL.get(), EntityRef.NULL, EntityRef.NULL); } } }
@ReceiveEvent public void onCrash( HorizontalCollisionEvent event, EntityRef entity, CharacterSoundComponent characterSounds, HealthComponent healthComponent) { Vector3f horizVelocity = new Vector3f(event.getVelocity()); horizVelocity.y = 0; float velocity = horizVelocity.length(); if (velocity > healthComponent.horizontalDamageSpeedThreshold) { if (characterSounds.lastSoundTime + CharacterSoundSystem.MIN_TIME < time.getGameTimeInMs()) { StaticSound sound = random.nextItem(characterSounds.landingSounds); if (sound != null) { entity.send(new PlaySoundEvent(sound, characterSounds.landingVolume)); characterSounds.lastSoundTime = time.getGameTimeInMs(); entity.saveComponent(characterSounds); } } } }
@ReceiveEvent public void onActivate( ActivateEvent event, EntityRef entity, TunnelActionComponent tunnelActionComponent) { Vector3f dir = new Vector3f(event.getDirection()); dir.scale(4.0f); Vector3f origin = new Vector3f(event.getOrigin()); origin.add(dir); Vector3i blockPos = new Vector3i(); int particleEffects = 0; int blockCounter = tunnelActionComponent.maxDestroyedBlocks; for (int s = 0; s <= tunnelActionComponent.maxTunnelDepth; s++) { origin.add(dir); if (!worldProvider.isBlockRelevant(origin)) { break; } for (int i = 0; i < tunnelActionComponent.maxRaysCast; i++) { Vector3f direction = random.nextVector3f(); Vector3f impulse = new Vector3f(direction); impulse.scale(tunnelActionComponent.explosiveForce); for (int j = 0; j < 3; j++) { Vector3f target = new Vector3f(origin); target.x += direction.x * j; target.y += direction.y * j; target.z += direction.z * j; blockPos.set((int) target.x, (int) target.y, (int) target.z); Block currentBlock = worldProvider.getBlock(blockPos); if (currentBlock.isDestructible()) { if (particleEffects < tunnelActionComponent.maxParticalEffects) { EntityBuilder builder = entityManager.newBuilder("engine:smokeExplosion"); builder.getComponent(LocationComponent.class).setWorldPosition(target); builder.build(); particleEffects++; } if (random.nextFloat() < tunnelActionComponent.thoroughness) { EntityRef blockEntity = blockEntityRegistry.getEntityAt(blockPos); blockEntity.send( new DoDamageEvent( tunnelActionComponent.damageAmount, tunnelActionComponent.damageType)); } blockCounter--; } if (blockCounter <= 0) { return; } } } } // No blocks were destroyed, so cancel the event if (blockCounter == tunnelActionComponent.maxDestroyedBlocks) { event.consume(); } }
private void walk( final CharacterMovementComponent movementComp, final CharacterStateEvent state, CharacterMoveInputEvent input, EntityRef entity) { Vector3f desiredVelocity = new Vector3f(input.getMovementDirection()); float lengthSquared = desiredVelocity.lengthSquared(); // If the length of desired movement is > 1, normalise it to prevent movement being faster than // allowed. // (Desired velocity < 1 is allowed, as the character may wish to walk/crawl/otherwise move // slowly) if (lengthSquared > 1) { desiredVelocity.normalize(); } desiredVelocity.scale(movementComp.speedMultiplier); float maxSpeed = getMaxSpeed(entity, movementComp); if (input.isRunning()) { maxSpeed *= movementComp.runFactor; } // As we can't use it, remove the y component of desired movement while maintaining speed. if (movementComp.grounded && desiredVelocity.y != 0) { float speed = desiredVelocity.length(); desiredVelocity.y = 0; if (desiredVelocity.x != 0 || desiredVelocity.z != 0) { desiredVelocity.normalize(); desiredVelocity.scale(speed); } } desiredVelocity.scale(maxSpeed); if (movementComp.mode == MovementMode.CLIMBING) { climb(state, input, desiredVelocity); } // Modify velocity towards desired, up to the maximum rate determined by friction Vector3f velocityDiff = new Vector3f(desiredVelocity); velocityDiff.sub(state.getVelocity()); velocityDiff.scale(Math.min(movementComp.mode.scaleInertia * input.getDelta(), 1.0f)); Vector3f endVelocity = new Vector3f(state.getVelocity()); endVelocity.x += velocityDiff.x; endVelocity.z += velocityDiff.z; if (movementComp.mode.scaleGravity == 0) { // apply the velocity without gravity endVelocity.y += velocityDiff.y; } else if (movementComp.mode.applyInertiaToVertical) { endVelocity.y += Math.max( -TERMINAL_VELOCITY, velocityDiff.y - (GRAVITY * movementComp.mode.scaleGravity) * input.getDelta()); } else { endVelocity.y = Math.max( -TERMINAL_VELOCITY, state.getVelocity().y - (GRAVITY * movementComp.mode.scaleGravity) * input.getDelta()); } Vector3f moveDelta = new Vector3f(endVelocity); moveDelta.scale(input.getDelta()); CharacterCollider collider = movementComp.mode.useCollision ? physics.getCharacterCollider(entity) : null; MoveResult moveResult = move( state.getPosition(), moveDelta, (state.getMode() != MovementMode.CLIMBING && state.isGrounded() && movementComp.mode.canBeGrounded) ? movementComp.stepHeight : 0, movementComp.slopeFactor, collider); Vector3f distanceMoved = new Vector3f(moveResult.getFinalPosition()); distanceMoved.sub(state.getPosition()); state.getPosition().set(moveResult.getFinalPosition()); if (input.isFirstRun() && distanceMoved.length() > 0) { entity.send(new MovedEvent(distanceMoved, state.getPosition())); } if (moveResult.isBottomHit()) { if (!state.isGrounded() && movementComp.mode.canBeGrounded) { if (input.isFirstRun()) { Vector3f landVelocity = new Vector3f(state.getVelocity()); landVelocity.y += (distanceMoved.y / moveDelta.y) * (endVelocity.y - state.getVelocity().y); logger.debug("Landed at " + landVelocity); entity.send(new VerticalCollisionEvent(state.getPosition(), landVelocity)); } state.setGrounded(true); } endVelocity.y = 0; // Jumping is only possible, if the entity is standing on ground if (input.isJumpRequested()) { state.setGrounded(false); endVelocity.y += movementComp.jumpSpeed; if (input.isFirstRun()) { entity.send(new JumpEvent()); } } } else { if (moveResult.isTopHit() && endVelocity.y > 0) { endVelocity.y = -0.5f * endVelocity.y; } state.setGrounded(false); } state.getVelocity().set(endVelocity); if (input.isFirstRun() && moveResult.isHorizontalHit()) { entity.send(new HorizontalCollisionEvent(state.getPosition(), state.getVelocity())); } if (state.isGrounded() || movementComp.mode == MovementMode.SWIMMING || movementComp.mode == MovementMode.DIVING) { state.setFootstepDelta( state.getFootstepDelta() + distanceMoved.length() / movementComp.distanceBetweenFootsteps); if (state.getFootstepDelta() > 1) { state.setFootstepDelta(state.getFootstepDelta() - 1); if (input.isFirstRun()) { switch (movementComp.mode) { case WALKING: entity.send(new FootstepEvent()); break; case DIVING: case SWIMMING: entity.send(new SwimStrokeEvent(worldProvider.getBlock(state.getPosition()))); break; } } } } }
private boolean moveHorizontal( Vector3f horizMove, CharacterCollider collider, Vector3f position, float slopeFactor, float stepHeight) { float remainingFraction = 1.0f; float dist = horizMove.length(); if (dist < physics.getEpsilon()) { return false; } boolean horizontalHit = false; Vector3f normalizedDir = Vector3fUtil.safeNormalize(horizMove, new Vector3f()); if (collider == null) { // ignore collision normalizedDir.scale(dist); position.add(normalizedDir); return false; } Vector3f targetPos = new Vector3f(normalizedDir); targetPos.scale(dist + HORIZONTAL_PENETRATION_LEEWAY); targetPos.add(position); int iteration = 0; Vector3f lastHitNormal = new Vector3f(0, 1, 0); while (remainingFraction >= 0.01f && iteration++ < 10) { SweepCallback callback = collider.sweep(position, targetPos, HORIZONTAL_PENETRATION, slopeFactor); /* Note: this isn't quite correct (after the first iteration the closestHitFraction is only for part of the moment) but probably close enough */ float actualDist = Math.max( 0, (dist + HORIZONTAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction() - HORIZONTAL_PENETRATION_LEEWAY); if (actualDist != 0) { remainingFraction -= actualDist / dist; } if (callback.hasHit()) { if (actualDist > physics.getEpsilon()) { Vector3f actualMove = new Vector3f(normalizedDir); actualMove.scale(actualDist); position.add(actualMove); } dist -= actualDist; Vector3f newDir = new Vector3f(normalizedDir); newDir.scale(dist); float slope = callback.getHitNormalWorld().dot(new Vector3f(0, 1, 0)); // We step up if we're hitting a big slope, or if we're grazing // the ground, otherwise we move up a shallow slope. if (slope < slopeFactor || 1 - slope < physics.getEpsilon()) { boolean stepping = checkStep(collider, position, newDir, callback, slopeFactor, stepHeight); if (!stepping) { horizontalHit = true; Vector3f newHorizDir = new Vector3f(newDir.x, 0, newDir.z); Vector3f horizNormal = new Vector3f(callback.getHitNormalWorld().x, 0, callback.getHitNormalWorld().z); if (horizNormal.lengthSquared() > physics.getEpsilon()) { horizNormal.normalize(); if (lastHitNormal.dot(horizNormal) > physics.getEpsilon()) { break; } lastHitNormal.set(horizNormal); extractResidualMovement(horizNormal, newHorizDir); } newDir.set(newHorizDir); } } else { // Hitting a shallow slope, move up it Vector3f newHorizDir = new Vector3f(newDir.x, 0, newDir.z); extractResidualMovement(callback.getHitNormalWorld(), newDir); Vector3f modHorizDir = new Vector3f(newDir); modHorizDir.y = 0; newDir.scale(newHorizDir.length() / modHorizDir.length()); } float sqrDist = newDir.lengthSquared(); if (sqrDist > physics.getEpsilon()) { newDir.normalize(); if (newDir.dot(normalizedDir) <= 0.0f) { break; } } else { break; } dist = (float) Math.sqrt(sqrDist); normalizedDir.set(newDir); targetPos.set(normalizedDir); targetPos.scale(dist + HORIZONTAL_PENETRATION_LEEWAY); targetPos.add(position); } else { normalizedDir.scale(dist); position.add(normalizedDir); break; } } return horizontalHit; }
private boolean moveDown( float dist, float slopeFactor, CharacterCollider collider, Vector3f position) { if (collider == null) { position.y += dist; return false; } float remainingDist = -dist; Vector3f targetPos = new Vector3f(position); targetPos.y -= remainingDist + VERTICAL_PENETRATION_LEEWAY; Vector3f normalizedDir = new Vector3f(0, -1, 0); boolean hit = false; int iteration = 0; while (remainingDist > physics.getEpsilon() && iteration++ < 10) { SweepCallback callback = collider.sweep(position, targetPos, VERTICAL_PENETRATION, -1.0f); float actualDist = Math.max( 0, (remainingDist + VERTICAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction() - VERTICAL_PENETRATION_LEEWAY); Vector3f expectedMove = new Vector3f(targetPos); expectedMove.sub(position); if (expectedMove.lengthSquared() > physics.getEpsilon()) { expectedMove.normalize(); expectedMove.scale(actualDist); position.add(expectedMove); } remainingDist -= actualDist; if (remainingDist < physics.getEpsilon()) { break; } if (callback.hasHit()) { float originalSlope = callback.getHitNormalWorld().dot(new Vector3f(0, 1, 0)); if (originalSlope < slopeFactor) { float slope = callback.calculateAverageSlope(originalSlope, CHECK_FORWARD_DIST); if (slope < slopeFactor) { remainingDist -= actualDist; expectedMove.set(targetPos); expectedMove.sub(position); extractResidualMovement(callback.getHitNormalWorld(), expectedMove); float sqrDist = expectedMove.lengthSquared(); if (sqrDist > physics.getEpsilon()) { expectedMove.normalize(); if (expectedMove.dot(normalizedDir) <= 0.0f) { hit = true; break; } } else { hit = true; break; } if (expectedMove.y > -physics.getEpsilon()) { hit = true; break; } normalizedDir.set(expectedMove); expectedMove.scale(-remainingDist / expectedMove.y + HORIZONTAL_PENETRATION_LEEWAY); targetPos.set(position); targetPos.add(expectedMove); } else { hit = true; break; } } else { hit = true; break; } } else { break; } } if (iteration >= 10) { hit = true; } return hit; }
/** * 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); } }