diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java index b4dab5758..249377298 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/Contraption.java @@ -10,19 +10,18 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.stream.Collectors; import javax.annotation.Nullable; -import com.simibubi.create.foundation.render.backend.instancing.IFlywheelWorld; -import com.simibubi.create.foundation.render.backend.light.GridAlignedBB; -import com.simibubi.create.foundation.utility.*; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -55,7 +54,15 @@ import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBl import com.simibubi.create.content.logistics.block.redstone.RedstoneContactBlock; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.fluid.CombinedTankWrapper; +import com.simibubi.create.foundation.render.backend.instancing.IFlywheelWorld; import com.simibubi.create.foundation.render.backend.light.EmptyLighter; +import com.simibubi.create.foundation.render.backend.light.GridAlignedBB; +import com.simibubi.create.foundation.utility.BlockFace; +import com.simibubi.create.foundation.utility.Coordinate; +import com.simibubi.create.foundation.utility.Iterate; +import com.simibubi.create.foundation.utility.NBTHelper; +import com.simibubi.create.foundation.utility.NBTProcessors; +import com.simibubi.create.foundation.utility.UniqueLinkedList; import com.simibubi.create.foundation.utility.worldWrappers.WrappedWorld; import net.minecraft.block.AbstractButtonBlock; @@ -86,6 +93,9 @@ import net.minecraft.util.Rotation; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.shapes.IBooleanFunction; +import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.util.math.shapes.VoxelShapes; import net.minecraft.util.palette.PaletteHashMap; import net.minecraft.village.PointOfInterestType; import net.minecraft.world.IWorld; @@ -106,6 +116,7 @@ import net.minecraftforge.registries.GameData; public abstract class Contraption { + public Optional> simplifiedEntityColliders; public AbstractContraptionEntity entity; public CombinedInvWrapper inventory; public CombinedTankWrapper fluidInventory; @@ -126,6 +137,8 @@ public abstract class Contraption { private Map initialPassengers; private List pendingSubContraptions; + private CompletableFuture> simplifiedEntityColliderProvider; + // Client public Map presentTileEntities; public List maybeInstancedTileEntities; @@ -148,13 +161,12 @@ public abstract class Contraption { specialRenderedTileEntities = new ArrayList<>(); pendingSubContraptions = new ArrayList<>(); stabilizedSubContraptions = new HashMap<>(); + simplifiedEntityColliders = Optional.empty(); } public ContraptionWorld getContraptionWorld() { - if (world == null) { + if (world == null) world = new ContraptionWorld(entity.world, this); - } - return world; } @@ -181,6 +193,7 @@ public abstract class Contraption { String type = nbt.getString("Type"); Contraption contraption = ContraptionType.fromType(type); contraption.readNBT(world, nbt, spawnData); + contraption.gatherBBsOffThread(); return contraption; } @@ -244,6 +257,7 @@ public abstract class Contraption { .collect(Collectors.toList()); fluidInventory = new CombinedTankWrapper( Arrays.copyOf(fluidHandlers.toArray(), fluidHandlers.size(), IFluidHandler[].class)); + gatherBBsOffThread(); } public void onEntityInitialize(World world, AbstractContraptionEntity contraptionEntity) { @@ -268,6 +282,15 @@ public abstract class Contraption { } public void onEntityTick(World world) { + if (simplifiedEntityColliderProvider != null && simplifiedEntityColliderProvider.isDone()) { + try { + simplifiedEntityColliders = Optional.of(simplifiedEntityColliderProvider.join()); + } catch (Exception e) { + e.printStackTrace(); + } + simplifiedEntityColliderProvider = null; + } + fluidStorage.forEach((pos, mfs) -> mfs.tick(entity, pos, world.isRemote)); } @@ -1158,6 +1181,22 @@ public abstract class Contraption { return new EmptyLighter(this); } + private void gatherBBsOffThread() { + simplifiedEntityColliderProvider = CompletableFuture.supplyAsync(() -> { + VoxelShape combinedShape = VoxelShapes.empty(); + for (Entry entry : blocks.entrySet()) { + BlockInfo info = entry.getValue(); + BlockPos localPos = entry.getKey(); + VoxelShape collisionShape = info.state.getCollisionShape(this.world, localPos); + if (collisionShape.isEmpty()) + continue; + combinedShape = VoxelShapes.combineAndSimplify(combinedShape, + collisionShape.withOffset(localPos.getX(), localPos.getY(), localPos.getZ()), IBooleanFunction.OR); + } + return combinedShape.toBoundingBoxList(); + }); + } + public static float getRadius(Set blocks, Direction.Axis axis) { switch (axis) { case X: diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/ContraptionCollider.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/ContraptionCollider.java index 281cb1785..b95319e45 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/ContraptionCollider.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/ContraptionCollider.java @@ -23,6 +23,7 @@ import com.simibubi.create.foundation.collision.ContinuousOBBCollider.Continuous import com.simibubi.create.foundation.collision.Matrix3d; import com.simibubi.create.foundation.collision.OrientedBB; import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.utility.BlockHelper; import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.VecHelper; @@ -37,6 +38,8 @@ import net.minecraft.util.Direction; import net.minecraft.util.Direction.Axis; import net.minecraft.util.Direction.AxisDirection; import net.minecraft.util.ReuseableStream; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvents; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -104,40 +107,45 @@ public class ContraptionCollider { AxisAlignedBB entityBounds = entity.getBoundingBox(); Vec3d motion = entity.getMotion(); float yawOffset = rotation.getYawOffset(); - Vec3d position = getWorldToLocalTranslation(entity, anchorVec, rotationMatrix, yawOffset); - // Find all potential block shapes to collide with + // Prepare entity bounds AxisAlignedBB localBB = entityBounds.offset(position) .grow(1.0E-7D); - ReuseableStream potentialHits = - getPotentiallyCollidedShapes(world, contraption, localBB.expand(motion)); - if (potentialHits.createStream() - .count() == 0) - continue; - - // Prepare entity bounds OrientedBB obb = new OrientedBB(localBB); obb.setRotation(rotationMatrix); motion = motion.subtract(contraptionMotion); motion = rotationMatrix.transform(motion); + // Use simplified bbs when present + // TODO: is it worth filtering out far away bbs? + final Vec3d motionCopy = motion; + List collidableBBs = contraption.simplifiedEntityColliders.orElseGet(() -> { + + // Else find 'nearby' individual block shapes to collide with + List bbs = new ArrayList<>(); + ReuseableStream potentialHits = + getPotentiallyCollidedShapes(world, contraption, localBB.expand(motionCopy)); + potentialHits.createStream() + .forEach(shape -> shape.toBoundingBoxList() + .forEach(bbs::add)); + return bbs; + + }); + MutableObject collisionResponse = new MutableObject<>(Vec3d.ZERO); + MutableObject normal = new MutableObject<>(Vec3d.ZERO); + MutableObject location = new MutableObject<>(Vec3d.ZERO); MutableBoolean surfaceCollision = new MutableBoolean(false); MutableFloat temporalResponse = new MutableFloat(1); Vec3d obbCenter = obb.getCenter(); // Apply separation maths - List bbs = new ArrayList<>(); - potentialHits.createStream() - .forEach(shape -> shape.toBoundingBoxList() - .forEach(bbs::add)); - boolean doHorizontalPass = !rotation.hasVerticalRotation(); for (boolean horizontalPass : Iterate.trueAndFalse) { boolean verticalPass = !horizontalPass || !doHorizontalPass; - for (AxisAlignedBB bb : bbs) { + for (AxisAlignedBB bb : collidableBBs) { Vec3d currentResponse = collisionResponse.getValue(); obb.setCenter(obbCenter.add(currentResponse)); ContinuousSeparationManifold intersect = obb.intersect(bb, motion); @@ -148,15 +156,28 @@ public class ContraptionCollider { surfaceCollision.setValue(intersect.isSurfaceCollision()); double timeOfImpact = intersect.getTimeOfImpact(); - if (timeOfImpact > 0 && timeOfImpact < 1) { - if (temporalResponse.getValue() > timeOfImpact) - temporalResponse.setValue(timeOfImpact); - continue; + boolean isTemporal = timeOfImpact > 0 && timeOfImpact < 1; + Vec3d collidingNormal = intersect.getCollisionNormal(); + Vec3d collisionPosition = intersect.getCollisionPosition(); + + if (!isTemporal) { + Vec3d separation = intersect.asSeparationVec(entity.stepHeight); + if (separation != null && !separation.equals(Vec3d.ZERO)) { + collisionResponse.setValue(currentResponse.add(separation)); + timeOfImpact = 0; + } } - Vec3d separation = intersect.asSeparationVec(entity.stepHeight); - if (separation != null && !separation.equals(Vec3d.ZERO)) - collisionResponse.setValue(currentResponse.add(separation)); + boolean nearest = timeOfImpact >= 0 && temporalResponse.getValue() > timeOfImpact; + if (collidingNormal != null && nearest) + normal.setValue(collidingNormal); + if (collisionPosition != null && nearest) + location.setValue(collisionPosition); + + if (isTemporal) { + if (temporalResponse.getValue() > timeOfImpact) + temporalResponse.setValue(timeOfImpact); + } } if (verticalPass) @@ -175,6 +196,9 @@ public class ContraptionCollider { // Resolve collision Vec3d entityMotion = entity.getMotion(); + Vec3d entityMotionNoTemporal = entityMotion; + Vec3d collisionNormal = normal.getValue(); + Vec3d collisionLocation = location.getValue(); Vec3d totalResponse = collisionResponse.getValue(); boolean hardCollision = !totalResponse.equals(Vec3d.ZERO); boolean temporalCollision = temporalResponse.getValue() != 1; @@ -187,8 +211,47 @@ public class ContraptionCollider { .add(contraptionMotion); totalResponse = rotationMatrix.transform(totalResponse); totalResponse = VecHelper.rotate(totalResponse, yawOffset, Axis.Y); + collisionNormal = rotationMatrix.transform(collisionNormal); + collisionNormal = VecHelper.rotate(collisionNormal, yawOffset, Axis.Y); + collisionLocation = rotationMatrix.transform(collisionLocation); + collisionLocation = VecHelper.rotate(collisionLocation, yawOffset, Axis.Y); rotationMatrix.transpose(); + double bounce = 0; + double slide = 0; + + if (!collisionLocation.equals(Vec3d.ZERO)) { + collisionLocation = collisionLocation.add(entity.getPositionVec() + .add(entity.getBoundingBox() + .getCenter()) + .scale(.5f)); + if (temporalCollision) + collisionLocation = collisionLocation.add(0, motionResponse.y, 0); + BlockPos pos = new BlockPos(contraptionEntity.toLocalVector(collisionLocation, 0)); + if (contraption.getBlocks() + .containsKey(pos)) { + BlockState blockState = contraption.getBlocks() + .get(pos).state; + bounce = BlockHelper.getBounceMultiplier(blockState.getBlock()); + slide = Math.max(0, blockState.getSlipperiness(contraption.world, pos, entity) - .6f); + } + } + + boolean hasNormal = !collisionNormal.equals(Vec3d.ZERO); + boolean anyCollision = hardCollision || temporalCollision; + + if (bounce > 0 && hasNormal && anyCollision) { + collisionNormal = collisionNormal.normalize(); + Vec3d newNormal = collisionNormal.crossProduct(collisionNormal.crossProduct(entityMotionNoTemporal)) + .normalize(); + if (bounceEntity(entity, newNormal, contraptionEntity, bounce)) { + entity.world.playSound(playerType == PlayerType.CLIENT ? (PlayerEntity) entity : null, + entity.getX(), entity.getY(), entity.getZ(), SoundEvents.BLOCK_SLIME_BLOCK_FALL, + SoundCategory.BLOCKS, .5f, 1); + continue; + } + } + if (temporalCollision) { double idealVerticalMotion = motionResponse.y; if (idealVerticalMotion != entityMotion.y) { @@ -216,6 +279,18 @@ public class ContraptionCollider { entityMotion = entityMotion.mul(1, 1, 0); } + if (bounce == 0 && slide > 0 && hasNormal && anyCollision && rotation.hasVerticalRotation()) { + collisionNormal = collisionNormal.normalize(); + Vec3d motionIn = entityMotionNoTemporal.mul(0, 1, 0) + .add(0, -.01f, 0); + Vec3d slideNormal = collisionNormal.crossProduct(motionIn.crossProduct(collisionNormal)) + .normalize(); + entity.setMotion(entityMotion.mul(.8, 0, .8) + .add(slideNormal.scale((.2f + slide) * motionIn.length()) + .add(0, -0.1, 0))); + entityMotion = entity.getMotion(); + } + if (!hardCollision && surfaceCollision.isFalse()) continue; @@ -229,10 +304,14 @@ public class ContraptionCollider { if (surfaceCollision.isTrue()) { entity.fallDistance = 0; - entity.onGround = true; contraptionEntity.collidingEntities.put(entity, new MutableInt(0)); - if (entity instanceof ItemEntity) - entityMotion = entityMotion.mul(.5f, 1, .5f); + boolean canWalk = bounce != 0 || slide == 0; + if (canWalk || !rotation.hasVerticalRotation()) { + if (canWalk) + entity.onGround = true; + if (entity instanceof ItemEntity) + entityMotion = entityMotion.mul(.5f, 1, .5f); + } contactPointMotion = contraptionEntity.getContactPointMotion(entityPosition); allowedMovement = getAllowedMovement(contactPointMotion, entity); entity.setPosition(entityPosition.x + allowedMovement.x, entityPosition.y, @@ -254,6 +333,42 @@ public class ContraptionCollider { } + static boolean bounceEntity(Entity entity, Vec3d normal, AbstractContraptionEntity contraption, double factor) { + if (factor == 0) + return false; + if (entity.bypassesLandingEffects()) + return false; + if (normal.equals(Vec3d.ZERO)) + return false; + + Vec3d contraptionVec = Vec3d.ZERO; + Vec3d contactPointMotion = contraption.getContactPointMotion(entity.getPositionVec()); + Vec3d motion = entity.getMotion() + .subtract(contactPointMotion); + + Vec3d v2 = motion.crossProduct(normal) + .normalize(); + if (v2 != Vec3d.ZERO) + contraptionVec = normal.scale(contraptionVec.dotProduct(normal)) + .add(v2.scale(contraptionVec.dotProduct(v2))); + else + v2 = normal.crossProduct(normal.add(Math.random(), Math.random(), Math.random())) + .normalize(); + + Vec3d v3 = normal.crossProduct(v2); + motion = motion.subtract(contraptionVec); + Vec3d lr = new Vec3d(factor * motion.dotProduct(normal), -motion.dotProduct(v2), -motion.dotProduct(v3)); + + if (lr.dotProduct(lr) > 1 / 16f) { + Vec3d newMot = contactPointMotion.add(normal.x * lr.x + v2.x * lr.y + v3.x * lr.z, + normal.y * lr.x + v2.y * lr.y + v3.y * lr.z, normal.z * lr.x + v2.z * lr.y + v3.z * lr.z); + entity.setMotion(newMot); + return true; + } + + return false; + } + public static Vec3d getWorldToLocalTranslation(Entity entity, AbstractContraptionEntity contraptionEntity) { return getWorldToLocalTranslation(entity, contraptionEntity.getAnchorVec(), contraptionEntity.getRotationState()); } diff --git a/src/main/java/com/simibubi/create/foundation/collision/ContinuousOBBCollider.java b/src/main/java/com/simibubi/create/foundation/collision/ContinuousOBBCollider.java index bc4015ac7..d5ed883fa 100644 --- a/src/main/java/com/simibubi/create/foundation/collision/ContinuousOBBCollider.java +++ b/src/main/java/com/simibubi/create/foundation/collision/ContinuousOBBCollider.java @@ -35,24 +35,25 @@ public class ContinuousOBBCollider extends OBBCollider { checkCount = 0; mf.stepSeparationAxis = uB1; mf.stepSeparation = Double.MAX_VALUE; + mf.normalSeparation = Double.MAX_VALUE; if ( // Separate along A's local axes (global XYZ) - !(separate(mf, uA0, diff.x, eA.x, a00 * eB.x + a01 * eB.y + a02 * eB.z, motion.x) - || separate(mf, uA1, diff.y, eA.y, a10 * eB.x + a11 * eB.y + a12 * eB.z, motion.y) - || separate(mf, uA2, diff.z, eA.z, a20 * eB.x + a21 * eB.y + a22 * eB.z, motion.z) + !(separate(mf, uA0, diff.x, eA.x, a00 * eB.x + a01 * eB.y + a02 * eB.z, motion.x, true) + || separate(mf, uA1, diff.y, eA.y, a10 * eB.x + a11 * eB.y + a12 * eB.z, motion.y, true) + || separate(mf, uA2, diff.z, eA.z, a20 * eB.x + a21 * eB.y + a22 * eB.z, motion.z, true) // Separate along B's local axes - || separate(mf, uB0, diff2.x, eA.x * a00 + eA.y * a10 + eA.z * a20, eB.x, motion2.x) - || separate(mf, uB1, diff2.y, eA.x * a01 + eA.y * a11 + eA.z * a21, eB.y, motion2.y) - || separate(mf, uB2, diff2.z, eA.x * a02 + eA.y * a12 + eA.z * a22, eB.z, motion2.z))) + || separate(mf, uB0, diff2.x, eA.x * a00 + eA.y * a10 + eA.z * a20, eB.x, motion2.x, false) + || separate(mf, uB1, diff2.y, eA.x * a01 + eA.y * a11 + eA.z * a21, eB.y, motion2.y, false) + || separate(mf, uB2, diff2.z, eA.x * a02 + eA.y * a12 + eA.z * a22, eB.z, motion2.z, false))) return mf; return null; } static boolean separate(ContinuousSeparationManifold mf, Vec3d axis, double TL, double rA, double rB, - double projectedMotion) { + double projectedMotion, boolean axisOfObjA) { checkCount++; double distance = abs(TL); double diff = distance - (rA + rB); @@ -81,7 +82,12 @@ public class ContinuousOBBCollider extends OBBCollider { Vec3d normalizedAxis = axis.normalize(); boolean isBestSeperation = distance != 0 && -(diff) <= abs(mf.separation); -// boolean isBestSeperation = discreteCollision && checkCount == 5; // Debug specific separations + // boolean isBestSeperation = discreteCollision && checkCount == 5; // Debug specific separations + + if (axisOfObjA && distance != 0 && -(diff) <= abs(mf.normalSeparation)) { + mf.normalAxis = normalizedAxis; + mf.normalSeparation = seperation; + } double dot = mf.stepSeparationAxis.dotProduct(axis); if (dot != 0 && discreteCollision) { @@ -97,45 +103,19 @@ public class ContinuousOBBCollider extends OBBCollider { stepSeparationVec = sepVec.subtract(axisPlane.scale(sepVec.dotProduct(stepPlane) / axisPlane.dotProduct(stepPlane))); stepSeparation = stepSeparationVec.length(); - - - if (abs(mf.stepSeparation) > abs(stepSeparation) && stepSeparation != 0) { -// CollisionDebugger.showDebugLine(Vec3d.ZERO, sepVec, 0x111155, "stepsep", -16); + if (abs(mf.stepSeparation) > abs(stepSeparation) && stepSeparation != 0) mf.stepSeparation = stepSeparation; - } } else { - if (abs(mf.stepSeparation) > stepSeparation) { + if (abs(mf.stepSeparation) > stepSeparation) mf.stepSeparation = stepSeparation; -// CollisionDebugger.showDebugLine(Vec3d.ZERO, stepSeparationVec, 0xff9999, "axis", -16); - } } - -// if (abs(mf.separation) < abs(stepSeparation) && stepSeparation != 0) } if (isBestSeperation) { - mf.axis = normalizedAxis; mf.separation = seperation; - - // Visualize values -// if (CollisionDebugger.AABB != null) { -// Vec3d normalizedAxis = axis.normalize(); -// showDebugLine(Vec3d.ZERO, normalizedAxis.scale(projectedMotion), 0x111155, "motion", 5); -// showDebugLine(Vec3d.ZERO, normalizedAxis.scale(TL), 0xbb00bb, "tl", 4); -// showDebugLine(Vec3d.ZERO, normalizedAxis.scale(sTL * rA), 0xff4444, "ra", 3); -// showDebugLine(normalizedAxis.scale(sTL * rA), -// normalizedAxis.scale(sTL * rA - entryTime * projectedMotion), 0x44ff44, "entry", 0); -// showDebugLine(normalizedAxis.scale(sTL * rA - entryTime * projectedMotion), -// normalizedAxis.scale(sTL * rA - entryTime * projectedMotion + exitTime * projectedMotion), 0x44ffff, -// "exit", -1); -// showDebugLine(normalizedAxis.scale(sTL * (distance - rB)), normalizedAxis.scale(TL), 0x4444ff, "rb", 2); -// showDebugLine(normalizedAxis.scale(sTL * (distance - rB)), -// normalizedAxis.scale(sTL * (distance - rB) + value), 0xff9966, "separation", 1); -//// System.out.println("TL:" + TL + ", rA: " + rA + ", rB: " + rB); -// } - + mf.collisionPosition = normalizedAxis.scale(signum(TL) * (axisOfObjA ? -rB : -rB) - signum(seperation) * .125f); } return false; @@ -147,10 +127,14 @@ public class ContinuousOBBCollider extends OBBCollider { double latestCollisionEntryTime = UNDEFINED; double earliestCollisionExitTime = Double.MAX_VALUE; boolean isDiscreteCollision = true; + Vec3d collisionPosition; Vec3d stepSeparationAxis; double stepSeparation; + Vec3d normalAxis; + double normalSeparation; + public double getTimeOfImpact() { if (latestCollisionEntryTime == UNDEFINED) return UNDEFINED; @@ -163,9 +147,17 @@ public class ContinuousOBBCollider extends OBBCollider { return true; } + public Vec3d getCollisionNormal() { + return normalAxis == null ? null : createSeparationVec(normalSeparation, normalAxis); + } + + public Vec3d getCollisionPosition() { + return collisionPosition; + } + public Vec3d asSeparationVec(double obbStepHeight) { if (isDiscreteCollision) { - if (stepSeparation <= obbStepHeight) + if (stepSeparation <= obbStepHeight) return createSeparationVec(stepSeparation, stepSeparationAxis); return super.asSeparationVec(); } @@ -174,7 +166,7 @@ public class ContinuousOBBCollider extends OBBCollider { return null; return Vec3d.ZERO; } - + @Override public Vec3d asSeparationVec() { return asSeparationVec(0); diff --git a/src/main/java/com/simibubi/create/foundation/utility/BlockHelper.java b/src/main/java/com/simibubi/create/foundation/utility/BlockHelper.java index bd2045f84..181f1177f 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/BlockHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/BlockHelper.java @@ -8,10 +8,13 @@ import org.apache.commons.lang3.mutable.MutableInt; import com.simibubi.create.AllBlocks; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.content.contraptions.components.actors.SeatBlock; +import net.minecraft.block.BedBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; +import net.minecraft.block.SlimeBlock; import net.minecraft.client.particle.DiggingParticle; import net.minecraft.client.particle.ParticleManager; import net.minecraft.entity.player.PlayerEntity; @@ -255,5 +258,13 @@ public class BlockHelper { } catch (Exception e) { } } + + public static double getBounceMultiplier(Block block) { + if (block instanceof SlimeBlock) + return 0.8D; + if (block instanceof BedBlock || block instanceof SeatBlock) + return 0.66 * 0.8D; + return 0; + } }