Compare commits

..

13 Commits

63 changed files with 1471 additions and 223 deletions

View File

@ -0,0 +1,19 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.frame.EthernetFrame;
import net.shadowfacts.phycon.api.util.MACAddress;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface Interface {
@NotNull
MACAddress getMACAddress();
void receive(@NotNull EthernetFrame frame);
void send(@NotNull EthernetFrame frame);
}

View File

@ -3,6 +3,6 @@ package net.shadowfacts.phycon.api;
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
public interface NetworkCable extends NetworkComponent { public interface NetworkCableBlock extends NetworkComponentBlock {
} }

View File

@ -1,17 +0,0 @@
package net.shadowfacts.phycon.api;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import java.util.Collection;
/**
* @author shadowfacts
*/
public interface NetworkComponent {
Collection<Direction> getNetworkConnectedSides(BlockState state, World world, BlockPos pos);
}

View File

@ -0,0 +1,23 @@
package net.shadowfacts.phycon.api;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author shadowfacts
*/
public interface NetworkComponentBlock {
@NotNull
Collection<Direction> getNetworkConnectedSides(@NotNull BlockState state, @NotNull World world, @NotNull BlockPos pos);
@Nullable
Interface getNetworkInterfaceForSide(@NotNull Direction side, @NotNull BlockState state, @NotNull World world, @NotNull BlockPos pos);
}

View File

@ -0,0 +1,23 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.util.IPAddress;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface NetworkDevice {
/**
* The IP address of this device.
*
* If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one.
*
* The address of a network device should never be the broadcast address.
*
* @return The IP address of this device.
*/
@NotNull
IPAddress getIPAddress();
}

View File

@ -1,14 +0,0 @@
package net.shadowfacts.phycon.api;
import net.minecraft.item.ItemStack;
import java.util.Map;
/**
* @author shadowfacts
*/
public interface NetworkInterface extends PacketSink {
Map<ItemStack, Integer> readAll();
}

View File

@ -1,16 +1,12 @@
package net.shadowfacts.phycon.api; package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.packet.Packet; import net.shadowfacts.phycon.api.packet.Packet;
import net.shadowfacts.phycon.api.util.MACAddress;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
public interface PacketSink { public interface PacketSink extends NetworkDevice {
@NotNull
MACAddress getMACAddress();
void handle(@NotNull Packet packet); void handle(@NotNull Packet packet);

View File

@ -1,15 +1,18 @@
package net.shadowfacts.phycon.api; package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.packet.Packet; import net.shadowfacts.phycon.api.packet.Packet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
public interface PacketSource { // todo: does PacketSource actually need to extend NetworkDevice?
public interface PacketSource extends NetworkDevice {
// todo: better name for this // todo: better name for this
void sendToSingle(Packet packet); void sendPacket(@NotNull Packet packet);
void sendToAll(Packet packet); // void sendToAll(@NotNull Packet packet);
//
void sendToAll(Packet packet, Iterable<PacketSink> destinations); // void sendToAll(@NotNull Packet packet, @NotNull Iterable<PacketSink> destinations);
} }

View File

@ -9,6 +9,5 @@ import alexiil.mc.lib.attributes.Attributes;
public class PhyAttributes { public class PhyAttributes {
public static final Attribute<PacketSink> PACKET_SINK = Attributes.create(PacketSink.class); public static final Attribute<PacketSink> PACKET_SINK = Attributes.create(PacketSink.class);
public static final Attribute<NetworkInterface> NETWORK_INTERFACE = Attributes.create(NetworkInterface.class);
} }

View File

@ -0,0 +1,17 @@
package net.shadowfacts.phycon.api.frame;
import net.shadowfacts.phycon.api.util.MACAddress;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface EthernetFrame {
@NotNull
MACAddress getSource();
@NotNull
MACAddress getDestination();
}

View File

@ -0,0 +1,14 @@
package net.shadowfacts.phycon.api.frame;
import net.shadowfacts.phycon.api.packet.Packet;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface PacketFrame extends EthernetFrame {
@NotNull
Packet getPacket();
}

View File

@ -1,6 +1,6 @@
package net.shadowfacts.phycon.api.packet; package net.shadowfacts.phycon.api.packet;
import net.shadowfacts.phycon.api.util.MACAddress; import net.shadowfacts.phycon.api.util.IPAddress;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
@ -9,9 +9,9 @@ import org.jetbrains.annotations.NotNull;
public interface Packet { public interface Packet {
@NotNull @NotNull
MACAddress getSource(); IPAddress getSource();
@NotNull @NotNull
MACAddress getDestination(); IPAddress getDestination();
} }

View File

@ -0,0 +1,75 @@
package net.shadowfacts.phycon.api.util;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
/**
* @author shadowfacts
*/
public final class IPAddress {
@NotNull
public static IPAddress random(@NotNull Random random) {
int value = 0;
// don't accidentally generate reserved, broadcast, or loopback addresses
while (value == 0 || value == 0xff_ff_ff_ff || (value >> 24) == 127) {
value = random.nextInt();
}
return new IPAddress(value);
}
private static final Random ipAddressRandom = new Random();
@NotNull
public static IPAddress random() {
return random(ipAddressRandom);
}
public static final IPAddress BROADCAST = new IPAddress(0xff_ff_ff_ff);
public final int address;
public IPAddress(int address) {
if (address == 0) {
throw new RuntimeException("IP address 0.0.0.0 is reserved");
}
this.address = address;
}
public IPAddress(int a, int b, int c, int d) {
this(((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff));
}
/**
* @return If this IP address is a broadcast address (255.255.255.255).
*/
public boolean isBroadcast() {
return address == 0xff_ff_ff_ff;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IPAddress ipAddress = (IPAddress) o;
return address == ipAddress.address;
}
@Override
public int hashCode() {
return address;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 3; i >= 0; i--) {
if (i < 3) {
builder.append('.');
}
int octet = (address >> (i * 8)) & 0xff;
builder.append(octet);
}
return builder.toString();
}
}

View File

@ -15,12 +15,14 @@ public final class MACAddress {
public static final MACAddress BROADCAST = new MACAddress(0xffffffffffffL); public static final MACAddress BROADCAST = new MACAddress(0xffffffffffffL);
@NotNull
public static MACAddress random(Random random) { public static MACAddress random(Random random) {
long value = random.nextLong() & 0xfeffffffffffL; long value = random.nextLong() & 0xfeffffffffffL;
return new MACAddress(value); return new MACAddress(value);
} }
private static final Random macAddressRandom = new Random(); private static final Random macAddressRandom = new Random();
@NotNull
public static MACAddress random() { public static MACAddress random() {
return random(macAddressRandom); return random(macAddressRandom);
} }

View File

@ -4,6 +4,7 @@ import net.fabricmc.api.ModInitializer
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens
/** /**
* @author shadowfacts * @author shadowfacts
@ -16,6 +17,7 @@ object PhysicalConnectivity: ModInitializer {
PhyBlocks.init() PhyBlocks.init()
PhyBlockEntities.init() PhyBlockEntities.init()
PhyItems.init() PhyItems.init()
PhyScreens.init()
} }
} }

View File

@ -5,12 +5,20 @@ import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.extractor.ExtractorBlockEntity
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlockEntity
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.DestBlockEntity
import net.shadowfacts.phycon.network.block.test.SourceBlock
import net.shadowfacts.phycon.network.block.test.SourceBlockEntity
/** /**
* @author shadowfacts * @author shadowfacts
@ -20,15 +28,25 @@ object PhyBlockEntities {
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE) val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL) val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH) val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
val MINER = create(::MinerBlockEntity, PhyBlocks.MINER)
val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE)
val DEST = create(::DestBlockEntity, PhyBlocks.DEST)
private fun <T: BlockEntity> create(builder: () -> T, block: Block): BlockEntityType<T> { private fun <T: BlockEntity> create(builder: () -> T, block: Block): BlockEntityType<T> {
return BlockEntityType.Builder.create(builder, arrayOf(block)).build(null) return BlockEntityType.Builder.create(builder, block).build(null)
} }
fun init() { fun init() {
register(InterfaceBlock.ID, INTERFACE) register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL) register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH) register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
} }
private fun register(id: Identifier, type: BlockEntityType<*>) { private fun register(id: Identifier, type: BlockEntityType<*>) {

View File

@ -4,9 +4,13 @@ import net.minecraft.block.Block
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.network.block.cable.CableBlock import net.shadowfacts.phycon.network.block.cable.CableBlock
import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
/** /**
* @author shadowfacts * @author shadowfacts
@ -17,12 +21,22 @@ object PhyBlocks {
val TERMINAL = TerminalBlock() val TERMINAL = TerminalBlock()
val SWITCH = SwitchBlock() val SWITCH = SwitchBlock()
val CABLE = CableBlock() val CABLE = CableBlock()
val EXTRACTOR = ExtractorBlock()
val MINER = MinerBlock()
val SOURCE = SourceBlock()
val DEST = DestBlock()
fun init() { fun init() {
register(InterfaceBlock.ID, INTERFACE) register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL) register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH) register(SwitchBlock.ID, SWITCH)
register(CableBlock.ID, CABLE) register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
} }
private fun register(id: Identifier, block: Block) { private fun register(id: Identifier, block: Block) {

View File

@ -4,11 +4,16 @@ import net.minecraft.item.BlockItem
import net.minecraft.item.Item import net.minecraft.item.Item
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.item.ConsoleItem
import net.shadowfacts.phycon.item.ScrewdriverItem import net.shadowfacts.phycon.item.ScrewdriverItem
import net.shadowfacts.phycon.network.block.cable.CableBlock import net.shadowfacts.phycon.network.block.cable.CableBlock
import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
/** /**
* @author shadowfacts * @author shadowfacts
@ -19,16 +24,28 @@ object PhyItems {
val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings()) val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings())
val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings()) val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings())
val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings()) val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings())
val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
val MINER = BlockItem(PhyBlocks.MINER, Item.Settings())
val SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings())
val DEST = BlockItem(PhyBlocks.DEST , Item.Settings())
val SCREWDRIVER = ScrewdriverItem() val SCREWDRIVER = ScrewdriverItem()
val CONSOLE = ConsoleItem()
fun init() { fun init() {
register(InterfaceBlock.ID, INTERFACE) register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL) register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH) register(SwitchBlock.ID, SWITCH)
register(CableBlock.ID, CABLE) register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
register(ScrewdriverItem.ID, SCREWDRIVER) register(ScrewdriverItem.ID, SCREWDRIVER)
register(ConsoleItem.ID, CONSOLE)
} }
private fun register(id: Identifier, item: Item) { private fun register(id: Identifier, item: Item) {

View File

@ -1,12 +1,18 @@
package net.shadowfacts.phycon.init package net.shadowfacts.phycon.init
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
object PhyScreens { object PhyScreens {
val TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler) lateinit var TERMINAL_SCREEN_HANDLER: ScreenHandlerType<TerminalScreenHandler>
private set
fun init() {
TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler)
}
} }

View File

@ -0,0 +1,47 @@
package net.shadowfacts.phycon.item
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.MinecraftClient
import net.minecraft.item.Item
import net.minecraft.item.ItemUsageContext
import net.minecraft.text.LiteralText
import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.screen.DeviceConsoleScreen
/**
* @author shadowfacts
*/
class ConsoleItem: Item(Settings()) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "console")
}
override fun useOnBlock(context: ItemUsageContext): ActionResult {
val state = context.world.getBlockState(context.blockPos)
val block = state.block
if (block is DeviceBlock<*>) {
val be = block.getBlockEntity(context.world, context.blockPos)
if (be != null) {
if (context.world.isClient) {
this.openScreen(be)
} else {
be.sync()
}
return ActionResult.SUCCESS
}
}
return ActionResult.PASS
}
@Environment(EnvType.CLIENT)
private fun openScreen(be: DeviceBlockEntity) {
val screen = DeviceConsoleScreen(be)
MinecraftClient.getInstance().openScreen(screen)
}
}

