Added network card, connectors and cables.

This commit is contained in:
Florian Nücke
2021-01-08 20:02:17 +01:00
parent ef895a4bf9
commit e0047dc58a
36 changed files with 1373 additions and 12 deletions

View File

@@ -0,0 +1,50 @@
package li.cil.oc2.api.bus.device.capabilities;
import li.cil.oc2.api.bus.device.ItemDevice;
import javax.annotation.Nullable;
/**
* This interface provides interaction with the network bus.
* <p>
* Network connectors will check for this capability on blocks they are placed on.
* If found, they will actively poll frames via {@link #readEthernetFrame()} and push
* forwarded frames via {@link #writeEthernetFrame(NetworkInterface, byte[], int)}.
* <p>
* As with all capabilities, this capability can be provided by {@link ItemDevice}s.
*/
public interface NetworkInterface {
/**
* Tries to read an ethernet frame from this network interface.
* <p>
* The frame <em>should</em>> be a Layer 2 Ethernet frame.
* <p>
* When no data is available, {@code null} should be returned.
*
* @return a pending frame or {@code null}.
*/
@Nullable
byte[] readEthernetFrame();
/**
* Tries to write an ethernet frame to this network interface.
* <p>
* The frame <em>should</em> be a Layer 2 Ethernet frame, but this is
* not guaranteed. Implementations should not rely on this, and if relying
* on this at least add appropriate validation and discard the frame otherwise.
* <p>
* If the device is not ready to receive data, it may ignore the call.
* <p>
* The {@code timeToLive} parameter is not to be confused with the IP protocol's
* TTL field. This parameter is used when pushing frames through the network bus
* to prevent infinite loops in case of cycles. Pure consumers can ignore this
* argument. Any implementation forwarding directly (by pushing it to some other
* {@link NetworkInterface} implementation) should call {@code writeEthernetFrame}
* with the time to live reduced by some value, usually by one.
*
* @param source the device that last forwarded the frame.
* @param frame the frame offered to the network interface.
* @param timeToLive the number of hops remaining before the frame should be discarded.
*/
void writeEthernetFrame(NetworkInterface source, byte[] frame, final int timeToLive);
}

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.client;
import li.cil.oc2.client.gui.ComputerContainerScreen;
import li.cil.oc2.client.renderer.NetworkCableRenderer;
import li.cil.oc2.client.renderer.tileentity.ComputerTileEntityRenderer;
import li.cil.oc2.common.container.Containers;
import li.cil.oc2.common.tileentity.TileEntities;
@@ -10,6 +11,8 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
public final class ClientSetup {
public static void run(final FMLClientSetupEvent event) {
NetworkCableRenderer.initialize();
ScreenManager.registerFactory(Containers.COMPUTER_CONTAINER.get(), ComputerContainerScreen::new);
ClientRegistry.bindTileEntityRenderer(TileEntities.COMPUTER_TILE_ENTITY.get(), ComputerTileEntityRenderer::new);

View File

@@ -0,0 +1,230 @@
package li.cil.oc2.client.renderer;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.culling.ClippingHelper;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
public final class NetworkCableRenderer {
private static final int MAX_RENDER_DISTANCE = 100;
private static final int CABLE_VERTEX_COUNT = 9;
private static final float CABLE_THICKNESS = 0.025f;
private static final float CABLE_HANG_MIN = 0.1f;
private static final float CABLE_HANG_MAX = 0.5f;
private static final float CABLE_MAX_LENGTH = 8f;
private static final Vector3f CABLE_COLOR = new Vector3f(0.0f, 0.33f, 0.4f);
private static final ArrayList<NetworkConnectorTileEntity> connectors = new ArrayList<>();
private static final ArrayList<Connection> connections = new ArrayList<>();
private static boolean isDirty;
///////////////////////////////////////////////////////////////////
public static void initialize() {
MinecraftForge.EVENT_BUS.addListener(NetworkCableRenderer::handleRenderWorld);
}
public static void addNetworkConnector(final NetworkConnectorTileEntity connector) {
connectors.add(connector);
invalidateConnections();
}
public static void invalidateConnections() {
isDirty = true;
}
@SubscribeEvent
public static void handleRenderWorld(final RenderWorldLastEvent event) {
validateConnectors();
validatePairs();
if (connections.isEmpty()) {
return;
}
final World world = Minecraft.getInstance().world;
if (world == null) {
return;
}
final MatrixStack matrixStack = event.getMatrixStack();
final ActiveRenderInfo activeRenderInfo = Minecraft.getInstance().gameRenderer.getActiveRenderInfo();
final Vector3d eye = activeRenderInfo.getProjectedView();
final ClippingHelper frustum = new ClippingHelper(matrixStack.getLast().getMatrix(), event.getProjectionMatrix());
frustum.setCameraPosition(eye.getX(), eye.getY(), eye.getZ());
matrixStack.push();
matrixStack.translate(-eye.getX(), -eye.getY(), -eye.getZ());
final Matrix4f viewMatrix = matrixStack.getLast().getMatrix();
final RenderType renderType = OpenComputersRenderType.getNetworkCable();
final IRenderTypeBuffer.Impl bufferSource = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
final float r = CABLE_COLOR.getX();
final float g = CABLE_COLOR.getY();
final float b = CABLE_COLOR.getZ();
for (final Connection connection : connections) {
final Vector3d p0 = connection.from;
final Vector3d p1 = connection.to;
if (!p0.isWithinDistanceOf(eye, MAX_RENDER_DISTANCE) && !p1.isWithinDistanceOf(eye, MAX_RENDER_DISTANCE)) {
continue;
}
// We may easily get false positives here for diagonal cables, but it's good enough for now.
if (!frustum.isBoundingBoxInFrustum(connection.bounds)) {
continue;
}
final Vector3d p2 = addNoisyMovement(lerp(p0, p1, 0.5f).subtract(0, computeCableHang(p0, p1), 0), connection.hashCode());
final IVertexBuilder buffer = bufferSource.getBuffer(renderType);
for (int i = 0; i < CABLE_VERTEX_COUNT; i++) {
final float t = i / (CABLE_VERTEX_COUNT - 1f);
final Vector3d p = quadraticBezier(p0, p1, p2, t);
final Vector3d n = getExtrusionVector(eye, p, connection.forward);
final BlockPos blockPos = new BlockPos(p);
final int blockLight = world.getLightFor(LightType.BLOCK, blockPos);
final int skyLight = world.getLightFor(LightType.SKY, blockPos);
final int packedLight = LightTexture.packLight(blockLight, skyLight);
final Vector3f v0 = new Vector3f(p.subtract(n));
final Vector3f v1 = new Vector3f(p.add(n));
buffer.pos(viewMatrix, v0.getX(), v0.getY(), v0.getZ())
.color(r, g, b, 1f)
.lightmap(packedLight)
.endVertex();
buffer.pos(viewMatrix, v1.getX(), v1.getY(), v1.getZ())
.color(r, g, b, 1f)
.lightmap(packedLight)
.endVertex();
}
bufferSource.finish(renderType);
}
matrixStack.pop();
}
///////////////////////////////////////////////////////////////////
private static Vector3d lerp(final Vector3d a, final Vector3d b, final float t) {
return a.add(b.subtract(a).scale(t)); // a + (b - a)*t = a*(1-t) + b*t
}
private static Vector3d quadraticBezier(final Vector3d a, final Vector3d b, final Vector3d c, final float t) {
final Vector3d a1 = lerp(a, c, t);
final Vector3d b1 = lerp(c, b, t);
return lerp(a1, b1, t);
}
private static Vector3d getExtrusionVector(final Vector3d eye, final Vector3d v, final Vector3d forward) {
return forward.crossProduct(eye.subtract(v)).normalize().scale(CABLE_THICKNESS);
}
private static float computeCableHang(final Vector3d a, final Vector3d b) {
final double length = a.distanceTo(b);
final double hangFactor = MathHelper.clamp(length / CABLE_MAX_LENGTH, 0, 1);
return (float) (CABLE_HANG_MIN + (CABLE_HANG_MAX - CABLE_HANG_MIN) * hangFactor);
}
private static Vector3d addNoisyMovement(final Vector3d c, final int seed) {
final float relTime = ((System.currentTimeMillis() + seed) % 10000) / 10000f;
final float relRadialTime = relTime * 2 * (float) Math.PI;
return c.add(0.1f * MathHelper.cos(relRadialTime), 0.025f + 0.025f * MathHelper.sin(relRadialTime), 0.1f * MathHelper.cos(relRadialTime));
}
private static void validateConnectors() {
for (int i = connectors.size() - 1; i >= 0; i--) {
final NetworkConnectorTileEntity connector = connectors.get(i);
if (connector.isRemoved()) {
connectors.remove(i);
invalidateConnections();
}
}
}
private static void validatePairs() {
if (!isDirty) {
return;
}
isDirty = false;
connections.clear();
final HashSet<Connection> seen = new HashSet<>();
for (final NetworkConnectorTileEntity connector : connectors) {
final BlockPos position = connector.getPos();
for (final BlockPos connectedPosition : connector.getConnectedPositions()) {
final Connection connection = new Connection(position, connectedPosition);
if (seen.add(connection)) {
connections.add(connection);
}
}
}
}
///////////////////////////////////////////////////////////////////
private static final class Connection {
public final BlockPos fromPos, toPos;
public final Vector3d from, to, forward;
public final AxisAlignedBB bounds;
private Connection(final BlockPos fromPos, final BlockPos toPos) {
if (fromPos.compareTo(toPos) > 0) {
this.fromPos = toPos;
this.toPos = fromPos;
} else {
this.fromPos = fromPos;
this.toPos = toPos;
}
from = Vector3d.copyCentered(fromPos);
to = Vector3d.copyCentered(toPos);
forward = to.subtract(from).normalize();
bounds = new AxisAlignedBB(from, to).grow(0, CABLE_HANG_MAX, 0);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Connection that = (Connection) o;
return fromPos.equals(that.fromPos) && toPos.equals(that.toPos);
}
@Override
public int hashCode() {
return Objects.hash(fromPos, toPos);
}
}
}

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.client.renderer;
import li.cil.oc2.api.API;
import net.minecraft.client.renderer.RenderState;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
@@ -23,6 +24,19 @@ public abstract class OpenComputersRenderType extends RenderType {
state);
}
public static RenderType getNetworkCable() {
final State state = State.getBuilder()
.transparency(RenderState.NO_TRANSPARENCY)
.diffuseLighting(RenderState.DIFFUSE_LIGHTING_ENABLED)
.lightmap(RenderState.LIGHTMAP_ENABLED)
.build(false);
return RenderType.makeType(API.MOD_ID + ":network_cable",
DefaultVertexFormats.POSITION_COLOR_LIGHTMAP,
GL11.GL_QUAD_STRIP,
256,
state);
}
///////////////////////////////////////////////////////////////////
private OpenComputersRenderType(final String name, final VertexFormat format, final int drawMode, final int bufferSize, final boolean useDelegate, final boolean needsSorting, final Runnable setupTask, final Runnable clearTask) {

View File

@@ -16,17 +16,22 @@ public final class Constants {
public static final String COMPUTER_BLOCK_NAME = "computer";
public static final String BUS_CABLE_BLOCK_NAME = "bus_cable";
public static final String NETWORK_CONNECTOR_BLOCK_NAME = "network_connector";
public static final String REDSTONE_INTERFACE_BLOCK_NAME = "redstone_interface";
public static final String SCREEN_BLOCK_NAME = "screen";
///////////////////////////////////////////////////////////////////
public static final String WRENCH_ITEM_NAME = "wrench";
public static final String BUS_INTERFACE_ITEM_NAME = "bus_interface";
public static final String NETWORK_CABLE_NAME = "network_cable";
public static final String FLASH_MEMORY_ITEM_NAME = "flash_memory";
public static final String MEMORY_ITEM_NAME = "memory";
public static final String HARD_DRIVE_ITEM_NAME = "hard_drive";
public static final String REDSTONE_INTERFACE_CARD_NAME = "redstone_interface_card";
public static final String NETWORK_INTERFACE_CARD_NAME = "network_interface_card";
///////////////////////////////////////////////////////////////////
@@ -46,4 +51,10 @@ public final class Constants {
public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete";
public static final String COMPUTER_BUS_STATE_TOO_COMPLEX = "gui.oc2.computer.bus_state.too_complex";
public static final String COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS = "gui.oc2.computer.bus_state.multiple_controllers";
///////////////////////////////////////////////////////////////////
public static final String CONNECTOR_ERROR_FULL = "message.oc2.connector.error.full";
public static final String CONNECTOR_ERROR_TOO_FAR = "message.oc2.connector.error.too_far";
public static final String CONNECTOR_ERROR_OBSTRUCTED = "message.oc2.connector.error.obstructed";
}

View File

@@ -15,6 +15,7 @@ public final class Blocks {
public static final RegistryObject<ComputerBlock> COMPUTER_BLOCK = BLOCKS.register(Constants.COMPUTER_BLOCK_NAME, ComputerBlock::new);
public static final RegistryObject<BusCableBlock> BUS_CABLE_BLOCK = BLOCKS.register(Constants.BUS_CABLE_BLOCK_NAME, BusCableBlock::new);
public static final RegistryObject<NetworkConnectorBlock> NETWORK_CONNECTOR_BLOCK = BLOCKS.register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, NetworkConnectorBlock::new);
public static final RegistryObject<RedstoneInterfaceBlock> REDSTONE_INTERFACE_BLOCK = BLOCKS.register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, RedstoneInterfaceBlock::new);
public static final RegistryObject<ScreenBlock> SCREEN_BLOCK = BLOCKS.register(Constants.SCREEN_BLOCK_NAME, ScreenBlock::new);

View File

@@ -0,0 +1,101 @@
package li.cil.oc2.common.block;
import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity;
import li.cil.oc2.common.tileentity.TileEntities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.HorizontalFaceBlock;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.AttachFace;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Objects;
public final class NetworkConnectorBlock extends HorizontalFaceBlock {
private static final VoxelShape NEG_Z_SHAPE = Block.makeCuboidShape(5, 5, 7, 11, 11, 16);
private static final VoxelShape POS_Z_SHAPE = Block.makeCuboidShape(5, 5, 0, 11, 11, 9);
private static final VoxelShape NEG_X_SHAPE = Block.makeCuboidShape(7, 5, 5, 16, 11, 11);
private static final VoxelShape POS_X_SHAPE = Block.makeCuboidShape(0, 5, 5, 9, 11, 11);
private static final VoxelShape NEG_Y_SHAPE = Block.makeCuboidShape(5, 0, 5, 11, 9, 11);
private static final VoxelShape POS_Y_SHAPE = Block.makeCuboidShape(5, 7, 5, 11, 16, 11);
///////////////////////////////////////////////////////////////////
public NetworkConnectorBlock() {
super(Properties
.create(Material.IRON)
.sound(SoundType.METAL)
.hardnessAndResistance(1.5f, 6.0f));
setDefaultState(getStateContainer().getBaseState()
.with(HORIZONTAL_FACING, Direction.NORTH)
.with(FACE, AttachFace.WALL));
}
///////////////////////////////////////////////////////////////////
public static Direction getFacing(final BlockState state) {
return HorizontalFaceBlock.getFacing(state);
}
@Override
public boolean hasTileEntity(final BlockState state) {
return true;
}
@Nullable
@Override
public TileEntity createTileEntity(final BlockState state, final IBlockReader world) {
return TileEntities.NETWORK_CONNECTOR_TILE_ENTITY.get().create();
}
@SuppressWarnings("deprecation")
@Override
public void neighborChanged(final BlockState state, final World world, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) {
if (Objects.equals(changedBlockPos, pos.offset(getFacing(state).getOpposite()))) {
final TileEntity tileEntity = world.getTileEntity(pos);
if (tileEntity instanceof NetworkConnectorTileEntity) {
final NetworkConnectorTileEntity connector = (NetworkConnectorTileEntity) tileEntity;
connector.setLocalInterfaceChanged();
}
}
}
@SuppressWarnings("deprecation")
@Override
public VoxelShape getShape(final BlockState state, final IBlockReader world, final BlockPos pos, final ISelectionContext context) {
switch (state.get(FACE)) {
case WALL:
switch (state.get(HORIZONTAL_FACING)) {
case EAST:
return POS_X_SHAPE;
case WEST:
return NEG_X_SHAPE;
case SOUTH:
return POS_Z_SHAPE;
case NORTH:
default:
return NEG_Z_SHAPE;
}
case CEILING:
return POS_Y_SHAPE;
case FLOOR:
default:
return NEG_Y_SHAPE;
}
}
///////////////////////////////////////////////////////////////////
protected void fillStateContainer(final StateContainer.Builder<Block, BlockState> builder) {
builder.add(FACE, HORIZONTAL_FACING);
}
}

View File

@@ -0,0 +1,149 @@
package li.cil.oc2.common.bus.device.item;
import li.cil.oc2.api.bus.device.ItemDevice;
import li.cil.oc2.api.bus.device.capabilities.NetworkInterface;
import li.cil.oc2.api.bus.device.vm.*;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
import li.cil.oc2.common.bus.device.util.OptionalInterrupt;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.serialization.NBTSerialization;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.sedna.device.virtio.VirtIONetworkDevice;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice, ICapabilityProvider {
private static final String DEVICE_TAG_NAME = "device";
private static final String ADDRESS_NBT_TAG_NAME = "address";
private static final String INTERRUPT_NBT_TAG_NAME = "interrupt";
///////////////////////////////////////////////////////////////
private VirtIONetworkDevice device;
private final NetworkInterface networkInterface = new NetworkInterfaceImpl();
private boolean isRunning;
private final OptionalAddress address = new OptionalAddress();
private final OptionalInterrupt interrupt = new OptionalInterrupt();
private CompoundNBT deviceNbt;
///////////////////////////////////////////////////////////////
public NetworkInterfaceCardItemDevice(final ItemStack identity) {
super(identity);
}
///////////////////////////////////////////////////////////////
@NotNull
@Override
public <T> LazyOptional<T> getCapability(@NotNull final Capability<T> cap, @Nullable final Direction side) {
if (cap == Capabilities.NETWORK_INTERFACE && side != null) {
return LazyOptional.of(() -> networkInterface).cast();
}
return LazyOptional.empty();
}
@Override
public VMDeviceLoadResult load(final VMContext context) {
device = new VirtIONetworkDevice(context.getMemoryMap());
if (!address.claim(context, device)) {
return VMDeviceLoadResult.fail();
}
if (interrupt.claim(context)) {
device.getInterrupt().set(interrupt.getAsInt(), context.getInterruptController());
} else {
return VMDeviceLoadResult.fail();
}
if (deviceNbt != null) {
NBTSerialization.deserialize(deviceNbt, device);
}
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case RESUMED_RUNNING:
isRunning = true;
break;
case UNLOAD:
unload();
break;
}
}
@Override
public CompoundNBT serializeNBT() {
final CompoundNBT tag = new CompoundNBT();
if (device != null) {
deviceNbt = NBTSerialization.serialize(device);
}
if (deviceNbt != null) {
tag.put(DEVICE_TAG_NAME, deviceNbt);
}
if (address.isPresent()) {
tag.putLong(ADDRESS_NBT_TAG_NAME, address.getAsLong());
}
if (interrupt.isPresent()) {
tag.putInt(INTERRUPT_NBT_TAG_NAME, interrupt.getAsInt());
}
return tag;
}
@Override
public void deserializeNBT(final CompoundNBT tag) {
if (tag.contains(DEVICE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
deviceNbt = tag.getCompound(DEVICE_TAG_NAME);
}
if (tag.contains(ADDRESS_NBT_TAG_NAME, NBTTagIds.TAG_LONG)) {
address.set(tag.getLong(ADDRESS_NBT_TAG_NAME));
}
if (tag.contains(INTERRUPT_NBT_TAG_NAME, NBTTagIds.TAG_INT)) {
interrupt.set(tag.getInt(INTERRUPT_NBT_TAG_NAME));
}
}
///////////////////////////////////////////////////////////////
private void unload() {
device = null;
isRunning = false;
address.clear();
interrupt.clear();
}
///////////////////////////////////////////////////////////////
private final class NetworkInterfaceImpl implements NetworkInterface {
@Override
public byte[] readEthernetFrame() {
if (device != null && isRunning) {
return device.readEthernetFrame();
} else {
return null;
}
}
@Override
public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) {
if (device != null && isRunning) {
device.writeEthernetFrame(frame);
}
}
}
}

View File

@@ -4,10 +4,7 @@ import li.cil.oc2.api.API;
import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider;
import li.cil.oc2.api.bus.device.provider.ItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.block.*;
import li.cil.oc2.common.bus.device.provider.item.FlashMemoryItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.item.HardDriveItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.item.MemoryItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.item.RedstoneInterfaceCardItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.item.*;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
@@ -38,6 +35,7 @@ public final class Providers {
ITEM_DEVICE_PROVIDERS.register("item_hard_drive", HardDriveItemDeviceProvider::new);
ITEM_DEVICE_PROVIDERS.register("item_flash_memory", FlashMemoryItemDeviceProvider::new);
ITEM_DEVICE_PROVIDERS.register("item_redstone_interface_card", RedstoneInterfaceCardItemDeviceProvider::new);
ITEM_DEVICE_PROVIDERS.register("item_network_interface_card", NetworkInterfaceCardItemDeviceProvider::new);
BLOCK_DEVICE_PROVIDERS.register(FMLJavaModLoadingContext.get().getModEventBus());
ITEM_DEVICE_PROVIDERS.register(FMLJavaModLoadingContext.get().getModEventBus());

View File

@@ -0,0 +1,23 @@
package li.cil.oc2.common.bus.device.provider.item;
import li.cil.oc2.api.bus.device.ItemDevice;
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import li.cil.oc2.common.bus.device.item.NetworkInterfaceCardItemDevice;
import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider;
import li.cil.oc2.common.item.Items;
import java.util.Optional;
public final class NetworkInterfaceCardItemDeviceProvider extends AbstractItemDeviceProvider {
public NetworkInterfaceCardItemDeviceProvider() {
super(Items.NETWORK_INTERFACE_CARD_ITEM);
}
///////////////////////////////////////////////////////////////////
@Override
protected Optional<ItemDevice> getItemDevice(final ItemDeviceQuery query) {
return query.getContainerTileEntity().map(tileEntity ->
new NetworkInterfaceCardItemDevice(query.getItemStack()));
}
}

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.common.capabilities;
import li.cil.oc2.api.bus.DeviceBusElement;
import li.cil.oc2.api.bus.device.capabilities.NetworkInterface;
import li.cil.oc2.api.bus.device.capabilities.RedstoneEmitter;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
@@ -25,11 +26,15 @@ public final class Capabilities {
@CapabilityInject(RedstoneEmitter.class)
public static Capability<RedstoneEmitter> REDSTONE_EMITTER = null;
@CapabilityInject(NetworkInterface.class)
public static Capability<NetworkInterface> NETWORK_INTERFACE = null;
///////////////////////////////////////////////////////////////////
public static void initialize() {
register(DeviceBusElement.class);
register(RedstoneEmitter.class);
register(NetworkInterface.class);
}
///////////////////////////////////////////////////////////////////

View File

@@ -20,17 +20,22 @@ public final class Items {
public static final RegistryObject<Item> COMPUTER_ITEM = register(Constants.COMPUTER_BLOCK_NAME, Blocks.COMPUTER_BLOCK);
public static final RegistryObject<Item> BUS_CABLE_ITEM = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE_BLOCK);
public static final RegistryObject<Item> NETWORK_CONNECTOR_ITEM = register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, Blocks.NETWORK_CONNECTOR_BLOCK);
public static final RegistryObject<Item> REDSTONE_INTERFACE_ITEM = register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, Blocks.REDSTONE_INTERFACE_BLOCK);
public static final RegistryObject<Item> SCREEN_ITEM = register(Constants.SCREEN_BLOCK_NAME, Blocks.SCREEN_BLOCK);
///////////////////////////////////////////////////////////////////
public static final RegistryObject<Item> BUS_INTERFACE_ITEM = register(Constants.BUS_INTERFACE_ITEM_NAME, BusInterfaceItem::new);
public static final RegistryObject<Item> WRENCH_ITEM = register(Constants.WRENCH_ITEM_NAME, WrenchItem::new);
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_NAME, NetworkCableItem::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());
public static final RegistryObject<Item> FLASH_MEMORY_ITEM = register(Constants.FLASH_MEMORY_ITEM_NAME, FlashMemoryItem::new, new Item.Properties());
public static final RegistryObject<Item> REDSTONE_INTERFACE_CARD_ITEM = register(Constants.REDSTONE_INTERFACE_CARD_NAME);
public static final RegistryObject<Item> NETWORK_INTERFACE_CARD_ITEM = register(Constants.NETWORK_INTERFACE_CARD_NAME);
///////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,113 @@
package li.cil.oc2.common.item;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity;
import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity.ConnectionResult;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUseContext;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import java.util.Objects;
import java.util.WeakHashMap;
public final class NetworkCableItem extends Item {
private static final WeakHashMap<ServerPlayerEntity, BlockPos> LINK_STARTS = new WeakHashMap<>();
///////////////////////////////////////////////////////////////////
public NetworkCableItem(final Properties properties) {
super(properties);
}
///////////////////////////////////////////////////////////////////
@Override
public ActionResult<ItemStack> onItemRightClick(final World world, final PlayerEntity player, final Hand hand) {
if (player.isSneaking()) {
if (player instanceof ServerPlayerEntity) {
LINK_STARTS.remove(player);
}
return ActionResult.resultSuccess(player.getHeldItem(hand));
}
return super.onItemRightClick(world, player, hand);
}
@Override
public ActionResultType onItemUse(final ItemUseContext context) {
final PlayerEntity player = context.getPlayer();
if (player == null) {
return super.onItemUse(context);
}
final ItemStack stack = player.getHeldItem(context.getHand());
if (stack.isEmpty() || stack.getItem() != this) {
return super.onItemUse(context);
}
final World world = context.getWorld();
final BlockPos currentPos = context.getPos();
final TileEntity currentTileEntity = world.getTileEntity(currentPos);
if (!(currentTileEntity instanceof NetworkConnectorTileEntity)) {
return super.onItemUse(context);
}
if (!world.isRemote() && player instanceof ServerPlayerEntity) {
final BlockPos startPos = LINK_STARTS.remove(player);
if (startPos == null || Objects.equals(startPos, currentPos)) {
if (((NetworkConnectorTileEntity) currentTileEntity).canConnectMore()) {
LINK_STARTS.put((ServerPlayerEntity) player, currentPos);
} else {
player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_FULL), true);
}
} else {
final TileEntity startTileEntity = world.getTileEntity(startPos);
if (!(startTileEntity instanceof NetworkConnectorTileEntity)) {
// Starting connector was removed in the meantime.
return super.onItemUse(context);
}
final NetworkConnectorTileEntity connectorA = (NetworkConnectorTileEntity) startTileEntity;
final NetworkConnectorTileEntity connectorB = (NetworkConnectorTileEntity) currentTileEntity;
final ConnectionResult connectionResult = NetworkConnectorTileEntity.connect(connectorA, connectorB);
switch (connectionResult) {
case SUCCESS:
if (!player.isCreative()) {
stack.shrink(1);
}
break;
case FAILURE:
LINK_STARTS.put((ServerPlayerEntity) player, startPos);
break;
case FAILURE_FULL:
LINK_STARTS.put((ServerPlayerEntity) player, startPos);
player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_FULL), true);
break;
case FAILURE_TOO_FAR:
LINK_STARTS.put((ServerPlayerEntity) player, startPos);
player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_TOO_FAR), true);
break;
case FAILURE_OBSTRUCTED:
LINK_STARTS.put((ServerPlayerEntity) player, startPos);
player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_OBSTRUCTED), true);
break;
}
}
}
return ActionResultType.SUCCESS;
}
}

