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
*/
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;
import net.shadowfacts.phycon.api.packet.Packet;
import net.shadowfacts.phycon.api.util.MACAddress;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface PacketSink {
@NotNull
MACAddress getMACAddress();
public interface PacketSink extends NetworkDevice {
void handle(@NotNull Packet packet);

View File

@ -1,15 +1,18 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.packet.Packet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author shadowfacts
*/
public interface PacketSource {
// todo: does PacketSource actually need to extend NetworkDevice?
public interface PacketSource extends NetworkDevice {
// todo: better name for this
void sendToSingle(Packet packet);
void sendPacket(@NotNull Packet packet);
void sendToAll(Packet packet);
void sendToAll(Packet packet, Iterable<PacketSink> destinations);
// void sendToAll(@NotNull Packet packet);
//
// 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 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;
import net.shadowfacts.phycon.api.util.MACAddress;
import net.shadowfacts.phycon.api.util.IPAddress;
import org.jetbrains.annotations.NotNull;
/**
@ -9,9 +9,9 @@ import org.jetbrains.annotations.NotNull;
public interface Packet {
@NotNull
MACAddress getSource();
IPAddress getSource();
@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);
@NotNull
public static MACAddress random(Random random) {
long value = random.nextLong() & 0xfeffffffffffL;
return new MACAddress(value);
}
private static final Random macAddressRandom = new Random();
@NotNull
public static MACAddress random() {
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.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens
/**
* @author shadowfacts
@ -16,6 +17,7 @@ object PhysicalConnectivity: ModInitializer {
PhyBlocks.init()
PhyBlockEntities.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.util.Identifier
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.InterfaceBlockEntity
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
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
@ -20,15 +28,25 @@ object PhyBlockEntities {
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
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> {
return BlockEntityType.Builder.create(builder, arrayOf(block)).build(null)
return BlockEntityType.Builder.create(builder, block).build(null)
}
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
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<*>) {

View File

@ -4,9 +4,13 @@ import net.minecraft.block.Block
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
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.netswitch.SwitchBlock
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
@ -17,12 +21,22 @@ object PhyBlocks {
val TERMINAL = TerminalBlock()
val SWITCH = SwitchBlock()
val CABLE = CableBlock()
val EXTRACTOR = ExtractorBlock()
val MINER = MinerBlock()
val SOURCE = SourceBlock()
val DEST = DestBlock()
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH)
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) {

View File

@ -4,11 +4,16 @@ import net.minecraft.item.BlockItem
import net.minecraft.item.Item
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.item.ConsoleItem
import net.shadowfacts.phycon.item.ScrewdriverItem
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.netswitch.SwitchBlock
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
@ -19,16 +24,28 @@ object PhyItems {
val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings())
val SWITCH = BlockItem(PhyBlocks.SWITCH, 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 CONSOLE = ConsoleItem()
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH)
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(ConsoleItem.ID, CONSOLE)
}
private fun register(id: Identifier, item: Item) {

View File

@ -1,12 +1,18 @@
package net.shadowfacts.phycon.init
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
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.entity.player.PlayerEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.World
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.BlockWithEntity
/**
* @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) {
super.onBreak(world, pos, state, player)

View File

@ -1,83 +1,174 @@
package net.shadowfacts.phycon.network
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.shadowfacts.phycon.api.PacketSink
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.util.IPAddress
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 java.lang.RuntimeException
import java.util.*
/**
* @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
override fun getMACAddress(): MACAddress {
return macAddress
var ipAddress: IPAddress = IPAddress.random()
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) {
if (acceptsPacket(packet)) {
handlePacket(packet)
override fun receive(frame: EthernetFrame) {
println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame")
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)
protected open fun acceptsPacket(packet: Packet): Boolean {
return when (packet.destination.type) {
MACAddress.Type.BROADCAST -> true
MACAddress.Type.UNICAST -> macAddress == packet.destination
MACAddress.Type.MULTICAST -> acceptsMulticastPacket(packet)
private fun handleARPQuery(frame: ARPQueryFrame) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}")
arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) {
println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress")
send(ARPResponseFrame(ipAddress, macAddress, frame.source))
}
}
open fun acceptsMulticastPacket(packet: Packet): Boolean {
return false
private fun handleARPResponse(frame: ARPResponseFrame) {
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) {
destination.handle(packet)
override fun sendPacket(packet: 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) {
val destinations = NetworkUtil.findDestinations(world!!, pos)
if (destinations.size != 1) {
// todo: handle this better
println("Can't send packet, multiple destinations available: $destinations")
return
open fun findDestination(): Interface? {
val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
if (sides.size != 1) {
throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
}
send(packet, destinations.first())
return NetworkUtil.findConnectedInterface(world!!, pos, sides.first())
}
override fun sendToAll(packet: Packet) {
sendToAll(packet, NetworkUtil.findDestinations(world!!, pos))
}
override fun tick() {
counter++
override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) {
destinations.forEach {
it.handle(packet)
if (!world!!.isClient) {
val toRemove = packetQueue.filter { entry ->
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 {
tag.putInt("IPAddress", ipAddress.address)
tag.putLong("MACAddress", macAddress.address)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
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"))
}
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.Direction
import net.minecraft.world.World
import net.shadowfacts.phycon.api.NetworkCable
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PhyAttributes
import net.shadowfacts.phycon.api.*
import java.util.*
/**
@ -14,6 +11,32 @@ import java.util.*
*/
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> {
val results = LinkedList<PacketSink>()
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) {
val state = world.getBlockState(pos)
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)
for (side in connections) {
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.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkCable
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkCableBlock
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.util.CableConnection
import java.util.*
@ -31,7 +32,7 @@ import java.util.*
class CableBlock: Block(
Settings.of(CABLE_MATERIAL)
.nonOpaque()
), NetworkCable {
), NetworkCableBlock {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "cable")
val CABLE_MATERIAL = Material(MaterialColor.IRON, false, false, true, false, true, false, PistonBehavior.NORMAL)
@ -108,11 +109,16 @@ class CableBlock: Block(
else -> CableConnection.ON
}
}
is NetworkComponent -> CableConnection.ON
is NetworkComponentBlock -> CableConnection.ON
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(
state: BlockState,
world: World,
@ -141,7 +147,7 @@ class CableBlock: Block(
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)
}

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.AttributeProvider
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.block.ShapeContext
import net.minecraft.block.*
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
@ -21,15 +18,19 @@ import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.block.BlockWithEntity
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
/**
* @author shadowfacts
*/
class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider {
class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
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> {
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>) {
@ -83,7 +94,7 @@ class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material
private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? {
for (side in Direction.values()) {
val offsetPos = pos.offset(side)
if (world.getBlockState(offsetPos).block is NetworkComponent) {
if (world.getBlockState(offsetPos).block is NetworkComponentBlock) {
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 {
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

View File

@ -10,16 +10,21 @@ import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
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.packet.*
/**
* @author shadowfacts
*/
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler {
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
ItemStackPacketHandler,
NetworkStackProvider,
NetworkStackReceiver {
private val facing: Direction
get() = world!!.getBlockState(pos)[InterfaceBlock.FACING]
get() = cachedState[InterfaceBlock.FACING]
// todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null
@ -37,7 +42,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemS
return inventory
}
override fun handlePacket(packet: Packet) {
override fun handle(packet: Packet) {
when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet)
@ -49,21 +54,21 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemS
private fun handleRequestInventory(packet: RequestInventoryPacket) {
getInventory()?.also { inv ->
sendToSingle(ReadInventoryPacket(inv, macAddress, packet.source))
sendPacket(ReadInventoryPacket(inv, ipAddress, packet.source))
}
}
private fun handleLocateStack(packet: LocateStackPacket) {
getInventory()?.also { inv ->
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) {
getInventory()?.also { inv ->
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 ->
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
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.World
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 java.util.*
/**
* @author shadowfacts
*/
class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider {
class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch")
}
@ -26,6 +30,10 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(Settings.of(Material.METAL
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 addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {

View File

@ -1,82 +1,119 @@
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.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.Interface
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.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil
import net.shadowfacts.phycon.network.packet.ItemStackPacket
import java.lang.RuntimeException
import java.lang.ref.WeakReference
/**
* @author shadowfacts
*/
class SwitchBlockEntity: DeviceBlockEntity(PhyBlockEntities.SWITCH), Tickable {
class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
BlockEntityClientSerializable,
Tickable {
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 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) {
if (packetsHandledThisTick >= SWITCHING_CAPACITY) {
if (packet is ItemStackPacket) {
private fun handle(frame: EthernetFrame, fromItf: SwitchInterface) {
macTable[frame.source] = fromItf.side
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
val entity = ItemEntity(world!!, pos.x.toDouble(), pos.y + 1.0, pos.z.toDouble(), packet.stack)
world!!.spawnEntity(entity)
}
return
}
packetsHandledThisTick++
if (packet.destination == MACAddress.BROADCAST) {
val allDestinations = NetworkUtil.findDestinations(world!!, pos).filter { it.macAddress != packet.source }
sendToAll(packet, allDestinations)
} else {
val direction = macTable[packet.destination]
if (direction != null) {
val dest = findDestination(direction)
if (dest != null && packet.destination == dest.macAddress) {
send(packet, dest)
return
}
}
flood(packet)
itemsHandledThisTick += packet.stack.count
}
}
private fun findDestination(direction: Direction): PacketSink? {
val allDestinations = NetworkUtil.findDestinations(world!!, pos, direction)
if (allDestinations.size > 1) {
// todo: do this better
println("Can't send packet, multiple destinations: $allDestinations")
return null
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!!
println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir")
interfaceForSide(dir).send(frame)
} else {
println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame")
flood(frame, fromItf)
}
return allDestinations.firstOrNull()
}
private fun flood(packet: Packet) {
for (dir in Direction.values()) {
val dest = findDestination(dir)
if (dest != null && packet.destination == dest.macAddress) {
macTable[packet.destination] = dir
send(packet, dest)
break
private fun flood(frame: EthernetFrame, source: Interface) {
for (itf in interfaces) {
if (source == itf) continue
itf.send(frame)
}
}
private fun findDestination(fromItf: Interface): Interface? {
val side = (fromItf as SwitchInterface).side
return NetworkUtil.findConnectedInterface(world!!, pos, side)
}
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.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.block.BlockWithEntity
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.network.DeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class TerminalBlock: BlockWithEntity<TerminalBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider {
class TerminalBlock: DeviceBlock<TerminalBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}

View File

@ -1,6 +1,6 @@
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.ItemStackUtil
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
@ -17,14 +17,18 @@ import net.minecraft.nbt.ListTag
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
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.math.Direction
import net.shadowfacts.phycon.api.Interface
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.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.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.*
import java.util.*
@ -40,7 +44,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val INSERTION_TIMEOUT = 40
}
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
val internalBuffer = TerminalBufferInventory(18)
private val locateRequestQueue = LinkedList<StackLocateRequest>()
@ -50,13 +54,22 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var counter = 0
init {
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) {
is ReadInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
@ -83,7 +96,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
}
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) {
stackLocateRequestCompleted(request)
}
@ -106,7 +119,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack)
}
if (insertion != null) {
insertion.results.add(packet.capacity to packet.receivingInterface)
insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(counter)) {
finishInsertion(insertion)
}
@ -137,7 +150,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
if (slot in pendingInsertions) continue
val stack = internalBuffer.getStack(slot)
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) {
pendingRequests.add(request)
sendToSingle(LocateStackPacket(request.stack, macAddress))
sendPacket(LocateStackPacket(request.stack, ipAddress))
}
locateRequestQueue.clear()
}
@ -179,19 +192,26 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
}
override fun tick() {
if (observers > 0 && (++counter % 10) == 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
super.tick()
if (counter % 20 == 0L) {
if (!world!!.isClient) {
sendEnqueuedLocateRequests()
finishPendingRequests()
beginInsertions()
finishPendingInsertions()
}
if (observers > 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
sync()
}
}
}
}
fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
@ -199,13 +219,13 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
sync()
inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress))
sendPacket(RequestInventoryPacket(ipAddress))
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
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) {
buf.writeBlockPos(this@TerminalBlockEntity.pos)
@ -230,7 +250,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
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()) {
val (capacity, receivingInterface) = sortedResults.removeAt(0)
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
remaining.count -= capacity
}
@ -304,27 +324,27 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), Invento
data class StackLocateRequest(
val stack: ItemStack,
val amount: Int,
val timestamp: Int,
var results: MutableSet<Pair<Int, InterfaceBlockEntity>> = mutableSetOf()
val timestamp: Long,
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
) {
val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean {
return totalResultAmount > amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
fun isFinishable(currentTimestamp: Long): Boolean {
return totalResultAmount >= amount || currentTimestamp - timestamp >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
}
}
data class PendingStackInsertion(
val bufferSlot: Int,
val stack: ItemStack,
val timestamp: Int,
val results: MutableSet<Pair<Int, InterfaceBlockEntity>> = mutableSetOf(),
val timestamp: Long,
val results: MutableSet<Pair<Int, NetworkStackReceiver>> = mutableSetOf(),
) {
val totalCapacity: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Int): Boolean {
return totalCapacity > stack.count || currentTimestamp - timestamp >= TerminalBlockEntity.INSERTION_TIMEOUT
fun isFinishable(currentTimestamp: Long): Boolean {
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.entity.player.PlayerInventory
import net.minecraft.screen.slot.Slot
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.minecraft.util.Identifier
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) {
renderBackground(matrixStack)
GlStateManager.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND)
val x = (width - backgroundWidth) / 2
@ -40,6 +41,11 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
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
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
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)
// if there are any items remaining, send them back to the source with incremented bounce count
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
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
abstract class BasePacket(
@JvmField private val source: MACAddress,
@JvmField private val destination: MACAddress
@JvmField private val source: IPAddress,
@JvmField private val destination: IPAddress
): Packet {
override fun getSource() = source

View File

@ -1,11 +1,17 @@
package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
/**
* @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
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @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
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.DeviceBlockEntity
/**
* @author shadowfacts
*/
class DeviceRemovedPacket(source: MACAddress, destination: MACAddress = MACAddress.BROADCAST): BasePacket(source, destination) {
constructor(device: DeviceBlockEntity): this(device.macAddress)
class DeviceRemovedPacket(source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination) {
constructor(device: DeviceBlockEntity): this(device.ipAddress)
}

View File

@ -1,10 +1,10 @@
package net.shadowfacts.phycon.network.packet
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @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
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: MACAddress, destination: MACAddress): BasePacket(source, destination) {
constructor(stack: ItemStack, source: MACAddress, destination: MACAddress): this(stack, 0, source, destination)
class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: IPAddress, destination: IPAddress): BasePacket(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
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @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
import alexiil.mc.lib.attributes.item.GroupedItemInv
import net.shadowfacts.phycon.api.util.MACAddress
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ReadInventoryPacket(
val inventory: GroupedItemInv,
source: MACAddress,
destination: MACAddress
val inventory: GroupedItemInvView,
source: IPAddress,
destination: IPAddress
): BasePacket(source, destination)

View File

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