View File

@ -3,13 +3,22 @@ package net.shadowfacts.phycon.network
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World import net.minecraft.world.World
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity import net.shadowfacts.phycon.block.BlockWithEntity
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithEntity<T>(settings) { abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithEntity<T>(settings), NetworkComponentBlock {
abstract override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction>
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)!!
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) { override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
super.onBreak(world, pos, state, player) super.onBreak(world, pos, state, player)

View File

@ -1,83 +1,174 @@
package net.shadowfacts.phycon.network package net.shadowfacts.phycon.network
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.ARPQueryFrame
import net.shadowfacts.phycon.network.frame.ARPResponseFrame
import net.shadowfacts.phycon.network.frame.BasePacketFrame
import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket
import java.lang.RuntimeException
import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), PacketSink, PacketSource { abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
BlockEntityClientSerializable,
Tickable,
PacketSink,
PacketSource,
Interface {
var macAddress = MACAddress.random() companion object {
private const val ARP_RETRY_TIMEOUT = 200
}
var macAddress: MACAddress = MACAddress.random()
protected set protected set
override fun getMACAddress(): MACAddress { var ipAddress: IPAddress = IPAddress.random()
return macAddress protected set
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
private val packetQueue = LinkedList<PendingPacket>()
protected var counter: Long = 0
override fun getIPAddress() = ipAddress
override fun getMACAddress() = macAddress
abstract override fun handle(packet: Packet)
override fun send(frame: EthernetFrame) {
findDestination()?.receive(frame)
} }
override fun handle(packet: Packet) { override fun receive(frame: EthernetFrame) {
if (acceptsPacket(packet)) { println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame")
handlePacket(packet) when (frame) {
is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame)
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
println("$this ($ipAddress) received packet: ${frame.packet}")
handle(frame.packet)
}
}
} }
} }
protected abstract fun handlePacket(packet: Packet) private fun handleARPQuery(frame: ARPQueryFrame) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}")
protected open fun acceptsPacket(packet: Packet): Boolean { arpTable[frame.sourceIP] = frame.source
return when (packet.destination.type) { if (frame.queryIP == ipAddress) {
MACAddress.Type.BROADCAST -> true println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress")
MACAddress.Type.UNICAST -> macAddress == packet.destination send(ARPResponseFrame(ipAddress, macAddress, frame.source))
MACAddress.Type.MULTICAST -> acceptsMulticastPacket(packet)
} }
} }
open fun acceptsMulticastPacket(packet: Packet): Boolean { private fun handleARPResponse(frame: ARPResponseFrame) {
return false arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}")
val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) {
send(BasePacketFrame(packet, macAddress, frame.source))
true
} else {
false
}
}
packetQueue.removeAll(toRemove)
} }
fun send(packet: Packet, destination: PacketSink) { override fun sendPacket(packet: Packet) {
destination.handle(packet) if (packet.destination.isBroadcast) {
send(BasePacketFrame(packet, macAddress, MACAddress.BROADCAST))
} else if (arpTable.containsKey(packet.destination)) {
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
} else {
packetQueue.add(PendingPacket(packet, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}")
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
}
} }
override fun sendToSingle(packet: Packet) { open fun findDestination(): Interface? {
val destinations = NetworkUtil.findDestinations(world!!, pos) val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
if (destinations.size != 1) { if (sides.size != 1) {
// todo: handle this better throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
println("Can't send packet, multiple destinations available: $destinations")
return
} }
send(packet, destinations.first()) return NetworkUtil.findConnectedInterface(world!!, pos, sides.first())
} }
override fun sendToAll(packet: Packet) { override fun tick() {
sendToAll(packet, NetworkUtil.findDestinations(world!!, pos)) counter++
}
override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) { if (!world!!.isClient) {
destinations.forEach { val toRemove = packetQueue.filter { entry ->
it.handle(packet) val (packet, timestamp) = entry
if (arpTable.containsKey(packet.destination)) {
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
true
} else if (counter - timestamp >= ARP_RETRY_TIMEOUT) {
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
entry.timestamp = counter
// todo: should there be a retry counter?
true
} else {
false
}
}
packetQueue.removeAll(toRemove)
} }
} }
override fun toTag(tag: CompoundTag): CompoundTag { override fun toTag(tag: CompoundTag): CompoundTag {
tag.putInt("IPAddress", ipAddress.address)
tag.putLong("MACAddress", macAddress.address) tag.putLong("MACAddress", macAddress.address)
return super.toTag(tag) return super.toTag(tag)
} }
override fun fromTag(state: BlockState, tag: CompoundTag) { override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag) super.fromTag(state, tag)
ipAddress = IPAddress(tag.getInt("IPAddress"))
macAddress = MACAddress(tag.getLong("MACAddress"))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putInt("IPAddress", ipAddress.address)
tag.putLong("MACAddress", macAddress.address)
return tag
}
override fun fromClientTag(tag: CompoundTag) {
ipAddress = IPAddress(tag.getInt("IPAddress"))
macAddress = MACAddress(tag.getLong("MACAddress")) macAddress = MACAddress(tag.getLong("MACAddress"))
} }
fun onBreak() { fun onBreak() {
sendToAll(DeviceRemovedPacket(this)) if (!world!!.isClient) {
sendPacket(DeviceRemovedPacket(this))
}
} }
data class PendingPacket(
val packet: Packet,
var timestamp: Long,
)
} }