View File

@@ -61,6 +61,12 @@ public final class Network {
.decoder(ComputerPowerMessage::new)
.consumer(ComputerPowerMessage::handleMessage)
.add();
INSTANCE.messageBuilder(NetworkConnectorConnectionsMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(NetworkConnectorConnectionsMessage::toBytes)
.decoder(NetworkConnectorConnectionsMessage::new)
.consumer(NetworkConnectorConnectionsMessage::handleMessage)
.add();
}
public static <T> void sendToClientsTrackingChunk(final T message, final Chunk chunk) {

View File

@@ -0,0 +1,52 @@
package li.cil.oc2.common.network.message;
import li.cil.oc2.common.network.MessageUtils;
import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.network.NetworkEvent;
import java.util.ArrayList;
import java.util.function.Supplier;
public final class NetworkConnectorConnectionsMessage {
private BlockPos pos;
private ArrayList<BlockPos> connectedPositions;
///////////////////////////////////////////////////////////////////
public NetworkConnectorConnectionsMessage(final NetworkConnectorTileEntity connector) {
this.pos = connector.getPos();
this.connectedPositions = new ArrayList<>(connector.getConnectedPositions());
}
public NetworkConnectorConnectionsMessage(final PacketBuffer buffer) {
fromBytes(buffer);
}
///////////////////////////////////////////////////////////////////
public static boolean handleMessage(final NetworkConnectorConnectionsMessage message, final Supplier<NetworkEvent.Context> context) {
context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, NetworkConnectorTileEntity.class,
(tileEntity) -> tileEntity.setConnectedPositionsClient(message.connectedPositions)));
return true;
}
public void fromBytes(final PacketBuffer buffer) {
pos = buffer.readBlockPos();
connectedPositions = new ArrayList<>();
final int positionCount = buffer.readVarInt();
for (int i = 0; i < positionCount; i++) {
final BlockPos pos = buffer.readBlockPos();
connectedPositions.add(pos);
}
}
public static void toBytes(final NetworkConnectorConnectionsMessage message, final PacketBuffer buffer) {
buffer.writeBlockPos(message.pos);
buffer.writeVarInt(message.connectedPositions.size());
for (final BlockPos pos : message.connectedPositions) {
buffer.writeBlockPos(pos);
}
}
}

