Files
oc2r/src/main/java/li/cil/oc2/common/entity/Robot.java
2022-02-03 17:31:33 +01:00

920 lines
34 KiB
Java

package li.cil.oc2.common.entity;
import li.cil.oc2.api.bus.DeviceBusElement;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.DeviceTypes;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.object.Parameter;
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import li.cil.oc2.api.capabilities.TerminalUserProvider;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.bus.AbstractDeviceBusElement;
import li.cil.oc2.common.bus.CommonDeviceBusController;
import li.cil.oc2.common.bus.device.util.Devices;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.container.DeviceItemStackHandler;
import li.cil.oc2.common.container.FixedSizeItemStackHandler;
import li.cil.oc2.common.container.RobotInventoryContainer;
import li.cil.oc2.common.container.RobotTerminalContainer;
import li.cil.oc2.common.energy.FixedEnergyStorage;
import li.cil.oc2.common.entity.robot.*;
import li.cil.oc2.common.integration.Wrenches;
import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.network.message.*;
import li.cil.oc2.common.serialization.NBTSerialization;
import li.cil.oc2.common.util.LevelUtils;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.NBTUtils;
import li.cil.oc2.common.util.TerminalUtils;
import li.cil.oc2.common.vm.*;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Cursor3D;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.network.NetworkHooks;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Consumer;
import static java.util.Collections.singleton;
import static li.cil.oc2.common.Constants.*;
public final class Robot extends Entity implements li.cil.oc2.api.capabilities.Robot, TerminalUserProvider {
public static final EntityDataAccessor<BlockPos> TARGET_POSITION = SynchedEntityData.defineId(Robot.class, EntityDataSerializers.BLOCK_POS);
public static final EntityDataAccessor<Direction> TARGET_DIRECTION = SynchedEntityData.defineId(Robot.class, EntityDataSerializers.DIRECTION);
public static final EntityDataAccessor<Byte> SELECTED_SLOT = SynchedEntityData.defineId(Robot.class, EntityDataSerializers.BYTE);
private static final String TERMINAL_TAG_NAME = "terminal";
private static final String STATE_TAG_NAME = "state";
private static final String BUS_ELEMENT_TAG_NAME = "bus_element";
private static final String DEVICES_TAG_NAME = "devices";
private static final String COMMAND_PROCESSOR_TAG_NAME = "commands";
private static final String INVENTORY_TAG_NAME = "inventory";
private static final String SELECTED_SLOT_TAG_NAME = "selected_slot";
private static final int MAX_QUEUED_ACTIONS = 16;
private static final int MAX_QUEUED_RESULTS = 16;
private static final int MEMORY_SLOTS = 4;
private static final int HARD_DRIVE_SLOTS = 2;
private static final int FLASH_MEMORY_SLOTS = 1;
private static final int MODULE_SLOTS = 4;
private static final int INVENTORY_SIZE = 12;
///////////////////////////////////////////////////////////////////
private final Consumer<ChunkEvent.Unload> chunkUnloadListener = this::handleChunkUnload;
private final Consumer<WorldEvent.Unload> worldUnloadListener = this::handleWorldUnload;
private final BlockPos.MutableBlockPos mutablePosition = new BlockPos.MutableBlockPos();
private final AnimationState animationState = new AnimationState();
private final RobotActionProcessor actionProcessor = new RobotActionProcessor();
private final Terminal terminal = new Terminal();
private final RobotVirtualMachine virtualMachine;
private final RobotBusElement busElement = new RobotBusElement();
private final RobotItemStackHandlers deviceItems = new RobotItemStackHandlers();
private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.robotEnergyStorage);
private final ItemStackHandler inventory = new FixedSizeItemStackHandler(INVENTORY_SIZE);
private final Set<Player> terminalUsers = Collections.newSetFromMap(new WeakHashMap<>());
private long lastPistonMovement;
///////////////////////////////////////////////////////////////////
public Robot(final EntityType<?> type, final Level world) {
super(type, world);
this.blocksBuilding = true;
setNoGravity(true);
final CommonDeviceBusController busController = new CommonDeviceBusController(busElement, Config.robotEnergyPerTick);
virtualMachine = new RobotVirtualMachine(busController);
virtualMachine.state.builtinDevices.rtcMinecraft.setLevel(world);
}
///////////////////////////////////////////////////////////////////
@OnlyIn(Dist.CLIENT)
public AnimationState getAnimationState() {
return animationState;
}
public Terminal getTerminal() {
return terminal;
}
public VirtualMachine getVirtualMachine() {
return virtualMachine;
}
public VMItemStackHandlers getItemStackHandlers() {
return deviceItems;
}
@Override
public ItemStackHandler getInventory() {
return inventory;
}
@Override
public int getSelectedSlot() {
return getEntityData().get(SELECTED_SLOT);
}
@Override
public void setSelectedSlot(final int value) {
getEntityData().set(SELECTED_SLOT, (byte) Mth.clamp(value, 0, INVENTORY_SIZE - 1));
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(final Capability<T> capability, @Nullable final Direction side) {
if (capability == Capabilities.ITEM_HANDLER) {
return LazyOptional.of(() -> inventory).cast();
}
if (capability == Capabilities.ENERGY_STORAGE && Config.robotsUseEnergy()) {
return LazyOptional.of(() -> energy).cast();
}
if (capability == Capabilities.ROBOT) {
return LazyOptional.of(() -> this).cast();
}
final LazyOptional<T> optional = super.getCapability(capability, side);
if (optional.isPresent()) {
return optional;
}
for (final Device device : virtualMachine.busController.getDevices()) {
if (device instanceof final ICapabilityProvider capabilityProvider) {
final LazyOptional<T> value = capabilityProvider.getCapability(capability, side);
if (value.isPresent()) {
return value;
}
}
}
return LazyOptional.empty();
}
public long getLastPistonMovement() {
return lastPistonMovement;
}
public void start() {
if (!level.isClientSide()) {
virtualMachine.start();
}
}
public void stop() {
if (!level.isClientSide()) {
virtualMachine.stop();
}
}
public void openTerminalScreen(final ServerPlayer player) {
RobotTerminalContainer.createServer(this, energy, virtualMachine.busController, player);
}
public void openInventoryScreen(final ServerPlayer player) {
RobotInventoryContainer.createServer(this, energy, virtualMachine.busController, player);
}
public void addTerminalUser(final Player player) {
terminalUsers.add(player);
}
public void removeTerminalUser(final Player player) {
terminalUsers.remove(player);
}
@Override
public Iterable<Player> getTerminalUsers() {
return terminalUsers;
}
public void dropSelf() {
if (!isAlive()) {
return;
}
final ItemStack stack = new ItemStack(Items.ROBOT.get());
exportToItemStack(stack);
spawnAtLocation(stack);
discard();
LevelUtils.playSound(level, blockPosition(), SoundType.METAL, SoundType::getBreakSound);
}
@Override
public void tick() {
final boolean isClient = level.isClientSide();
if (firstTick) {
if (isClient) {
requestInitialState();
} else {
registerListeners();
RobotActions.initializeData(this);
if (actionProcessor.action != null) {
actionProcessor.action.initialize(this);
}
}
}
super.tick();
if (isClient) {
terminal.clientTick();
}
if (!isClient) {
virtualMachine.tick();
}
actionProcessor.tick();
if (!isClient && level instanceof final ServerLevel serverLevel) {
final VoxelShape shape = Shapes.create(getBoundingBox());
final Cursor3D iterator = getBlockPosIterator();
while (iterator.advance()) {
final int x = iterator.nextX();
final int y = iterator.nextY();
final int z = iterator.nextZ();
mutablePosition.set(x, y, z);
final BlockState blockState = serverLevel.getBlockState(mutablePosition);
if (blockState.isAir() ||
blockState.is(Blocks.MOVING_PISTON) ||
blockState.is(Blocks.PISTON_HEAD)) {
continue;
}
final VoxelShape blockShape = blockState.getCollisionShape(serverLevel, mutablePosition);
if (Shapes.joinIsNotEmpty(shape, blockShape.move(x, y, z), BooleanOp.AND)) {
final BlockEntity blockEntity = serverLevel.getBlockEntity(mutablePosition);
final LootContext.Builder builder = new LootContext.Builder(serverLevel)
.withRandom(serverLevel.random)
.withParameter(LootContextParams.THIS_ENTITY, this)
.withParameter(LootContextParams.ORIGIN, position())
.withParameter(LootContextParams.TOOL, ItemStack.EMPTY)
.withParameter(LootContextParams.BLOCK_STATE, blockState)
.withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity);
final List<ItemStack> drops = blockState.getDrops(builder);
serverLevel.setBlockAndUpdate(mutablePosition, Blocks.AIR.defaultBlockState());
for (final ItemStack drop : drops) {
Block.popResource(serverLevel, mutablePosition, drop);
}
}
}
}
}
@Override
public InteractionResult interact(final Player player, final InteractionHand hand) {
final ItemStack stack = player.getItemInHand(hand);
if (!level.isClientSide()) {
if (Wrenches.isWrench(stack)) {
if (player.isShiftKeyDown()) {
dropSelf();
} else if (player instanceof final ServerPlayer serverPlayer) {
openInventoryScreen(serverPlayer);
}
} else {
if (player.isShiftKeyDown()) {
start();
} else if (player instanceof final ServerPlayer serverPlayer) {
openTerminalScreen(serverPlayer);
}
}
}
return InteractionResult.sidedSuccess(level.isClientSide());
}
@Override
public Packet<?> getAddEntityPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}
@Override
public void setRemoved(final RemovalReason reason) {
super.setRemoved(reason);
if (!level.isClientSide()) {
// Full unload to release out-of-nbt persisted runtime-only data such as ram.
virtualMachine.stop();
virtualMachine.dispose();
}
}
@Override
public boolean isPickable() {
return true;
}
@Override
public boolean canCollideWith(final Entity entity) {
return entity != this;
}
@Override
public void push(final Entity entity) {
}
@Override
public boolean canBeCollidedWith() {
return true;
}
@Override
public boolean canSpawnSprintParticle() {
return false;
}
public void exportToItemStack(final ItemStack stack) {
final CompoundTag itemsTag = NBTUtils.getOrCreateChildTag(stack.getOrCreateTag(), MOD_TAG_NAME, ITEMS_TAG_NAME);
deviceItems.saveItems(itemsTag); // Puts one tag per device type, as expected by TooltipUtils.
itemsTag.put(INVENTORY_TAG_NAME, inventory.serializeNBT()); // Won't show up in tooltip.
NBTUtils.getOrCreateChildTag(stack.getOrCreateTag(), MOD_TAG_NAME)
.put(ENERGY_TAG_NAME, energy.serializeNBT());
}
public void importFromItemStack(final ItemStack stack) {
final CompoundTag itemsTag = NBTUtils.getChildTag(stack.getTag(), MOD_TAG_NAME, ITEMS_TAG_NAME);
deviceItems.loadItems(itemsTag);
inventory.deserializeNBT(itemsTag.getCompound(INVENTORY_TAG_NAME));
energy.deserializeNBT(NBTUtils.getChildTag(stack.getTag(), MOD_TAG_NAME, ENERGY_TAG_NAME));
}
///////////////////////////////////////////////////////////////////
@Override
protected void defineSynchedData() {
final SynchedEntityData dataManager = getEntityData();
dataManager.define(TARGET_POSITION, BlockPos.ZERO);
dataManager.define(TARGET_DIRECTION, Direction.NORTH);
dataManager.define(SELECTED_SLOT, (byte) 0);
}
@Override
protected void addAdditionalSaveData(final CompoundTag tag) {
if (virtualMachine.getRunState() != VMRunState.STOPPED) {
tag.put(STATE_TAG_NAME, virtualMachine.serialize());
tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal));
}
tag.put(COMMAND_PROCESSOR_TAG_NAME, actionProcessor.serialize());
tag.put(BUS_ELEMENT_TAG_NAME, busElement.serialize());
tag.put(ITEMS_TAG_NAME, deviceItems.saveItems());
tag.put(DEVICES_TAG_NAME, deviceItems.saveDevices());
tag.put(ENERGY_TAG_NAME, energy.serializeNBT());
tag.put(INVENTORY_TAG_NAME, inventory.serializeNBT());
tag.putByte(SELECTED_SLOT_TAG_NAME, getEntityData().get(SELECTED_SLOT));
}
@Override
protected void readAdditionalSaveData(final CompoundTag tag) {
virtualMachine.deserialize(tag.getCompound(STATE_TAG_NAME));
NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal);
actionProcessor.deserialize(tag.getCompound(COMMAND_PROCESSOR_TAG_NAME));
busElement.deserialize(tag.getCompound(BUS_ELEMENT_TAG_NAME));
deviceItems.loadItems(tag.getCompound(ITEMS_TAG_NAME));
deviceItems.loadDevices(tag.getCompound(DEVICES_TAG_NAME));
energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME));
inventory.deserializeNBT(tag.getCompound(INVENTORY_TAG_NAME));
setSelectedSlot(tag.getByte(SELECTED_SLOT_TAG_NAME));
}
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.NONE;
}
@Override
protected void checkInsideBlocks() {
}
@Override
protected Vec3 limitPistonMovement(final Vec3 pos) {
lastPistonMovement = level.getGameTime();
return super.limitPistonMovement(pos);
}
///////////////////////////////////////////////////////////////////
@OnlyIn(Dist.CLIENT)
private void requestInitialState() {
Network.sendToServer(new RobotInitializationRequestMessage(this));
}
private void registerListeners() {
MinecraftForge.EVENT_BUS.addListener(chunkUnloadListener);
MinecraftForge.EVENT_BUS.addListener(worldUnloadListener);
}
private void unregisterListeners() {
MinecraftForge.EVENT_BUS.unregister(chunkUnloadListener);
MinecraftForge.EVENT_BUS.unregister(worldUnloadListener);
}
private void handleChunkUnload(final ChunkEvent.Unload event) {
if (event.getWorld() != level) {
return;
}
final ChunkPos chunkPos = new ChunkPos(blockPosition());
if (!Objects.equals(chunkPos, event.getChunk().getPos())) {
return;
}
unregisterListeners();
virtualMachine.suspend();
virtualMachine.dispose();
}
private void handleWorldUnload(final WorldEvent.Unload event) {
if (event.getWorld() != level) {
return;
}
unregisterListeners();
virtualMachine.suspend();
virtualMachine.dispose();
}
private Cursor3D getBlockPosIterator() {
final AABB bounds = getBoundingBox();
return new Cursor3D(
Mth.floor(bounds.minX), Mth.floor(bounds.minY), Mth.floor(bounds.minZ),
Mth.floor(bounds.maxX), Mth.floor(bounds.maxY), Mth.floor(bounds.maxZ)
);
}
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 (getVirtualMachine().isRunning() || actionProcessor.hasQueuedActions()) {
topRenderHover = topRenderHover + deltaTime * HOVER_ANIMATION_SPEED;
final float topOffsetY = Mth.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 static final class RobotActionProcessorResult {
private static final String ACTION_ID_TAG_NAME = "action_id";
private static final String RESULT_TAG_NAME = "result";
public int actionId;
public RobotActionResult result;
public RobotActionProcessorResult(final int actionId, final RobotActionResult result) {
this.actionId = actionId;
this.result = result;
}
public RobotActionProcessorResult(final CompoundTag tag) {
deserialize(tag);
}
public CompoundTag serialize() {
final CompoundTag tag = new CompoundTag();
tag.putInt(ACTION_ID_TAG_NAME, actionId);
NBTUtils.putEnum(tag, RESULT_TAG_NAME, result);
return tag;
}
public void deserialize(final CompoundTag tag) {
actionId = tag.getInt(ACTION_ID_TAG_NAME);
result = NBTUtils.getEnum(tag, RESULT_TAG_NAME, RobotActionResult.class);
}
}
private final class RobotActionProcessor {
private static final String QUEUE_TAG_NAME = "queue";
private static final String ACTION_TAG_NAME = "action";
private static final String RESULTS_TAG_NAME = "results";
private static final String LAST_ACTION_ID_TAG_NAME = "last_action_id";
private final Queue<AbstractRobotAction> queue = new ArrayDeque<>(MAX_QUEUED_ACTIONS - 1);
@Nullable private AbstractRobotAction action;
private final Queue<RobotActionProcessorResult> results = new ArrayDeque<>(MAX_QUEUED_RESULTS);
private int lastActionId;
public boolean hasQueuedActions() {
return action != null || !queue.isEmpty();
}
public int getQueuedActionCount() {
return (action != null ? 1 : 0) + queue.size();
}
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 (level.isClientSide()) {
RobotActions.performClient(Robot.this);
} else {
if (action != null) {
final RobotActionResult result = action.perform(Robot.this);
if (result != RobotActionResult.INCOMPLETE) {
synchronized (results) {
if (results.size() == MAX_QUEUED_RESULTS) {
results.remove();
}
results.add(new RobotActionProcessorResult(action.getId(), result));
}
action = null;
}
}
if (action == null) {
action = queue.poll();
if (action != null) {
action.initialize(Robot.this);
}
}
RobotActions.performServer(Robot.this, action);
}
}
public void clear() {
queue.clear();
results.clear();
lastActionId = 0;
}
public CompoundTag serialize() {
final CompoundTag tag = new CompoundTag();
final ListTag queueTag = new ListTag();
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));
}
final ListTag resultsTag = new ListTag();
for (final RobotActionProcessorResult result : results) {
resultsTag.add(result.serialize());
}
tag.put(RESULTS_TAG_NAME, resultsTag);
tag.putInt(LAST_ACTION_ID_TAG_NAME, lastActionId);
return tag;
}
public void deserialize(final CompoundTag tag) {
queue.clear();
results.clear();
final ListTag queueTag = tag.getList(QUEUE_TAG_NAME, NBTTagIds.TAG_COMPOUND);
for (int i = 0; i < Math.min(queueTag.size(), MAX_QUEUED_ACTIONS - 1); i++) {
final AbstractRobotAction action = RobotActions.deserialize(queueTag.getCompound(i));
if (action != null) {
queue.add(action);
}
}
action = RobotActions.deserialize(tag.getCompound(ACTION_TAG_NAME));
final ListTag resultsTag = tag.getList(RESULTS_TAG_NAME, NBTTagIds.TAG_COMPOUND);
for (int i = 0; i < Math.min(resultsTag.size(), MAX_QUEUED_RESULTS); i++) {
final RobotActionProcessorResult result = new RobotActionProcessorResult(resultsTag.getCompound(i));
if (result.actionId != 0) {
results.add(result);
}
}
lastActionId = tag.getInt(LAST_ACTION_ID_TAG_NAME);
}
private boolean addAction(final AbstractRobotAction action) {
if (level.isClientSide()) {
return false;
}
if (!getVirtualMachine().isRunning()) {
return false;
}
if (queue.size() < MAX_QUEUED_ACTIONS - 1) { // -1 for current action
lastActionId = (lastActionId + 1) & 0x7FFFFFFF; // only positive ids; unlikely to ever wrap, but eh.
action.setId(lastActionId);
synchronized (queue) {
queue.add(action);
}
return true;
} else {
return false;
}
}
}
private final class RobotItemStackHandlers extends AbstractVMItemStackHandlers {
public RobotItemStackHandlers() {
super(
new GroupDefinition(DeviceTypes.MEMORY, MEMORY_SLOTS),
new GroupDefinition(DeviceTypes.HARD_DRIVE, HARD_DRIVE_SLOTS),
new GroupDefinition(DeviceTypes.FLASH_MEMORY, FLASH_MEMORY_SLOTS),
new GroupDefinition(DeviceTypes.ROBOT_MODULE, MODULE_SLOTS)
);
}
@Override
protected ItemDeviceQuery getDeviceQuery(final ItemStack stack) {
return Devices.makeQuery(Robot.this, stack);
}
@Override
protected void onContentsChanged(final DeviceItemStackHandler itemHandler, final int slot) {
if (!level.isClientSide()) {
virtualMachine.busController.scheduleBusScan();
}
}
}
private final class RobotBusElement extends AbstractDeviceBusElement {
private static final String DEVICE_ID_TAG_NAME = "device_id";
private final Device device = new ObjectDevice(new RobotDevice(), "robot");
private UUID deviceId = UUID.randomUUID();
@Override
public Optional<Collection<LazyOptional<DeviceBusElement>>> getNeighbors() {
return Optional.of(singleton(LazyOptional.of(() -> deviceItems.busElement)));
}
@Override
public Collection<Device> getLocalDevices() {
return singleton(device);
}
@Override
public Optional<UUID> getDeviceIdentifier(final Device device) {
if (device == this.device) {
return Optional.of(deviceId);
}
return super.getDeviceIdentifier(device);
}
public CompoundTag serialize() {
final CompoundTag tag = new CompoundTag();
tag.putUUID(DEVICE_ID_TAG_NAME, deviceId);
return tag;
}
public void deserialize(final CompoundTag tag) {
if (tag.hasUUID(DEVICE_ID_TAG_NAME)) {
deviceId = tag.getUUID(DEVICE_ID_TAG_NAME);
}
}
}
private final class RobotVMRunner extends AbstractTerminalVMRunner {
public RobotVMRunner(final AbstractVirtualMachine virtualMachine, final Terminal terminal) {
super(virtualMachine, terminal);
}
@Override
protected void sendTerminalUpdateToClient(final ByteBuffer output) {
Network.sendToClientsTrackingEntity(new RobotTerminalOutputMessage(Robot.this, output), Robot.this);
}
}
private final class RobotVirtualMachine extends AbstractVirtualMachine {
private RobotVirtualMachine(final CommonDeviceBusController busController) {
super(busController);
state.vmAdapter.setBaseAddressProvider(deviceItems::getDeviceAddressBase);
}
@Override
protected boolean consumeEnergy(final int amount, final boolean simulate) {
if (!Config.robotsUseEnergy()) {
return true;
}
if (amount > energy.getEnergyStored()) {
return false;
}
energy.extractEnergy(amount, simulate);
return true;
}
@Override
protected void stopRunnerAndReset() {
super.stopRunnerAndReset();
TerminalUtils.resetTerminal(terminal, output -> Network.sendToClientsTrackingEntity(
new RobotTerminalOutputMessage(Robot.this, output), Robot.this));
actionProcessor.clear();
}
@Override
protected AbstractTerminalVMRunner createRunner() {
return new RobotVMRunner(this, terminal);
}
@Override
protected void handleBusStateChanged(final CommonDeviceBusController.BusState value) {
Network.sendToClientsTrackingEntity(new RobotBusStateMessage(Robot.this, value), Robot.this);
}
@Override
protected void handleRunStateChanged(final VMRunState value) {
Network.sendToClientsTrackingEntity(new RobotRunStateMessage(Robot.this, value), Robot.this);
}
@Override
protected void handleBootErrorChanged(@Nullable final Component value) {
Network.sendToClientsTrackingEntity(new RobotBootErrorMessage(Robot.this, value), Robot.this);
}
}
public final class RobotDevice {
@Callback(synchronize = false)
public int getEnergyStored() {
return energy.getEnergyStored();
}
@Callback(synchronize = false)
public int getEnergyCapacity() {
return energy.getMaxEnergyStored();
}
@Callback(synchronize = false)
public int getSelectedSlot() {
return Robot.this.getSelectedSlot();
}
@Callback(synchronize = false)
public void setSelectedSlot(@Parameter("slot") final int slot) {
Robot.this.setSelectedSlot(slot);
}
@Callback
public ItemStack getStackInSlot(@Parameter("slot") final int slot) {
return inventory.getStackInSlot(slot);
}
@Callback(synchronize = false)
public boolean move(@Parameter("direction") @Nullable final MovementDirection direction) {
if (direction == null) throw new IllegalArgumentException();
return actionProcessor.move(direction);
}
@Callback(synchronize = false)
public boolean turn(@Parameter("direction") @Nullable final RotationDirection direction) {
if (direction == null) throw new IllegalArgumentException();
return actionProcessor.rotate(direction);
}
@Callback(synchronize = false)
public int getLastActionId() {
return actionProcessor.lastActionId;
}
@Callback(synchronize = false)
public int getQueuedActionCount() {
return actionProcessor.getQueuedActionCount();
}
@Nullable
@Callback(synchronize = false)
public RobotActionResult getActionResult(@Parameter("actionId") final int actionId) {
final AbstractRobotAction currentAction = actionProcessor.action;
if (currentAction != null && currentAction.getId() == actionId) {
return RobotActionResult.INCOMPLETE;
}
synchronized (actionProcessor.queue) {
for (final AbstractRobotAction action : actionProcessor.queue) {
if (action.getId() == actionId) {
return RobotActionResult.INCOMPLETE;
}
}
}
synchronized (actionProcessor.results) {
for (final RobotActionProcessorResult result : actionProcessor.results) {
if (result.actionId == actionId) {
return result.result;
}
}
}
return null;
}
private RobotDevice() {
}
}
}