View File

@ -3,10 +3,7 @@ package net.shadowfacts.phycon.network
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.world.World import net.minecraft.world.World
import net.shadowfacts.phycon.api.NetworkCable import net.shadowfacts.phycon.api.*
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PhyAttributes
import java.util.* import java.util.*
/** /**
@ -14,6 +11,32 @@ import java.util.*
*/ */
object NetworkUtil { object NetworkUtil {
fun findConnectedInterface(world: World, startPos: BlockPos, startSide: Direction): Interface? {
var curSide = startSide
var pos = startPos.offset(startSide)
var state = world.getBlockState(pos)
while (state.block is NetworkComponentBlock) {
val block = state.block as NetworkComponentBlock
val itf = block.getNetworkInterfaceForSide(curSide.opposite, state, world, pos)
if (itf != null) {
return itf
}
val connectedSides = block.getNetworkConnectedSides(state, world, pos).filter { it != curSide.opposite }
if (connectedSides.size == 1) {
curSide = connectedSides.first()
pos = pos.offset(curSide)
if (!world.isChunkLoaded(pos)) {
// avoid loading unloaded chunks
return null
}
state = world.getBlockState(pos)
} else {
return null
}
}
return null
}
fun findDestinations(world: World, startPos: BlockPos, direction: Direction? = null): List<PacketSink> { fun findDestinations(world: World, startPos: BlockPos, direction: Direction? = null): List<PacketSink> {
val results = LinkedList<PacketSink>() val results = LinkedList<PacketSink>()
val visited = hashSetOf(startPos) val visited = hashSetOf(startPos)
@ -43,7 +66,7 @@ object NetworkUtil {
private fun findEdges(queue: MutableList<BlockPos>, visited: Set<BlockPos>, world: World, pos: BlockPos, includeNonCables: Boolean = false) { private fun findEdges(queue: MutableList<BlockPos>, visited: Set<BlockPos>, world: World, pos: BlockPos, includeNonCables: Boolean = false) {
val state = world.getBlockState(pos) val state = world.getBlockState(pos)
val block = state.block val block = state.block
if (block is NetworkComponent && (includeNonCables || block is NetworkCable)) { if (block is NetworkComponentBlock && (includeNonCables || block is NetworkCableBlock)) {
val connections = block.getNetworkConnectedSides(state, world, pos) val connections = block.getNetworkConnectedSides(state, world, pos)
for (side in connections) { for (side in connections) {
val newPos = pos.offset(side) val newPos = pos.offset(side)

View File

@ -19,8 +19,9 @@ import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldAccess import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkCable import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponent import net.shadowfacts.phycon.api.NetworkCableBlock
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.util.CableConnection import net.shadowfacts.phycon.util.CableConnection
import java.util.* import java.util.*
@ -31,7 +32,7 @@ import java.util.*
class CableBlock: Block( class CableBlock: Block(
Settings.of(CABLE_MATERIAL) Settings.of(CABLE_MATERIAL)
.nonOpaque() .nonOpaque()
), NetworkCable { ), NetworkCableBlock {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "cable") val ID = Identifier(PhysicalConnectivity.MODID, "cable")
val CABLE_MATERIAL = Material(MaterialColor.IRON, false, false, true, false, true, false, PistonBehavior.NORMAL) val CABLE_MATERIAL = Material(MaterialColor.IRON, false, false, true, false, true, false, PistonBehavior.NORMAL)
@ -108,11 +109,16 @@ class CableBlock: Block(
else -> CableConnection.ON else -> CableConnection.ON
} }
} }
is NetworkComponent -> CableConnection.ON is NetworkComponentBlock -> CableConnection.ON
else -> CableConnection.OFF else -> CableConnection.OFF
} }
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
// cables don't have network interfaces
return null
}
override fun onUse( override fun onUse(
state: BlockState, state: BlockState,
world: World, world: World,
@ -141,7 +147,7 @@ class CableBlock: Block(
world.setBlockState(connectedToPos, connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON)) world.setBlockState(connectedToPos, connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON))
} }
state.with(prop, if (connectedTo.block is NetworkComponent) CableConnection.ON else CableConnection.OFF) state.with(prop, if (connectedTo.block is NetworkComponentBlock) CableConnection.ON else CableConnection.OFF)
} }
else -> state.with(prop, CableConnection.DISABLED) else -> state.with(prop, CableConnection.DISABLED)
} }

View File

@ -0,0 +1,110 @@
package net.shadowfacts.phycon.network.block.extractor
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.block.ShapeContext
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class ExtractorBlock: DeviceBlock<ExtractorBlockEntity>(Settings.of(Material.METAL)) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "extractor")
val FACING = Properties.FACING
private val EXTRACTOR_SHAPES = mutableMapOf<Direction, VoxelShape>()
init {
val components = arrayOf(
doubleArrayOf(0.0, 0.0, 0.0, 16.0, 2.0, 16.0),
doubleArrayOf(2.0, 2.0, 2.0, 14.0, 4.0, 14.0),
doubleArrayOf(4.0, 4.0, 4.0, 12.0, 6.0, 12.0),
doubleArrayOf(6.0, 6.0, 6.0, 10.0, 16.0, 10.0)
)
val directions = arrayOf(
Triple(Direction.DOWN, null, false),
Triple(Direction.UP, null, true),
Triple(Direction.NORTH, 2, false),
Triple(Direction.SOUTH, 2, true),
Triple(Direction.WEST, 1, false),
Triple(Direction.EAST, 1, true),
)
for ((dir, rotate, flip) in directions) {
val shapes = components.map { it ->
val arr = it.copyOf()
if (rotate != null) {
for (i in 0 until 3) {
arr[i] = it[(i + rotate) % 3]
arr[3 + i] = it[3 + ((i + rotate) % 3)]
}
}
if (flip) {
for (i in arr.indices) {
arr[i] = 16.0 - arr[i]
}
}
createCuboidShape(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5])
}
EXTRACTOR_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
}
}
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[FACING].opposite)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return if (side == state[FACING].opposite) {
getBlockEntity(world, pos)
} else {
null
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
}
override fun createBlockEntity(world: BlockView) = ExtractorBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
return defaultState.with(FACING, facing)
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return EXTRACTOR_SHAPES[state[FACING]]!!
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
}

View File

@ -0,0 +1,59 @@
package net.shadowfacts.phycon.network.block.extractor
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.item.FixedItemInv
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.CapacityPacket
import net.shadowfacts.phycon.network.packet.CheckCapacityPacket
import net.shadowfacts.phycon.network.packet.ItemStackPacket
/**
* @author shadowfacts
*/
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) {
private val facing: Direction
get() = cachedState[ExtractorBlock.FACING]
private var inventory: GroupedItemInv? = null
private var shouldExtract = false
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option)
}
private fun getInventory(): GroupedItemInv? {
if (inventory == null) updateInventory()
return inventory
}
override fun handle(packet: Packet) {
if (packet is CapacityPacket && shouldExtract) {
getInventory()?.also { inv ->
shouldExtract = false
val extracted = inv.extract(packet.stack, packet.capacity)
sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress))
}
}
}
override fun tick() {
super.tick()
if (!world!!.isClient && counter % 40 == 0L) {
getInventory()?.also {
val stack = it.storedStacks.firstOrNull() ?: return
shouldExtract = true
sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST))
}
}
}
}

View File

@ -0,0 +1,68 @@
package net.shadowfacts.phycon.network.block.miner
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import java.util.*
/**
* @author shadowfacts
*/
class MinerBlock: DeviceBlock<MinerBlockEntity>(Settings.of(Material.METAL)) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "miner")
val FACING = Properties.FACING
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[FACING].opposite)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {
getBlockEntity(world, pos)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
}
override fun createBlockEntity(world: BlockView) = MinerBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState? {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
return defaultState.with(ExtractorBlock.FACING, facing)
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, itemStack: ItemStack) {
if (!world.isClient) {
// getBlockEntity(world, pos)!!.updateBlockToMine()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) {
if (!world.isClient) {
// getBlockEntity(world, pos)!!.updateBlockToMine()
}
}
}

View File

@ -0,0 +1,127 @@
package net.shadowfacts.phycon.network.block.miner
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import alexiil.mc.lib.attributes.item.ItemStackUtil
import alexiil.mc.lib.attributes.item.filter.ItemFilter
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.packet.*
import kotlin.math.min
/**
* @author shadowfacts
*/
class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
NetworkStackProvider {
private val facing: Direction
get() = cachedState[MinerBlock.FACING]
private val invProxy = MinerInvProxy(this)
override fun handle(packet: Packet) {
when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet)
is ExtractStackPacket -> handleExtractStack(packet)
}
}
private fun handleRequestInventory(packet: RequestInventoryPacket) {
sendPacket(ReadInventoryPacket(invProxy, ipAddress, packet.source))
}
private fun handleLocateStack(packet: LocateStackPacket) {
val amount = invProxy.getAmount(packet.stack)
if (amount > 0) {
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
}
}
private fun handleExtractStack(packet: ExtractStackPacket) {
// always recalculate immediately before breaking
val drops = invProxy.getDrops(true)
if (invProxy.getAmount(packet.stack) > 0) {
world!!.breakBlock(pos.offset(facing), false)
// send the requested amount back to the requester
var remaining = packet.amount
for (droppedStack in drops) {
if (remaining <= 0) {
break
}
if (!ItemStackUtil.areEqualIgnoreAmounts(droppedStack, packet.stack)) {
continue
}
val copy = droppedStack.copy()
val toDecr = min(droppedStack.count, remaining)
droppedStack.decrement(toDecr)
remaining -= toDecr
// todo: should this try to combine stacks and send as few packets as possible?
copy.count = toDecr
sendPacket(ItemStackPacket(copy, ipAddress, packet.source))
}
// dump any remaining drops into the network
for (droppedStack in drops) {
if (droppedStack.isEmpty) continue
sendPacket(ItemStackPacket(droppedStack, ipAddress, IPAddress.BROADCAST))
}
}
}
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView {
private var cachedState: BlockState? = null
private var cachedDrops: List<ItemStack>? = null
private val world: World
get() = miner.world!!
private val pos: BlockPos
get() = miner.pos!!
private val facing: Direction
get() = miner.facing
fun getDrops(recalculate: Boolean = false): List<ItemStack> {
val targetPos = pos.offset(facing)
val realState = world.getBlockState(targetPos)
// todo: does BlockState.equals actually work?
if (cachedDrops == null || realState != cachedState || recalculate) {
cachedState = realState
val be = if (realState.block.hasBlockEntity()) world.getBlockEntity(targetPos) else null
cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be)
}
return cachedDrops!!
}
override fun getStoredStacks(): Set<ItemStack> {
return getDrops().toSet()
}
override fun getTotalCapacity(): Int {
return Int.MAX_VALUE
}
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
var totalCount = 0
for (s in getDrops()) {
if (filter.matches(s)) {
totalCount += s.count
}
}
return GroupedItemInvView.ItemInvStatistic(filter, totalCount, 0, Int.MAX_VALUE)
}
}
}

View File

@ -2,10 +2,7 @@ package net.shadowfacts.phycon.network.block.netinterface
import alexiil.mc.lib.attributes.AttributeList import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.Block import net.minecraft.block.*
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.block.ShapeContext
import net.minecraft.entity.LivingEntity import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
@ -21,15 +18,19 @@ import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldAccess import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.* import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider { class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface") val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
val FACING = Properties.FACING val FACING = Properties.FACING
@ -63,7 +64,17 @@ class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material
} }
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> { override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[CABLE_CONNECTION]) val set = EnumSet.of(state[CABLE_CONNECTION])
set.remove(state[FACING])
return set
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {
getBlockEntity(world, pos)
}
} }
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) { override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
@ -83,7 +94,7 @@ class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material
private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? { private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? {
for (side in Direction.values()) { for (side in Direction.values()) {
val offsetPos = pos.offset(side) val offsetPos = pos.offset(side)
if (world.getBlockState(offsetPos).block is NetworkComponent) { if (world.getBlockState(offsetPos).block is NetworkComponentBlock) {
return side return side
} }
} }
@ -103,7 +114,7 @@ class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material
} }
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState { override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (neighborState.block is NetworkComponent && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponent) { if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
return state.with(CABLE_CONNECTION, side) return state.with(CABLE_CONNECTION, side)
} }
return state return state

View File

@ -10,16 +10,21 @@ import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler { class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
ItemStackPacketHandler,
NetworkStackProvider,
NetworkStackReceiver {
private val facing: Direction private val facing: Direction
get() = world!!.getBlockState(pos)[InterfaceBlock.FACING] get() = cachedState[InterfaceBlock.FACING]
// todo: should this be a weak ref? // todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null private var inventory: GroupedItemInv? = null
@ -37,7 +42,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemS
return inventory return inventory
} }
override fun handlePacket(packet: Packet) { override fun handle(packet: Packet) {
when (packet) { when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet) is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet) is LocateStackPacket -> handleLocateStack(packet)
@ -49,21 +54,21 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemS
private fun handleRequestInventory(packet: RequestInventoryPacket) { private fun handleRequestInventory(packet: RequestInventoryPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
sendToSingle(ReadInventoryPacket(inv, macAddress, packet.source)) sendPacket(ReadInventoryPacket(inv, ipAddress, packet.source))
} }
} }
private fun handleLocateStack(packet: LocateStackPacket) { private fun handleLocateStack(packet: LocateStackPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
val amount = inv.getAmount(packet.stack) val amount = inv.getAmount(packet.stack)
sendToSingle(StackLocationPacket(packet.stack, amount, this, macAddress, packet.source)) sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
} }
} }
private fun handleExtractStack(packet: ExtractStackPacket) { private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
val extracted = inv.extract(packet.stack, packet.amount) val extracted = inv.extract(packet.stack, packet.amount)
sendToSingle(ItemStackPacket(extracted, macAddress, packet.source)) sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
} }
} }
@ -71,7 +76,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemS
getInventory()?.also { inv -> getInventory()?.also { inv ->
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE) val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
val couldAccept = packet.stack.count - remaining.count val couldAccept = packet.stack.count - remaining.count
sendToSingle(CapacityPacket(packet.stack, couldAccept, this, macAddress, packet.source)) sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source))
} }
} }

View File

@ -10,14 +10,18 @@ import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity import net.shadowfacts.phycon.block.BlockWithEntity
import java.util.* import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider { class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch") val ID = Identifier(PhysicalConnectivity.MODID, "switch")
} }
@ -26,6 +30,10 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL
return EnumSet.allOf(Direction::class.java) return EnumSet.allOf(Direction::class.java)
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
}
override fun createBlockEntity(world: BlockView) = SwitchBlockEntity() override fun createBlockEntity(world: BlockView) = SwitchBlockEntity()
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) { override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {

View File

@ -1,82 +1,119 @@
package net.shadowfacts.phycon.network.block.netswitch package net.shadowfacts.phycon.network.block.netswitch
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.entity.ItemEntity import net.minecraft.entity.ItemEntity
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil import net.shadowfacts.phycon.network.NetworkUtil
import net.shadowfacts.phycon.network.packet.ItemStackPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket
import java.lang.RuntimeException import java.lang.ref.WeakReference
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class SwitchBlockEntity: DeviceBlockEntity(PhyBlockEntities.SWITCH), Tickable { class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
BlockEntityClientSerializable,
Tickable {
companion object { companion object {
var SWITCHING_CAPACITY = 256 var SWITCHING_CAPACITY = 256 // 256 items/tick
} }
val interfaces = Direction.values().map { SwitchInterface(it, WeakReference(this), MACAddress.random()) }
private val macTable = mutableMapOf<MACAddress, Direction>() private val macTable = mutableMapOf<MACAddress, Direction>()
private var packetsHandledThisTick = 0 private var itemsHandledThisTick = 0
override fun acceptsPacket(packet: Packet) = true fun interfaceForSide(side: Direction): SwitchInterface {
return interfaces.find { it.side == side }!!
}
override fun handlePacket(packet: Packet) { private fun handle(frame: EthernetFrame, fromItf: SwitchInterface) {
if (packetsHandledThisTick >= SWITCHING_CAPACITY) { macTable[frame.source] = fromItf.side
if (packet is ItemStackPacket) {
if (frame is PacketFrame && frame.packet is ItemStackPacket) {
val packet = frame.packet as ItemStackPacket
if (itemsHandledThisTick + packet.stack.count > SWITCHING_CAPACITY) {
// todo: calculate entity spawn point by finding non-obstructed location // todo: calculate entity spawn point by finding non-obstructed location
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), packet.stack) val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), packet.stack)
world!!.spawnEntity(entity) world!!.spawnEntity(entity)
}
return return
}
packetsHandledThisTick++
if (packet.destination == MACAddress.BROADCAST) {
val allDestinations = NetworkUtil.findDestinations(world!!, pos).filter { it.macAddress != packet.source }
sendToAll(packet, allDestinations)
} else { } else {
val direction = macTable[packet.destination] itemsHandledThisTick += packet.stack.count
if (direction != null) {
val dest = findDestination(direction)
if (dest != null && packet.destination == dest.macAddress) {
send(packet, dest)
return
}
}
flood(packet)
} }
} }
private fun findDestination(direction: Direction): PacketSink? { if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val allDestinations = NetworkUtil.findDestinations(world!!, pos, direction) val dir = macTable[frame.destination]!!
if (allDestinations.size > 1) { println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir")
// todo: do this better interfaceForSide(dir).send(frame)
println("Can't send packet, multiple destinations: $allDestinations") } else {
return null println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame")
flood(frame, fromItf)
} }
return allDestinations.firstOrNull()
} }
private fun flood(packet: Packet) { private fun flood(frame: EthernetFrame, source: Interface) {
for (dir in Direction.values()) { for (itf in interfaces) {
val dest = findDestination(dir) if (source == itf) continue
if (dest != null && packet.destination == dest.macAddress) { itf.send(frame)
macTable[packet.destination] = dir
send(packet, dest)
break
} }
} }
private fun findDestination(fromItf: Interface): Interface? {
val side = (fromItf as SwitchInterface).side
return NetworkUtil.findConnectedInterface(world!!, pos, side)
} }
override fun tick() { override fun tick() {
packetsHandledThisTick = 0 itemsHandledThisTick = 0
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return tag
}
override fun fromClientTag(tag: CompoundTag) {
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
class SwitchInterface(
val side: Direction,
val switch: WeakReference<SwitchBlockEntity>,
@JvmField var macAddress: MACAddress,
): Interface {
override fun getMACAddress() = macAddress
override fun receive(frame: EthernetFrame) {
switch.get()?.handle(frame, this)
}
override fun send(frame: EthernetFrame) {
switch.get()?.findDestination(this)?.receive(frame)
}
} }
} }

View File

@ -14,14 +14,17 @@ import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity import net.shadowfacts.phycon.network.DeviceBlock
import java.util.* import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBlock: BlockWithEntity<TerminalBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider { class TerminalBlock: DeviceBlock<TerminalBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal") val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
} }

View File

@ -1,6 +1,6 @@
package net.shadowfacts.phycon.network.block.terminal package net.shadowfacts.phycon.network.block.terminal
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInvView
import alexiil.mc.lib.attributes.item.ItemStackCollections import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil import alexiil.mc.lib.attributes.item.ItemStackUtil
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
@ -17,14 +17,18 @@ import net.minecraft.nbt.ListTag
import net.minecraft.network.PacketByteBuf import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.LiteralText import net.minecraft.text.TranslatableText
import net.minecraft.util.Tickable import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.NetworkUtil
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
import java.util.* import java.util.*
@ -40,7 +44,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val INSERTION_TIMEOUT = 40 val INSERTION_TIMEOUT = 40
} }
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>() private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
val internalBuffer = TerminalBufferInventory(18) val internalBuffer = TerminalBufferInventory(18)
private val locateRequestQueue = LinkedList<StackLocateRequest>() private val locateRequestQueue = LinkedList<StackLocateRequest>()
@ -50,13 +54,22 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
private var observers = 0 private var observers = 0
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>() var cachedSortedNetItems = listOf<ItemStack>()
var counter = 0
init { init {
internalBuffer.addListener(this) internalBuffer.addListener(this)
} }
override fun handlePacket(packet: Packet) { override fun findDestination(): Interface? {
for (dir in Direction.values()) {
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
if (itf != null) {
return itf
}
}
return null
}
override fun handle(packet: Packet) {
when (packet) { when (packet) {
is ReadInventoryPacket -> handleReadInventory(packet) is ReadInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet)
@ -83,7 +96,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
} }
if (request != null) { if (request != null) {
request.results.add(packet.amount to packet.sourceInterface) request.results.add(packet.amount to packet.stackProvider)
if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) { if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) {
stackLocateRequestCompleted(request) stackLocateRequestCompleted(request)
} }
@ -106,7 +119,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack) ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack)
} }
if (insertion != null) { if (insertion != null) {
insertion.results.add(packet.capacity to packet.receivingInterface) insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(counter)) { if (insertion.isFinishable(counter)) {
finishInsertion(insertion) finishInsertion(insertion)
} }
@ -137,7 +150,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
if (slot in pendingInsertions) continue if (slot in pendingInsertions) continue
val stack = internalBuffer.getStack(slot) val stack = internalBuffer.getStack(slot)
pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter) pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter)
sendToSingle(CheckCapacityPacket(stack, macAddress, MACAddress.BROADCAST)) sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST))
} }
} }
@ -155,7 +168,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
for (request in locateRequestQueue) { for (request in locateRequestQueue) {
pendingRequests.add(request) pendingRequests.add(request)
sendToSingle(LocateStackPacket(request.stack, macAddress)) sendPacket(LocateStackPacket(request.stack, ipAddress))
} }
locateRequestQueue.clear() locateRequestQueue.clear()
} }
@ -179,19 +192,26 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
} }
override fun tick() { override fun tick() {
if (observers > 0 && (++counter % 10) == 0) { super.tick()
if (world!!.isClient) {
println(cachedNetItems) if (counter % 20 == 0L) {
} else { if (!world!!.isClient) {
updateNetItems()
sendEnqueuedLocateRequests() sendEnqueuedLocateRequests()
finishPendingRequests() finishPendingRequests()
beginInsertions() beginInsertions()
finishPendingInsertions() finishPendingInsertions()
}
if (observers > 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
sync() sync()
} }
} }
} }
}
fun onActivate(player: PlayerEntity) { fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) { if (!world!!.isClient) {
@ -199,13 +219,13 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
sync() sync()
inventoryCache.clear() inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress)) sendPacket(RequestInventoryPacket(ipAddress))
val factory = object: ExtendedScreenHandlerFactory { val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity) return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity)
} }
override fun getDisplayName() = LiteralText("Terminal") override fun getDisplayName() = TranslatableText("block.phycon.terminal")
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) { override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
buf.writeBlockPos(this@TerminalBlockEntity.pos) buf.writeBlockPos(this@TerminalBlockEntity.pos)
@ -230,7 +250,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0) val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
val amountToRequest = min(sourceAmount, request.amount - amountRequested) val amountToRequest = min(sourceAmount, request.amount - amountRequested)
amountRequested += amountToRequest amountRequested += amountToRequest
sendToSingle(ExtractStackPacket(request.stack, amountToRequest, macAddress, sourceInterface.macAddress)) sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
} }
} }
@ -243,7 +263,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
while (!remaining.isEmpty && sortedResults.isNotEmpty()) { while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeAt(0) val (capacity, receivingInterface) = sortedResults.removeAt(0)
if (capacity <= 0) continue if (capacity <= 0) continue
sendToSingle(ItemStackPacket(remaining.copy(), macAddress, receivingInterface.macAddress)) sendPacket(ItemStackPacket(remaining.copy(), ipAddress, receivingInterface.ipAddress))
// todo: the interface should confirm how much was actually inserted, in case of race condition // todo: the interface should confirm how much was actually inserted, in case of race condition
remaining.count -= capacity remaining.count -= capacity
} }
@ -304,27 +324,27 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
data class StackLocateRequest( data class StackLocateRequest(
val stack: ItemStack, val stack: ItemStack,
val amount: Int, val amount: Int,
val timestamp: Int, val timestamp: Long,
var results: MutableSet<Pair<Int, InterfaceBlockEntity>> = mutableSetOf() var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
) { ) {
val totalResultAmount: Int val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean { fun isFinishable(currentTimestamp: Long): Boolean {
return totalResultAmount > amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT return totalResultAmount >= amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
} }
} }
data class PendingStackInsertion( data class PendingStackInsertion(
val bufferSlot: Int, val bufferSlot: Int,
val stack: ItemStack, val stack: ItemStack,
val timestamp: Int, val timestamp: Long,
val results: MutableSet<Pair<Int, InterfaceBlockEntity>> = mutableSetOf(), val results: MutableSet<Pair<Int, NetworkStackReceiver>> = mutableSetOf(),
) { ) {
val totalCapacity: Int val totalCapacity: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount } get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean { fun isFinishable(currentTimestamp: Long): Boolean {
return totalCapacity > stack.count || currentTimestamp - timestamp >= TerminalBlockEntity.INSERTION_TIMEOUT return totalCapacity >= stack.count || currentTimestamp - timestamp >= TerminalBlockEntity.INSERTION_TIMEOUT
} }
} }

