Started working on the robbit.

This commit is contained in:
Florian Nücke
2021-01-13 01:02:09 +01:00
parent aeff6aba64
commit 54bac6077c
22 changed files with 1060 additions and 0 deletions

View File

@@ -6,11 +6,13 @@ import li.cil.oc2.client.gui.ComputerContainerScreen;
import li.cil.oc2.client.item.CustomItemModelProperties;
import li.cil.oc2.client.model.BusCableModelLoader;
import li.cil.oc2.client.renderer.NetworkCableRenderer;
import li.cil.oc2.client.renderer.entity.RobotEntityRenderer;
import li.cil.oc2.client.renderer.tileentity.ComputerTileEntityRenderer;
import li.cil.oc2.client.renderer.tileentity.NetworkConnectorTileEntityRenderer;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.DeviceTypes;
import li.cil.oc2.common.container.Containers;
import li.cil.oc2.common.entity.Entities;
import li.cil.oc2.common.tileentity.TileEntities;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.inventory.container.PlayerContainer;
@@ -20,6 +22,7 @@ import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
public final class ClientSetup {
@@ -32,6 +35,8 @@ public final class ClientSetup {
ClientRegistry.bindTileEntityRenderer(TileEntities.COMPUTER_TILE_ENTITY.get(), ComputerTileEntityRenderer::new);
ClientRegistry.bindTileEntityRenderer(TileEntities.NETWORK_CONNECTOR_TILE_ENTITY.get(), NetworkConnectorTileEntityRenderer::new);
RenderingRegistry.registerEntityRenderingHandler(Entities.ROBOT.get(), RobotEntityRenderer::new);
}
@SubscribeEvent

View File

@@ -0,0 +1,110 @@
package li.cil.oc2.client.renderer.entity;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import li.cil.oc2.api.API;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.entity.model.EntityModel;
import net.minecraft.client.renderer.model.ModelRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.EntityRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.common.model.TransformationHelper;
public final class RobotEntityRenderer extends EntityRenderer<RobotEntity> {
private final RobotModel model = new RobotModel();
///////////////////////////////////////////////////////////////////
public RobotEntityRenderer(final EntityRendererManager renderManager) {
super(renderManager);
}
///////////////////////////////////////////////////////////////////
@Override
public ResourceLocation getEntityTexture(final RobotEntity entity) {
return new ResourceLocation(API.MOD_ID, "textures/entity/robot/robot.png");
}
@Override
public void render(final RobotEntity entity, final float entityYaw, final float partialTicks, final MatrixStack matrixStack, final IRenderTypeBuffer buffer, final int packedLight) {
final RobotEntity.AnimationState state = entity.getAnimationState();
state.update(partialTicks, entity.world.rand);
matrixStack.push();
// NB: we don't entityYaw given to use because that uses a plain lerp which can lead to ugly
// jumps in case we get a wrapped rotationYaw synced from the server (leading to ~360
// degree delta to the last known previous rotation). Haven't figured out where to
// alternatively prevent this wrapping or patch the prev value instead.
final float partialRotation = MathHelper.degreesDifferenceAbs(entity.prevRotationYaw, entity.rotationYaw) * partialTicks;
final float rotation = MathHelper.approachDegrees(entity.prevRotationYaw, entity.rotationYaw, partialRotation);
matrixStack.rotate(Vector3f.YN.rotationDegrees(rotation));
model.setRotationAngles(entity, 0, 0, 0, 0, 0);
final IVertexBuilder builder = buffer.getBuffer(model.getRenderType(getEntityTexture(entity)));
model.render(matrixStack, builder, packedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1);
matrixStack.pop();
final RayTraceResult hit = Minecraft.getInstance().objectMouseOver;
if (hit instanceof EntityRayTraceResult && entity == ((EntityRayTraceResult) hit).getEntity()) {
super.renderName(entity, new StringTextComponent("hi"), matrixStack, buffer, packedLight);
}
}
///////////////////////////////////////////////////////////////////
private static final class RobotModel extends EntityModel<RobotEntity> {
private final ModelRenderer topRenderer;
private final ModelRenderer baseRenderer;
private final ModelRenderer coreRenderer;
private float baseY, topY;
private final float[] topRotation = new float[3];
public RobotModel() {
topRenderer = new ModelRenderer(this, 1, 1)
.setTextureSize(64, 64)
.addBox(-7, 8, -7, 14, 6, 14);
baseRenderer = new ModelRenderer(this, 1, 23)
.setTextureSize(64, 64)
.addBox(-7, 0, -7, 14, 7, 14);
coreRenderer = new ModelRenderer(this, 1, 34)
.setTextureSize(64, 64)
.addBox(-6, 7, -6, 12, 1, 12);
}
@Override
public void setRotationAngles(final RobotEntity entity, final float limbSwing, final float limbSwingAmount, final float ageInTicks, final float netHeadYaw, final float headPitch) {
final RobotEntity.AnimationState state = entity.getAnimationState();
baseY = state.baseRenderOffsetY;
topY = state.topRenderOffsetY;
topRotation[1] = state.topRenderRotationY;
}
@Override
public void render(final MatrixStack matrixStack, final IVertexBuilder buffer, final int packedLight, final int packedOverlay, final float red, final float green, final float blue, final float alpha) {
matrixStack.push();
matrixStack.translate(0, topY, 0);
matrixStack.rotate(TransformationHelper.quatFromXYZ(topRotation, true));
topRenderer.render(matrixStack, buffer, packedLight, packedOverlay);
matrixStack.pop();
matrixStack.push();
matrixStack.translate(0, baseY, 0);
baseRenderer.render(matrixStack, buffer, packedLight, packedOverlay);
coreRenderer.render(matrixStack, buffer, LightTexture.packLight(15, 15), packedOverlay);
matrixStack.pop();
}
}
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.client.renderer.entity;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -41,6 +41,10 @@ public final class Constants {
///////////////////////////////////////////////////////////////////
public static final String ROBOT_ENTITY_NAME = "robot";
///////////////////////////////////////////////////////////////////
public static final String CONFIG_MAX_ALLOCATED_MEMORY = "config.oc2.maxAllocatedMemory";
public static final String CONFIG_MAX_MEMORY_SIZE = "config.oc2.maxMemorySize";
public static final String CONFIG_MAX_HARD_DRIVE_SIZE = "config.oc2.maxHardDriveSize";

View File

@@ -9,6 +9,7 @@ import li.cil.oc2.common.bus.device.data.BaseBlockDevices;
import li.cil.oc2.common.bus.device.data.Firmwares;
import li.cil.oc2.common.bus.device.provider.Providers;
import li.cil.oc2.common.container.Containers;
import li.cil.oc2.common.entity.Entities;
import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.serialization.serializers.Serializers;
import li.cil.oc2.common.tileentity.TileEntities;
@@ -28,6 +29,7 @@ public final class Main {
Items.initialize();
Blocks.initialize();
TileEntities.initialize();
Entities.initialize();
Containers.initialize();
Providers.initialize();
DeviceTypes.initialize();

View File

@@ -0,0 +1,33 @@
package li.cil.oc2.common.entity;
import li.cil.oc2.api.API;
import li.cil.oc2.common.Constants;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityClassification;
import net.minecraft.entity.EntityType;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.function.Function;
public final class Entities {
private static final DeferredRegister<EntityType<?>> ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITIES, API.MOD_ID);
///////////////////////////////////////////////////////////////////
public static final RegistryObject<EntityType<RobotEntity>> ROBOT = register(Constants.ROBOT_ENTITY_NAME, RobotEntity::new, EntityClassification.MISC, b -> b.size(14f / 16f, 14f / 16f).immuneToFire().disableSummoning());
///////////////////////////////////////////////////////////////////
public static void initialize() {
ENTITIES.register(FMLJavaModLoadingContext.get().getModEventBus());
}
///////////////////////////////////////////////////////////////////
private static <T extends Entity> RegistryObject<EntityType<T>> register(final String name, final EntityType.IFactory<T> factory, final EntityClassification classification, final Function<EntityType.Builder<T>, EntityType.Builder<T>> customizer) {
return ENTITIES.register(name, () -> customizer.apply(EntityType.Builder.create(factory, classification)).build(name));
}
}

View File

@@ -0,0 +1,323 @@
package li.cil.oc2.common.entity;
import li.cil.oc2.common.entity.robot.*;
import li.cil.oc2.common.integration.Wrenches;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.WorldUtils;
import net.minecraft.block.SoundType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.network.IPacket;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.network.NetworkHooks;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Random;
public final class RobotEntity extends Entity {
public static int MAX_QUEUED_ACTIONS = 16;
private static final String COMMAND_PROCESSOR_TAG_NAME = "commands";
private static final DataParameter<Boolean> IS_RUNNING = EntityDataManager.createKey(RobotEntity.class, DataSerializers.BOOLEAN);
///////////////////////////////////////////////////////////////////
private final AnimationState animationState = new AnimationState();
private final CommandProcessor commandProcessor = new CommandProcessor();
///////////////////////////////////////////////////////////////////
public RobotEntity(final EntityType<?> type, final World world) {
super(type, world);
this.preventEntitySpawning = true;
setNoGravity(true);
}
///////////////////////////////////////////////////////////////////
@OnlyIn(Dist.CLIENT)
public AnimationState getAnimationState() {
return animationState;
}
public boolean isRunning() {
return dataManager.get(IS_RUNNING);
}
public void start() {
// todo start vm
}
public void stop() {
// todo stop vm
commandProcessor.clear();
}
@Override
public void tick() {
super.tick();
commandProcessor.tick();
}
@Override
public ActionResultType processInitialInteract(final PlayerEntity player, final Hand hand) {
final ItemStack stack = player.getHeldItem(hand);
if (Wrenches.isWrench(stack)) {
if (!world.isRemote()) {
if (player.isSneaking()) {
remove();
WorldUtils.playSound(world, getPosition(), SoundType.METAL, SoundType::getBreakSound);
} else {
// todo open container
}
}
} else {
if (player.isSneaking()) {
// TODO start machine
if (!world.isRemote()) {
dataManager.set(IS_RUNNING, !dataManager.get(IS_RUNNING));
}
} else {
// if (rand.nextBoolean()) {
// commandProcessor.move(MovementDirection.values()[rand.nextInt(MovementDirection.values().length)]);
// } else {
// commandProcessor.rotate(rand.nextBoolean() ? RotationDirection.LEFT : RotationDirection.RIGHT);
commandProcessor.rotate(RotationDirection.RIGHT);
// }
// TODO open terminal + inventory screen
}
}
return ActionResultType.SUCCESS;
}
@Override
public IPacket<?> createSpawnPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
@Override
public boolean canBeCollidedWith() {
return true;
}
@Override
public boolean canCollide(final Entity entity) {
return entity != this;
}
@Override
public void applyEntityCollision(final Entity entity) {
}
@Override
public boolean func_241845_aY() {
return true;
}
@Override
public boolean shouldSpawnRunningEffects() {
return false;
}
///////////////////////////////////////////////////////////////////
@Override
protected void registerData() {
getDataManager().register(IS_RUNNING, false);
RobotActions.registerData(getDataManager());
}
@Override
protected void writeAdditional(final CompoundNBT compound) {
compound.put(COMMAND_PROCESSOR_TAG_NAME, commandProcessor.serialize());
}
@Override
protected void readAdditional(final CompoundNBT compound) {
commandProcessor.deserialize(compound.getCompound(COMMAND_PROCESSOR_TAG_NAME));
}
@Override
protected boolean canTriggerWalking() {
return false;
}
@Override
protected void doBlockCollisions() {
}
///////////////////////////////////////////////////////////////////
private static float lerpClamped(final float from, final float to, final float delta) {
if (from < to) {
return Math.min(from + delta, to);
} else if (from > to) {
return Math.max(from - delta, to);
} else {
return from;
}
}
private static float remapFrom01To(final float x, final float a1, final float b1) {
if (a1 == b1) {
return a1;
} else {
return x * (b1 - a1) + a1;
}
}
///////////////////////////////////////////////////////////////////
public final class AnimationState {
private static final float TOP_IDLE_Y = -2f / 16f;
private static final float BASE_IDLE_Y = -1f / 16f;
private static final float TRANSLATION_SPEED = 0.005f;
private static final float ROTATION_SPEED = 1f;
private static final float MAX_ROTATION = 5f;
private static final float MIN_ROTATION_SPEED = 0.055f;
private static final float MAX_ROTATION_SPEED = 0.060f;
private static final float HOVER_ANIMATION_SPEED = 0.01f;
public float topRenderOffsetY = TOP_IDLE_Y;
public float baseRenderOffsetY = BASE_IDLE_Y;
public float topRenderRotationY;
public float topRenderTargetRotationY;
public float topRenderRotationSpeed;
public float topRenderHover = -(hashCode() & 0xFFFF); // init to "random" to avoid synchronous hovering
public void update(final float deltaTime, final Random random) {
if (isRunning() || commandProcessor.hasQueuedActions()) {
topRenderHover = topRenderHover + deltaTime * HOVER_ANIMATION_SPEED;
final float topOffsetY = MathHelper.sin(topRenderHover) / 32f;
topRenderOffsetY = lerpClamped(topRenderOffsetY, topOffsetY, deltaTime * TRANSLATION_SPEED);
baseRenderOffsetY = lerpClamped(baseRenderOffsetY, topOffsetY, deltaTime * TRANSLATION_SPEED);
topRenderRotationY = lerpClamped(topRenderRotationY, topRenderTargetRotationY, deltaTime * topRenderRotationSpeed);
if (topRenderRotationY == topRenderTargetRotationY) {
topRenderTargetRotationY = remapFrom01To(random.nextFloat(), -MAX_ROTATION, MAX_ROTATION);
topRenderRotationSpeed = remapFrom01To(random.nextFloat(), MIN_ROTATION_SPEED, MAX_ROTATION_SPEED);
}
} else {
topRenderOffsetY = lerpClamped(topRenderOffsetY, TOP_IDLE_Y, deltaTime * TRANSLATION_SPEED * 2);
baseRenderOffsetY = lerpClamped(baseRenderOffsetY, BASE_IDLE_Y, deltaTime * TRANSLATION_SPEED);
topRenderRotationY = lerpClamped(topRenderRotationY, 0, deltaTime * ROTATION_SPEED);
}
}
}
private final class CommandProcessor {
private static final String QUEUE_TAG_NAME = "queue";
private static final String ACTION_TAG_NAME = "action";
private final Queue<AbstractRobotAction> queue = new ArrayDeque<>(MAX_QUEUED_ACTIONS);
@Nullable private AbstractRobotAction action;
public boolean hasQueuedActions() {
return action != null || !queue.isEmpty();
}
public boolean move(final MovementDirection direction) {
return addAction(new RobotMovementAction(direction));
}
public boolean rotate(final RotationDirection direction) {
return addAction(new RobotRotationAction(direction));
}
public void tick() {
if (getEntityWorld().isRemote()) {
RobotActions.performClient(RobotEntity.this);
} else {
if (action != null) {
if (action.perform(RobotEntity.this)) {
action = null;
}
}
if (action == null) {
action = queue.poll();
if (action != null) {
action.initialize(RobotEntity.this);
}
}
}
}
public void clear() {
queue.clear();
}
public CompoundNBT serialize() {
final CompoundNBT tag = new CompoundNBT();
final ListNBT queueTag = new ListNBT();
for (final AbstractRobotAction action : queue) {
queueTag.add(RobotActions.serialize(action));
}
tag.put(QUEUE_TAG_NAME, queueTag);
if (action != null) {
tag.put(ACTION_TAG_NAME, RobotActions.serialize(action));
}
return tag;
}
public void deserialize(final CompoundNBT tag) {
queue.clear();
action = null;
final ListNBT queueTag = tag.getList(QUEUE_TAG_NAME, NBTTagIds.TAG_COMPOUND);
for (int i = 0; i < queueTag.size(); i++) {
final AbstractRobotAction action = RobotActions.deserialize(queueTag.getCompound(i));
if (action != null) {
queue.add(action);
}
}
if (tag.contains(ACTION_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
action = RobotActions.deserialize(tag.getCompound(ACTION_TAG_NAME));
action.initialize(RobotEntity.this);
}
}
///////////////////////////////////////////////////////////////////
private boolean addAction(final AbstractRobotAction action) {
if (getEntityWorld().isRemote()) {
return false;
}
if (!isRunning()) {
return false;
}
if (queue.size() < MAX_QUEUED_ACTIONS) {
queue.add(action);
return true;
} else {
return false;
}
}
}
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.common.entity;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -0,0 +1,39 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.nbt.CompoundNBT;
public abstract class AbstractRobotAction {
private final AbstractRobotActionType type;
///////////////////////////////////////////////////////////////////
public AbstractRobotAction(final AbstractRobotActionType type) {
this.type = type;
}
public AbstractRobotAction(final AbstractRobotActionType type, final CompoundNBT tag) {
this(type);
deserialize(tag);
}
///////////////////////////////////////////////////////////////////
public AbstractRobotActionType getType() {
return type;
}
public void initialize(final RobotEntity robot) {
}
public abstract boolean perform(RobotEntity robot);
public CompoundNBT serialize() {
return new CompoundNBT();
}
///////////////////////////////////////////////////////////////////
protected void deserialize(final CompoundNBT tag) {
}
}

View File

@@ -0,0 +1,32 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.EntityDataManager;
public abstract class AbstractRobotActionType {
private final int id;
///////////////////////////////////////////////////////////////////
protected AbstractRobotActionType(final int id) {
this.id = id;
}
///////////////////////////////////////////////////////////////////
public int getId() {
return id;
}
public void registerData(final EntityDataManager dataManager) {
}
public void initializeData(final RobotEntity robot) {
}
public void performClient(final RobotEntity robot) {
}
public abstract AbstractRobotAction deserialize(final CompoundNBT tag);
}

View File

@@ -0,0 +1,8 @@
package li.cil.oc2.common.entity.robot;
public enum MovementDirection {
UP,
DOWN,
FORWARD,
BACKWARD,
}

View File

@@ -0,0 +1,65 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.EntityDataManager;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.function.IntFunction;
public final class RobotActions {
private static final String ACTION_TYPE_TAG_NAME = "action_type";
private static final ArrayList<AbstractRobotActionType> ACTIONS = new ArrayList<>();
///////////////////////////////////////////////////////////////////
public static final AbstractRobotActionType MOVEMENT = register(RobotMovementActionType::new);
public static final AbstractRobotActionType ROTATION = register(RobotRotationActionType::new);
///////////////////////////////////////////////////////////////////
public static void registerData(final EntityDataManager dataManager) {
for (final AbstractRobotActionType type : ACTIONS) {
type.registerData(dataManager);
}
}
public static void initializeData(final RobotEntity robot) {
for (final AbstractRobotActionType type : ACTIONS) {
type.initializeData(robot);
}
}
public static void performClient(final RobotEntity robot) {
for (final AbstractRobotActionType type : ACTIONS) {
type.performClient(robot);
}
}
public static CompoundNBT serialize(final AbstractRobotAction action) {
final CompoundNBT actionTag = action.serialize();
actionTag.putInt(ACTION_TYPE_TAG_NAME, action.getType().getId());
return actionTag;
}
@Nullable
public static AbstractRobotAction deserialize(final CompoundNBT tag) {
final int type = tag.getInt(ACTION_TYPE_TAG_NAME);
if (type < 1 || type > ACTIONS.size()) {
return null;
}
final AbstractRobotActionType actionType = ACTIONS.get(type - 1);
return actionType.deserialize(tag);
}
///////////////////////////////////////////////////////////////////
private static AbstractRobotActionType register(final IntFunction<? extends AbstractRobotActionType> factory) {
final AbstractRobotActionType type = factory.apply(ACTIONS.size() + 1);
ACTIONS.add(type);
return type;
}
}

View File

@@ -0,0 +1,142 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.entity.Entities;
import li.cil.oc2.common.entity.RobotEntity;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.NBTUtils;
import net.minecraft.entity.MoverType;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import javax.annotation.Nullable;
public final class RobotMovementAction extends AbstractRobotAction {
public static final DataParameter<BlockPos> TARGET_POSITION = EntityDataManager.createKey(RobotEntity.class, DataSerializers.BLOCK_POS);
public static final double TARGET_EPSILON = 0.0001;
///////////////////////////////////////////////////////////////////
private static final float MOVEMENT_SPEED = 0.5f / Constants.TICK_SECONDS; // In blocks per second.
private static final String DIRECTION_TAG_NAME = "direction";
private static final String START_TAG_NAME = "start";
private static final String TARGET_TAG_NAME = "start";
///////////////////////////////////////////////////////////////////
private MovementDirection direction;
@Nullable private BlockPos start;
@Nullable private Vector3d target;
///////////////////////////////////////////////////////////////////
public RobotMovementAction(final MovementDirection direction) {
super(RobotActions.MOVEMENT);
this.direction = direction;
}
RobotMovementAction(final CompoundNBT tag) {
super(RobotActions.MOVEMENT);
deserialize(tag);
}
///////////////////////////////////////////////////////////////////
public static Vector3d getTargetPositionInBlock(final BlockPos position) {
return Vector3d.copyCenteredHorizontally(position).add(0, 0.5f * (1 - Entities.ROBOT.get().getHeight()), 0);
}
public static void moveTowards(final RobotEntity robot, final Vector3d targetPosition) {
Vector3d delta = targetPosition.subtract(robot.getPositionVec());
if (delta.lengthSquared() > MOVEMENT_SPEED * MOVEMENT_SPEED) {
delta = delta.normalize().scale(MOVEMENT_SPEED);
}
robot.move(MoverType.SELF, delta);
}
@Override
public void initialize(final RobotEntity robot) {
if (target == null) {
start = robot.getPosition();
BlockPos targetPosition = start;
switch (direction) {
case UP:
targetPosition = targetPosition.offset(Direction.UP);
break;
case DOWN:
targetPosition = targetPosition.offset(Direction.DOWN);
break;
case FORWARD:
targetPosition = targetPosition.offset(robot.getHorizontalFacing());
break;
case BACKWARD:
targetPosition = targetPosition.offset(robot.getHorizontalFacing().getOpposite());
break;
}
target = getTargetPositionInBlock(targetPosition);
}
robot.getDataManager().set(TARGET_POSITION, new BlockPos(target));
}
@Override
public boolean perform(final RobotEntity robot) {
if (target == null) {
return true;
}
moveTowards(robot, target);
final boolean didCollide = robot.collidedHorizontally || robot.collidedVertically;
if (didCollide && !robot.getEntityWorld().isRemote()) {
if (start != null) {
target = getTargetPositionInBlock(start);
robot.getDataManager().set(TARGET_POSITION, start);
start = null;
} else {
// todo if it's a block, try to break it. or just drop ourselves?
}
}
return robot.getPositionVec().squareDistanceTo(target) < TARGET_EPSILON;
}
///////////////////////////////////////////////////////////////////
@Override
public CompoundNBT serialize() {
final CompoundNBT tag = super.serialize();
NBTUtils.putEnum(tag, DIRECTION_TAG_NAME, direction);
if (start != null) {
NBTUtils.putBlockPos(tag, START_TAG_NAME, start);
}
if (target != null) {
NBTUtils.putVector3d(tag, TARGET_TAG_NAME, target);
}
return tag;
}
@Override
protected void deserialize(final CompoundNBT tag) {
super.deserialize(tag);
direction = NBTUtils.getEnum(tag, DIRECTION_TAG_NAME, MovementDirection.class);
if (tag.contains(START_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
start = NBTUtils.getBlockPos(tag, START_TAG_NAME);
}
if (tag.contains(TARGET_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
target = NBTUtils.getVector3d(tag, TARGET_TAG_NAME);
}
}
}

View File

@@ -0,0 +1,38 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
public final class RobotMovementActionType extends AbstractRobotActionType {
public RobotMovementActionType(final int id) {
super(id);
}
///////////////////////////////////////////////////////////////////
@Override
public void registerData(final EntityDataManager dataManager) {
dataManager.register(RobotMovementAction.TARGET_POSITION, BlockPos.ZERO);
}
@Override
public void initializeData(final RobotEntity robot) {
robot.getDataManager().set(RobotMovementAction.TARGET_POSITION, robot.getPosition());
}
@Override
public void performClient(final RobotEntity robot) {
final Vector3d target = RobotMovementAction.getTargetPositionInBlock(robot.getDataManager().get(RobotMovementAction.TARGET_POSITION));
if (robot.getPositionVec().squareDistanceTo(target) > RobotMovementAction.TARGET_EPSILON) {
RobotMovementAction.moveTowards(robot, target);
}
}
@Override
public AbstractRobotAction deserialize(final CompoundNBT tag) {
return new RobotMovementAction(tag);
}
}

View File

@@ -0,0 +1,101 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.entity.RobotEntity;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.NBTUtils;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.Direction;
import net.minecraft.util.math.MathHelper;
import javax.annotation.Nullable;
public final class RobotRotationAction extends AbstractRobotAction {
public static final DataParameter<Direction> TARGET_DIRECTION = EntityDataManager.createKey(RobotEntity.class, DataSerializers.DIRECTION);
public static final float TARGET_EPSILON = 0.0001f;
///////////////////////////////////////////////////////////////////
private static final float ROTATION_SPEED = 45f / Constants.TICK_SECONDS; // In degrees per second.
private static final String DIRECTION_TAG_NAME = "direction";
private static final String TARGET_TAG_NAME = "start";
///////////////////////////////////////////////////////////////////
private RotationDirection direction;
@Nullable private Direction target;
///////////////////////////////////////////////////////////////////
public RobotRotationAction(final RotationDirection direction) {
super(RobotActions.ROTATION);
this.direction = direction;
}
RobotRotationAction(final CompoundNBT tag) {
super(RobotActions.ROTATION);
deserialize(tag);
}
///////////////////////////////////////////////////////////////////
public static void rotateTowards(final RobotEntity robot, final Direction targetRotation) {
robot.rotationYaw = MathHelper.approachDegrees(robot.rotationYaw, targetRotation.getHorizontalAngle(), ROTATION_SPEED);
}
@Override
public void initialize(final RobotEntity robot) {
if (target == null) {
target = robot.getHorizontalFacing();
switch (direction) {
case LEFT:
target = target.rotateYCCW();
break;
case RIGHT:
target = target.rotateY();
break;
}
}
robot.getDataManager().set(TARGET_DIRECTION, target);
}
@Override
public boolean perform(final RobotEntity robot) {
if (target == null) {
return true;
}
rotateTowards(robot, target);
return MathHelper.degreesDifferenceAbs(robot.rotationYaw, target.getHorizontalAngle()) < TARGET_EPSILON;
}
///////////////////////////////////////////////////////////////////
@Override
public CompoundNBT serialize() {
final CompoundNBT tag = super.serialize();
NBTUtils.putEnum(tag, DIRECTION_TAG_NAME, direction);
if (target != null) {
NBTUtils.putEnum(tag, TARGET_TAG_NAME, target);
}
return tag;
}
@Override
protected void deserialize(final CompoundNBT tag) {
super.deserialize(tag);
direction = NBTUtils.getEnum(tag, DIRECTION_TAG_NAME, RotationDirection.class);
if (tag.contains(TARGET_TAG_NAME, NBTTagIds.TAG_INT)) {
target = NBTUtils.getEnum(tag, TARGET_TAG_NAME, Direction.class);
}
}
}

View File

@@ -0,0 +1,38 @@
package li.cil.oc2.common.entity.robot;
import li.cil.oc2.common.entity.RobotEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.util.Direction;
import net.minecraft.util.math.MathHelper;
public final class RobotRotationActionType extends AbstractRobotActionType {
public RobotRotationActionType(final int id) {
super(id);
}
///////////////////////////////////////////////////////////////////
@Override
public void registerData(final EntityDataManager dataManager) {
dataManager.register(RobotRotationAction.TARGET_DIRECTION, Direction.NORTH);
}
@Override
public void initializeData(final RobotEntity robot) {
robot.getDataManager().set(RobotRotationAction.TARGET_DIRECTION, robot.getHorizontalFacing());
}
@Override
public void performClient(final RobotEntity robot) {
final Direction target = robot.getDataManager().get(RobotRotationAction.TARGET_DIRECTION);
if (MathHelper.degreesDifferenceAbs(robot.rotationYaw, target.getHorizontalAngle()) > RobotRotationAction.TARGET_EPSILON) {
RobotRotationAction.rotateTowards(robot, target);
}
}
@Override
public AbstractRobotAction deserialize(final CompoundNBT tag) {
return new RobotRotationAction(tag);
}
}

View File

@@ -0,0 +1,6 @@
package li.cil.oc2.common.entity.robot;
public enum RotationDirection {
LEFT,
RIGHT,
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.common.entity.robot;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -30,6 +30,7 @@ public final class Items {
public static final RegistryObject<Item> BUS_INTERFACE_ITEM = register(Constants.BUS_INTERFACE_ITEM_NAME, BusInterfaceItem::new);
public static final RegistryObject<Item> NETWORK_CABLE_ITEM = register(Constants.NETWORK_CABLE_ITEM_NAME, NetworkCableItem::new);
public static final RegistryObject<Item> ROBOT_ITEM = register(Constants.ROBOT_ENTITY_NAME, RobotItem::new);
public static final RegistryObject<Item> MEMORY_ITEM = register(Constants.MEMORY_ITEM_NAME, MemoryItem::new, new Item.Properties());
public static final RegistryObject<Item> HARD_DRIVE_ITEM = register(Constants.HARD_DRIVE_ITEM_NAME, HardDriveItem::new, new Item.Properties());

View File

@@ -0,0 +1,56 @@
package li.cil.oc2.common.item;
import li.cil.oc2.common.entity.Entities;
import li.cil.oc2.common.entity.RobotEntity;
import li.cil.oc2.common.entity.robot.RobotActions;
import li.cil.oc2.common.util.WorldUtils;
import net.minecraft.block.SoundType;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.Item;
import net.minecraft.item.ItemUseContext;
import net.minecraft.stats.Stats;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
public final class RobotItem extends Item {
public RobotItem(final Properties properties) {
super(properties);
}
@Override
public ActionResultType onItemUse(final ItemUseContext context) {
final World world = context.getWorld();
final BlockPos pos = context.getPos();
final Vector3d position;
if (world.getBlockState(pos).isReplaceable(new BlockItemUseContext(context))) {
position = Vector3d.copyCentered(pos);
} else {
position = Vector3d.copyCentered(pos.offset(context.getFace()));
}
final RobotEntity robot = Entities.ROBOT.get().create(context.getWorld());
robot.setLocationAndAngles(position.getX(), position.getY() - robot.getHeight() * 0.5f, position.getZ(),
Direction.fromAngle(context.getPlacementYaw()).getOpposite().getHorizontalAngle(), 0);
if (!world.hasNoCollisions(robot)) {
return super.onItemUse(context);
}
RobotActions.initializeData(robot);
if (!world.isRemote()) {
WorldUtils.playSound(world, new BlockPos(position), SoundType.METAL, SoundType::getPlaceSound);
world.addEntity(robot);
if (!context.getPlayer().isCreative()) {
context.getItem().shrink(1);
}
}
context.getPlayer().addStat(Stats.ITEM_USED.get(this));
return ActionResultType.SUCCESS;
}
}

View File

@@ -2,6 +2,8 @@ package li.cil.oc2.common.util;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraftforge.items.ItemStackHandler;
import javax.annotation.Nullable;
@@ -27,6 +29,40 @@ public final class NBTUtils {
}
}
public static void putBlockPos(final CompoundNBT tag, final String key, final BlockPos value) {
final CompoundNBT valueTag = new CompoundNBT();
valueTag.putInt("x", value.getX());
valueTag.putInt("y", value.getY());
valueTag.putInt("z", value.getZ());
tag.put(key, valueTag);
}
public static BlockPos getBlockPos(final CompoundNBT tag, final String key) {
final CompoundNBT valueTag = tag.getCompound(key);
return new BlockPos(
valueTag.getInt("x"),
valueTag.getInt("y"),
valueTag.getInt("z")
);
}
public static void putVector3d(final CompoundNBT tag, final String key, final Vector3d value) {
final CompoundNBT valueTag = new CompoundNBT();
valueTag.putDouble("x", value.getX());
valueTag.putDouble("y", value.getY());
valueTag.putDouble("z", value.getZ());
tag.put(key, valueTag);
}
public static Vector3d getVector3d(final CompoundNBT tag, final String key) {
final CompoundNBT valueTag = tag.getCompound(key);
return new Vector3d(
valueTag.getDouble("x"),
valueTag.getDouble("y"),
valueTag.getDouble("z")
);
}
public static CompoundNBT makeInventoryTag(final ItemStack... items) {
final ItemStackHandler itemStackHandler = new ItemStackHandler(items.length);
for (int i = 0; i < items.length; i++) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB