Better Acting

- Contraption actors can now expose a few more properties and behaviours.
- Contraption actors can save data within the context
- Redstone Contacts now work when being rotated
- Fixed harvester and drill offsets and their motion-aware animation
- Fixed Mounted contraptions calculating their yaw inconsistently (again)
This commit is contained in:
simibubi 2019-12-09 13:32:39 +01:00
parent f991c88fc2
commit 5db014c45b
11 changed files with 188 additions and 110 deletions

View file

@ -4,6 +4,7 @@ import java.util.Random;
import net.minecraft.nbt.DoubleNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
@ -11,9 +12,15 @@ import net.minecraft.util.math.Vec3i;
public class VecHelper {
public static Vec3d rotate(Vec3d vec, float deg, Axis axis) {
float angle = (float) (deg / 180f * Math.PI);
public static Vec3d rotate(Vec3d vec, double xRot, double yRot, double zRot) {
return rotate(rotate(rotate(vec, xRot, Axis.X), yRot, Axis.Y), zRot, Axis.Z);
}
public static Vec3d rotate(Vec3d vec, double deg, Axis axis) {
if (deg == 0)
return vec;
float angle = (float) (deg / 180f * Math.PI);
double sin = MathHelper.sin(angle);
double cos = MathHelper.cos(angle);
double x = vec.x;
@ -29,6 +36,10 @@ public class VecHelper {
return vec;
}
public static boolean isVecPointingTowards(Vec3d vec, Direction direction) {
return new Vec3d(direction.getDirectionVec()).distanceTo(vec.normalize()) < .75;
}
public static Vec3d getCenterOf(Vec3i pos) {
return new Vec3d(pos).add(.5f, .5f, .5f);
}

View file

@ -5,6 +5,7 @@ import java.util.List;
import com.simibubi.create.foundation.block.IRenderUtilityBlock;
import com.simibubi.create.foundation.block.IWithTileEntity;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.VoxelShapers;
import com.simibubi.create.modules.contraptions.base.DirectionalKineticBlock;
import com.simibubi.create.modules.contraptions.receivers.contraptions.IHaveMovementBehavior;
@ -85,18 +86,22 @@ public class DrillBlock extends DirectionalKineticBlock
}
@Override
public void visitPosition(MovementContext context) {
Direction movement = context.getMovementDirection();
// BlockState block = context.state;
// if (movement == block.get(FACING).getOpposite())
// return;
public boolean isActive(MovementContext context) {
return !VecHelper.isVecPointingTowards(context.relativeMotion, context.state.get(FACING).getOpposite());
}
@Override
public Vec3d getActiveAreaOffset(MovementContext context) {
return new Vec3d(context.state.get(FACING).getDirectionVec()).scale(.65f);
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
World world = context.world;
BlockPos pos = context.currentGridPos;
// pos = pos.offset(movement);
BlockState stateVisited = world.getBlockState(pos);
if (world.isRemote)
return;
if (stateVisited.getCollisionShape(world, pos).isEmpty())
return;
if (stateVisited.getBlockHardness(world, pos) == -1)
@ -108,8 +113,7 @@ public class DrillBlock extends DirectionalKineticBlock
for (ItemStack stack : drops) {
ItemEntity itemEntity = new ItemEntity(world, pos.getX() + .5f, pos.getY() + .25f, pos.getZ() + .5f, stack);
itemEntity.setMotion(
new Vec3d(movement.getDirectionVec()).add(0, 0.5f, 0).scale(world.rand.nextFloat() * .3f));
itemEntity.setMotion(context.motion.add(0, 0.5f, 0).scale(world.rand.nextFloat() * .3f));
world.addEntity(itemEntity);
}
}

View file

@ -6,6 +6,7 @@ import com.simibubi.create.AllBlocks;
import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.base.IRotate;
import com.simibubi.create.modules.contraptions.base.KineticTileEntity;
import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer;
@ -29,7 +30,9 @@ public class DrillTileEntityRenderer extends KineticTileEntityRenderer {
BlockState state = context.state;
SuperByteBuffer buffer = CreateClient.bufferCache.renderBlockState(KINETIC_TILE, getRenderedBlockState(state));
float speed = (float) (context.getMovementDirection() == state.get(FACING) ? context.getAnimationSpeed() : 0);
float speed = (float) (!VecHelper.isVecPointingTowards(context.relativeMotion, state.get(FACING).getOpposite())
? context.getAnimationSpeed()
: 0);
Axis axis = ((IRotate) state.getBlock()).getRotationAxis(state);
float time = AnimationTickHolder.getRenderTick();
float angle = (float) (((time * speed) % 360) / 180 * (float) Math.PI);

View file

@ -5,6 +5,7 @@ import java.util.List;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.foundation.block.IRenderUtilityBlock;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.VoxelShaper;
import com.simibubi.create.modules.contraptions.receivers.contraptions.IHaveMovementBehavior;
@ -107,18 +108,25 @@ public class HarvesterBlock extends HorizontalBlock implements IHaveMovementBeha
}
@Override
public void visitPosition(MovementContext context) {
Direction movement = context.getMovementDirection();
public boolean isActive(MovementContext context) {
return !VecHelper.isVecPointingTowards(context.relativeMotion,
context.state.get(HORIZONTAL_FACING).getOpposite());
}
@Override
public Vec3d getActiveAreaOffset(MovementContext context) {
return new Vec3d(context.state.get(HORIZONTAL_FACING).getDirectionVec()).scale(.5);
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
World world = context.world;
BlockState block = context.state;
BlockPos pos = context.currentGridPos;
// if (movement != block.get(HORIZONTAL_FACING))
// return;
BlockState stateVisited = world.getBlockState(pos);
boolean notCropButCuttable = false;
if (world.isRemote)
return;
if (stateVisited.getBlock() == Blocks.SUGAR_CANE) {
notCropButCuttable = true;
pos = pos.up();
@ -143,8 +151,7 @@ public class HarvesterBlock extends HorizontalBlock implements IHaveMovementBeha
seedSubtracted = true;
}
ItemEntity itemEntity = new ItemEntity(world, pos.getX() + .5f, pos.getY() + .25f, pos.getZ() + .5f, stack);
itemEntity.setMotion(
new Vec3d(movement.getDirectionVec()).add(0, 0.5f, 0).scale(world.rand.nextFloat() * .3f));
itemEntity.setMotion(context.motion.add(0, 0.5f, 0).scale(world.rand.nextFloat() * .3f));
world.addEntity(itemEntity);
}
}

View file

@ -7,6 +7,7 @@ import com.simibubi.create.AllBlocks;
import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.receivers.contraptions.IHaveMovementBehavior.MovementContext;
import net.minecraft.block.BlockState;
@ -28,13 +29,9 @@ public class HarvesterTileEntityRenderer extends TileEntityRenderer<HarvesterTil
public static SuperByteBuffer renderInContraption(MovementContext context) {
BlockState state = context.state;
Direction facing = context.getMovementDirection();
float speed = -500 * state.get(HORIZONTAL_FACING).getAxisDirection().getOffset();
// float speed = (float) (facing != state.get(HORIZONTAL_FACING)
// ? context.getAnimationSpeed() * facing.getAxisDirection().getOffset()
// : 0);
// if (facing.getAxis() == Axis.X)
// speed = -speed;
float speed = (float) (!VecHelper.isVecPointingTowards(context.relativeMotion, state.get(HORIZONTAL_FACING).getOpposite())
? context.getAnimationSpeed() * state.get(HORIZONTAL_FACING).getAxisDirection().getOffset()
: 0);
float time = AnimationTickHolder.getRenderTick();
float angle = (float) (((time * speed) % 360) / 180 * (float) Math.PI);

View file

@ -45,7 +45,6 @@ import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.template.Template.BlockInfo;
@ -597,10 +596,9 @@ public class Contraption {
public void initActors(World world) {
for (MutablePair<BlockInfo, MovementContext> pair : actors) {
MovementContext context = new MovementContext(world, pair.left.state);
context.world = world;
context.motion = Vec3d.ZERO;
context.currentGridPos = BlockPos.ZERO;
BlockState blockState = pair.left.state;
MovementContext context = new MovementContext(world, blockState);
((IHaveMovementBehavior) blockState.getBlock()).startMoving(context);
pair.setRight(context);
}
}

View file

@ -1,5 +1,7 @@
package com.simibubi.create.modules.contraptions.receivers.contraptions;
import org.apache.commons.lang3.tuple.MutablePair;
import com.simibubi.create.AllEntities;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.receivers.contraptions.IHaveMovementBehavior.MovementContext;
@ -11,25 +13,25 @@ import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.network.IPacket;
import net.minecraft.network.PacketBuffer;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.template.Template.BlockInfo;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.network.NetworkHooks;
public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnData {
private Contraption contraption;
protected Contraption contraption;
protected float initialAngle;
protected BlockPos controllerPos;
protected IControlContraption controllerTE;
public float movementSpeedModifier;
public float prevYaw;
public float prevPitch;
public float prevRoll;
@ -57,6 +59,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
this.prevYaw = initialAngle;
this.yaw = initialAngle;
this.targetYaw = initialAngle;
movementSpeedModifier = 1;
}
public <T extends TileEntity & IControlContraption> ContraptionEntity controlledBy(T controller) {
@ -82,11 +85,17 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
yaw += 360;
}
float speed = 0.2f;
prevYaw = yaw;
yaw = angleLerp(speed, yaw, targetYaw);
prevPitch = pitch;
pitch = angleLerp(speed, pitch, targetPitch);
if (Math.abs(getShortestAngleDiff(yaw, targetYaw)) >= 175) {
initialAngle += 180;
yaw += 180;
prevYaw = yaw;
} else {
float speed = 0.2f;
prevYaw = yaw;
yaw = angleLerp(speed, yaw, targetYaw);
prevPitch = pitch;
pitch = angleLerp(speed, pitch, targetPitch);
}
tickActors(movementVector);
super.tick();
@ -102,31 +111,48 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
}
public void tickActors(Vec3d movementVector) {
getContraption().getActors().forEach(pair -> {
movementSpeedModifier = 1;
float anglePitch = getPitch(1);
float angleYaw = getYaw(1);
float angleRoll = getRoll(1);
Vec3d rotationVec = new Vec3d(angleRoll, angleYaw, anglePitch);
Vec3d rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
for (MutablePair<BlockInfo, MovementContext> pair : contraption.actors) {
MovementContext context = pair.right;
float deg = (getRidingEntity() != null ? yaw + 180 : yaw) + initialAngle;
context.motion = VecHelper.rotate(movementVector, deg, Axis.Y);
BlockInfo blockInfo = pair.left;
IHaveMovementBehavior actor = (IHaveMovementBehavior) blockInfo.state.getBlock();
if (context.world == null)
context.world = world;
Vec3d actorPosition = new Vec3d(blockInfo.pos);
actorPosition = actorPosition.add(actor.getActiveAreaOffset(context));
actorPosition = VecHelper.rotate(actorPosition, angleRoll, angleYaw, anglePitch);
actorPosition = actorPosition.add(rotationOffset).add(posX, posY, posZ);
Vec3d rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
Vec3d offset = new Vec3d(pair.left.pos);
offset = VecHelper.rotate(offset, deg, Axis.Y);
offset = offset.add(rotationOffset);
offset = offset.add(posX, posY, posZ);
Vec3d previousPosition = context.position;
BlockPos gridPosition = new BlockPos(actorPosition);
boolean newPosVisited = true;
if (world.isRemote)
return;
if (previousPosition != null) {
context.motion = actorPosition.subtract(previousPosition);
Vec3d relativeMotion = context.motion;
relativeMotion = VecHelper.rotate(relativeMotion, -angleRoll, -angleYaw, -anglePitch);
context.relativeMotion = relativeMotion;
newPosVisited = !new BlockPos(previousPosition).equals(gridPosition);
}
BlockPos gridPos = new BlockPos(offset);
if (context.currentGridPos.equals(gridPos))
return;
context.currentGridPos = gridPos;
context.rotation = rotationVec;
context.position = actorPosition;
if (actor.isActive(context)) {
if (newPosVisited)
actor.visitNewPosition(context, gridPosition);
actor.tick(context);
}
IHaveMovementBehavior actor = (IHaveMovementBehavior) pair.left.state.getBlock();
actor.visitPosition(context);
});
if (movementSpeedModifier > context.movementSpeedModifier)
movementSpeedModifier = context.movementSpeedModifier;
}
}
public void moveTo(double x, double y, double z) {
@ -182,11 +208,12 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
}
public static float yawFromMotion(Vec3d motion) {
return (float) ((Math.PI / 2 - Math.atan2(motion.z, motion.x)) / Math.PI * 180);
return (float) ((3 * Math.PI / 2 + Math.atan2(motion.z, motion.x)) / Math.PI * 180);
}
public float getYaw(float partialTicks) {
return (partialTicks == 1.0F ? yaw : angleLerp(partialTicks, prevYaw, yaw)) - initialAngle;
return (getRidingEntity() == null ? 1 : -1)
* (partialTicks == 1.0F ? yaw : angleLerp(partialTicks, prevYaw, yaw)) + initialAngle;
}
public float getPitch(float partialTicks) {

View file

@ -36,6 +36,13 @@ public class ContraptionEntityRenderer extends EntityRenderer<ContraptionEntity>
return;
GlStateManager.pushMatrix();
long randomBits = (long) entity.getEntityId() * 493286711L;
randomBits = randomBits * randomBits * 4392167121L + randomBits * 98761L;
float xNudge = (((float) (randomBits >> 16 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
float yNudge = (((float) (randomBits >> 20 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
float zNudge = (((float) (randomBits >> 24 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
GlStateManager.translatef(xNudge, yNudge, zNudge);
float angleYaw = (float) (entity.getYaw(partialTicks) / 180 * Math.PI);
float anglePitch = (float) (entity.getPitch(partialTicks) / 180 * Math.PI);
float angleRoll = (float) (entity.getRoll(partialTicks) / 180 * Math.PI);
@ -44,20 +51,12 @@ public class ContraptionEntityRenderer extends EntityRenderer<ContraptionEntity>
if (ridingEntity != null && ridingEntity instanceof AbstractMinecartEntity) {
AbstractMinecartEntity cart = (AbstractMinecartEntity) ridingEntity;
long i = (long) entity.getEntityId() * 493286711L;
i = i * i * 4392167121L + i * 98761L;
float f = (((float) (i >> 16 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
float f1 = (((float) (i >> 20 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
float f2 = (((float) (i >> 24 & 7L) + 0.5F) / 8.0F - 0.5F) * 0.004F;
GlStateManager.translatef(f, f1, f2);
double cartX = MathHelper.lerp((double) partialTicks, cart.lastTickPosX, cart.posX);
double cartY = MathHelper.lerp((double) partialTicks, cart.lastTickPosY, cart.posY);
double cartZ = MathHelper.lerp((double) partialTicks, cart.lastTickPosZ, cart.posZ);
Vec3d cartPos = cart.getPos(cartX, cartY, cartZ);
if (cartPos != null) {
Vec3d cartPosFront = cart.getPosOffset(cartX, cartY, cartZ, (double) 0.3F);
Vec3d cartPosBack = cart.getPosOffset(cartX, cartY, cartZ, (double) -0.3F);
if (cartPosFront == null)
@ -73,10 +72,7 @@ public class ContraptionEntityRenderer extends EntityRenderer<ContraptionEntity>
}
}
// BlockPos anchor = entity.getContraption().getAnchor();
Vec3d rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
// Vec3d offset = VecHelper.getCenterOf(anchor).scale(-1);
TessellatorHelper.prepareFastRender();
TessellatorHelper.begin(DefaultVertexFormats.BLOCK);
ContraptionRenderer.render(entity.world, entity.getContraption(), superByteBuffer -> {

View file

@ -2,12 +2,10 @@ package com.simibubi.create.modules.contraptions.receivers.contraptions;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.receivers.contraptions.piston.MechanicalPistonTileEntity;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
@ -17,60 +15,82 @@ import net.minecraftforge.common.util.Constants.NBT;
public interface IHaveMovementBehavior {
public enum MoverType {
PISTON, BEARING, MINECART;
}
default void visitPosition(MovementContext context) {
}
default void tick(MechanicalPistonTileEntity piston) {
}
default boolean hasSpecialRenderer() {
return false;
}
public class MovementContext {
public BlockPos currentGridPos;
public Vec3d position;
public Vec3d motion;
public float movementSpeedModifier = 1;
public Vec3d relativeMotion;
public Vec3d rotation;
public World world;
public BlockState state;
public float movementSpeedModifier;
public CompoundNBT data;
public MovementContext(World world, BlockState state) {
this.world = world;
this.state = state;
}
public Direction getMovementDirection() {
return Direction.getFacingFromVector(motion.x, motion.y, motion.z);
motion = Vec3d.ZERO;
relativeMotion = Vec3d.ZERO;
rotation = Vec3d.ZERO;
position = null;
data = new CompoundNBT();
movementSpeedModifier = 1;
}
public float getAnimationSpeed() {
int modifier = 1000;
return ((int) (motion.length() * modifier)) / 100 * 100;
double length = -motion.length();
if (Math.abs(length) < 1 / 512f)
return 0;
return (((int) (length * modifier + 100 * Math.signum(length))) / 100) * 100;
}
public static MovementContext readNBT(World world, CompoundNBT nbt) {
MovementContext context = new MovementContext(world, NBTUtil.readBlockState(nbt.getCompound("State")));
BlockState state = NBTUtil.readBlockState(nbt.getCompound("State"));
MovementContext context = new MovementContext(world, state);
context.motion = VecHelper.readNBT(nbt.getList("Motion", NBT.TAG_DOUBLE));
context.relativeMotion = VecHelper.readNBT(nbt.getList("RelativeMotion", NBT.TAG_DOUBLE));
context.rotation = VecHelper.readNBT(nbt.getList("Rotation", NBT.TAG_DOUBLE));
if (nbt.contains("Position"))
context.position = VecHelper.readNBT(nbt.getList("Position", NBT.TAG_DOUBLE));
context.movementSpeedModifier = nbt.getFloat("SpeedModifier");
context.currentGridPos = NBTUtil.readBlockPos(nbt.getCompound("GridPos"));
context.data = nbt.getCompound("Data");
return context;
}
public CompoundNBT writeToNBT(CompoundNBT nbt) {
nbt.put("State", NBTUtil.writeBlockState(state));
nbt.put("Motion", VecHelper.writeNBT(motion));
nbt.put("RelativeMotion", VecHelper.writeNBT(relativeMotion));
nbt.put("Rotation", VecHelper.writeNBT(rotation));
if (position != null)
nbt.put("Position", VecHelper.writeNBT(position));
nbt.putFloat("SpeedModifier", movementSpeedModifier);
nbt.put("GridPos", NBTUtil.writeBlockPos(currentGridPos));
nbt.put("Data", data);
return nbt;
}
}
default boolean isActive(MovementContext context) {
return true;
}
default void tick(MovementContext context) {
}
default void startMoving(MovementContext context) {
}
default void visitNewPosition(MovementContext context, BlockPos pos) {
}
default Vec3d getActiveAreaOffset(MovementContext context) {
return Vec3d.ZERO;
}
@OnlyIn(value = Dist.CLIENT)
default SuperByteBuffer renderInContraption(MovementContext context) {
return null;

View file

@ -170,7 +170,7 @@ public class MechanicalBearingTileEntity extends GeneratingKineticTileEntity imp
if (movedContraption != null) {
Direction direction = getBlockState().get(BlockStateProperties.FACING);
Vec3d vec = new Vec3d(1, 1, 1).scale(angle * 180 / Math.PI).mul(new Vec3d(direction.getDirectionVec()));
movedContraption.rotateTo(-vec.x, vec.y, -vec.z);
movedContraption.rotateTo(vec.x, vec.y, -vec.z);
}
}

View file

@ -4,6 +4,7 @@ import java.util.Random;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.foundation.block.ProperDirectionalBlock;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.modules.contraptions.receivers.contraptions.IHaveMovementBehavior;
import net.minecraft.block.Block;
@ -15,6 +16,7 @@ import net.minecraft.state.StateContainer.Builder;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.TickPriority;
@ -100,19 +102,32 @@ public class ContactBlock extends ProperDirectionalBlock implements IHaveMovemen
}
@Override
public void visitPosition(MovementContext context) {
public Vec3d getActiveAreaOffset(MovementContext context) {
return new Vec3d(context.state.get(FACING).getDirectionVec()).scale(.65f);
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
BlockState block = context.state;
World world = context.world;
BlockPos pos = context.currentGridPos;
Direction direction = block.get(FACING);
if (!hasValidContact(world, pos, direction))
if (world.isRemote)
return;
int ticksToStayActive = (int) Math
.ceil(1 / Math.abs(context.motion.length()));
world.setBlockState(pos.offset(direction), world.getBlockState(pos.offset(direction)).with(POWERED, true));
world.getPendingBlockTicks().scheduleTick(pos.offset(direction), this, ticksToStayActive, TickPriority.NORMAL);
BlockState visitedState = world.getBlockState(pos);
if (!AllBlocks.CONTACT.typeOf(visitedState))
return;
Vec3d contact = new Vec3d(block.get(FACING).getDirectionVec());
contact = VecHelper.rotate(contact, context.rotation.x, context.rotation.y, context.rotation.z);
Direction direction = Direction.getFacingFromVector(contact.x, contact.y, contact.z);
if (!hasValidContact(world, pos.offset(direction.getOpposite()), direction))
return;
int ticksToStayActive = 4;
world.setBlockState(pos, visitedState.with(POWERED, true));
world.getPendingBlockTicks().scheduleTick(pos, this, ticksToStayActive, TickPriority.NORMAL);
return;
}