View File

@ -6,7 +6,6 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.util.math.MatrixStack import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.text.LiteralText
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
@ -33,6 +32,8 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
} }
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) { override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
renderBackground(matrixStack)
GlStateManager.color4f(1f, 1f, 1f, 1f) GlStateManager.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND) client!!.textureManager.bindTexture(BACKGROUND)
val x = (width - backgroundWidth) / 2 val x = (width - backgroundWidth) / 2
@ -40,6 +41,11 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight) drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
} }
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
super.render(matrixStack, mouseX, mouseY, delta)
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
}
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) { fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
if (!handler.isBufferSlot(slot.id)) { if (!handler.isBufferSlot(slot.id)) {

View File

@ -0,0 +1,46 @@
package net.shadowfacts.phycon.network.block.test
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class DestBlock: DeviceBlock<DestBlockEntity>(Settings.of(Material.METAL)) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "dest")
}
override fun createBlockEntity(world: BlockView): DestBlockEntity {
return DestBlockEntity()
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): MutableCollection<Direction> {
return EnumSet.allOf(Direction::class.java)
}
override fun onUse(
blockState: BlockState?,
world: World,
pos: BlockPos,
playerEntity: PlayerEntity?,
hand: Hand?,
blockHitResult: BlockHitResult?
): ActionResult {
println("dest IP: ${getBlockEntity(world, pos)!!.ipAddress}")
return ActionResult.SUCCESS
}
}

View File

@ -0,0 +1,29 @@
package net.shadowfacts.phycon.network.block.test
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil
/**
* @author shadowfacts
*/
class DestBlockEntity: DeviceBlockEntity(PhyBlockEntities.DEST) {
override fun handle(packet: Packet) {
println("$this ($ipAddress) received packet: $packet")
}
override fun findDestination(): Interface? {
for (dir in Direction.values()) {
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
if (itf != null) {
return itf
}
}
return null
}
}

View File

@ -0,0 +1,31 @@
package net.shadowfacts.phycon.network.block.test
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class SourceBlock: DeviceBlock<SourceBlockEntity>(Settings.of(Material.METAL)) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "source")
}
override fun createBlockEntity(world: BlockView): SourceBlockEntity {
return SourceBlockEntity()
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): MutableCollection<Direction> {
return EnumSet.allOf(Direction::class.java)
}
}

View File

@ -0,0 +1,38 @@
package net.shadowfacts.phycon.network.block.test
import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil
/**
* @author shadowfacts
*/
class SourceBlockEntity: DeviceBlockEntity(PhyBlockEntities.SOURCE), Tickable {
override fun handle(packet: Packet) {
TODO("Not yet implemented")
}
override fun tick() {
super.tick()
if (!world!!.isClient && counter % 40 == 0L) {
sendPacket(TestPacket(ipAddress, IPAddress(170, 171, 101, 168)))
}
}
override fun findDestination(): Interface? {
for (dir in Direction.values()) {
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
if (itf != null) {
return itf
}
}
return null
}
}

View File

@ -0,0 +1,10 @@
package net.shadowfacts.phycon.network.block.test
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.packet.BasePacket
/**
* @author shadowfacts
*/
class TestPacket(source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
}

View File

@ -26,6 +26,6 @@ fun <BE> BE.handleItemStack(packet: ItemStackPacket) where BE: BlockEntity, BE:
val remainder = doHandleItemStack(packet) val remainder = doHandleItemStack(packet)
// if there are any items remaining, send them back to the source with incremented bounce count // if there are any items remaining, send them back to the source with incremented bounce count
if (!remainder.isEmpty) { if (!remainder.isEmpty) {
sendToSingle(ItemStackPacket(remainder, packet.bounceCount + 1, macAddress, packet.source)) // sendToSingle(ItemStackPacket(remainder, packet.bounceCount + 1, macAddress, packet.source))
} }
} }

View File

@ -0,0 +1,9 @@
package net.shadowfacts.phycon.network.component
import net.shadowfacts.phycon.api.NetworkDevice
/**
* @author shadowfacts
*/
interface NetworkStackProvider: NetworkDevice {
}

View File

@ -0,0 +1,9 @@
package net.shadowfacts.phycon.network.component
import net.shadowfacts.phycon.api.NetworkDevice
/**
* @author shadowfacts
*/
interface NetworkStackReceiver: NetworkDevice {
}

View File

@ -0,0 +1,22 @@
package net.shadowfacts.phycon.network.frame
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
class ARPQueryFrame(
val queryIP: IPAddress,
val sourceIP: IPAddress,
source: MACAddress,
): BaseFrame(source, MACAddress.BROADCAST) {
}
class ARPResponseFrame(
val query: IPAddress,
source: MACAddress,
destination: MACAddress,
): BaseFrame(source, destination) {
}

View File

@ -0,0 +1,17 @@
package net.shadowfacts.phycon.network.frame
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
open class BaseFrame(
@JvmField private val source: MACAddress,
@JvmField private val destination: MACAddress
): EthernetFrame {
override fun getSource() = source
override fun getDestination() = destination
}

View File

@ -0,0 +1,16 @@
package net.shadowfacts.phycon.network.frame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
class BasePacketFrame(
@JvmField private val packet: Packet,
source: MACAddress,
destination: MACAddress,
): BaseFrame(source, destination), PacketFrame {
override fun getPacket() = packet
}

View File

