Pick me up

- Seats now pick up non-player entities when touching them while being moved
- Seats now drop off entities when being moved into a solid block
- Previously colliding entities now get moved to match their position on the placed structure on disassembly
- Contraption motion no longer gets fed into the collision response for non-minecart contraptions
- The server now gets frequent updates about client players riding a contraption, fixes instabilities with projectiles, damage and anti-fly detection
- Players can now take fall damage when colliding with contraptions
This commit is contained in:
simibubi 2020-07-22 18:56:48 +02:00
parent 9a04c51418
commit f48d1f7b1c
10 changed files with 221 additions and 102 deletions

View file

@ -116,6 +116,7 @@ public class AllShapes {
.build(),
CRUSHING_WHEEL_COLLISION_SHAPE = cuboid(0, 0, 0, 16, 22, 16),
SEAT = cuboid(0, 0, 0, 16, 8, 16),
SEAT_COLLISION = cuboid(0, 0, 0, 16, 6, 16),
MECHANICAL_PROCESSOR_SHAPE = shape(VoxelShapes.fullCube()).erase(4, 0, 4, 12, 16, 12)
.build(),
TURNTABLE_SHAPE = shape(1, 4, 1, 15, 8, 15).add(5, 0, 5, 11, 4, 11)

View file

@ -72,6 +72,12 @@ public class SeatBlock extends Block implements IPortableBlock {
ISelectionContext p_220053_4_) {
return AllShapes.SEAT;
}
@Override
public VoxelShape getCollisionShape(BlockState p_220071_1_, IBlockReader p_220071_2_, BlockPos p_220071_3_,
ISelectionContext p_220071_4_) {
return AllShapes.SEAT_COLLISION;
}
@Override
public ActionResultType onUse(BlockState p_225533_1_, World world, BlockPos pos, PlayerEntity player,

View file

@ -1,9 +1,84 @@
package com.simibubi.create.content.contraptions.components.actors;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementBehaviour;
import com.simibubi.create.content.contraptions.components.structureMovement.MovementContext;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.BlockState;
import net.minecraft.block.SlabBlock;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.state.properties.SlabType;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
public class SeatMovementBehaviour extends MovementBehaviour {
@Override
public void startMoving(MovementContext context) {
super.startMoving(context);
int indexOf = context.contraption.getSeats()
.indexOf(context.localPos);
context.data.putInt("SeatIndex", indexOf);
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
super.visitNewPosition(context, pos);
ContraptionEntity contraptionEntity = context.contraption.entity;
if (contraptionEntity == null)
return;
int index = context.data.getInt("SeatIndex");
if (index == -1)
return;
Map<UUID, Integer> seatMapping = context.contraption.getSeatMapping();
BlockState blockState = context.world.getBlockState(pos);
boolean slab = blockState.getBlock() instanceof SlabBlock && blockState.get(SlabBlock.TYPE) == SlabType.BOTTOM;
boolean solid = blockState.isSolid() || slab;
// Occupied
if (seatMapping.containsValue(index)) {
if (!solid)
return;
Entity toDismount = null;
for (Map.Entry<UUID, Integer> entry : seatMapping.entrySet()) {
if (entry.getValue() != index)
continue;
for (Entity entity : contraptionEntity.getPassengers()) {
if (!entry.getKey()
.equals(entity.getUniqueID()))
continue;
toDismount = entity;
}
}
if (toDismount != null) {
toDismount.stopRiding();
Vec3d position = VecHelper.getCenterOf(pos)
.add(0, slab ? .5f : 1f, 0);
toDismount.setPositionAndUpdate(position.x, position.y, position.z);
toDismount.getPersistentData()
.remove("ContraptionDismountLocation");
}
return;
}
if (solid)
return;
List<LivingEntity> entitiesWithinAABB = context.world.getEntitiesWithinAABB(LivingEntity.class,
new AxisAlignedBB(pos).shrink(1 / 16f), e -> !(e instanceof PlayerEntity));
if (entitiesWithinAABB.isEmpty())
return;
LivingEntity passenger = entitiesWithinAABB.get(0);
contraptionEntity.addSittingPassenger(passenger, index);
}
}

View file

@ -0,0 +1,55 @@
package com.simibubi.create.content.contraptions.components.structureMovement;
import java.util.function.Supplier;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.fml.network.NetworkEvent.Context;
public class ClientMotionPacket extends SimplePacketBase {
private Vec3d motion;
private boolean onGround;
public ClientMotionPacket(Vec3d motion, boolean onGround) {
this.motion = motion;
this.onGround = onGround;
}
public ClientMotionPacket(PacketBuffer buffer) {
motion = new Vec3d(buffer.readFloat(), buffer.readFloat(), buffer.readFloat());
onGround = buffer.readBoolean();
}
@Override
public void write(PacketBuffer buffer) {
buffer.writeFloat((float) motion.x);
buffer.writeFloat((float) motion.y);
buffer.writeFloat((float) motion.z);
buffer.writeBoolean(onGround);
}
@Override
public void handle(Supplier<Context> context) {
context.get()
.enqueueWork(() -> {
ServerPlayerEntity sender = context.get()
.getSender();
if (sender == null)
return;
sender.setMotion(motion);
sender.onGround = onGround;
if (onGround) {
sender.handleFallDamage(sender.fallDistance, 1);
sender.fallDistance = 0;
sender.connection.floatingTickCount = 0;
}
});
context.get()
.setPacketHandled(true);
}
}

View file

@ -89,6 +89,7 @@ public abstract class Contraption {
public CombinedInvWrapper inventory;
public List<TileEntity> customRenderTEs;
public Set<Pair<BlockPos, Direction>> superglue;
public ContraptionEntity entity;
public AxisAlignedBB bounds;
public boolean stalled;
@ -203,13 +204,14 @@ public abstract class Contraption {
}
public void mountPassengers(ContraptionEntity contraptionEntity) {
this.entity = contraptionEntity;
if (contraptionEntity.world.isRemote)
return;
for (BlockPos seatPos : seats) {
for (BlockPos seatPos : getSeats()) {
Entity passenger = initialPassengers.get(seatPos);
if (passenger == null)
continue;
int seatIndex = seats.indexOf(seatPos);
int seatIndex = getSeats().indexOf(seatPos);
if (seatIndex == -1)
continue;
contraptionEntity.addSittingPassenger(passenger, seatIndex);
@ -252,7 +254,7 @@ public abstract class Contraption {
// Seats transfer their passenger to the contraption
if (state.getBlock() instanceof SeatBlock) {
BlockPos local = toLocalPos(pos);
seats.add(local);
getSeats().add(local);
List<SeatEntity> seatsEntities = world.getEntitiesWithinAABB(SeatEntity.class, new AxisAlignedBB(pos));
if (!seatsEntities.isEmpty()) {
SeatEntity seat = seatsEntities.get(0);
@ -516,11 +518,12 @@ public abstract class Contraption {
if (nbt.contains("BoundsFront"))
bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", 5));
seats.clear();
NBTHelper.iterateCompoundList(nbt.getList("Seats", NBT.TAG_COMPOUND), c -> seats.add(NBTUtil.readBlockPos(c)));
seatMapping.clear();
getSeats().clear();
NBTHelper.iterateCompoundList(nbt.getList("Seats", NBT.TAG_COMPOUND),
c -> getSeats().add(NBTUtil.readBlockPos(c)));
getSeatMapping().clear();
NBTHelper.iterateCompoundList(nbt.getList("Passengers", NBT.TAG_COMPOUND),
c -> seatMapping.put(NBTUtil.readUniqueId(c.getCompound("Id")), c.getInt("Seat")));
c -> getSeatMapping().put(NBTUtil.readUniqueId(c.getCompound("Id")), c.getInt("Seat")));
stalled = nbt.getBoolean("Stalled");
anchor = NBTUtil.readBlockPos(nbt.getCompound("Anchor"));
@ -568,8 +571,8 @@ public abstract class Contraption {
storageNBT.add(c);
}
nbt.put("Seats", NBTHelper.writeCompoundList(seats, NBTUtil::writeBlockPos));
nbt.put("Passengers", NBTHelper.writeCompoundList(seatMapping.entrySet(), e -> {
nbt.put("Seats", NBTHelper.writeCompoundList(getSeats(), NBTUtil::writeBlockPos));
nbt.put("Passengers", NBTHelper.writeCompoundList(getSeatMapping().entrySet(), e -> {
CompoundNBT tag = new CompoundNBT();
tag.put("Id", NBTUtil.writeUniqueId(e.getKey()));
tag.putInt("Seat", e.getValue());
@ -635,10 +638,8 @@ public abstract class Contraption {
}
}
public void addBlocksToWorld(World world, BlockPos offset, Vec3d rotation, List<Entity> seatedEntities) {
public void addBlocksToWorld(World world, StructureTransform transform) {
stop(world);
StructureTransform transform = new StructureTransform(offset, rotation);
for (boolean nonBrittles : Iterate.trueAndFalse) {
for (BlockInfo block : blocks.values()) {
if (nonBrittles == BlockMovementTraits.isBrittle(block.state))
@ -718,12 +719,14 @@ public abstract class Contraption {
world.addEntity(entity);
}
}
}
public void addPassengersToWorld(World world, StructureTransform transform, List<Entity> seatedEntities) {
for (Entity seatedEntity : seatedEntities) {
if (seatMapping.isEmpty())
if (getSeatMapping().isEmpty())
continue;
Integer seatIndex = seatMapping.get(seatedEntity.getUniqueID());
BlockPos seatPos = seats.get(seatIndex);
Integer seatIndex = getSeatMapping().get(seatedEntity.getUniqueID());
BlockPos seatPos = getSeats().get(seatIndex);
seatPos = transform.apply(seatPos);
if (!(world.getBlockState(seatPos)
.getBlock() instanceof SeatBlock))
@ -732,7 +735,6 @@ public abstract class Contraption {
continue;
SeatBlock.sitDown(world, seatPos, seatedEntity);
}
}
public void initActors(World world) {
@ -796,14 +798,22 @@ public abstract class Contraption {
}
public BlockPos getSeat(UUID entityId) {
if (!seatMapping.containsKey(entityId))
if (!getSeatMapping().containsKey(entityId))
return null;
int seatIndex = seatMapping.get(entityId);
if (seatIndex >= seats.size())
int seatIndex = getSeatMapping().get(entityId);
if (seatIndex >= getSeats().size())
return null;
return seats.get(seatIndex);
return getSeats().get(seatIndex);
}
protected abstract AllContraptionTypes getType();
public Map<UUID, Integer> getSeatMapping() {
return seatMapping;
}
public List<BlockPos> getSeats() {
return seats;
}
}

View file

@ -23,6 +23,7 @@ import com.simibubi.create.content.contraptions.components.actors.BlockBreakingM
import com.simibubi.create.foundation.collision.ContinuousOBBCollider.ContinuousSeparationManifold;
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.AngleHelper;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.VecHelper;
@ -143,6 +144,8 @@ public class ContraptionCollider {
AxisAlignedBB bounds = contraptionEntity.getBoundingBox();
Vec3d contraptionPosition = contraptionEntity.getPositionVec();
Vec3d contraptionRotation = contraptionEntity.getRotationVec();
Vec3d contraptionMotion = contraptionEntity.stationary ? Vec3d.ZERO
: contraptionPosition.subtract(contraptionEntity.getPrevPositionVec());
contraptionEntity.collidingEntities.clear();
if (contraption == null)
@ -154,14 +157,14 @@ public class ContraptionCollider {
double conRotX = contraptionRotation.x;
double conRotY = contraptionRotation.y;
double conRotZ = contraptionRotation.z;
Vec3d conMotion = contraptionPosition.subtract(contraptionEntity.getPrevPositionVec());
Vec3d contraptionCentreOffset = contraptionEntity.stationary ? centerOfBlock : Vec3d.ZERO.add(0, 0.5, 0);
boolean axisAlignedCollision = contraptionRotation.equals(Vec3d.ZERO);
Matrix3d rotation = null;
for (Entity entity : world.getEntitiesWithinAABB((EntityType<?>) null, bounds.grow(2)
.expand(0, 32, 0), contraptionEntity::canCollideWith)) {
boolean serverPlayer = entity instanceof PlayerEntity && !world.isRemote;
boolean player = entity instanceof PlayerEntity;
boolean serverPlayer = player && !world.isRemote;
// Init matrix
if (rotation == null) {
@ -199,8 +202,8 @@ public class ContraptionCollider {
// Prepare entity bounds
OrientedBB obb = new OrientedBB(localBB);
obb.setRotation(rotation);
motion = motion.subtract(conMotion);
motion = rotation.transform(motion);
motion = motion.subtract(contraptionMotion);
// Vec3d visualizerOrigin = new Vec3d(10, 64, 0);
// CollisionDebugger.OBB = obb.copy();
@ -268,7 +271,7 @@ public class ContraptionCollider {
rotation.transpose();
motionResponse = rotation.transform(motionResponse)
.add(conMotion);
.add(contraptionMotion);
totalResponse = rotation.transform(totalResponse);
rotation.transpose();
@ -282,10 +285,10 @@ public class ContraptionCollider {
Vec3d contactPointMotion = Vec3d.ZERO;
if (surfaceCollision.isTrue()) {
// entity.handleFallDamage(entity.fallDistance, 1); tunnelling issue
entity.fallDistance = 0;
entity.onGround = true;
if (!serverPlayer)
contraptionEntity.collidingEntities.add(entity);
if (!serverPlayer)
contactPointMotion = contraptionEntity.getContactPointMotion(entityPosition);
}
@ -321,6 +324,9 @@ public class ContraptionCollider {
entity.setPosition(entityPosition.x + allowedMovement.x, entityPosition.y + allowedMovement.y,
entityPosition.z + allowedMovement.z);
entity.setMotion(entityMotion);
if (!serverPlayer && player)
AllPackets.channel.sendToServer(new ClientMotionPacket(entityMotion, true));
}
}

View file

@ -7,11 +7,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.MutablePair;
import com.google.common.collect.ImmutableSet;
import com.simibubi.create.AllEntityTypes;
import com.simibubi.create.content.contraptions.components.actors.SeatEntity;
import com.simibubi.create.content.contraptions.components.structureMovement.bearing.BearingContraption;
@ -49,15 +47,10 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.ReuseableStream;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.template.Template.BlockInfo;
import net.minecraftforge.api.distmarker.Dist;
@ -166,9 +159,10 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
passenger.startRiding(this, true);
if (world.isRemote)
return;
contraption.seatMapping.put(passenger.getUniqueID(), seatIndex);
contraption.getSeatMapping()
.put(passenger.getUniqueID(), seatIndex);
AllPackets.channel.send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionSeatMappingPacket(getEntityId(), contraption.seatMapping));
new ContraptionSeatMappingPacket(getEntityId(), contraption.getSeatMapping()));
}
@Override
@ -185,9 +179,10 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
if (transformedVector != null)
passenger.getPersistentData()
.put("ContraptionDismountLocation", VecHelper.writeNBT(transformedVector));
contraption.seatMapping.remove(passenger.getUniqueID());
contraption.getSeatMapping()
.remove(passenger.getUniqueID());
AllPackets.channel.send(PacketDistributor.TRACKING_ENTITY.with(() -> this),
new ContraptionSeatMappingPacket(getEntityId(), contraption.seatMapping));
new ContraptionSeatMappingPacket(getEntityId(), contraption.getSeatMapping()));
}
@Override
@ -199,7 +194,7 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
return;
callback.accept(passenger, transformedVector.x, transformedVector.y, transformedVector.z);
}
protected Vec3d getPassengerPosition(Entity passenger) {
AxisAlignedBB bb = passenger.getBoundingBox();
double ySize = bb.getYSize();
@ -214,18 +209,21 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
@Override
protected boolean canFitPassenger(Entity p_184219_1_) {
return getPassengers().size() < contraption.seats.size();
return getPassengers().size() < contraption.getSeats()
.size();
}
public boolean handlePlayerInteraction(PlayerEntity player, BlockPos localPos, Direction side,
Hand interactionHand) {
int indexOfSeat = contraption.seats.indexOf(localPos);
int indexOfSeat = contraption.getSeats()
.indexOf(localPos);
if (indexOfSeat == -1)
return false;
// Eject potential existing passenger
Entity toDismount = null;
for (Entry<UUID, Integer> entry : contraption.seatMapping.entrySet()) {
for (Entry<UUID, Integer> entry : contraption.getSeatMapping()
.entrySet()) {
if (entry.getValue() != indexOfSeat)
continue;
for (Entity entity : getPassengers()) {
@ -631,9 +629,20 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
remove();
BlockPos offset = new BlockPos(getAnchorVec().add(.5, .5, .5));
Vec3d rotation = getRotationVec();
contraption.addBlocksToWorld(world, offset, rotation, getPassengers());
StructureTransform transform = new StructureTransform(offset, rotation);
contraption.addBlocksToWorld(world, transform);
contraption.addPassengersToWorld(world, transform, getPassengers());
removePassengers();
// preventMovedEntitiesFromGettingStuck();
for (Entity entity : collidingEntities) {
Vec3d positionVec = getPositionVec();
Vec3d localVec = entity.getPositionVec()
.subtract(positionVec);
localVec = VecHelper.rotate(localVec, getRotationVec().scale(-1));
Vec3d transformed = transform.apply(localVec);
entity.setPositionAndUpdate(transformed.x, transformed.y, transformed.z);
}
}
}
@ -660,73 +669,18 @@ public class ContraptionEntity extends Entity implements IEntityAdditionalSpawnD
@Override
protected void doWaterSplashEffect() {}
public void preventMovedEntitiesFromGettingStuck() {
Vec3d stuckTest = new Vec3d(0, -2, 0);
for (Entity e : collidingEntities) {
e.fallDistance = 0;
e.onGround = true;
Vec3d vec = stuckTest;
AxisAlignedBB axisalignedbb = e.getBoundingBox()
.offset(0, 2, 0);
ISelectionContext iselectioncontext = ISelectionContext.forEntity(this);
VoxelShape voxelshape = e.world.getWorldBorder()
.getShape();
Stream<VoxelShape> stream =
VoxelShapes.compare(voxelshape, VoxelShapes.create(axisalignedbb.shrink(1.0E-7D)), IBooleanFunction.AND)
? Stream.empty()
: Stream.of(voxelshape);
Stream<VoxelShape> stream1 =
this.world.getEmptyCollisionShapes(e, axisalignedbb.expand(vec), ImmutableSet.of());
ReuseableStream<VoxelShape> reuseablestream = new ReuseableStream<>(Stream.concat(stream1, stream));
Vec3d vec3d = vec.lengthSquared() == 0.0D ? vec
: collideBoundingBoxHeuristically(this, vec, axisalignedbb, e.world, iselectioncontext,
reuseablestream);
boolean flag = vec.x != vec3d.x;
boolean flag1 = vec.y != vec3d.y;
boolean flag2 = vec.z != vec3d.z;
boolean flag3 = e.onGround || flag1 && vec.y < 0.0D;
if (this.stepHeight > 0.0F && flag3 && (flag || flag2)) {
Vec3d vec3d1 = collideBoundingBoxHeuristically(e, new Vec3d(vec.x, (double) e.stepHeight, vec.z),
axisalignedbb, e.world, iselectioncontext, reuseablestream);
Vec3d vec3d2 = collideBoundingBoxHeuristically(e, new Vec3d(0.0D, (double) e.stepHeight, 0.0D),
axisalignedbb.expand(vec.x, 0.0D, vec.z), e.world, iselectioncontext, reuseablestream);
if (vec3d2.y < (double) e.stepHeight) {
Vec3d vec3d3 = collideBoundingBoxHeuristically(e, new Vec3d(vec.x, 0.0D, vec.z),
axisalignedbb.offset(vec3d2), e.world, iselectioncontext, reuseablestream).add(vec3d2);
if (horizontalMag(vec3d3) > horizontalMag(vec3d1)) {
vec3d1 = vec3d3;
}
}
if (horizontalMag(vec3d1) > horizontalMag(vec3d)) {
vec3d = vec3d1.add(collideBoundingBoxHeuristically(e, new Vec3d(0.0D, -vec3d1.y + vec.y, 0.0D),
axisalignedbb.offset(vec3d1), e.world, iselectioncontext, reuseablestream));
}
}
vec = vec3d.subtract(stuckTest);
if (vec.equals(Vec3d.ZERO))
continue;
e.setPosition(e.getX() + vec.x, e.getY() + vec.y, e.getZ() + vec.z);
}
}
@SuppressWarnings("deprecation")
@Override
public CompoundNBT writeWithoutTypeId(CompoundNBT nbt) {
Vec3d vec = getPositionVec();
List<Entity> passengers = getPassengers();
List<Vec3d> positionVecs = new ArrayList<>(passengers.size());
for (Entity entity : passengers) {
Vec3d prevVec = entity.getPositionVec();
positionVecs.add(prevVec);
// setPos has world accessing side-effects when removed == false
entity.removed = true;
// Gather passengers into same chunk when saving
Vec3d prevVec = entity.getPositionVec();
entity.setPos(vec.x, prevVec.y, vec.z);
// Super requires all passengers to not be removed in order to write them to the

View file

@ -4,6 +4,7 @@ import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
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;
@ -17,6 +18,7 @@ public class MovementContext {
public Vec3d rotation;
public World world;
public BlockState state;
public BlockPos localPos;
public CompoundNBT tileData;
public boolean stall;
@ -29,7 +31,8 @@ public class MovementContext {
this.world = world;
this.state = info.state;
this.tileData = info.nbt;
localPos = info.pos;
firstMovement = true;
motion = Vec3d.ZERO;
relativeMotion = Vec3d.ZERO;

View file

@ -69,6 +69,13 @@ public class StructureTransform {
}
public Vec3d apply(Vec3d localVec) {
Vec3d vec = localVec;
vec = VecHelper.rotateCentered(vec, angle, rotationAxis);
vec = vec.add(new Vec3d(offset));
return vec;
}
public BlockPos apply(BlockPos localPos) {
Vec3d vec = VecHelper.getCenterOf(localPos);
vec = VecHelper.rotateCentered(vec, angle, rotationAxis);

View file

@ -5,6 +5,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.components.structureMovement.ClientMotionPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionInteractionPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionSeatMappingPacket;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionStallPacket;
@ -46,6 +47,7 @@ public enum AllPackets {
CONFIGURE_SCROLLABLE(ScrollValueUpdatePacket.class, ScrollValueUpdatePacket::new),
EXTENDO_INTERACT(ExtendoGripInteractionPacket.class, ExtendoGripInteractionPacket::new),
CONTRAPTION_INTERACT(ContraptionInteractionPacket.class, ContraptionInteractionPacket::new),
CLIENT_MOTION(ClientMotionPacket.class, ClientMotionPacket::new),
PLACE_ARM(ArmPlacementPacket.class, ArmPlacementPacket::new),
// Server to Client