View File

@@ -0,0 +1,452 @@
package li.cil.oc2.common.tileentity;
import li.cil.oc2.api.bus.device.capabilities.NetworkInterface;
import li.cil.oc2.client.renderer.NetworkCableRenderer;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.block.NetworkConnectorBlock;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.network.message.NetworkConnectorConnectionsMessage;
import li.cil.oc2.common.util.ItemStackUtils;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.util.ServerScheduler;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
public final class NetworkConnectorTileEntity extends AbstractTileEntity implements ITickableTileEntity {
public enum ConnectionResult {
SUCCESS,
FAILURE,
FAILURE_FULL,
FAILURE_TOO_FAR,
FAILURE_OBSTRUCTED,
ALREADY_CONNECTED
}
private static final String CONNECTIONS_TAG_NAME = "connections";
private static final String IS_OWNER_TAG_NAME = "is_owner";
private static final int RETRY_UNLOADED_CHUNK_INTERVAL = 5 * Constants.TICK_SECONDS;
private static final int MAX_CONNECTION_COUNT = 2;
private static final int MAX_CONNECTION_DISTANCE = 16;
private static final int INITIAL_PACKET_TIME_TO_LIVE = 8;
private static final int BYTES_PER_SECOND = 64 * 1024;
private static final int BYTES_PER_TICK = BYTES_PER_SECOND / Constants.TICK_SECONDS;
private static final int MIN_ETHERNET_FRAME_SIZE = 42;
///////////////////////////////////////////////////////////////////
private final NetworkConnectorNetworkInterface networkInterface = new NetworkConnectorNetworkInterface();
private LazyOptional<NetworkInterface> localInterface = LazyOptional.empty();
private boolean isLocalConnectionDirty = true;
private final HashSet<BlockPos> connectorPositions = new HashSet<>();
private final HashSet<BlockPos> ownedCables = new HashSet<>();
private final HashSet<BlockPos> dirtyConnectors = new HashSet<>();
private final HashMap<BlockPos, NetworkConnectorTileEntity> connectors = new HashMap<>();
///////////////////////////////////////////////////////////////////
public NetworkConnectorTileEntity() {
super(TileEntities.NETWORK_CONNECTOR_TILE_ENTITY.get());
}
///////////////////////////////////////////////////////////////////
public static ConnectionResult connect(final NetworkConnectorTileEntity connectorA, final NetworkConnectorTileEntity connectorB) {
if (connectorA == connectorB || connectorA.isRemoved() || connectorB.isRemoved()) {
return ConnectionResult.FAILURE;
}
final World world = connectorA.getWorld();
if (world == null || world.isRemote()) {
return ConnectionResult.FAILURE;
}
if (connectorB.getWorld() != world) {
return ConnectionResult.FAILURE;
}
if (!connectorA.canConnectMore() || !connectorB.canConnectMore()) {
return ConnectionResult.FAILURE_FULL;
}
final BlockPos posA = connectorA.getPos();
final BlockPos posB = connectorB.getPos();
if (!posA.withinDistance(posB, MAX_CONNECTION_DISTANCE)) {
return ConnectionResult.FAILURE_TOO_FAR;
}
if (isObstructed(world, posA, posB)) {
return ConnectionResult.FAILURE_OBSTRUCTED;
}
if (connectorA.connectorPositions.add(posB)) {
connectorA.dirtyConnectors.add(posB);
connectorA.onConnectedPositionsChanged();
}
if (connectorB.connectorPositions.add(posA)) {
connectorB.dirtyConnectors.add(posA);
connectorB.onConnectedPositionsChanged();
}
final ConnectionResult result;
if (connectorA.ownedCables.contains(posB) || connectorB.ownedCables.contains(posA)) {
connectorA.ownedCables.add(posB);
connectorB.ownedCables.remove(posA);
result = ConnectionResult.ALREADY_CONNECTED;
} else {
connectorA.ownedCables.add(posB);
result = ConnectionResult.SUCCESS;
}
connectorA.markDirty();
connectorB.markDirty();
return result;
}
public void disconnectFrom(final BlockPos pos) {
dirtyConnectors.remove(pos);
connectors.remove(pos);
if (ownedCables.remove(pos)) {
final World world = getWorld();
if (world != null) {
final Vector3d middle = Vector3d.copyCentered(getPos().add(pos)).scale(0.5f);
ItemStackUtils.spawnAsEntity(world, middle, new ItemStack(Items.NETWORK_CABLE_ITEM.get()));
}
}
if (!isRemoved()) {
if (connectorPositions.remove(pos)) {
onConnectedPositionsChanged();
}
markDirty();
}
}
public boolean canConnectMore() {
return connectorPositions.size() < MAX_CONNECTION_COUNT;
}
public Collection<BlockPos> getConnectedPositions() {
return connectorPositions;
}
public void setLocalInterfaceChanged() {
isLocalConnectionDirty = true;
}
@OnlyIn(Dist.CLIENT)
public void setConnectedPositionsClient(final ArrayList<BlockPos> positions) {
connectorPositions.clear();
connectorPositions.addAll(positions);
NetworkCableRenderer.invalidateConnections();
}
@Override
public void tick() {
if (isLocalConnectionDirty) {
isLocalConnectionDirty = false;
resolveLocalInterface();
}
if (!dirtyConnectors.isEmpty()) {
final ArrayList<BlockPos> list = new ArrayList<>(dirtyConnectors);
dirtyConnectors.clear();
for (final BlockPos connectedPosition : list) {
resolveConnectedInterface(connectedPosition);
}
}
final NetworkInterface src = localInterface.orElse(NullNetworkInterface.INSTANCE);
int byteBudget = BYTES_PER_TICK;
byte[] frame;
while ((frame = src.readEthernetFrame()) != null && byteBudget > 0) {
byteBudget -= Math.max(frame.length, MIN_ETHERNET_FRAME_SIZE); // Avoid bogus packets messing with us.
networkInterface.writeEthernetFrame(src, frame, INITIAL_PACKET_TIME_TO_LIVE);
}
}
@Override
public CompoundNBT getUpdateTag() {
final CompoundNBT tag = super.getUpdateTag();
final ListNBT connections = new ListNBT();
for (final BlockPos position : connectorPositions) {
final CompoundNBT connectionTag = NBTUtil.writeBlockPos(position);
connections.add(connectionTag);
}
tag.put(CONNECTIONS_TAG_NAME, connections);
return tag;
}
@Override
public void handleUpdateTag(final BlockState state, final CompoundNBT tag) {
super.handleUpdateTag(state, tag);
final ListNBT connections = tag.getList(CONNECTIONS_TAG_NAME, NBTTagIds.TAG_COMPOUND);
for (int i = 0; i < Math.min(connections.size(), MAX_CONNECTION_COUNT); i++) {
final CompoundNBT connectionTag = connections.getCompound(i);
final BlockPos position = NBTUtil.readBlockPos(connectionTag);
connectorPositions.add(position);
dirtyConnectors.add(position);
}
}
@Override
public CompoundNBT write(CompoundNBT tag) {
tag = super.write(tag);
final ListNBT connections = new ListNBT();
for (final BlockPos position : connectorPositions) {
final CompoundNBT connectionTag = NBTUtil.writeBlockPos(position);
if (ownedCables.contains(position)) {
connectionTag.putBoolean(IS_OWNER_TAG_NAME, true);
}
connections.add(connectionTag);
}
tag.put(CONNECTIONS_TAG_NAME, connections);
return tag;
}
@Override
public void read(final BlockState state, final CompoundNBT tag) {
super.read(state, tag);
final ListNBT connections = tag.getList(CONNECTIONS_TAG_NAME, NBTTagIds.TAG_COMPOUND);
for (int i = 0; i < Math.min(connections.size(), MAX_CONNECTION_COUNT); i++) {
final CompoundNBT connectionTag = connections.getCompound(i);
final BlockPos position = NBTUtil.readBlockPos(connectionTag);
connectorPositions.add(position);
dirtyConnectors.add(position);
if (connectionTag.getBoolean(IS_OWNER_TAG_NAME)) {
ownedCables.add(position);
}
}
}
@Override
protected void loadClient() {
super.loadClient();
NetworkCableRenderer.addNetworkConnector(this);
}
@Override
public void remove() {
super.remove();
// When we're being removed we want to break the actual link to any connected
// connectors. This will also cause cables to be dropped.
final ArrayList<NetworkConnectorTileEntity> list = new ArrayList<>(connectors.values());
connectors.clear();
for (final NetworkConnectorTileEntity connector : list) {
disconnectFrom(connector.getPos());
connector.disconnectFrom(getPos());
}
}
@Override
protected void unloadServer() {
super.unloadServer();
// When unloading, we just want to remove the reference to this tile entity
// from connected connectors; we don't want to actually break the link.
final BlockPos pos = getPos();
for (final NetworkConnectorTileEntity connector : connectors.values()) {
connector.connectors.remove(pos);
if (connector.connectorPositions.contains(pos)) {
connector.dirtyConnectors.add(pos);
}
}
}
///////////////////////////////////////////////////////////////////
@Override
protected void collectCapabilities(final CapabilityCollector collector, @org.jetbrains.annotations.Nullable final Direction direction) {
collector.offer(Capabilities.NETWORK_INTERFACE, networkInterface);
}
///////////////////////////////////////////////////////////////////
private void resolveLocalInterface() {
localInterface = LazyOptional.empty();
if (isRemoved()) {
return;
}
final World world = getWorld();
if (world == null || world.isRemote()) {
return;
}
final Direction facing = NetworkConnectorBlock.getFacing(getBlockState());
final BlockPos sourcePos = getPos().offset(facing.getOpposite());
final ChunkPos sourceChunk = new ChunkPos(sourcePos);
if (!world.chunkExists(sourceChunk.x, sourceChunk.z)) {
ServerScheduler.schedule(world, this::setLocalInterfaceChanged, RETRY_UNLOADED_CHUNK_INTERVAL);
return;
}
final TileEntity tileEntity = world.getTileEntity(sourcePos);
if (tileEntity == null) {
return;
}
localInterface = tileEntity.getCapability(Capabilities.NETWORK_INTERFACE, facing);
if (localInterface.isPresent()) {
localInterface.addListener(unused -> setLocalInterfaceChanged());
}
}
private void resolveConnectedInterface(final BlockPos connectedPosition) {
connectors.remove(connectedPosition);
if (isRemoved()) {
return;
}
final World world = getWorld();
if (world == null || world.isRemote()) {
return;
}
final ChunkPos destinationChunk = new ChunkPos(connectedPosition);
if (!world.chunkExists(destinationChunk.x, destinationChunk.z)) {
ServerScheduler.schedule(world, () -> dirtyConnectors.add(connectedPosition), RETRY_UNLOADED_CHUNK_INTERVAL);
return;
}
final TileEntity tileEntity = world.getTileEntity(connectedPosition);
if (!(tileEntity instanceof NetworkConnectorTileEntity)) {
disconnectFrom(connectedPosition);
return;
}
final NetworkConnectorTileEntity connector = (NetworkConnectorTileEntity) tileEntity;
if (!connectedPosition.withinDistance(getPos(), MAX_CONNECTION_DISTANCE)) {
disconnectFrom(connectedPosition);
connector.disconnectFrom(getPos());
return;
}
if (isObstructed(world, getPos(), connectedPosition)) {
disconnectFrom(connectedPosition);
connector.disconnectFrom(getPos());
return;
}
connectors.put(connectedPosition, connector);
}
private static boolean isObstructed(final World world, final BlockPos a, final BlockPos b) {
final Vector3d va = Vector3d.copyCentered(a);
final Vector3d vb = Vector3d.copyCentered(b);
final Vector3d ab = vb.subtract(va).normalize().scale(0.5);
// Because of floating point inaccuracies the raytrace is not necessarily
// symmetric. In particular when grazing corners perfectly, e.g. two connectors
// attached to the same block at a 90 degree angle. So we check both ways.
final BlockRayTraceResult hitAB = world.rayTraceBlocks(new RayTraceContext(
va.add(ab),
vb.subtract(ab),
RayTraceContext.BlockMode.COLLIDER,
RayTraceContext.FluidMode.NONE,
null
));
final BlockRayTraceResult hitBA = world.rayTraceBlocks(new RayTraceContext(
vb.subtract(ab),
va.add(ab),
RayTraceContext.BlockMode.COLLIDER,
RayTraceContext.FluidMode.NONE,
null
));
return hitAB.getType() != RayTraceResult.Type.MISS ||
hitBA.getType() != RayTraceResult.Type.MISS;
}
private void onConnectedPositionsChanged() {
final World world = getWorld();
if (world != null && !world.isRemote()) {
final NetworkConnectorConnectionsMessage message = new NetworkConnectorConnectionsMessage(this);
final Chunk chunk = world.getChunkAt(getPos());
Network.sendToClientsTrackingChunk(message, chunk);
}
}
///////////////////////////////////////////////////////////////////
private static final class NullNetworkInterface implements NetworkInterface {
public static final NetworkInterface INSTANCE = new NullNetworkInterface();
@Override
public byte[] readEthernetFrame() {
return null;
}
@Override
public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) {
}
}
private final class NetworkConnectorNetworkInterface implements NetworkInterface {
@Override
public byte[] readEthernetFrame() {
return null;
}
@Override
public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) {
if (timeToLive <= 0) {
return;
}
localInterface.ifPresent(dst -> {
if (dst == source) {
return;
}
dst.writeEthernetFrame(this, frame, timeToLive - 1);
});
for (final NetworkConnectorTileEntity dst : connectors.values()) {
if (dst.isRemoved() || dst.networkInterface == source) {
continue;
}
dst.networkInterface.writeEthernetFrame(this, frame, timeToLive - 1);
}
}
}
}

View File

@@ -1,7 +1,7 @@
package li.cil.oc2.common.tileentity;
import li.cil.oc2.common.Constants;
import li.cil.oc2.api.API;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.block.Blocks;
import net.minecraft.block.Block;
import net.minecraft.tileentity.TileEntity;
@@ -21,6 +21,7 @@ public final class TileEntities {
public static final RegistryObject<TileEntityType<RedstoneInterfaceTileEntity>> REDSTONE_INTERFACE_TILE_ENTITY = register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, Blocks.REDSTONE_INTERFACE_BLOCK, RedstoneInterfaceTileEntity::new);
public static final RegistryObject<TileEntityType<BusCableTileEntity>> BUS_CABLE_TILE_ENTITY = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE_BLOCK, BusCableTileEntity::new);
public static final RegistryObject<TileEntityType<ComputerTileEntity>> COMPUTER_TILE_ENTITY = register(Constants.COMPUTER_BLOCK_NAME, Blocks.COMPUTER_BLOCK, ComputerTileEntity::new);
public static final RegistryObject<TileEntityType<NetworkConnectorTileEntity>> NETWORK_CONNECTOR_TILE_ENTITY = register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, Blocks.NETWORK_CONNECTOR_BLOCK, NetworkConnectorTileEntity::new);
///////////////////////////////////////////////////////////////////

View File

@@ -31,6 +31,18 @@ public class BlockStates extends BlockStateProvider {
horizontalBlock(Blocks.COMPUTER_BLOCK, Items.COMPUTER_ITEM);
horizontalBlock(Blocks.REDSTONE_INTERFACE_BLOCK, Items.REDSTONE_INTERFACE_ITEM);
horizontalBlock(Blocks.SCREEN_BLOCK, Items.SCREEN_ITEM);
horizontalFaceBlock(Blocks.NETWORK_CONNECTOR_BLOCK, Items.NETWORK_CONNECTOR_ITEM)
.transforms()
.transform(ModelBuilder.Perspective.GUI)
.rotation(30, 315, 0)
.translation(0, 2, 0)
.scale(0.75f, 0.75f, 0.75f)
.end()
.transform(ModelBuilder.Perspective.FIXED)
.rotation(270, 0, 0)
.translation(0, 0, -5)
.end()
.end();
registerCableStates();
}
@@ -187,8 +199,13 @@ public class BlockStates extends BlockStateProvider {
.end();
}
private <T extends Block> void horizontalBlock(final RegistryObject<T> block, final RegistryObject<Item> item) {
private <T extends Block> ItemModelBuilder horizontalBlock(final RegistryObject<T> block, final RegistryObject<Item> item) {
horizontalBlock(block.get(), models().getBuilder(block.getId().getPath()));
itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId()));
return itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId()));
}
private <T extends Block> ItemModelBuilder horizontalFaceBlock(final RegistryObject<T> block, final RegistryObject<Item> item) {
horizontalFaceBlock(block.get(), models().getBuilder(block.getId().getPath()));
return itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId()));
}
}

