Mechanical Arm Implementation

- The arm blockitem can now be used to select inputs & outputs
- Arms now transfer items between inputs and outputs
- Arm support for Belts, Depots and Funnels
- Some safety checks in net code
- Minor refactor to NBTHelper
This commit is contained in:
simibubi 2020-07-01 22:02:00 +02:00
parent 5411bc3565
commit f820e2be27
44 changed files with 1104 additions and 129 deletions

View file

@ -92,6 +92,11 @@ public class AllBlockPartials {
ARM_CLAW_BASE = get("mechanical_arm/claw_base"),
ARM_CLAW_GRIP = get("mechanical_arm/claw_grip"),
FLAG_SHORT_IN = get("mechanical_arm/flag/short_in"),
FLAG_SHORT_OUT = get("mechanical_arm/flag/short_out"),
FLAG_LONG_IN = get("mechanical_arm/flag/long_in"),
FLAG_LONG_OUT = get("mechanical_arm/flag/long_out"),
MECHANICAL_PUMP_ARROW = get("mechanical_pump/arrow"),
MECHANICAL_PUMP_COG = get("mechanical_pump/cog"),
FLUID_PIPE_CASING = get("fluid_pipe/casing");

View file

@ -96,6 +96,7 @@ import com.simibubi.create.content.logistics.block.funnel.VerticalFunnelGenerato
import com.simibubi.create.content.logistics.block.inventories.AdjustableCrateBlock;
import com.simibubi.create.content.logistics.block.inventories.CreativeCrateBlock;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmBlock;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmItem;
import com.simibubi.create.content.logistics.block.packager.PackagerBlock;
import com.simibubi.create.content.logistics.block.realityFunnel.BeltFunnelBlock;
import com.simibubi.create.content.logistics.block.realityFunnel.BeltFunnelGenerator;
@ -781,7 +782,7 @@ public class AllBlocks {
.initialProperties(SharedProperties::softMetal)
.blockstate((c, p) -> p.simpleBlock(c.getEntry(), AssetLookup.partialBaseModel(c, p)))
.transform(StressConfigDefaults.setImpact(8.0))
.item()
.item(ArmItem::new)
.transform(customItemModel())
.register();

View file

@ -109,7 +109,6 @@ public class ClientEvents {
return;
double delta = event.getScrollDelta();
boolean cancelled = CreateClient.schematicHandler.mouseScrolled(delta)
|| CreateClient.schematicAndQuillHandler.mouseScrolled(delta) || FilteringHandler.onScroll(delta)
|| ScrollValueHandler.onScroll(delta);

View file

@ -14,6 +14,7 @@ import com.simibubi.create.content.curiosities.tools.ExtendoGripRenderHandler;
import com.simibubi.create.content.curiosities.zapper.ZapperRenderHandler;
import com.simibubi.create.content.curiosities.zapper.blockzapper.BlockzapperRenderHandler;
import com.simibubi.create.content.curiosities.zapper.terrainzapper.WorldshaperRenderHandler;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmInteractionPointHandler;
import com.simibubi.create.content.schematics.ClientSchematicLoader;
import com.simibubi.create.content.schematics.client.SchematicAndQuillHandler;
import com.simibubi.create.content.schematics.client.SchematicHandler;
@ -109,6 +110,7 @@ public class CreateClient {
KineticDebugger.tick();
ZapperRenderHandler.tick();
ExtendoGripRenderHandler.tick();
ArmInteractionPointHandler.tick();
outliner.tickOutlines();
}

View file

@ -76,7 +76,7 @@ public class CuckooClockRenderer extends KineticTileEntityRenderer {
.renderInto(ms, vb);
// Figure
if (clock.animationType != null) {
if (clock.animationType != Animation.NONE) {
offset = -(angle / 135) * 1 / 2f + 10 / 16f;
SuperByteBuffer figure =
(clock.animationType == Animation.PIG ? AllBlockPartials.CUCKOO_PIG : AllBlockPartials.CUCKOO_CREEPER)

View file

@ -32,17 +32,18 @@ public class CuckooClockTileEntity extends KineticTileEntity {
private boolean sendAnimationUpdate;
enum Animation {
PIG, CREEPER, SURPRISE;
PIG, CREEPER, SURPRISE, NONE;
}
public CuckooClockTileEntity(TileEntityType<? extends CuckooClockTileEntity> type) {
super(type);
animationType = Animation.NONE;
}
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
if (sendAnimationUpdate)
compound.putString("Animation", animationType == null ? "none" : NBTHelper.writeEnum(animationType));
NBTHelper.writeEnum(compound, "Animation", animationType);
sendAnimationUpdate = false;
return super.writeToClient(compound);
}
@ -50,11 +51,7 @@ public class CuckooClockTileEntity extends KineticTileEntity {
@Override
public void readClientUpdate(CompoundNBT tag) {
if (tag.contains("Animation")) {
String string = tag.getString("Animation");
if ("none".equals(string))
animationType = null;
else
animationType = NBTHelper.readEnum(string, Animation.class);
animationType = NBTHelper.readEnum(tag, "Animation", Animation.class);
animationProgress.lastValue = 0;
animationProgress.value = 0;
}
@ -72,7 +69,7 @@ public class CuckooClockTileEntity extends KineticTileEntity {
int minutes = (dayTime % 1000) * 60 / 1000;
if (!world.isRemote) {
if (animationType == null) {
if (animationType == Animation.NONE) {
if (hours == 12 && minutes < 5)
startAnimation(Animation.PIG);
if (hours == 18 && minutes < 36 && minutes > 31)
@ -81,7 +78,7 @@ public class CuckooClockTileEntity extends KineticTileEntity {
float value = animationProgress.value;
animationProgress.set(value + 1);
if (value > 100)
animationType = null;
animationType = Animation.NONE;
if (animationType == Animation.SURPRISE && animationProgress.value == 50) {
Vec3d center = VecHelper.getCenterOf(pos);
@ -96,7 +93,7 @@ public class CuckooClockTileEntity extends KineticTileEntity {
if (world.isRemote) {
moveHands(hours, minutes);
if (animationType == null) {
if (animationType == Animation.NONE) {
if (AnimationTickHolder.ticks % 32 == 0)
playSound(SoundEvents.BLOCK_NOTE_BLOCK_HAT, 1 / 16f, 2f);
else if (AnimationTickHolder.ticks % 16 == 0)

View file

@ -162,7 +162,7 @@ public class DeployerMovementBehaviour extends MovementBehaviour {
}
private Mode getMode(MovementContext context) {
return NBTHelper.readEnum(context.tileData.getString("Mode"), Mode.class);
return NBTHelper.readEnum(context.tileData, "Mode", Mode.class);
}
@Override

View file

@ -160,7 +160,7 @@ public class DeployerRenderer extends SafeTileEntityRenderer<DeployerTileEntity>
IVertexBuilder builder = buffer.getBuffer(RenderType.getSolid());
BlockState blockState = context.state;
BlockPos pos = BlockPos.ZERO;
Mode mode = NBTHelper.readEnum(context.tileData.getString("Mode"), Mode.class);
Mode mode = NBTHelper.readEnum(context.tileData, "Mode", Mode.class);
World world = context.world;
AllBlockPartials handPose =
mode == Mode.PUNCH ? AllBlockPartials.DEPLOYER_HAND_PUNCHING : AllBlockPartials.DEPLOYER_HAND_POINTING;

View file

@ -333,8 +333,8 @@ public class DeployerTileEntity extends KineticTileEntity {
@Override
public void read(CompoundNBT compound) {
state = NBTHelper.readEnum(compound.getString("State"), State.class);
mode = NBTHelper.readEnum(compound.getString("Mode"), Mode.class);
state = NBTHelper.readEnum(compound, "State", State.class);
mode = NBTHelper.readEnum(compound, "Mode", Mode.class);
timer = compound.getInt("Timer");
deferredInventoryList = compound.getList("Inventory", NBT.TAG_COMPOUND);
overflowItems = NBTHelper.readItemList(compound.getList("Overflow", NBT.TAG_COMPOUND));
@ -345,8 +345,8 @@ public class DeployerTileEntity extends KineticTileEntity {
@Override
public CompoundNBT write(CompoundNBT compound) {
compound.putString("Mode", NBTHelper.writeEnum(mode));
compound.putString("State", NBTHelper.writeEnum(state));
NBTHelper.writeEnum(compound, "Mode", mode);
NBTHelper.writeEnum(compound, "State", state);
compound.putInt("Timer", timer);
if (player != null) {
compound.put("HeldItem", player.getHeldItemMainhand().serializeNBT());

View file

@ -24,6 +24,8 @@ public class CancelPlayerFallPacket extends SimplePacketBase {
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity sender = context.get().getSender();
if (sender == null)
return;
sender.handleFallDamage(sender.fallDistance, 1.0F);
sender.fallDistance = 0;
sender.onGround = true;

View file

@ -98,14 +98,14 @@ public class ClockworkContraption extends Contraption {
CompoundNBT tag = super.writeNBT();
tag.putInt("facing", facing.getIndex());
tag.putInt("offset", offset);
tag.putString("HandType", NBTHelper.writeEnum(handType));
NBTHelper.writeEnum(tag, "HandType", handType);
return tag;
}
@Override
public void readNBT(World world, CompoundNBT tag) {
facing = Direction.byIndex(tag.getInt("Facing"));
handType = NBTHelper.readEnum(tag.getString("HandType"), HandType.class);
handType = NBTHelper.readEnum(tag, "HandType", HandType.class);
offset = tag.getInt("offset");
super.readNBT(world, tag);
}

View file

@ -80,13 +80,13 @@ public class MountedContraption extends Contraption {
@Override
public CompoundNBT writeNBT() {
CompoundNBT writeNBT = super.writeNBT();
writeNBT.putString("RotationMode", NBTHelper.writeEnum(rotationMode));
NBTHelper.writeEnum(writeNBT, "RotationMode", rotationMode);
return writeNBT;
}
@Override
public void readNBT(World world, CompoundNBT nbt) {
rotationMode = NBTHelper.readEnum(nbt.getString("RotationMode"), CartMovementMode.class);
rotationMode = NBTHelper.readEnum(nbt, "RotationMode", CartMovementMode.class);
super.readNBT(world, nbt);
}

View file

@ -88,16 +88,16 @@ public class Instruction {
CompoundNBT serialize() {
CompoundNBT tag = new CompoundNBT();
tag.putString("Type", NBTHelper.writeEnum(instruction));
tag.putString("Modifier", NBTHelper.writeEnum(speedModifier));
NBTHelper.writeEnum(tag, "Type", instruction);
NBTHelper.writeEnum(tag, "Modifier", speedModifier);
tag.putInt("Value", value);
return tag;
}
static Instruction deserialize(CompoundNBT tag) {
Instruction instruction =
new Instruction(NBTHelper.readEnum(tag.getString("Type"), SequencerInstructions.class));
instruction.speedModifier = NBTHelper.readEnum(tag.getString("Modifier"), InstructionSpeedModifiers.class);
new Instruction(NBTHelper.readEnum(tag, "Type", SequencerInstructions.class));
instruction.speedModifier = NBTHelper.readEnum(tag, "Modifier", InstructionSpeedModifiers.class);
instruction.value = tag.getInt("Value");
return instruction;
}

View file

@ -352,8 +352,8 @@ public class BeltInventory {
}
public TransportedItemStack getStackAtOffset(int offset) {
float min = offset + .5f - (SEGMENT_WINDOW / 2);
float max = offset + .5f + (SEGMENT_WINDOW / 2);
float min = offset;
float max = offset + 1;
for (TransportedItemStack stack : items) {
if (stack.beltPosition > max)
continue;

View file

@ -57,6 +57,8 @@ public class ExtendoGripInteractionPacket extends SimplePacketBase {
.enqueueWork(() -> {
ServerPlayerEntity sender = context.get()
.getSender();
if (sender == null)
return;
Entity entityByID = sender.getServerWorld()
.getEntityByID(target);
if (entityByID != null && ExtendoGripItem.isHoldingExtendoGrip(sender)) {

View file

@ -351,17 +351,16 @@ public class BlockzapperItem extends ZapperItem {
.contains(component.name()))
stack.getOrCreateTag()
.putString(component.name(), ComponentTier.None.name());
return NBTHelper.readEnum(stack.getTag()
.getString(component.name()), ComponentTier.class);
return NBTHelper.readEnum(stack.getTag(), component.name(), ComponentTier.class);
}
public static void setTier(Components component, ComponentTier tier, ItemStack stack) {
stack.getOrCreateTag()
.putString(component.name(), NBTHelper.writeEnum(tier));
NBTHelper.writeEnum(stack.getOrCreateTag(), component.name(), tier);
}
public static enum ComponentTier {
None(TextFormatting.DARK_GRAY), Brass(TextFormatting.GOLD), Chromatic(TextFormatting.LIGHT_PURPLE);
public TextFormatting color;
private ComponentTier(TextFormatting color) {

View file

@ -53,7 +53,7 @@ public class WorldshaperItem extends ZapperItem {
@Override
protected boolean canActivateWithoutSelectedBlock(ItemStack stack) {
CompoundNBT tag = stack.getOrCreateTag();
TerrainTools tool = NBTHelper.readEnum(tag.getString("Tool"), TerrainTools.class);
TerrainTools tool = NBTHelper.readEnum(tag, "Tool", TerrainTools.class);
return !tool.requiresSelectedBlock();
}
@ -65,11 +65,11 @@ public class WorldshaperItem extends ZapperItem {
List<BlockPos> affectedPositions = new ArrayList<>();
CompoundNBT tag = stack.getOrCreateTag();
Brush brush = NBTHelper.readEnum(tag.getString("Brush"), TerrainBrushes.class)
Brush brush = NBTHelper.readEnum(tag, "Brush", TerrainBrushes.class)
.get();
BlockPos params = NBTUtil.readBlockPos(tag.getCompound("BrushParams"));
PlacementOptions option = NBTHelper.readEnum(tag.getString("Placement"), PlacementOptions.class);
TerrainTools tool = NBTHelper.readEnum(tag.getString("Tool"), TerrainTools.class);
PlacementOptions option = NBTHelper.readEnum(tag, "Placement", PlacementOptions.class);
TerrainTools tool = NBTHelper.readEnum(tag, "Tool", TerrainTools.class);
brush.set(params.getX(), params.getY(), params.getZ());
targetPos = targetPos.add(brush.getOffset(player.getLookVec(), raytrace.getFace(), option));

View file

@ -69,9 +69,9 @@ public class WorldshaperRenderHandler {
return;
}
Brush brush = NBTHelper.readEnum(tag.getString("Brush"), TerrainBrushes.class)
Brush brush = NBTHelper.readEnum(tag, "Brush", TerrainBrushes.class)
.get();
PlacementOptions placement = NBTHelper.readEnum(tag.getString("Placement"), PlacementOptions.class);
PlacementOptions placement = NBTHelper.readEnum(tag, "Placement", PlacementOptions.class);
BlockPos params = NBTUtil.readBlockPos(tag.getCompound("BrushParams"));
brush.set(params.getX(), params.getY(), params.getZ());
renderedShape = brush.getIncludedPositions();

View file

@ -53,9 +53,12 @@ public class WorldshaperScreen extends ZapperScreen {
brushLabel = new Label(i + 58, j + 28, "").withShadow();
brushInput = new SelectionScrollInput(i + 55, j + 25, 78, 14).forOptions(brushOptions)
.titled(Lang.translate("gui.terrainzapper.brush")).writingTo(brushLabel).calling(this::brushChanged);
.titled(Lang.translate("gui.terrainzapper.brush"))
.writingTo(brushLabel)
.calling(this::brushChanged);
if (nbt.contains("Brush"))
brushInput.setState(NBTHelper.readEnum(nbt.getString("Brush"), TerrainBrushes.class).ordinal());
brushInput.setState(NBTHelper.readEnum(nbt, "Brush", TerrainBrushes.class)
.ordinal());
widgets.add(brushLabel);
widgets.add(brushInput);
@ -66,11 +69,13 @@ public class WorldshaperScreen extends ZapperScreen {
for (int id = 0; id < toolValues.length; id++) {
TerrainTools tool = toolValues[id];
toolButtons.add(new IconButton(i + 8 + id * 18, j + 76, tool.icon));
toolButtons.get(id).setToolTip(Lang.translate("gui.terrainzapper.tool." + tool.translationKey));
toolButtons.get(id)
.setToolTip(Lang.translate("gui.terrainzapper.tool." + tool.translationKey));
}
if (nbt.contains("Tool"))
toolButtons.get(NBTHelper.readEnum(nbt.getString("Tool"), TerrainTools.class).ordinal()).active = false;
toolButtons.get(NBTHelper.readEnum(nbt, "Tool", TerrainTools.class)
.ordinal()).active = false;
widgets.addAll(toolButtons);
placementButtons = new Vector<>(3);
@ -78,21 +83,25 @@ public class WorldshaperScreen extends ZapperScreen {
for (int id = 0; id < placementValues.length; id++) {
PlacementOptions option = placementValues[id];
placementButtons.add(new IconButton(i + 147 + id * 18, j + 76, option.icon));
placementButtons.get(id).setToolTip(Lang.translate("gui.terrainzapper.placement." + option.translationKey));
placementButtons.get(id)
.setToolTip(Lang.translate("gui.terrainzapper.placement." + option.translationKey));
}
if (nbt.contains("Placement"))
placementButtons
.get(NBTHelper.readEnum(nbt.getString("Placement"), PlacementOptions.class).ordinal()).active =
false;
placementButtons.get(NBTHelper.readEnum(nbt, "Placement", PlacementOptions.class)
.ordinal()).active = false;
widgets.addAll(placementButtons);
}
public void initBrushParams() {
if (brushParams != null) {
nbt.put("BrushParams", NBTUtil.writeBlockPos(new BlockPos(brushParams.get(0).getState(),
brushParams.get(1).getState(), brushParams.get(2).getState())));
nbt.put("BrushParams", NBTUtil.writeBlockPos(new BlockPos(brushParams.get(0)
.getState(),
brushParams.get(1)
.getState(),
brushParams.get(2)
.getState())));
widgets.removeAll(brushParamLabels);
widgets.removeAll(brushParams);
@ -109,8 +118,10 @@ public class WorldshaperScreen extends ZapperScreen {
brushParamLabels.add(label);
int indexFinal = index;
ScrollInput input = new ScrollInput(i + 55 + 18 * index, j + 43, 14, 14)
.withRange(currentBrush.getMin(index), currentBrush.getMax(index) + 1).writingTo(label)
.titled(currentBrush.getParamLabel(index)).calling(state -> {
.withRange(currentBrush.getMin(index), currentBrush.getMax(index) + 1)
.writingTo(label)
.titled(currentBrush.getParamLabel(index))
.calling(state -> {
label.x = i + 62 + 18 * indexFinal - font.getStringWidth(label.text) / 2;
});
input.setState(params[index]);
@ -140,7 +151,8 @@ public class WorldshaperScreen extends ZapperScreen {
if (placementButton.isHovered()) {
placementButtons.forEach(b -> b.active = true);
placementButton.active = false;
placementButton.playDownSound(Minecraft.getInstance().getSoundHandler());
placementButton.playDownSound(Minecraft.getInstance()
.getSoundHandler());
nbt.putString("Placement", PlacementOptions.values()[placementButtons.indexOf(placementButton)].name());
}
}
@ -149,7 +161,8 @@ public class WorldshaperScreen extends ZapperScreen {
if (toolButton.isHovered()) {
toolButtons.forEach(b -> b.active = true);
toolButton.active = false;
toolButton.playDownSound(Minecraft.getInstance().getSoundHandler());
toolButton.playDownSound(Minecraft.getInstance()
.getSoundHandler());
nbt.putString("Tool", TerrainTools.values()[toolButtons.indexOf(toolButton)].name());
}
}
@ -173,9 +186,13 @@ public class WorldshaperScreen extends ZapperScreen {
@Override
protected void writeAdditionalOptions(CompoundNBT nbt) {
super.writeAdditionalOptions(nbt);
nbt.putString("Brush", NBTHelper.writeEnum(TerrainBrushes.values()[brushInput.getState()]));
nbt.put("BrushParams", NBTUtil.writeBlockPos(new BlockPos(brushParams.get(0).getState(),
brushParams.get(1).getState(), brushParams.get(2).getState())));
NBTHelper.writeEnum(nbt, "Brush", TerrainBrushes.values()[brushInput.getState()]);
nbt.put("BrushParams", NBTUtil.writeBlockPos(new BlockPos(brushParams.get(0)
.getState(),
brushParams.get(1)
.getState(),
brushParams.get(2)
.getState())));
}
}

View file

@ -39,12 +39,12 @@ public class DepotRenderer extends SafeTileEntityRenderer<DepotTileEntity> {
msr.nudge(0);
float offset = MathHelper.lerp(partialTicks, transported.prevBeltPosition, transported.beltPosition);
float sideOffset = MathHelper.lerp(partialTicks, transported.prevSideOffset, transported.sideOffset);
Vec3d offsetVec = new Vec3d(transported.insertedFrom.getOpposite()
.getDirectionVec()).scale(.5f - offset);
ms.translate(offsetVec.x, offsetVec.y, offsetVec.z);
if (transported.insertedFrom.getAxis()
.isHorizontal()) {
Vec3d offsetVec = new Vec3d(transported.insertedFrom.getOpposite()
.getDirectionVec()).scale(.5f - offset);
ms.translate(offsetVec.x, offsetVec.y, offsetVec.z);
boolean alongX = transported.insertedFrom.rotateY()
.getAxis() == Axis.X;
if (!alongX)

View file

@ -85,6 +85,13 @@ public class DepotTileEntity extends SmartTileEntity {
sendData();
}
@Override
public void remove() {
super.remove();
if (lazyItemHandler != null)
lazyItemHandler.invalidate();
}
@Override
public CompoundNBT write(CompoundNBT compound) {
if (heldItem != null)

View file

@ -0,0 +1,77 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
public class ArmAngleTarget {
static ArmAngleTarget NO_TARGET = new ArmAngleTarget();
float baseAngle;
float lowerArmAngle;
float upperArmAngle;
float headAngle;
private ArmAngleTarget() {
lowerArmAngle = 155;
upperArmAngle = 60;
headAngle = -15;
}
public ArmAngleTarget(BlockPos armPos, Vec3d pointTarget, Direction clawFacing) {
Vec3d target = pointTarget;
Vec3d origin = VecHelper.getCenterOf(armPos)
.add(0, 4 / 16f, 0);
Vec3d clawTarget = target;
target = target.add(new Vec3d(clawFacing.getOpposite()
.getDirectionVec()).scale(.5f));
Vec3d diff = target.subtract(origin);
float horizontalDistance = (float) diff.mul(1, 0, 1)
.length();
float baseAngle = AngleHelper.deg(MathHelper.atan2(diff.x, diff.z)) + 180;
float alphaOffset = AngleHelper.deg(MathHelper.atan2(diff.y, horizontalDistance));
float a = 18 / 16f; // lower arm length
float a2 = a * a;
float b = 17 / 16f; // upper arm length
float b2 = b * b;
float diffLength =
MathHelper.clamp(MathHelper.sqrt(diff.y * diff.y + horizontalDistance * horizontalDistance), 1 / 8f, a + b);
float diffLength2 = diffLength * diffLength;
float alphaRatio = (-b2 + a2 + diffLength2) / (2 * a * diffLength);
float alpha = AngleHelper.deg(Math.acos(alphaRatio)) + alphaOffset;
float betaRatio = (-diffLength2 + a2 + b2) / (2 * b * a);
float beta = AngleHelper.deg(Math.acos(betaRatio));
if (Float.isNaN(alpha))
alpha = 0;
if (Float.isNaN(beta))
beta = 0;
Vec3d headPos = new Vec3d(0, 0, 0);
headPos = VecHelper.rotate(headPos.add(0, b, 0), beta + 180, Axis.X);
headPos = VecHelper.rotate(headPos.add(0, a, 0), alpha - 90, Axis.X);
headPos = VecHelper.rotate(headPos, baseAngle, Axis.Y);
headPos = headPos.add(origin);
Vec3d headDiff = clawTarget.subtract(headPos);
float horizontalHeadDistance = (float) headDiff.mul(1, 0, 1)
.length();
float headAngle =
(float) (alpha + beta + 135 - AngleHelper.deg(MathHelper.atan2(headDiff.y, horizontalHeadDistance)));
this.lowerArmAngle = alpha;
this.upperArmAngle = beta;
this.headAngle = -headAngle;
this.baseAngle = baseAngle;
}
}

View file

@ -6,18 +6,13 @@ import com.simibubi.create.content.contraptions.base.KineticBlock;
import com.simibubi.create.foundation.block.ITE;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
public class ArmBlock extends KineticBlock implements ITE<ArmTileEntity> {
@ -30,14 +25,6 @@ public class ArmBlock extends KineticBlock implements ITE<ArmTileEntity> {
return true;
}
@Override
public ActionResultType onUse(BlockState p_225533_1_, World p_225533_2_, BlockPos p_225533_3_,
PlayerEntity p_225533_4_, Hand p_225533_5_, BlockRayTraceResult p_225533_6_) {
withTileEntityDo(p_225533_2_, p_225533_3_, ArmTileEntity::toggleRave);
return ActionResultType.SUCCESS;
}
@Override
public VoxelShape getShape(BlockState p_220053_1_, IBlockReader p_220053_2_, BlockPos p_220053_3_,
ISelectionContext p_220053_4_) {

View file

@ -0,0 +1,234 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import javax.annotation.Nullable;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.AllBlockPartials;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.logistics.block.realityFunnel.RealityFunnelBlock;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.inventory.InsertingBehaviour;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.tileentity.TileEntity;
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.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
public abstract class ArmInteractionPoint {
static enum Mode {
DEPOSIT, TAKE
}
BlockPos pos;
BlockState state;
Mode mode;
private LazyOptional<IItemHandler> cachedHandler;
private ArmAngleTarget cachedAngles;
public ArmInteractionPoint() {
cachedHandler = LazyOptional.empty();
}
@OnlyIn(Dist.CLIENT)
void transformFlag(MatrixStack stack) {}
AllBlockPartials getFlagType() {
return mode == Mode.TAKE ? AllBlockPartials.FLAG_LONG_OUT : AllBlockPartials.FLAG_LONG_IN;
}
void cycleMode() {
mode = mode == Mode.DEPOSIT ? Mode.TAKE : Mode.DEPOSIT;
}
Vec3d getInteractionPositionVector() {
return VecHelper.getCenterOf(pos);
}
Direction getInteractionDirection() {
return Direction.DOWN;
}
abstract boolean isValid(BlockState state);
static boolean isInteractable(BlockState state) {
return AllBlocks.DEPOT.has(state) || AllBlocks.BELT.has(state) || AllBlocks.CHUTE.has(state)
|| AllBlocks.REALITY_FUNNEL.has(state);
}
ArmAngleTarget getTargetAngles(BlockPos armPos) {
if (cachedAngles == null)
cachedAngles = new ArmAngleTarget(armPos, getInteractionPositionVector(), getInteractionDirection());
return cachedAngles;
}
@Nullable
IItemHandler getHandler(World world) {
if (!cachedHandler.isPresent()) {
TileEntity te = world.getTileEntity(pos);
if (te == null)
return null;
cachedHandler = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, Direction.UP);
}
return cachedHandler.orElse(null);
}
ItemStack insert(World world, ItemStack stack, boolean simulate) {
IItemHandler handler = getHandler(world);
if (handler == null)
return stack;
return ItemHandlerHelper.insertItem(handler, stack, simulate);
}
ItemStack extract(World world, int slot, int amount, boolean simulate) {
IItemHandler handler = getHandler(world);
if (handler == null)
return ItemStack.EMPTY;
return handler.extractItem(slot, amount, simulate);
}
ItemStack extract(World world, int slot, boolean simulate) {
return extract(world, slot, 64, simulate);
}
int getSlotCount(World world) {
IItemHandler handler = getHandler(world);
if (handler == null)
return 0;
return handler.getSlots();
}
@Nullable
static ArmInteractionPoint createAt(IBlockReader world, BlockPos pos) {
BlockState state = world.getBlockState(pos);
ArmInteractionPoint point = null;
if (AllBlocks.DEPOT.has(state))
point = new Depot();
if (AllBlocks.BELT.has(state))
point = new Belt();
if (AllBlocks.CHUTE.has(state))
point = new Chute();
if (AllBlocks.REALITY_FUNNEL.has(state))
point = new Funnel();
if (point != null) {
point.state = state;
point.pos = pos;
point.mode = Mode.DEPOSIT;
}
return point;
}
CompoundNBT serialize() {
CompoundNBT nbt = new CompoundNBT();
nbt.put("Pos", NBTUtil.writeBlockPos(pos));
NBTHelper.writeEnum(nbt, "Mode", mode);
return nbt;
}
static ArmInteractionPoint deserialize(IBlockReader world, CompoundNBT nbt) {
BlockPos pos = NBTUtil.readBlockPos(nbt.getCompound("Pos"));
ArmInteractionPoint interactionPoint = createAt(world, pos);
interactionPoint.mode = NBTHelper.readEnum(nbt, "Mode", Mode.class);
return interactionPoint;
}
static class Depot extends ArmInteractionPoint {
@Override
Vec3d getInteractionPositionVector() {
return new Vec3d(pos).add(.5f, 14 / 16f, .5f);
}
@Override
boolean isValid(BlockState state) {
return AllBlocks.DEPOT.has(state);
}
}
static class Belt extends Depot {
@Override
boolean isValid(BlockState state) {
return AllBlocks.BELT.has(state);
}
}
static class Chute extends ArmInteractionPoint {
@Override
Vec3d getInteractionPositionVector() {
return new Vec3d(pos).add(.5f, 1, .5f);
}
@Override
boolean isValid(BlockState state) {
return AllBlocks.CHUTE.has(state);
}
}
static class Funnel extends ArmInteractionPoint {
@Override
Vec3d getInteractionPositionVector() {
return VecHelper.getCenterOf(pos)
.add(new Vec3d(RealityFunnelBlock.getFunnelFacing(state)
.getDirectionVec()).scale(.5f));
}
@Override
int getSlotCount(World world) {
return 0;
}
@Override
ItemStack extract(World world, int slot, int amount, boolean simulate) {
return ItemStack.EMPTY;
}
@Override
Direction getInteractionDirection() {
return RealityFunnelBlock.getFunnelFacing(state).getOpposite();
}
@Override
ItemStack insert(World world, ItemStack stack, boolean simulate) {
FilteringBehaviour filtering = TileEntityBehaviour.get(world, pos, FilteringBehaviour.TYPE);
InsertingBehaviour inserter = TileEntityBehaviour.get(world, pos, InsertingBehaviour.TYPE);
if (inserter == null)
return stack;
if (filtering != null && !filtering.test(stack))
return stack;
return inserter.insert(stack, simulate);
}
@Override
boolean isValid(BlockState state) {
return AllBlocks.REALITY_FUNNEL.has(state);
}
@Override
void cycleMode() {}
}
}

View file

@ -0,0 +1,115 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.CreateClient;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmInteractionPoint.Mode;
import com.simibubi.create.foundation.networking.AllPackets;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
@EventBusSubscriber(value = Dist.CLIENT)
public class ArmInteractionPointHandler {
static Map<BlockPos, ArmInteractionPoint> currentSelection = new HashMap<>();
static ItemStack currentItem;
@SubscribeEvent
public static void rightClickingBlocksSelectsThem(PlayerInteractEvent.RightClickBlock event) {
if (currentItem == null)
return;
BlockPos pos = event.getPos();
World world = event.getWorld();
if (!world.isRemote)
return;
if (!currentSelection.containsKey(pos)) {
ArmInteractionPoint point = ArmInteractionPoint.createAt(world, pos);
if (point == null)
return;
currentSelection.put(pos, point);
}
currentSelection.get(pos)
.cycleMode();
event.setCanceled(true);
event.setCancellationResult(ActionResultType.SUCCESS);
}
@SubscribeEvent
public static void leftClickingBlocksDeselectsThem(PlayerInteractEvent.LeftClickBlock event) {
if (currentItem == null)
return;
if (!event.getWorld().isRemote)
return;
BlockPos pos = event.getPos();
if (currentSelection.remove(pos) != null) {
event.setCanceled(true);
event.setCancellationResult(ActionResultType.SUCCESS);
}
}
public static void flushSettings(BlockPos pos) {
if (currentItem == null)
return;
AllPackets.channel.sendToServer(new ArmPlacementPacket(currentSelection.values(), pos));
currentSelection.clear();
currentItem = null;
}
public static void tick() {
PlayerEntity player = Minecraft.getInstance().player;
World world = Minecraft.getInstance().world;
if (player == null)
return;
ItemStack heldItemMainhand = player.getHeldItemMainhand();
if (!AllBlocks.MECHANICAL_ARM.isIn(heldItemMainhand)) {
currentItem = null;
return;
}
if (heldItemMainhand != currentItem) {
currentSelection.clear();
currentItem = heldItemMainhand;
}
for (Iterator<Entry<BlockPos, ArmInteractionPoint>> iterator = currentSelection.entrySet()
.iterator(); iterator.hasNext();) {
Entry<BlockPos, ArmInteractionPoint> entry = iterator.next();
BlockPos pos = entry.getKey();
BlockState state = world.getBlockState(pos);
ArmInteractionPoint point = entry.getValue();
if (!point.isValid(state)) {
iterator.remove();
continue;
}
VoxelShape shape = state.getShape(world, pos);
if (shape.isEmpty())
continue;
int color = point.mode == Mode.DEPOSIT ? 0xffcb74 : 0x4f8a8b;
CreateClient.outliner.showAABB(point, shape.getBoundingBox()
.offset(pos))
.colored(color)
.lineWidth(1 / 16f);
}
}
}

View file

@ -0,0 +1,45 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUseContext;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
@EventBusSubscriber
public class ArmItem extends BlockItem {
public ArmItem(Block p_i48527_1_, Properties p_i48527_2_) {
super(p_i48527_1_, p_i48527_2_);
}
@Override
public ActionResultType onItemUse(ItemUseContext ctx) {
if (ArmInteractionPoint.isInteractable(ctx.getWorld()
.getBlockState(ctx.getPos())))
return ActionResultType.SUCCESS;
return super.onItemUse(ctx);
}
@Override
protected boolean onBlockPlaced(BlockPos pos, World world, PlayerEntity p_195943_3_, ItemStack p_195943_4_,
BlockState p_195943_5_) {
if (world.isRemote)
DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> ArmInteractionPointHandler.flushSettings(pos));
return super.onBlockPlaced(pos, world, p_195943_3_, p_195943_4_, p_195943_5_);
}
@Override
public boolean canPlayerBreakBlockWhileHolding(BlockState state, World p_195938_2_, BlockPos p_195938_3_,
PlayerEntity p_195938_4_) {
return !ArmInteractionPoint.isInteractable(state);
}
}

View file

@ -0,0 +1,70 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import java.util.Collection;
import java.util.function.Supplier;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.network.PacketBuffer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.fml.network.NetworkEvent.Context;
public class ArmPlacementPacket extends SimplePacketBase {
private Collection<ArmInteractionPoint> points;
private ListNBT receivedTag;
private BlockPos pos;
public ArmPlacementPacket(Collection<ArmInteractionPoint> points, BlockPos pos) {
this.points = points;
this.pos = pos;
}
public ArmPlacementPacket(PacketBuffer buffer) {
CompoundNBT nbt = buffer.readCompoundTag();
receivedTag = nbt.getList("Points", NBT.TAG_COMPOUND);
pos = buffer.readBlockPos();
}
@Override
public void write(PacketBuffer buffer) {
CompoundNBT nbt = new CompoundNBT();
ListNBT pointsNBT = new ListNBT();
points.stream()
.map(ArmInteractionPoint::serialize)
.forEach(pointsNBT::add);
nbt.put("Points", pointsNBT);
buffer.writeCompoundTag(nbt);
buffer.writeBlockPos(pos);
}
@Override
public void handle(Supplier<Context> context) {
context.get()
.enqueueWork(() -> {
ServerPlayerEntity player = context.get()
.getSender();
if (player == null)
return;
World world = player.world;
if (world == null)
return;
TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity == null || !(tileEntity instanceof ArmTileEntity))
return;
ArmTileEntity arm = (ArmTileEntity) tileEntity;
arm.interactionPointTag = receivedTag;
});
context.get()
.setPacketHandled(true);
}
}

View file

@ -5,17 +5,19 @@ import com.mojang.blaze3d.vertex.IVertexBuilder;
import com.simibubi.create.AllBlockPartials;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.contraptions.base.KineticTileEntityRenderer;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.ColorHelper;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.MatrixStacker;
import com.simibubi.create.foundation.utility.SuperByteBuffer;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.util.math.MathHelper;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
public class ArmRenderer extends KineticTileEntityRenderer {
@ -24,25 +26,15 @@ public class ArmRenderer extends KineticTileEntityRenderer {
}
@Override
protected void renderSafe(KineticTileEntity te, float partialTicks, MatrixStack ms, IRenderTypeBuffer buffer,
int light, int overlay) {
super.renderSafe(te, partialTicks, ms, buffer, light, overlay);
protected void renderSafe(KineticTileEntity te, float pt, MatrixStack ms, IRenderTypeBuffer buffer, int light,
int overlay) {
super.renderSafe(te, pt, ms, buffer, light, overlay);
ArmTileEntity arm = (ArmTileEntity) te;
IVertexBuilder builder = buffer.getBuffer(RenderType.getSolid());
BlockState blockState = te.getBlockState();
MatrixStacker msr = MatrixStacker.of(ms);
float angle = 0;
float clawAngle = -25;
float otherAngle = 0;
int color = 0xFFFFFF;
boolean rave = te instanceof ArmTileEntity && ((ArmTileEntity) te).debugRave;
if (rave) {
clawAngle = angle = MathHelper.lerp((MathHelper.sin(AnimationTickHolder.getRenderTick() / 2) + 1) / 2, -45, 15);
otherAngle = MathHelper.lerp((MathHelper.sin(AnimationTickHolder.getRenderTick() / 4) + 1) / 2, -95, 95);
color = ColorHelper.rainbowColor(AnimationTickHolder.ticks * 100);
}
ms.push();
SuperByteBuffer base = AllBlockPartials.ARM_BASE.renderOn(blockState);
@ -55,33 +47,51 @@ public class ArmRenderer extends KineticTileEntityRenderer {
msr.centre();
ms.translate(0, 4 / 16d, 0);
msr.rotateY(rave ? AnimationTickHolder.getRenderTick() * 10 : 0);
msr.rotateY(arm.baseAngle.get(pt));
base.renderInto(ms, builder);
ms.translate(0, 1 / 16d, -2 / 16d);
msr.rotateX(angle);
msr.rotateX(arm.lowerArmAngle.get(pt) - 135);
ms.translate(0, -1 / 16d, 0);
lowerBody.color(color).renderInto(ms, builder);
lowerBody.color(color)
.renderInto(ms, builder);
ms.translate(0, 12 / 16d, 12 / 16d);
msr.rotateX(-otherAngle / 2f);
upperBody.color(color).renderInto(ms, builder);
msr.rotateX(arm.upperArmAngle.get(pt) - 90);
upperBody.color(color)
.renderInto(ms, builder);
ms.translate(0, 11 / 16d, -11 / 16d);
msr.rotateX(-angle);
msr.rotateX(arm.headAngle.get(pt));
head.renderInto(ms, builder);
ms.translate(0, 0, -4 / 16d);
claw.renderInto(ms, builder);
ItemStack item = arm.heldItem;
ItemRenderer itemRenderer = Minecraft.getInstance()
.getItemRenderer();
boolean hasItem = !item.isEmpty();
boolean isBlockItem = hasItem && (item.getItem() instanceof BlockItem)
&& itemRenderer.getItemModelWithOverrides(item, Minecraft.getInstance().world, null)
.isGui3d();
for (int flip : Iterate.positiveAndNegative) {
ms.push();
ms.translate(0, flip * 3 / 16d, -1 / 16d);
msr.rotateX(flip * clawAngle);
msr.rotateX(flip * (hasItem ? isBlockItem ? 0 : -35 : 0));
clawGrip.renderInto(ms, builder);
ms.pop();
}
if (hasItem) {
float itemScale = isBlockItem ? .5f : .625f;
msr.rotateX(90);
ms.translate(0, -4 / 16f, 0);
ms.scale(itemScale, itemScale, itemScale);
itemRenderer
.renderItem(item, TransformType.FIXED, light, overlay, ms, buffer);
}
ms.pop();
}

View file

@ -1,30 +1,232 @@
package com.simibubi.create.content.logistics.block.mechanicalArm;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmInteractionPoint.Mode;
import com.simibubi.create.foundation.gui.widgets.InterpolatedAngle;
import com.simibubi.create.foundation.utility.AngleHelper;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.common.util.Constants.NBT;
public class ArmTileEntity extends KineticTileEntity {
boolean debugRave;
// Server
List<ArmInteractionPoint> inputs;
List<ArmInteractionPoint> outputs;
ListNBT interactionPointTag;
// Both
float chasedPointProgress;
int chasedPointIndex;
ItemStack heldItem;
Phase phase;
// Client
ArmAngleTarget previousTarget;
InterpolatedAngle lowerArmAngle;
InterpolatedAngle upperArmAngle;
InterpolatedAngle baseAngle;
InterpolatedAngle headAngle;
InterpolatedAngle clawAngle;
float previousBaseAngle;
boolean updateInteractionPoints;
enum Phase {
SEARCH_INPUTS, MOVE_TO_INPUT, SEARCH_OUTPUTS, MOVE_TO_OUTPUT, IDLE
}
public ArmTileEntity(TileEntityType<?> typeIn) {
super(typeIn);
inputs = new ArrayList<>();
outputs = new ArrayList<>();
heldItem = ItemStack.EMPTY;
phase = Phase.SEARCH_INPUTS;
baseAngle = new InterpolatedAngle();
lowerArmAngle = new InterpolatedAngle();
upperArmAngle = new InterpolatedAngle();
headAngle = new InterpolatedAngle();
clawAngle = new InterpolatedAngle();
previousTarget = ArmAngleTarget.NO_TARGET;
previousBaseAngle = previousTarget.baseAngle;
updateInteractionPoints = true;
}
@Override
public void tick() {
super.tick();
initInteractionPoints();
tickMovementProgress();
if (world.isRemote)
return;
if (chasedPointProgress < 1)
return;
if (phase == Phase.MOVE_TO_INPUT)
collectItem();
if (phase == Phase.MOVE_TO_OUTPUT)
depositItem();
}
@Override
public void lazyTick() {
if (hasWorld())
if (world.rand.nextInt(100) == 0)
toggleRave();
super.lazyTick();
}
public void toggleRave() {
if (world.isRemote)
return;
debugRave = !debugRave;
if (chasedPointProgress < .5f)
return;
if (phase == Phase.SEARCH_INPUTS)
searchForItem();
if (phase == Phase.SEARCH_OUTPUTS)
searchForDestination();
}
private void tickMovementProgress() {
chasedPointProgress += Math.min(256, Math.abs(getSpeed())) / 1024f;
if (chasedPointProgress > 1)
chasedPointProgress = 1;
if (!world.isRemote)
return;
ArmInteractionPoint targetedInteractionPoint = getTargetedInteractionPoint();
ArmAngleTarget previousTarget = this.previousTarget;
ArmAngleTarget target =
targetedInteractionPoint == null ? ArmAngleTarget.NO_TARGET : targetedInteractionPoint.getTargetAngles(pos);
baseAngle.set(AngleHelper.angleLerp(chasedPointProgress, previousBaseAngle,
target == ArmAngleTarget.NO_TARGET ? previousBaseAngle : target.baseAngle));
// Arm's angles first backup to resting position and then continue
if (chasedPointProgress < .5f)
target = ArmAngleTarget.NO_TARGET;
else
previousTarget = ArmAngleTarget.NO_TARGET;
float progress = chasedPointProgress == 1 ? 1 : (chasedPointProgress % .5f) * 2;
lowerArmAngle.set(MathHelper.lerp(progress, previousTarget.lowerArmAngle, target.lowerArmAngle));
upperArmAngle.set(MathHelper.lerp(progress, previousTarget.upperArmAngle, target.upperArmAngle));
headAngle.set(AngleHelper.angleLerp(progress, previousTarget.headAngle, target.headAngle));
}
@Nullable
private ArmInteractionPoint getTargetedInteractionPoint() {
if (chasedPointIndex == -1)
return null;
if (phase == Phase.MOVE_TO_INPUT && chasedPointIndex < inputs.size())
return inputs.get(chasedPointIndex);
if (phase == Phase.MOVE_TO_OUTPUT && chasedPointIndex < outputs.size())
return outputs.get(chasedPointIndex);
return null;
}
protected void searchForItem() {
for (int index = 0; index < inputs.size(); index++) {
ArmInteractionPoint armInteractionPoint = inputs.get(index);
for (int i = 0; i < armInteractionPoint.getSlotCount(world); i++) {
if (getDistributableAmount(armInteractionPoint, i) == 0)
continue;
phase = Phase.MOVE_TO_INPUT;
chasedPointIndex = index;
chasedPointProgress = 0;
sendData();
markDirty();
return;
}
}
}
protected void searchForDestination() {
ItemStack held = heldItem.copy();
for (int index = 0; index < outputs.size(); index++) {
ArmInteractionPoint armInteractionPoint = outputs.get(index);
ItemStack remainder = armInteractionPoint.insert(world, held, true);
if (remainder.equals(heldItem, false))
continue;
phase = Phase.MOVE_TO_OUTPUT;
chasedPointIndex = index;
chasedPointProgress = 0;
sendData();
markDirty();
return;
}
}
protected int getDistributableAmount(ArmInteractionPoint armInteractionPoint, int i) {
ItemStack stack = armInteractionPoint.extract(world, i, true);
ItemStack remainder = simulateInsertion(stack);
return stack.getCount() - remainder.getCount();
}
protected void depositItem() {
ArmInteractionPoint armInteractionPoint = getTargetedInteractionPoint();
ItemStack toInsert = heldItem.copy();
ItemStack remainder = armInteractionPoint.insert(world, toInsert, false);
heldItem = remainder;
phase = heldItem.isEmpty() ? Phase.SEARCH_INPUTS : Phase.SEARCH_OUTPUTS;
chasedPointProgress = 0;
chasedPointIndex = -1;
sendData();
markDirty();
}
protected void collectItem() {
ArmInteractionPoint armInteractionPoint = getTargetedInteractionPoint();
for (int i = 0; i < armInteractionPoint.getSlotCount(world); i++) {
int amountExtracted = getDistributableAmount(armInteractionPoint, i);
if (amountExtracted == 0)
continue;
heldItem = armInteractionPoint.extract(world, i, amountExtracted, false);
phase = Phase.SEARCH_OUTPUTS;
chasedPointProgress = 0;
chasedPointIndex = -1;
sendData();
markDirty();
return;
}
phase = Phase.SEARCH_INPUTS;
chasedPointProgress = 0;
chasedPointIndex = -1;
sendData();
markDirty();
}
private ItemStack simulateInsertion(ItemStack stack) {
for (ArmInteractionPoint armInteractionPoint : outputs) {
stack = armInteractionPoint.insert(world, stack, true);
if (stack.isEmpty())
break;
}
return stack;
}
protected void initInteractionPoints() {
if (interactionPointTag == null)
return;
inputs.clear();
outputs.clear();
for (INBT inbt : interactionPointTag) {
ArmInteractionPoint point = ArmInteractionPoint.deserialize(world, (CompoundNBT) inbt);
if (point.mode == Mode.DEPOSIT)
outputs.add(point);
if (point.mode == Mode.TAKE)
inputs.add(point);
}
interactionPointTag = null;
markDirty();
sendData();
}
@ -32,14 +234,64 @@ public class ArmTileEntity extends KineticTileEntity {
@Override
public CompoundNBT write(CompoundNBT compound) {
super.write(compound);
compound.putBoolean("DebugRave", debugRave);
ListNBT pointsNBT = new ListNBT();
inputs.stream()
.map(ArmInteractionPoint::serialize)
.forEach(pointsNBT::add);
outputs.stream()
.map(ArmInteractionPoint::serialize)
.forEach(pointsNBT::add);
NBTHelper.writeEnum(compound, "Phase", phase);
compound.put("InterationPoints", pointsNBT);
compound.put("HeldItem", heldItem.serializeNBT());
compound.putInt("TargetPointIndex", chasedPointIndex);
compound.putFloat("MovementProgress", chasedPointProgress);
return compound;
}
@Override
public CompoundNBT writeToClient(CompoundNBT compound) {
super.writeToClient(compound);
if (interactionPointTag != null)
compound.put("InitialInterationPoints", interactionPointTag);
return compound;
}
@Override
public void read(CompoundNBT compound) {
super.read(compound);
debugRave = compound.getBoolean("DebugRave");
heldItem = ItemStack.read(compound.getCompound("HeldItem"));
phase = NBTHelper.readEnum(compound, "Phase", Phase.class);
chasedPointIndex = compound.getInt("TargetPointIndex");
chasedPointProgress = compound.getFloat("MovementProgress");
if (!hasWorld() || !world.isRemote || updateInteractionPoints)
interactionPointTag = compound.getList("InterationPoints", NBT.TAG_COMPOUND);
updateInteractionPoints = false;
}
@Override
public void readClientUpdate(CompoundNBT tag) {
int previousIndex = chasedPointIndex;
Phase previousPhase = phase;
super.readClientUpdate(tag);
if (previousIndex != chasedPointIndex || (previousPhase != phase)) {
ArmInteractionPoint previousPoint = null;
if (previousPhase == Phase.MOVE_TO_INPUT && previousIndex < inputs.size())
previousPoint = inputs.get(previousIndex);
if (previousPhase == Phase.MOVE_TO_OUTPUT && previousIndex < outputs.size())
previousPoint = outputs.get(previousIndex);
previousTarget = previousPoint == null ? ArmAngleTarget.NO_TARGET : previousPoint.getTargetAngles(pos);
if (previousPoint != null)
previousBaseAngle = previousPoint.getTargetAngles(pos).baseAngle;
}
if (tag.contains("InitialInterationPoints"))
interactionPointTag = tag.getList("InitialInterationPoints", NBT.TAG_COMPOUND);
}
}

View file

@ -43,6 +43,8 @@ public class FilterScreenPacket extends SimplePacketBase {
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity player = context.get().getSender();
if (player == null)
return;
if (player.openContainer instanceof AbstractFilterContainer) {
AbstractFilterContainer c = (AbstractFilterContainer) player.openContainer;

View file

@ -49,6 +49,8 @@ public class ConfigureSchematicannonPacket extends SimplePacketBase {
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity player = context.get().getSender();
if (player == null)
return;
World world = player.world;
if (world == null)
return;

View file

@ -32,6 +32,8 @@ public class SchematicPlacePacket extends SimplePacketBase {
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity player = context.get().getSender();
if (player == null)
return;
Template t = SchematicItem.loadSchematic(stack);
PlacementSettings settings = SchematicItem.getSettings(stack);
settings.setIgnoreEntities(false);

View file

@ -68,6 +68,8 @@ public class SchematicUploadPacket extends SimplePacketBase {
.enqueueWork(() -> {
ServerPlayerEntity player = context.get()
.getSender();
if (player == null)
return;
if (code == BEGIN) {
BlockPos pos = ((SchematicTableContainer) player.openContainer).getTileEntity()
.getPos();

View file

@ -0,0 +1,11 @@
package com.simibubi.create.foundation.gui.widgets;
import com.simibubi.create.foundation.utility.AngleHelper;
public class InterpolatedAngle extends InterpolatedValue {
public float get(float partialTicks) {
return AngleHelper.angleLerp(partialTicks, lastValue, value);
}
}

View file

@ -4,6 +4,10 @@ import com.simibubi.create.foundation.utility.AngleHelper;
public class InterpolatedChasingAngle extends InterpolatedChasingValue {
public float get(float partialTicks) {
return AngleHelper.angleLerp(partialTicks, lastValue, value);
}
@Override
protected float getCurrentDiff() {
return AngleHelper.getShortestAngleDiff(value, getTarget());

View file

@ -12,6 +12,7 @@ import com.simibubi.create.content.contraptions.relays.advanced.sequencer.Config
import com.simibubi.create.content.curiosities.symmetry.SymmetryEffectPacket;
import com.simibubi.create.content.curiosities.tools.ExtendoGripInteractionPacket;
import com.simibubi.create.content.curiosities.zapper.ZapperBeamPacket;
import com.simibubi.create.content.logistics.block.mechanicalArm.ArmPlacementPacket;
import com.simibubi.create.content.logistics.item.filter.FilterScreenPacket;
import com.simibubi.create.content.logistics.packet.ConfigureFlexcratePacket;
import com.simibubi.create.content.logistics.packet.ConfigureStockswitchPacket;
@ -44,6 +45,7 @@ public enum AllPackets {
CONFIGURE_SCROLLABLE(ScrollValueUpdatePacket.class, ScrollValueUpdatePacket::new),
CANCEL_FALL(CancelPlayerFallPacket.class, CancelPlayerFallPacket::new),
EXTENDO_INTERACT(ExtendoGripInteractionPacket.class, ExtendoGripInteractionPacket::new),
PLACE_ARM(ArmPlacementPacket.class, ArmPlacementPacket::new),
// Server to Client
SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new),

View file

@ -40,6 +40,8 @@ public class NbtPacket extends SimplePacketBase {
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity player = context.get().getSender();
if (player == null)
return;
if (slot == -1) {
ItemStack heldItem = player.getHeldItem(hand);

View file

@ -35,6 +35,8 @@ public abstract class TileEntityConfigurationPacket<TE extends SyncedTileEntity>
public void handle(Supplier<Context> context) {
context.get().enqueueWork(() -> {
ServerPlayerEntity player = context.get().getSender();
if (player == null)
return;
World world = player.world;
if (world == null || world.getTileEntity(pos) == null)

View file

@ -12,8 +12,9 @@ import net.minecraft.util.math.AxisAlignedBB;
public class NBTHelper {
public static <T extends Enum<?>> T readEnum(String name, Class<T> enumClass) {
public static <T extends Enum<?>> T readEnum(CompoundNBT nbt, String key, Class<T> enumClass) {
T[] enumConstants = enumClass.getEnumConstants();
String name = nbt.getString(key);
if (enumConstants == null)
throw new IllegalArgumentException("Non-Enum class passed to readEnum(): " + enumClass.getName());
for (T t : enumConstants) {
@ -23,8 +24,8 @@ public class NBTHelper {
return enumConstants[0];
}
public static <T extends Enum<?>> String writeEnum(T enumConstant) {
return enumConstant.name();
public static <T extends Enum<?>> void writeEnum(CompoundNBT nbt, String key, T enumConstant) {
nbt.putString(key, enumConstant.name());
}
public static <T> ListNBT writeCompoundList(List<T> list, Function<T, CompoundNBT> serializer) {

View file

@ -0,0 +1,31 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"4": "create:block/marker_flag"
},
"elements": [
{
"from": [0, 0, 0],
"to": [1, 16, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"north": {"uv": [1, 0, 2, 16], "texture": "#4"},
"east": {"uv": [0, 0, 1, 16], "texture": "#4"},
"south": {"uv": [1, 0, 2, 16], "texture": "#4"},
"west": {"uv": [0, 0, 1, 16], "texture": "#4"},
"up": {"uv": [0, 0, 1, 1], "texture": "#4"},
"down": {"uv": [0, 0, 1, 1], "texture": "#4"}
}
},
{
"from": [0.4, 7.5, 1],
"to": [0.4, 15.5, 9],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"east": {"uv": [16, 0, 8, 8], "texture": "#4", "tintindex": 0},
"west": {"uv": [8, 0, 16, 8], "texture": "#4", "tintindex": 0}
}
}
]
}

View file

@ -0,0 +1,31 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"4": "create:block/marker_flag"
},
"elements": [
{
"from": [0, 0, 0],
"to": [1, 16, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"north": {"uv": [1, 0, 2, 16], "texture": "#4"},
"east": {"uv": [0, 0, 1, 16], "texture": "#4"},
"south": {"uv": [1, 0, 2, 16], "texture": "#4"},
"west": {"uv": [0, 0, 1, 16], "texture": "#4"},
"up": {"uv": [0, 0, 1, 1], "texture": "#4"},
"down": {"uv": [0, 0, 1, 1], "texture": "#4"}
}
},
{
"from": [0.4, 7.5, 1],
"to": [0.4, 15.5, 9],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"east": {"uv": [16, 8, 8, 16], "texture": "#4", "tintindex": 0},
"west": {"uv": [8, 8, 16, 16], "texture": "#4", "tintindex": 0}
}
}
]
}

View file

@ -0,0 +1,31 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"4": "create:block/marker_flag"
},
"elements": [
{
"from": [0, 0, 0],
"to": [1, 8, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"north": {"uv": [1, 8, 2, 16], "texture": "#4"},
"east": {"uv": [0, 0, 1, 8], "texture": "#4"},
"south": {"uv": [1, 0, 2, 8], "texture": "#4"},
"west": {"uv": [0, 0, 1, 8], "texture": "#4"},
"up": {"uv": [0, 0, 1, 1], "texture": "#4"},
"down": {"uv": [0, 0, 1, 1], "texture": "#4"}
}
},
{
"from": [0.4, -0.5, 1],
"to": [0.4, 7.5, 9],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"east": {"uv": [16, 0, 8, 8], "texture": "#4", "tintindex": 0},
"west": {"uv": [8, 0, 16, 8], "texture": "#4", "tintindex": 0}
}
}
]
}

View file

@ -0,0 +1,31 @@
{
"credit": "Made with Blockbench",
"parent": "block/block",
"textures": {
"4": "create:block/marker_flag"
},
"elements": [
{
"from": [0, 0, 0],
"to": [1, 8, 1],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"north": {"uv": [1, 8, 2, 16], "texture": "#4"},
"east": {"uv": [0, 0, 1, 8], "texture": "#4"},
"south": {"uv": [1, 0, 2, 8], "texture": "#4"},
"west": {"uv": [0, 0, 1, 8], "texture": "#4"},
"up": {"uv": [0, 0, 1, 1], "texture": "#4"},
"down": {"uv": [0, 0, 1, 1], "texture": "#4"}
}
},
{
"from": [0.4, -0.5, 1],
"to": [0.4, 7.5, 9],
"rotation": {"angle": 0, "axis": "y", "origin": [8, -3, 10]},
"faces": {
"east": {"uv": [16, 8, 8, 16], "texture": "#4", "tintindex": 0},
"west": {"uv": [8, 8, 16, 16], "texture": "#4", "tintindex": 0}
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B