@ -1,14 +1,14 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class BasePacket( abstract class BasePacket(
@JvmField private val source: MACAddress, @JvmField private val source: IPAddress,
@JvmField private val destination: MACAddress @JvmField private val destination: IPAddress
): Packet { ): Packet {
override fun getSource() = source override fun getSource() = source

View File

@ -1,11 +1,17 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.component.NetworkStackReceiver
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class CapacityPacket(val stack: ItemStack, val capacity: Int, val receivingInterface: InterfaceBlockEntity, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { class CapacityPacket(
val stack: ItemStack,
val capacity: Int,
val stackReceiver: NetworkStackReceiver,
source: IPAddress,
destination: IPAddress
): BasePacket(source, destination) {
} }

View File

@ -1,10 +1,10 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class CheckCapacityPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { class CheckCapacityPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
} }

View File

@ -1,11 +1,11 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class DeviceRemovedPacket(source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) { class DeviceRemovedPacket(source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination) {
constructor(device: DeviceBlockEntity): this(device.macAddress) constructor(device: DeviceBlockEntity): this(device.ipAddress)
} }

View File

@ -1,10 +1,10 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractStackPacket(val stack: ItemStack, val amount: Int, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { class ExtractStackPacket(val stack: ItemStack, val amount: Int, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
} }

View File

@ -1,11 +1,11 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: MACAddress, destination: MACAddress): BasePacket(source, destination) { class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
constructor(stack: ItemStack, source: MACAddress, destination: MACAddress): this(stack, 0, source, destination) constructor(stack: ItemStack, source: IPAddress, destination: IPAddress): this(stack, 0, source, destination)
} }

View File

@ -1,10 +1,10 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class LocateStackPacket(val stack: ItemStack, source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) { class LocateStackPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination) {
} }

View File

@ -1,13 +1,13 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInvView
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ReadInventoryPacket( class ReadInventoryPacket(
val inventory: GroupedItemInv, val inventory: GroupedItemInvView,
source: MACAddress, source: IPAddress,
destination: MACAddress destination: IPAddress
): BasePacket(source, destination) ): BasePacket(source, destination)

View File

@ -1,8 +1,8 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class RequestInventoryPacket(source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) class RequestInventoryPacket(source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination)

View File

@ -1,8 +1,8 @@
package net.shadowfacts.phycon.network.packet package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.component.NetworkStackProvider
/** /**
* @author shadowfacts * @author shadowfacts
@ -11,8 +11,8 @@ import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
class StackLocationPacket( class StackLocationPacket(
val stack: ItemStack, val stack: ItemStack,
val amount: Int, val amount: Int,
val sourceInterface: InterfaceBlockEntity, val stackProvider: NetworkStackProvider,
source: MACAddress, source: IPAddress,
destination: MACAddress destination: IPAddress
): BasePacket(source, destination) { ): BasePacket(source, destination) {
} }

View File

@ -0,0 +1,38 @@
package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.TranslatableText
import net.shadowfacts.phycon.network.DeviceBlockEntity
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class DeviceConsoleScreen(
val device: DeviceBlockEntity,
): Screen(TranslatableText("item.phycon.onsole")) {
override fun init() {
super.init()
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose();
return true;
}
return super.keyPressed(key, j, k)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
super.render(matrixStack, mouseX, mouseY, delta)
drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff)
drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff)
}
}

View File

@ -0,0 +1,29 @@
{
"variants": {
"facing=down": {
"model": "phycon:block/extractor"
},
"facing=up": {
"model": "phycon:block/extractor",
"x": 180
},
"facing=north": {
"model": "phycon:block/extractor",
"x": 270
},
"facing=south": {
"model": "phycon:block/extractor",
"x": 90
},
"facing=west": {
"model": "phycon:block/extractor",
"x": 90,
"y": 90
},
"facing=east": {
"model": "phycon:block/extractor",
"x": 90,
"y": 270
}
}
}

View File

@ -0,0 +1,11 @@
{
"block.phycon.switch": "Network Switch",
"block.phycon.network_interface": "Inventory Interface",
"block.phycon.terminal": "Terminal",
"block.phycon.cable": "Cable",
"block.phycon.extractor": "Inventory Extractor",
"block.phycon.miner": "Block Miner",
"item.phycon.screwdriver": "Screwdriver",
"item.phycon.console": "Console"
}

View File

@ -0,0 +1,50 @@
{
"parent": "block/block",
"elements": [
{
"from": [0, 0, 0],
"to": [16, 2, 16],
"faces": {
"down": {"texture": "phycon:block/extractor_front"},
"up": {"texture": "phycon:block/extractor_back"},
"north": {"texture": "phycon:block/extractor_side"},
"south": {"texture": "phycon:block/extractor_side"},
"west": {"texture": "phycon:block/extractor_side"},
"east": {"texture": "phycon:block/extractor_side"}
}
},
{
"from": [2, 2, 2],
"to": [14, 4, 14],
"faces": {
"up": {"texture": "phycon:block/extractor_back"},
"north": {"texture": "phycon:block/extractor_side"},
"south": {"texture": "phycon:block/extractor_side"},
"west": {"texture": "phycon:block/extractor_side"},
"east": {"texture": "phycon:block/extractor_side"}
}
},
{
"from": [4, 4, 4],
"to": [12, 6, 12],
"faces": {
"up": {"texture": "phycon:block/extractor_back"},
"north": {"texture": "phycon:block/extractor_side"},
"south": {"texture": "phycon:block/extractor_side"},
"west": {"texture": "phycon:block/extractor_side"},
"east": {"texture": "phycon:block/extractor_side"}
}
},
{
"from": [6, 6, 6],
"to": [10, 16, 10],
"faces": {
"up": {"texture": "phycon:block/cable_side"},
"north": {"texture": "phycon:block/cable_side"},
"south": {"texture": "phycon:block/cable_side"},
"west": {"texture": "phycon:block/cable_side"},
"east": {"texture": "phycon:block/cable_side"}
}
}
]
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "phycon:item/console"
}
}

View File

@ -0,0 +1,54 @@
{
"parent": "block/block",
"display": {
"firstperson_righthand": {
"rotation": [0, 215, 0],
"translation": [0, 0, 0],
"scale": [0.4, 0.4, 0.4]
},
"thirdperson_righthand": {
"rotation": [75, 215, 0],
"translation": [0, -2.5, 0],
"scale": [0.375, 0.375, 0.375]
}
},
"elements": [
{
"_comment": "cable center",
"from": [6, 6, 6],
"to": [10, 10, 10],
"faces": {
"down": { "texture": "phycon:block/cable_center" },
"up": { "texture": "phycon:block/cable_center" },
"north": { "texture": "phycon:block/cable_center" },
"south": { "texture": "phycon:block/cable_center" },
"west": { "texture": "phycon:block/cable_center" },
"east": { "texture": "phycon:block/cable_center" }
}
},
{
"_comment": "interface side",
"from": [2, 2, 14],
"to": [14, 14, 16],
"faces": {
"down": { "texture": "phycon:block/interface_side" },
"up": { "texture": "phycon:block/interface_side" },
"north": { "texture": "phycon:block/interface_front" },
"south": { "texture": "phycon:block/interface_back" },
"west": { "texture": "phycon:block/interface_side" },
"east": { "texture": "phycon:block/interface_side" }
}
},
{
"_comment": "interface middle",
"from": [6, 6, 10],
"to": [10, 10, 14],
"faces": {
"down": { "texture": "phycon:block/cable_side" },
"up": { "texture": "phycon:block/cable_side" },
"west": { "texture": "phycon:block/cable_side" },
"east": { "texture": "phycon:block/cable_side" }
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B