View File

@@ -1,7 +1,7 @@
package li.cil.oc2.data;
import li.cil.oc2.common.Constants;
import li.cil.oc2.api.API;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.item.HardDriveItem;
import li.cil.oc2.common.item.Items;
import li.cil.oc2.common.item.MemoryItem;
@@ -22,6 +22,8 @@ public final class ItemModels extends ItemModelProvider {
protected void registerModels() {
simple(Items.WRENCH_ITEM, "items/wrench");
simple(Items.NETWORK_CABLE_ITEM, "items/network_cable");
simple(Items.MEMORY_ITEM, "items/memory1")
.override()
.predicate(MemoryItem.CAPACITY_PROPERTY, 4 * Constants.MEGABYTE)
@@ -31,7 +33,6 @@ public final class ItemModels extends ItemModelProvider {
.predicate(MemoryItem.CAPACITY_PROPERTY, 8 * Constants.MEGABYTE)
.model(simple(Items.MEMORY_ITEM, "items/memory3", "3"))
.end();
simple(Items.HARD_DRIVE_ITEM, "items/hard_drive1")
.override()
.predicate(HardDriveItem.CAPACITY_PROPERTY, 4 * Constants.MEGABYTE)
@@ -41,9 +42,9 @@ public final class ItemModels extends ItemModelProvider {
.predicate(HardDriveItem.CAPACITY_PROPERTY, 8 * Constants.MEGABYTE)
.model(simple(Items.HARD_DRIVE_ITEM, "items/hard_drive3", "3"))
.end();
simple(Items.FLASH_MEMORY_ITEM, "items/flash_memory");
simple(Items.REDSTONE_INTERFACE_CARD_ITEM, "items/redstone_interface_card");
simple(Items.NETWORK_INTERFACE_CARD_ITEM, "items/network_interface_card");
}
private <T extends Item> ItemModelBuilder simple(final RegistryObject<T> item, final String texturePath) {

View File

@@ -45,6 +45,7 @@ public final class LootTables extends LootTableProvider {
registerDropSelfLootTable(Blocks.BUS_CABLE_BLOCK.get());
registerDropSelfLootTable(Blocks.REDSTONE_INTERFACE_BLOCK.get());
registerDropSelfLootTable(Blocks.SCREEN_BLOCK.get());
registerDropSelfLootTable(Blocks.NETWORK_CONNECTOR_BLOCK.get());
registerLootTable(Blocks.COMPUTER_BLOCK.get(), ModBlockLootTables::droppingWithInventory);
}

View File

@@ -0,0 +1,57 @@
{
"variants": {
"face=floor,facing=north": {
"model": "oc2:block/network_connector"
},
"face=wall,facing=north": {
"model": "oc2:block/network_connector",
"x": 90
},
"face=ceiling,facing=north": {
"model": "oc2:block/network_connector",
"x": 180,
"y": 180
},
"face=floor,facing=south": {
"model": "oc2:block/network_connector",
"y": 180
},
"face=wall,facing=south": {
"model": "oc2:block/network_connector",
"x": 90,
"y": 180
},
"face=ceiling,facing=south": {
"model": "oc2:block/network_connector",
"x": 180
},
"face=floor,facing=west": {
"model": "oc2:block/network_connector",
"y": 270
},
"face=wall,facing=west": {
"model": "oc2:block/network_connector",
"x": 90,
"y": 270
},
"face=ceiling,facing=west": {
"model": "oc2:block/network_connector",
"x": 180,
"y": 90
},
"face=floor,facing=east": {
"model": "oc2:block/network_connector",
"y": 90
},
"face=wall,facing=east": {
"model": "oc2:block/network_connector",
"x": 90,
"y": 90
},
"face=ceiling,facing=east": {
"model": "oc2:block/network_connector",
"x": 180,
"y": 270
}
}
}

View File

@@ -3,15 +3,18 @@
"block.oc2.computer": "Computer",
"block.oc2.bus_cable": "Bus Cable",
"block.oc2.network_connector": "Network Connector",
"block.oc2.redstone_interface": "Redstone Interface",
"block.oc2.screen": "Screen",
"item.oc2.wrench": "Scrench",
"item.oc2.bus_interface": "Bus Interface",
"item.oc2.network_cable": "Network Cable",
"item.oc2.memory": "Memory",
"item.oc2.hard_drive": "Hard Drive",
"item.oc2.flash_memory": "Flash Memory",
"item.oc2.redstone_interface_card": "Redstone Interface Card",
"item.oc2.network_interface_card": "Network Interface Card",
"gui.oc2.computer.boot_error.unknown": "Unknown Error",
"gui.oc2.computer.boot_error.no_memory": "Insufficient Memory",
@@ -27,5 +30,9 @@
"gui.oc2.device_type.memory": "Memory",
"gui.oc2.device_type.hard_drive": "Hard Drive",
"gui.oc2.device_type.flash_memory": "Flash Memory",
"gui.oc2.device_type.card": "Card"
"gui.oc2.device_type.card": "Card",
"message.oc2.connector.error.full": "Cannot attach more cables.",
"message.oc2.connector.error.too_far": "Distance between connectors is too large.",
"message.oc2.connector.error.obstructed": "No clear line of sight between connectors."
}

View File

@@ -0,0 +1 @@
{"parent":"block/block","textures":{"east":"oc2:blocks/network_connector/network_connector_east","west":"oc2:blocks/network_connector/network_connector_west","up":"oc2:blocks/network_connector/network_connector_up","down":"oc2:blocks/network_connector/network_connector_down","north":"oc2:blocks/network_connector/network_connector_north","south":"oc2:blocks/network_connector/network_connector_south","atlas0":"oc2:blocks/network_connector/network_connector_atlas0","particle":"#north"},"elements":[{"from":[5,0,5],"to":[11,1,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down","cullface":"down"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,1,10],"to":[10,3,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,2.5,1.0,2.75]},"south":{"texture":"south"}}},{"from":[5,1,6],"to":[11,3,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,0.0,1.5,1.0]},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,1,5],"to":[10,3,6],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,2.75,1.0,3.0]},"north":{"texture":"north"}}},{"from":[6,3,10],"to":[7,4,11],"faces":{"east":{"texture":"atlas0","uv":[0.0,3.5,0.25,3.75]},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"south":{"texture":"south"}}},{"from":[9,3,10],"to":[10,4,11],"faces":{"east":{"texture":"east"},"west":{"texture":"atlas0","uv":[0.0,3.75,0.25,4.0]},"up":{"texture":"up"},"down":{"texture":"down"},"south":{"texture":"south"}}},{"from":[5,3,9],"to":[11,4,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"atlas0","uv":[0.0,1.0,1.5,1.25]},"south":{"texture":"south"}}},{"from":[6,3,7],"to":[10,4,9],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[5,3,6],"to":[11,4,7],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"},"south":{"texture":"atlas0","uv":[0.0,1.25,1.5,1.5]}}},{"from":[6,3,5],"to":[7,4,6],"faces":{"east":{"texture":"atlas0","uv":[0.0,4.0,0.25,4.25]},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"}}},{"from":[9,3,5],"to":[10,4,6],"faces":{"east":{"texture":"east"},"west":{"texture":"atlas0","uv":[0.0,4.25,0.25,4.5]},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"}}},{"from":[6,4,10],"to":[10,7,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,3.0,1.0,3.25]},"south":{"texture":"south"}}},{"from":[5,4,6],"to":[11,7,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,1.5,1.5,2.5]},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,4,5],"to":[10,7,6],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,3.25,1.0,3.5]},"north":{"texture":"north"}}},{"from":[6,7,6],"to":[10,8,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[7,8,7],"to":[9,9,9],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"north":{"texture":"north"},"south":{"texture":"south"}}}]}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "oc2:items/network_cable"
}
}

View File

@@ -0,0 +1,32 @@
{
"parent": "oc2:block/network_connector",
"display": {
"gui": {
"rotation": [
30,
315,
0
],
"translation": [
0, 2, 0
],
"scale": [
0.75,
0.75,
0.75
]
},
"fixed": {
"rotation": [
270,
0,
0
],
"translation": [
0,
0,
-5
]
}
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "oc2:items/network_interface_card"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1,19 @@
{
"type": "minecraft:block",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "oc2:network_connector"
}
],
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
]
}
]
}