Implement layer 3 networking

This commit is contained in:
Shadowfacts 2021-02-13 18:24:36 -05:00
parent 26134cea9d
commit e8425b80fd
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
53 changed files with 1764 additions and 1059 deletions

View File

@ -0,0 +1,22 @@
package net.shadowfacts.phycon.api;
import net.shadowfacts.phycon.api.frame.EthernetFrame;
import net.shadowfacts.phycon.api.packet.Packet;
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);
// void send(@NotNull Packet packet);
}

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,17 +1,16 @@
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 {
public interface PacketSink extends NetworkDevice {
@NotNull
MACAddress getMACAddress();
Iterable<Interface> getDeviceInterfaces();
void handle(@NotNull Packet packet);
void handle(@NotNull Packet packet, @NotNull Interface itf);
}

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, @Nullable Interface itf);
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

@ -3,7 +3,7 @@ package net.shadowfacts.phycon.mixin.client;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.slot.Slot;
import net.shadowfacts.phycon.network.block.terminal.TerminalScreen;
//import net.shadowfacts.phycon.network.block.terminal.TerminalScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -17,10 +17,10 @@ public class MixinHandledScreen {
@Inject(method = "drawSlot(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V", at = @At(value = "INVOKE", target = "enableDepthTest()V"))
private void drawSlot(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
if ((Object)this instanceof TerminalScreen) {
TerminalScreen self = (TerminalScreen)(Object)this;
self.drawSlotUnderlay(matrixStack, slot);
}
// if ((Object)this instanceof TerminalScreen) {
// TerminalScreen self = (TerminalScreen)(Object)this;
// self.drawSlotUnderlay(matrixStack, slot);
// }
}
}

View File

@ -6,7 +6,7 @@ import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
import net.minecraft.client.render.RenderLayer
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.network.block.terminal.TerminalScreen
//import net.shadowfacts.phycon.network.block.terminal.TerminalScreen
/**
* @author shadowfacts
@ -15,6 +15,6 @@ object PhysicalConnectivityClient: ClientModInitializer {
override fun onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent())
ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
// ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
}
}

View File

@ -5,30 +5,39 @@ 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.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
//import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
//import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
//import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
//import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
/**
* @author shadowfacts
*/
object PhyBlockEntities {
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
// val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
// val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
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(InterfaceBlock.ID, INTERFACE)
// register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
}
private fun register(id: Identifier, type: BlockEntityType<*>) {

View File

@ -4,25 +4,29 @@ 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.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
*/
object PhyBlocks {
val INTERFACE = InterfaceBlock()
val TERMINAL = TerminalBlock()
// val INTERFACE = InterfaceBlock()
// val TERMINAL = TerminalBlock()
val SWITCH = SwitchBlock()
val CABLE = CableBlock()
val SOURCE = SourceBlock()
val DEST = DestBlock()
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
// register(InterfaceBlock.ID, INTERFACE)
// register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(CableBlock.ID, CABLE)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
}
private fun register(id: Identifier, block: Block) {

View File

@ -6,27 +6,34 @@ import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.item.ScrewdriverItem
import net.shadowfacts.phycon.network.block.cable.CableBlock
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
//import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
//import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
/**
* @author shadowfacts
*/
object PhyItems {
val INTERFACE = BlockItem(PhyBlocks.INTERFACE, Item.Settings())
val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings())
// val INTERFACE = BlockItem(PhyBlocks.INTERFACE, Item.Settings())
// val TERMINAL = BlockItem(PhyBlocks.TERMINAL, Item.Settings())
val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings())
val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings())
val SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings())
val DEST = BlockItem(PhyBlocks.DEST , Item.Settings())
val SCREWDRIVER = ScrewdriverItem()
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
// register(InterfaceBlock.ID, INTERFACE)
// register(TerminalBlock.ID, TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(CableBlock.ID, CABLE)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
register(ScrewdriverItem.ID, SCREWDRIVER)
}

View File

@ -3,10 +3,10 @@ package net.shadowfacts.phycon.init
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
//import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
object PhyScreens {
val TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler)
// val TERMINAL_SCREEN_HANDLER = ScreenHandlerRegistry.registerExtended(Identifier(PhysicalConnectivity.MODID, "terminal"), ::TerminalScreenHandler)
}

View File

@ -0,0 +1,53 @@
package net.shadowfacts.phycon.network
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.BaseFrame
import java.lang.ref.WeakReference
/**
* @author shadowfacts
*/
open class BaseInterface(
var macAddress: MACAddress,
private val delegate: WeakReference<InterfaceDelegate>
): Interface {
constructor(delegate: InterfaceDelegate): this(MACAddress.random(), WeakReference(delegate))
override fun getMACAddress(): MACAddress {
return macAddress
}
override fun receive(frame: EthernetFrame) {
delegate.get()?.handle(frame, this)
}
override fun send(frame: EthernetFrame) {
delegate.get()?.findDestination(this)?.receive(frame)
}
// override fun send(packet: Packet) {
// delegate.get()?.findDestination(this)?.also {
// it.receive(BaseFrame(packet, this.macAddress, it.macAddress))
// }
// }
override fun equals(other: Any?): Boolean {
return (other as? BaseInterface)?.macAddress == macAddress
}
override fun hashCode(): Int {
return macAddress.hashCode()
}
}
interface InterfaceDelegate {
fun findDestination(fromItf: Interface): Interface?
fun handle(frame: EthernetFrame, fromItf: Interface)
}

View File

@ -4,12 +4,13 @@ import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
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 {
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
super.onBreak(world, pos, state, player)

View File

@ -1,83 +1,235 @@
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,
InterfaceDelegate {
var macAddress = MACAddress.random()
companion object {
private const val ARP_RETRY_TIMEOUT = 200
}
// var macAddress: MACAddress = MACAddress.random()
// protected set
var ipAddress: IPAddress = IPAddress.random()
protected set
override fun getMACAddress(): MACAddress {
return macAddress
}
open val interfaces: List<BaseInterface> = listOf()
// abstract val itf: BaseInterface
override fun handle(packet: Packet) {
if (acceptsPacket(packet)) {
handlePacket(packet)
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
private val packetQueue = LinkedList<PendingPacket>()
protected var counter: Long = 0
override fun getIPAddress() = ipAddress
override fun getDeviceInterfaces() = interfaces
abstract override fun handle(packet: Packet, itf: Interface)
override fun handle(frame: EthernetFrame, fromItf: Interface) {
println("$this ($ipAddress, ${fromItf.macAddress}) received frame from ${frame.source}: $frame")
when (frame) {
is ARPQueryFrame -> handleARPQuery(frame, fromItf)
is ARPResponseFrame -> handleARPResponse(frame, fromItf)
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
handle(frame.packet, fromItf)
}
}
}
}
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, fromItf: Interface) {
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 ${fromItf.macAddress}")
fromItf.send(ARPResponseFrame(ipAddress, fromItf.macAddress, frame.source))
}
}
open fun acceptsMulticastPacket(packet: Packet): Boolean {
return false
}
private fun handleARPResponse(frame: ARPResponseFrame, fromItf: Interface) {
arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}")
fun send(packet: Packet, destination: PacketSink) {
destination.handle(packet)
}
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
packetQueue.removeIf { (packet, itf, _) ->
if (packet.destination == frame.query) {
itf.send(BasePacketFrame(packet, itf.macAddress, frame.source))
true
} else {
false
}
}
send(packet, destinations.first())
}
override fun sendToAll(packet: Packet) {
sendToAll(packet, NetworkUtil.findDestinations(world!!, pos))
// protected abstract fun handlePacket(packet: Packet, itf: Interface)
//
// protected open fun acceptsPacket(packet: Packet, itf: Interface): Boolean {
// return when (packet.destination.type) {
// MACAddress.Type.BROADCAST -> true
// MACAddress.Type.UNICAST -> itf.macAddress == packet.destination
// MACAddress.Type.MULTICAST -> acceptsMulticastPacket(packet)
// }
// }
// open fun acceptsMulticastPacket(packet: Packet): Boolean {
// return false
// }
fun sendPacket(packet: Packet) {
sendPacket(packet, null)
}
override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) {
destinations.forEach {
it.handle(packet)
override fun sendPacket(packet: Packet, itf: Interface?) {
@Suppress("NAME_SHADOWING") var itf = itf
if (itf == null) {
if (interfaces.size == 1) {
itf = interfaces.first()
} else {
throw RuntimeException("Cannot send packet from device with multiple interfaces without explicitly specifying interface")
}
}
val cached = arpTable[packet.destination]
if (cached != null) {
itf.send(BasePacketFrame(packet, itf.macAddress, cached))
} else {
// packetQueue.add(packet to itf)
packetQueue.add(PendingPacket(packet, itf, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}")
itf.send(ARPQueryFrame(packet.destination, ipAddress, itf.macAddress))
// after sending an ARP query we expect to have received a response and added an entry to our ARP table
// todo: this makes the assumption that packets are sent the entire way synchronously, and then a response
// is immediately sent and forwarded also synchronously
// cached = arpTable[packet.destination]
}
}
// fun findMACAddressFor(ipAddress: IPAddress): MACAddress? {
// if (arpTable.containsKey(ipAddress)) {
// return arpTable[ipAddress]
// } else {
//
// }
// }
// 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
// }
// send(packet))
// }
// override fun sendToAll(packet: Packet) {
// sendToAll(packet, NetworkUtil.findDestinations(world!!, pos))
// }
//
// override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) {
// destinations.forEach {
// it.handle(packet)
// }
// }
override fun findDestination(fromItf: Interface): 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")
}
return NetworkUtil.findConnectedInterface(world!!, pos, sides.first())
}
override fun tick() {
if (!world!!.isClient) {
counter++
packetQueue.removeIf { entry ->
val (packet, itf, timestamp) = entry
if (arpTable.containsKey(packet.destination)) {
itf.send(BasePacketFrame(packet, itf.macAddress, arpTable[packet.destination]!!))
true
} else if (counter - timestamp >= ARP_RETRY_TIMEOUT) {
itf.send(ARPQueryFrame(packet.destination, ipAddress, itf.macAddress))
entry.timestamp = counter
// todo: should there be a retry counter?
true
} else {
false
}
}
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putLong("MACAddress", macAddress.address)
tag.putInt("IPAddress", ipAddress.address)
// tag.putLong("MACAddress", macAddress.address)
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
macAddress = MACAddress(tag.getLong("MACAddress"))
ipAddress = IPAddress(tag.getInt("IPAddress"))
// todo: what happens if the defined number of ports changes between mod versions?
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putInt("IPAddress", ipAddress.address)
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return tag
}
override fun fromClientTag(tag: CompoundTag) {
ipAddress = IPAddress(tag.getInt("IPAddress"))
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
fun onBreak() {
sendToAll(DeviceRemovedPacket(this))
sendPacket(DeviceRemovedPacket(this))
}
data class PendingPacket(
val packet: Packet,
val sourceItf: Interface,
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,28 @@ 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)
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 +62,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

@ -1,120 +1,128 @@
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.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
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.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponent
import net.shadowfacts.phycon.block.BlockWithEntity
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
/**
* @author shadowfacts
*/
class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
val FACING = Properties.FACING
val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
private val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
)
private val CENTER_SHAPES = mapOf<Direction, VoxelShape>(
Direction.DOWN to createCuboidShape(6.0, 2.0, 6.0, 10.0, 10.0, 10.0),
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 14.0, 10.0),
Direction.NORTH to createCuboidShape(6.0, 6.0, 2.0, 10.0, 10.0, 10.0),
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 14.0),
Direction.WEST to createCuboidShape(2.0, 6.0, 6.0, 10.0, 10.0, 10.0),
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 14.0, 10.0, 10.0)
)
private val shapeCache = mutableMapOf<Pair<Direction, Direction>, VoxelShape>()
fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
return shapeCache.getOrPut(facing to cableConnection) {
VoxelShapes.union(
VoxelShapes.union(SIDE_SHAPES[facing], CENTER_SHAPES[facing]),
CableBlock.SIDE_SHAPES[cableConnection]
)
}
}
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[CABLE_CONNECTION])
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
builder.add(CABLE_CONNECTION)
}
override fun createBlockEntity(world: BlockView) = InterfaceBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
val cableConnection = getCableConnectionSide(context.world, context.blockPos) ?: facing.opposite
return defaultState.with(FACING, facing).with(CABLE_CONNECTION, cableConnection)
}
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) {
return side
}
}
return null
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, boolean_1: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
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) {
return state.with(CABLE_CONNECTION, side)
}
return state
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION])
}
}
//
//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.entity.LivingEntity
//import net.minecraft.item.ItemPlacementContext
//import net.minecraft.item.ItemStack
//import net.minecraft.state.StateManager
//import net.minecraft.state.property.EnumProperty
//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.minecraft.world.WorldAccess
//import net.shadowfacts.phycon.PhysicalConnectivity
//import net.shadowfacts.phycon.api.NetworkComponentBlock
//import net.shadowfacts.phycon.api.Interface
//import net.shadowfacts.phycon.block.BlockWithEntity
//import net.shadowfacts.phycon.network.block.cable.CableBlock
//import java.util.*
//
///**
// * @author shadowfacts
// */
//class InterfaceBlock: BlockWithEntity<InterfaceBlockEntity>(Settings.of(Material.METAL)),
// NetworkComponentBlock,
// AttributeProvider {
//
// companion object {
// val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
// val FACING = Properties.FACING
// val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
// private val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
// Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
// Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
// Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
// Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
// Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
// Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
// )
// private val CENTER_SHAPES = mapOf<Direction, VoxelShape>(
// Direction.DOWN to createCuboidShape(6.0, 2.0, 6.0, 10.0, 10.0, 10.0),
// Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 14.0, 10.0),
// Direction.NORTH to createCuboidShape(6.0, 6.0, 2.0, 10.0, 10.0, 10.0),
// Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 14.0),
// Direction.WEST to createCuboidShape(2.0, 6.0, 6.0, 10.0, 10.0, 10.0),
// Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 14.0, 10.0, 10.0)
// )
//
// private val shapeCache = mutableMapOf<Pair<Direction, Direction>, VoxelShape>()
// fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
// return shapeCache.getOrPut(facing to cableConnection) {
// VoxelShapes.union(
// VoxelShapes.union(SIDE_SHAPES[facing], CENTER_SHAPES[facing]),
// CableBlock.SIDE_SHAPES[cableConnection]
// )
// }
// }
// }
//
// override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
// return EnumSet.of(state[CABLE_CONNECTION])
// }
//
// override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
// return getBlockEntity(world, pos)?.deviceInterfaces?.first()
// }
//
// override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
// super.appendProperties(builder)
// builder.add(FACING)
// builder.add(CABLE_CONNECTION)
// }
//
// override fun createBlockEntity(world: BlockView) = InterfaceBlockEntity()
//
// override fun getPlacementState(context: ItemPlacementContext): BlockState {
// val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
// val cableConnection = getCableConnectionSide(context.world, context.blockPos) ?: facing.opposite
// return defaultState.with(FACING, facing).with(CABLE_CONNECTION, cableConnection)
// }
//
// private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? {
// for (side in Direction.values()) {
// val offsetPos = pos.offset(side)
// if (world.getBlockState(offsetPos).block is NetworkComponentBlock) {
// return side
// }
// }
// return null
// }
//
// override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
// if (!world.isClient) {
// getBlockEntity(world, pos)!!.updateInventory()
// }
// }
//
// override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, boolean_1: Boolean) {
// if (!world.isClient) {
// getBlockEntity(world, pos)!!.updateInventory()
// }
// }
//
// override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
// if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
// return state.with(CABLE_CONNECTION, side)
// }
// return state
// }
//
// override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
// to.offer(getBlockEntity(world, pos))
// }
//
// override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
// return getShape(state[FACING], state[CABLE_CONNECTION])
// }
//
//}

View File

@ -1,89 +1,93 @@
package net.shadowfacts.phycon.network.block.netinterface
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.item.ItemStack
import net.minecraft.util.math.Direction
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.handleItemStack
import net.shadowfacts.phycon.network.packet.*
/**
* @author shadowfacts
*/
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler {
private val facing: Direction
get() = world!!.getBlockState(pos)[InterfaceBlock.FACING]
// todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null
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 we don't have an inventory, try to get one
// this happens when readAll is called before a neighbor state changes, such as immediately after world load
if (inventory == null) updateInventory()
return inventory
}
override fun handlePacket(packet: Packet) {
when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet)
is LocateStackPacket -> handleLocateStack(packet)
is ExtractStackPacket -> handleExtractStack(packet)
is CheckCapacityPacket -> handleCheckCapacity(packet)
is ItemStackPacket -> handleItemStack(packet)
}
}
private fun handleRequestInventory(packet: RequestInventoryPacket) {
getInventory()?.also { inv ->
sendToSingle(ReadInventoryPacket(inv, macAddress, 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))
}
}
private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv ->
val extracted = inv.extract(packet.stack, packet.amount)
sendToSingle(ItemStackPacket(extracted, macAddress, packet.source))
}
}
private fun handleCheckCapacity(packet: CheckCapacityPacket) {
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))
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory()
if (inventory != null) {
val remaining = inventory.insert(packet.stack)
// whatever could not be inserted will be sent back to the packet's source
return remaining
} else {
return packet.stack
}
}
}
//
//import alexiil.mc.lib.attributes.SearchOptions
//import alexiil.mc.lib.attributes.Simulation
//import alexiil.mc.lib.attributes.item.GroupedItemInv
//import alexiil.mc.lib.attributes.item.ItemAttributes
//import net.minecraft.item.ItemStack
//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.BaseInterface
//import net.shadowfacts.phycon.network.DeviceBlockEntity
//import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
//import net.shadowfacts.phycon.network.component.handleItemStack
//import net.shadowfacts.phycon.network.packet.*
//
///**
// * @author shadowfacts
// */
//class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), ItemStackPacketHandler {
//
// override val interfaces = listOf(BaseInterface(this))
//
// private val facing: Direction
// get() = world!!.getBlockState(pos)[InterfaceBlock.FACING]
//
// // todo: should this be a weak ref?
// private var inventory: GroupedItemInv? = null
//
// 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 we don't have an inventory, try to get one
// // this happens when readAll is called before a neighbor state changes, such as immediately after world load
// if (inventory == null) updateInventory()
// return inventory
// }
//
// override fun handle(packet: Packet, itf: Interface) {
// when (packet) {
// is RequestInventoryPacket -> handleRequestInventory(packet)
// is LocateStackPacket -> handleLocateStack(packet)
// is ExtractStackPacket -> handleExtractStack(packet)
// is CheckCapacityPacket -> handleCheckCapacity(packet)
// is ItemStackPacket -> handleItemStack(packet)
// }
// }
//
// private fun handleRequestInventory(packet: RequestInventoryPacket) {
// getInventory()?.also { inv ->
// sendPacket(ReadInventoryPacket(inv, ipAddress, packet.source))
// }
// }
//
// private fun handleLocateStack(packet: LocateStackPacket) {
// getInventory()?.also { inv ->
// val amount = inv.getAmount(packet.stack)
// 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)
// sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
// }
// }
//
// private fun handleCheckCapacity(packet: CheckCapacityPacket) {
// getInventory()?.also { inv ->
// val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
// val couldAccept = packet.stack.count - remaining.count
// sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source))
// }
// }
//
// override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
// val inventory = getInventory()
// if (inventory != null) {
// val remaining = inventory.insert(packet.stack)
// // whatever could not be inserted will be sent back to the packet's source
// return remaining
// } else {
// return packet.stack
// }
// }
//
//}

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,129 @@
package net.shadowfacts.phycon.network.block.netswitch
import net.minecraft.entity.ItemEntity
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
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.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame
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.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.BaseInterface
import net.shadowfacts.phycon.network.InterfaceDelegate
import net.shadowfacts.phycon.network.NetworkUtil
import net.shadowfacts.phycon.network.packet.ItemStackPacket
import java.lang.RuntimeException
/**
* @author shadowfacts
*/
class SwitchBlockEntity: DeviceBlockEntity(PhyBlockEntities.SWITCH), Tickable {
class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
BlockEntityClientSerializable,
InterfaceDelegate,
Tickable {
companion object {
var SWITCHING_CAPACITY = 256
}
val interfaces = Direction.values().map { SwitchInterface(it, this) }
private val macTable = mutableMapOf<MACAddress, Direction>()
private var packetsHandledThisTick = 0
override fun acceptsPacket(packet: Packet) = true
// override fun handle(packet: Packet, itf: Interface) {
// if (packetsHandledThisTick >= SWITCHING_CAPACITY) {
// if (packet is ItemStackPacket) {
// // 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.isBroadcast) {
//// for (other in interfaces) {
//// if (other == itf) continue
//// sendPacket(packet, other)
//// }
// flood(packet, itf)
// } else {
// val direction = ipTable[packet.destination]
// if (direction != null) {
// sendPacket()
//// val dest = findDestination(direction)
//// if (dest != null && packet.destination == dest.macAddress) {
//// sendPacke(packet, dest)
//// return
//// }
// }
// flood(packet, itf)
// }
// }
override fun handlePacket(packet: Packet) {
if (packetsHandledThisTick >= SWITCHING_CAPACITY) {
if (packet is ItemStackPacket) {
// 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
}
fun interfaceForSide(side: Direction): SwitchInterface {
return interfaces.find { it.side == side }!!
}
packetsHandledThisTick++
override fun handle(frame: EthernetFrame, fromItf: Interface) {
val itfSide = (fromItf as SwitchInterface).side
macTable[frame.source] = itfSide
if (packet.destination == MACAddress.BROADCAST) {
val allDestinations = NetworkUtil.findDestinations(world!!, pos).filter { it.macAddress != packet.source }
sendToAll(packet, allDestinations)
val knownDir = macTable[frame.destination]
if (knownDir != null) {
println("$this ($itfSide, ${fromItf.macAddress}) forwarding $frame to side $knownDir")
interfaceForSide(knownDir).send(frame)
} 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)
println("$this ($itfSide, ${fromItf.macAddress}) flooding $frame")
flood(frame, fromItf)
}
}
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
private fun flood(frame: EthernetFrame, source: Interface) {
for (itf in interfaces) {
if (source == itf) continue
itf.send(frame)
}
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
}
}
override fun findDestination(fromItf: Interface): Interface? {
val side = (fromItf as SwitchInterface).side
return NetworkUtil.findConnectedInterface(world!!, pos, side)
}
override fun tick() {
packetsHandledThisTick = 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, delegate: InterfaceDelegate): BaseInterface(delegate) {
}
}

View File

@ -1,43 +1,44 @@
package net.shadowfacts.phycon.network.block.terminal
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
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.api.NetworkComponent
import net.shadowfacts.phycon.block.BlockWithEntity
import java.util.*
/**
* @author shadowfacts
*/
class TerminalBlock: BlockWithEntity<TerminalBlockEntity>(Settings.of(Material.METAL)), NetworkComponent, AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
return EnumSet.allOf(Direction::class.java)
}
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
getBlockEntity(world, pos)!!.onActivate(player)
return ActionResult.SUCCESS
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
}
//
//import alexiil.mc.lib.attributes.AttributeList
//import alexiil.mc.lib.attributes.AttributeProvider
//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.api.NetworkComponentBlock
//import net.shadowfacts.phycon.block.BlockWithEntity
//import java.util.*
//
///**
// * @author shadowfacts
// */
//class TerminalBlock: BlockWithEntity<TerminalBlockEntity>(Settings.of(Material.METAL)),
// NetworkComponentBlock, AttributeProvider {
// companion object {
// val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
// }
//
// override fun getNetworkConnectedSides(state: BlockState, world: World, pos: BlockPos): Collection<Direction> {
// return EnumSet.allOf(Direction::class.java)
// }
//
// override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
//
// override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
// getBlockEntity(world, pos)!!.onActivate(player)
// return ActionResult.SUCCESS
// }
//
// override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
// to.offer(getBlockEntity(world, pos))
// }
//}

View File

@ -1,330 +1,330 @@
package net.shadowfacts.phycon.network.block.terminal
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.Inventory
import net.minecraft.inventory.InventoryChangedListener
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
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.util.Tickable
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.MACAddress
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.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.*
import java.util.*
import kotlin.math.min
/**
* @author shadowfacts
*/
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryChangedListener, BlockEntityClientSerializable, Tickable, ItemStackPacketHandler {
companion object {
val LOCATE_REQUEST_TIMEOUT = 40 // ticks
val INSERTION_TIMEOUT = 40
}
private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
val internalBuffer = TerminalBufferInventory(18)
private val locateRequestQueue = LinkedList<StackLocateRequest>()
private val pendingRequests = LinkedList<StackLocateRequest>()
private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var counter = 0
init {
internalBuffer.addListener(this)
}
override fun handlePacket(packet: Packet) {
when (packet) {
is ReadInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet)
is CapacityPacket -> handleCapacity(packet)
}
}
private fun handleReadInventory(packet: ReadInventoryPacket) {
inventoryCache[packet.source] = packet.inventory
updateNetItems()
sync()
}
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
inventoryCache.remove(packet.source)
updateNetItems()
sync()
}
private fun handleStackLocation(packet: StackLocationPacket) {
val request = pendingRequests.firstOrNull {
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
}
if (request != null) {
request.results.add(packet.amount to packet.sourceInterface)
if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) {
stackLocateRequestCompleted(request)
}
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val remaining = internalBuffer.insertFromNetwork(packet.stack)
// this happens outside the normal update loop because by receiving the item stack packet
// we "know" how much the count in the source inventory has changed
updateNetItems()
sync()
return remaining
}
private fun handleCapacity(packet: CapacityPacket) {
val insertion = pendingInsertions.values.firstOrNull {
ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack)
}
if (insertion != null) {
insertion.results.add(packet.capacity to packet.receivingInterface)
if (insertion.isFinishable(counter)) {
finishInsertion(insertion)
}
}
}
private fun updateNetItems() {
cachedNetItems.clear()
for (inventory in inventoryCache.values) {
for (stack in inventory.storedStacks) {
val amount = inventory.getAmount(stack)
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
}
}
// todo: is the map necessary or is just the sorted list enough?
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
}
private fun beginInsertions() {
if (world!!.isClient) return
for (slot in 0 until internalBuffer.size()) {
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
if (slot in pendingInsertions) continue
val stack = internalBuffer.getStack(slot)
pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter)
sendToSingle(CheckCapacityPacket(stack, macAddress, MACAddress.BROADCAST))
}
}
private fun finishPendingInsertions() {
if (world!!.isClient) return
for (insertion in pendingInsertions.values) {
if (!insertion.isFinishable(counter)) continue
finishInsertion(insertion)
}
}
private fun sendEnqueuedLocateRequests() {
if (world!!.isClient) return
for (request in locateRequestQueue) {
pendingRequests.add(request)
sendToSingle(LocateStackPacket(request.stack, macAddress))
}
locateRequestQueue.clear()
}
private fun finishPendingRequests() {
if (world!!.isClient) return
for (request in pendingRequests) {
if (request.isFinishable(counter)) {
stackLocateRequestCompleted(request)
}
}
}
fun addObserver() {
observers++
}
fun removeObserver() {
observers--
}
override fun tick() {
if (observers > 0 && (++counter % 10) == 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
sendEnqueuedLocateRequests()
finishPendingRequests()
beginInsertions()
finishPendingInsertions()
sync()
}
}
}
fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
updateNetItems()
sync()
inventoryCache.clear()
sendToSingle(RequestInventoryPacket(macAddress))
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 writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
buf.writeBlockPos(this@TerminalBlockEntity.pos)
}
}
player.openHandledScreen(factory)
}
addObserver()
}
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
locateRequestQueue.add(StackLocateRequest(stack, amount, counter))
}
private fun stackLocateRequestCompleted(request: StackLocateRequest) {
pendingRequests.remove(request)
// todo: also sort results by interface priority
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
var amountRequested = 0
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
amountRequested += amountToRequest
sendToSingle(ExtractStackPacket(request.stack, amountToRequest, macAddress, sourceInterface.macAddress))
}
}
private fun finishInsertion(insertion: PendingStackInsertion) {
pendingInsertions.remove(insertion.bufferSlot)
// todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
val remaining = insertion.stack
while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeAt(0)
if (capacity <= 0) continue
sendToSingle(ItemStackPacket(remaining.copy(), macAddress, receivingInterface.macAddress))
// todo: the interface should confirm how much was actually inserted, in case of race condition
remaining.count -= capacity
}
internalBuffer.setStack(insertion.bufferSlot, remaining)
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
updateNetItems()
sync()
}
override fun onInventoryChanged(inv: Inventory) {
if (inv == internalBuffer && world != null && !world!!.isClient) {
markDirty()
sync()
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag())
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag())
val list = ListTag()
tag.put("CachedNetItems", list)
for ((stack, amount) in cachedNetItems) {
val entryTag = stack.toTag(CompoundTag())
entryTag.putInt("NetAmount", amount)
list.add(entryTag)
}
return tag
}
override fun fromClientTag(tag: CompoundTag) {
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
val list = tag.getList("CachedNetItems", 10)
cachedNetItems.clear()
for (entryTag in list) {
val stack = ItemStack.fromTag(entryTag as CompoundTag)
val netAmount = entryTag.getInt("NetAmount")
cachedNetItems[stack] = netAmount
}
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
}
}
data class StackLocateRequest(
val stack: ItemStack,
val amount: Int,
val timestamp: Int,
var results: MutableSet<Pair<Int, InterfaceBlockEntity>> = 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
}
}
data class PendingStackInsertion(
val bufferSlot: Int,
val stack: ItemStack,
val timestamp: Int,
val results: MutableSet<Pair<Int, InterfaceBlockEntity>> = 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
}
}
//
//import alexiil.mc.lib.attributes.item.GroupedItemInv
//import alexiil.mc.lib.attributes.item.ItemStackCollections
//import alexiil.mc.lib.attributes.item.ItemStackUtil
//import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
//import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
//import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
//import net.minecraft.block.BlockState
//import net.minecraft.entity.player.PlayerEntity
//import net.minecraft.entity.player.PlayerInventory
//import net.minecraft.inventory.Inventory
//import net.minecraft.inventory.InventoryChangedListener
//import net.minecraft.item.ItemStack
//import net.minecraft.nbt.CompoundTag
//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.util.Tickable
//import net.shadowfacts.phycon.api.packet.Packet
//import net.shadowfacts.phycon.api.util.MACAddress
//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.component.ItemStackPacketHandler
//import net.shadowfacts.phycon.network.component.handleItemStack
//import net.shadowfacts.phycon.network.packet.*
//import java.util.*
//import kotlin.math.min
//
///**
// * @author shadowfacts
// */
//class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), InventoryChangedListener, BlockEntityClientSerializable, Tickable, ItemStackPacketHandler {
//
// companion object {
// val LOCATE_REQUEST_TIMEOUT = 40 // ticks
// val INSERTION_TIMEOUT = 40
// }
//
// private val inventoryCache = mutableMapOf<MACAddress, GroupedItemInv>()
// val internalBuffer = TerminalBufferInventory(18)
//
// private val locateRequestQueue = LinkedList<StackLocateRequest>()
// private val pendingRequests = LinkedList<StackLocateRequest>()
// private val pendingInsertions = Int2ObjectArrayMap<PendingStackInsertion>()
//
// private var observers = 0
// val cachedNetItems = ItemStackCollections.intMap()
// var cachedSortedNetItems = listOf<ItemStack>()
// var counter = 0
//
// init {
// internalBuffer.addListener(this)
// }
//
// override fun handlePacket(packet: Packet) {
// when (packet) {
// is ReadInventoryPacket -> handleReadInventory(packet)
// is DeviceRemovedPacket -> handleDeviceRemoved(packet)
// is StackLocationPacket -> handleStackLocation(packet)
// is ItemStackPacket -> handleItemStack(packet)
// is CapacityPacket -> handleCapacity(packet)
// }
// }
//
// private fun handleReadInventory(packet: ReadInventoryPacket) {
// inventoryCache[packet.source] = packet.inventory
// updateNetItems()
// sync()
// }
//
// private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
// inventoryCache.remove(packet.source)
// updateNetItems()
// sync()
// }
//
// private fun handleStackLocation(packet: StackLocationPacket) {
// val request = pendingRequests.firstOrNull {
// ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
// }
// if (request != null) {
// request.results.add(packet.amount to packet.sourceInterface)
// if (request.totalResultAmount >= request.amount || counter - request.timestamp >= LOCATE_REQUEST_TIMEOUT || request.results.size >= inventoryCache.size) {
// stackLocateRequestCompleted(request)
// }
// }
// }
//
// override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
// val remaining = internalBuffer.insertFromNetwork(packet.stack)
//
// // this happens outside the normal update loop because by receiving the item stack packet
// // we "know" how much the count in the source inventory has changed
// updateNetItems()
// sync()
//
// return remaining
// }
//
// private fun handleCapacity(packet: CapacityPacket) {
// val insertion = pendingInsertions.values.firstOrNull {
// ItemStackUtil.areEqualIgnoreAmounts(packet.stack, it.stack)
// }
// if (insertion != null) {
// insertion.results.add(packet.capacity to packet.receivingInterface)
// if (insertion.isFinishable(counter)) {
// finishInsertion(insertion)
// }
// }
// }
//
// private fun updateNetItems() {
// cachedNetItems.clear()
// for (inventory in inventoryCache.values) {
// for (stack in inventory.storedStacks) {
// val amount = inventory.getAmount(stack)
// cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
// }
// }
// // todo: is the map necessary or is just the sorted list enough?
// cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
// val stack = it.key.copy()
// stack.count = it.intValue
// stack
// }
// }
//
// private fun beginInsertions() {
// if (world!!.isClient) return
//
// for (slot in 0 until internalBuffer.size()) {
// if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
// if (slot in pendingInsertions) continue
// val stack = internalBuffer.getStack(slot)
// pendingInsertions[slot] = PendingStackInsertion(slot, stack, counter)
// sendToSingle(CheckCapacityPacket(stack, macAddress, MACAddress.BROADCAST))
// }
// }
//
// private fun finishPendingInsertions() {
// if (world!!.isClient) return
//
// for (insertion in pendingInsertions.values) {
// if (!insertion.isFinishable(counter)) continue
// finishInsertion(insertion)
// }
// }
//
// private fun sendEnqueuedLocateRequests() {
// if (world!!.isClient) return
//
// for (request in locateRequestQueue) {
// pendingRequests.add(request)
// sendToSingle(LocateStackPacket(request.stack, macAddress))
// }
// locateRequestQueue.clear()
// }
//
// private fun finishPendingRequests() {
// if (world!!.isClient) return
//
// for (request in pendingRequests) {
// if (request.isFinishable(counter)) {
// stackLocateRequestCompleted(request)
// }
// }
// }
//
// fun addObserver() {
// observers++
// }
//
// fun removeObserver() {
// observers--
// }
//
// override fun tick() {
// if (observers > 0 && (++counter % 10) == 0) {
// if (world!!.isClient) {
// println(cachedNetItems)
// } else {
// updateNetItems()
// sendEnqueuedLocateRequests()
// finishPendingRequests()
// beginInsertions()
// finishPendingInsertions()
// sync()
// }
// }
// }
//
// fun onActivate(player: PlayerEntity) {
// if (!world!!.isClient) {
// updateNetItems()
// sync()
//
// inventoryCache.clear()
// sendToSingle(RequestInventoryPacket(macAddress))
// 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 writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
// buf.writeBlockPos(this@TerminalBlockEntity.pos)
// }
// }
// player.openHandledScreen(factory)
// }
// addObserver()
// }
//
// fun requestItem(stack: ItemStack, amount: Int = stack.count) {
// locateRequestQueue.add(StackLocateRequest(stack, amount, counter))
// }
//
// private fun stackLocateRequestCompleted(request: StackLocateRequest) {
// pendingRequests.remove(request)
//
// // todo: also sort results by interface priority
// val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
// var amountRequested = 0
// while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
// val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
// val amountToRequest = min(sourceAmount, request.amount - amountRequested)
// amountRequested += amountToRequest
// sendToSingle(ExtractStackPacket(request.stack, amountToRequest, macAddress, sourceInterface.macAddress))
// }
// }
//
// private fun finishInsertion(insertion: PendingStackInsertion) {
// pendingInsertions.remove(insertion.bufferSlot)
//
// // todo: also sort results by interface priority
// val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
// val remaining = insertion.stack
// while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
// val (capacity, receivingInterface) = sortedResults.removeAt(0)
// if (capacity <= 0) continue
// sendToSingle(ItemStackPacket(remaining.copy(), macAddress, receivingInterface.macAddress))
// // todo: the interface should confirm how much was actually inserted, in case of race condition
// remaining.count -= capacity
// }
// internalBuffer.setStack(insertion.bufferSlot, remaining)
//
// // as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
// updateNetItems()
// sync()
// }
//
// override fun onInventoryChanged(inv: Inventory) {
// if (inv == internalBuffer && world != null && !world!!.isClient) {
// markDirty()
// sync()
// }
// }
//
// override fun toTag(tag: CompoundTag): CompoundTag {
// tag.put("InternalBuffer", internalBuffer.toTag())
// return super.toTag(tag)
// }
//
// override fun fromTag(state: BlockState, tag: CompoundTag) {
// super.fromTag(state, tag)
// internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
// }
//
// override fun toClientTag(tag: CompoundTag): CompoundTag {
// tag.put("InternalBuffer", internalBuffer.toTag())
// val list = ListTag()
// tag.put("CachedNetItems", list)
// for ((stack, amount) in cachedNetItems) {
// val entryTag = stack.toTag(CompoundTag())
// entryTag.putInt("NetAmount", amount)
// list.add(entryTag)
// }
// return tag
// }
//
// override fun fromClientTag(tag: CompoundTag) {
// internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
// val list = tag.getList("CachedNetItems", 10)
// cachedNetItems.clear()
// for (entryTag in list) {
// val stack = ItemStack.fromTag(entryTag as CompoundTag)
// val netAmount = entryTag.getInt("NetAmount")
// cachedNetItems[stack] = netAmount
// }
// cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
// val stack = it.key.copy()
// stack.count = it.intValue
// stack
// }
// }
//
//}
//
//data class StackLocateRequest(
// val stack: ItemStack,
// val amount: Int,
// val timestamp: Int,
// var results: MutableSet<Pair<Int, InterfaceBlockEntity>> = 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
// }
//}
//
//data class PendingStackInsertion(
// val bufferSlot: Int,
// val stack: ItemStack,
// val timestamp: Int,
// val results: MutableSet<Pair<Int, InterfaceBlockEntity>> = 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
// }
//}

View File

@ -1,61 +1,61 @@
package net.shadowfacts.phycon.network.block.terminal
import net.minecraft.screen.slot.Slot
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.Inventory
import net.minecraft.item.ItemStack
/**
* @author shadowfacts
*/
class TerminalFakeSlot(val terminal: TerminalBlockEntity, slot: Int, x: Int, y: Int): Slot(FakeInventory(terminal, slot), slot, x, y) {
override fun canInsert(stack: ItemStack): Boolean {
return false
}
override fun setStack(stack: ItemStack) {
}
override fun canTakeItems(player: PlayerEntity): Boolean {
return false
}
}
class FakeInventory(val terminal: TerminalBlockEntity, val slot: Int): Inventory {
override fun getStack(_slot: Int): ItemStack {
if (slot >= terminal.cachedSortedNetItems.size) return ItemStack.EMPTY
return terminal.cachedSortedNetItems[slot]
}
override fun markDirty() {
}
override fun clear() {
}
override fun setStack(p0: Int, p1: ItemStack?) {
}
override fun removeStack(p0: Int): ItemStack {
return ItemStack.EMPTY
}
override fun canPlayerUse(p0: PlayerEntity?): Boolean {
return false
}
override fun size(): Int {
return 1
}
override fun removeStack(p0: Int, p1: Int): ItemStack {
return ItemStack.EMPTY
}
override fun isEmpty(): Boolean {
return false
}
}
//import net.minecraft.screen.slot.Slot
//import net.minecraft.entity.player.PlayerEntity
//import net.minecraft.inventory.Inventory
//import net.minecraft.item.ItemStack
//
///**
// * @author shadowfacts
// */
//class TerminalFakeSlot(val terminal: TerminalBlockEntity, slot: Int, x: Int, y: Int): Slot(FakeInventory(terminal, slot), slot, x, y) {
//
// override fun canInsert(stack: ItemStack): Boolean {
// return false
// }
//
// override fun setStack(stack: ItemStack) {
// }
//
// override fun canTakeItems(player: PlayerEntity): Boolean {
// return false
// }
//
//}
//
//class FakeInventory(val terminal: TerminalBlockEntity, val slot: Int): Inventory {
// override fun getStack(_slot: Int): ItemStack {
// if (slot >= terminal.cachedSortedNetItems.size) return ItemStack.EMPTY
// return terminal.cachedSortedNetItems[slot]
// }
//
// override fun markDirty() {
// }
//
// override fun clear() {
// }
//
// override fun setStack(p0: Int, p1: ItemStack?) {
// }
//
// override fun removeStack(p0: Int): ItemStack {
// return ItemStack.EMPTY
// }
//
// override fun canPlayerUse(p0: PlayerEntity?): Boolean {
// return false
// }
//
// override fun size(): Int {
// return 1
// }
//
// override fun removeStack(p0: Int, p1: Int): ItemStack {
// return ItemStack.EMPTY
// }
//
// override fun isEmpty(): Boolean {
// return false
// }
//
//}

View File

@ -1,58 +1,58 @@
package net.shadowfacts.phycon.network.block.terminal
import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.gui.DrawableHelper
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
/**
* @author shadowfacts
*/
// todo: translate title
class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen<TerminalScreenHandler>(handler, playerInv, title) {
companion object {
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
}
init {
backgroundWidth = 252
backgroundHeight = 222
}
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
// todo: translate this
textRenderer.draw(matrixStack, "Buffer", 7f, 6f, 0x404040)
}
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
GlStateManager.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
}
@ExperimentalUnsignedTypes
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
if (!handler.isBufferSlot(slot.id)) {
return
}
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
val color: UInt = when (mode) {
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
else -> return
}
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
}
}
//
//import com.mojang.blaze3d.platform.GlStateManager
//import net.minecraft.client.gui.DrawableHelper
//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
//
///**
// * @author shadowfacts
// */
//// todo: translate title
//class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen<TerminalScreenHandler>(handler, playerInv, title) {
// companion object {
// val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
// }
//
// init {
// backgroundWidth = 252
// backgroundHeight = 222
// }
//
// override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
// textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
// textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
// // todo: translate this
// textRenderer.draw(matrixStack, "Buffer", 7f, 6f, 0x404040)
// }
//
// override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
// GlStateManager.color4f(1f, 1f, 1f, 1f)
// client!!.textureManager.bindTexture(BACKGROUND)
// val x = (width - backgroundWidth) / 2
// val y = (height - backgroundHeight) / 2
// drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
// }
//
// @ExperimentalUnsignedTypes
// fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
// if (!handler.isBufferSlot(slot.id)) {
// return
// }
//
// val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
// val color: UInt = when (mode) {
// TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
// TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
// else -> return
// }
// DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
// }
//
//}

View File

@ -1,161 +1,161 @@
package net.shadowfacts.phycon.network.block.terminal
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import kotlin.math.ceil
import kotlin.math.min
/**
* @author shadowfacts
*/
class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init {
// network
for (y in 0 until 6) {
for (x in 0 until 9) {
addSlot(TerminalFakeSlot(terminal, y * 9 + x, 66 + x * 18, 18 + y * 18))
}
}
// internal buffer
for (y in 0 until 6) {
for (x in 0 until 3) {
addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18))
}
}
// player inv
for (y in 0 until 3) {
for (x in 0 until 9) {
addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18))
}
}
// hotbar
for (x in 0 until 9) {
addSlot(Slot(playerInv, x, 66 + x * 18, 198))
}
}
override fun canUse(player: PlayerEntity): Boolean {
return true
}
override fun close(player: PlayerEntity) {
super.close(player)
terminal.removeObserver()
}
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
if (isNetworkSlot(slotId)) {
// the slot clicked was one of the network stacks
if (actionType == SlotActionType.QUICK_MOVE) {
val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) {
terminal.requestItem(stack, min(stack.count, stack.maxCount))
}
} else if (actionType == SlotActionType.PICKUP && clickData == 1) {
if (clickData == 1) {
// right click, request half stack
val stack = slots[slotId].stack
if (!stack.isEmpty && !player.world.isClient) {
terminal.requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
}
} else {
// todo: left click, show amount dialog
}
}
return ItemStack.EMPTY
} else if (isBufferSlot(slotId)) {
// internal buffer
// todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
// placing cursor stack into buffer
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
}
}
return super.onSlotClick(slotId, clickData, actionType, player)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY;
}
val slot = slots[slotId]
if (!slot.hasStack()) {
return ItemStack.EMPTY
}
val result = slot.stack.copy()
if (isBufferSlot(slotId)) {
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, false)) {
return ItemStack.EMPTY
}
if (slot.stack.isEmpty) {
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
}
} else if (isPlayerSlot(slotId)) {
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
if (slot.stack.isEmpty) {
return ItemStack.EMPTY
}
}
return result
}
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
val slotsInsertedInto = mutableListOf<Int>()
for (index in slots) {
if (stack.isEmpty) break
if (!slotPredicate(index)) continue
val slot = this.slots[index]
val slotStack = slot.stack
if (slotStack.isEmpty) {
slot.stack = stack.copy()
stack.count = 0
slot.markDirty()
slotsInsertedInto.add(index)
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
val maxToMove = slotStack.maxCount - slotStack.count
val toMove = min(maxToMove, stack.count)
slotStack.increment(toMove)
stack.decrement(toMove)
slot.markDirty()
slotsInsertedInto.add(index)
}
}
return slotsInsertedInto
}
val bufferSlotsStart = 54
val playerSlotsStart = 72
val playerSlotsEnd = 72 + 36
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
}
//
//import net.minecraft.screen.slot.Slot
//import net.minecraft.screen.slot.SlotActionType
//import net.minecraft.entity.player.PlayerEntity
//import net.minecraft.entity.player.PlayerInventory
//import net.minecraft.item.ItemStack
//import net.minecraft.network.PacketByteBuf
//import net.minecraft.screen.ScreenHandler
//import net.minecraft.util.Identifier
//import net.shadowfacts.phycon.PhysicalConnectivity
//import net.shadowfacts.phycon.init.PhyBlocks
//import net.shadowfacts.phycon.init.PhyScreens
//import kotlin.math.ceil
//import kotlin.math.min
//
///**
// * @author shadowfacts
// */
//class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId) {
//
// companion object {
// val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
// }
//
// constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
// this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
//
// init {
// // network
// for (y in 0 until 6) {
// for (x in 0 until 9) {
// addSlot(TerminalFakeSlot(terminal, y * 9 + x, 66 + x * 18, 18 + y * 18))
// }
// }
//
// // internal buffer
// for (y in 0 until 6) {
// for (x in 0 until 3) {
// addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18))
// }
// }
//
// // player inv
// for (y in 0 until 3) {
// for (x in 0 until 9) {
// addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18))
// }
// }
// // hotbar
// for (x in 0 until 9) {
// addSlot(Slot(playerInv, x, 66 + x * 18, 198))
// }
// }
//
// override fun canUse(player: PlayerEntity): Boolean {
// return true
// }
//
// override fun close(player: PlayerEntity) {
// super.close(player)
//
// terminal.removeObserver()
// }
//
// override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
// if (isNetworkSlot(slotId)) {
// // the slot clicked was one of the network stacks
// if (actionType == SlotActionType.QUICK_MOVE) {
// val stack = slots[slotId].stack
// if (!stack.isEmpty && !player.world.isClient) {
// terminal.requestItem(stack, min(stack.count, stack.maxCount))
// }
// } else if (actionType == SlotActionType.PICKUP && clickData == 1) {
// if (clickData == 1) {
// // right click, request half stack
// val stack = slots[slotId].stack
// if (!stack.isEmpty && !player.world.isClient) {
// terminal.requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
// }
// } else {
// // todo: left click, show amount dialog
// }
// }
// return ItemStack.EMPTY
// } else if (isBufferSlot(slotId)) {
// // internal buffer
// // todo: why does this think it's quick_craft sometimes?
// if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
// // placing cursor stack into buffer
// val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
// terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
// }
// }
// return super.onSlotClick(slotId, clickData, actionType, player)
// }
//
// override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
// if (isNetworkSlot(slotId)) {
// return ItemStack.EMPTY;
// }
//
// val slot = slots[slotId]
// if (!slot.hasStack()) {
// return ItemStack.EMPTY
// }
//
// val result = slot.stack.copy()
//
// if (isBufferSlot(slotId)) {
// if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, false)) {
// return ItemStack.EMPTY
// }
// if (slot.stack.isEmpty) {
// terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
// }
// } else if (isPlayerSlot(slotId)) {
// val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
// slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
// if (slot.stack.isEmpty) {
// return ItemStack.EMPTY
// }
// }
//
// return result
// }
//
// private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
// val slotsInsertedInto = mutableListOf<Int>()
// for (index in slots) {
// if (stack.isEmpty) break
// if (!slotPredicate(index)) continue
//
// val slot = this.slots[index]
// val slotStack = slot.stack
// if (slotStack.isEmpty) {
// slot.stack = stack.copy()
// stack.count = 0
//
// slot.markDirty()
// slotsInsertedInto.add(index)
// } else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
// val maxToMove = slotStack.maxCount - slotStack.count
// val toMove = min(maxToMove, stack.count)
// slotStack.increment(toMove)
// stack.decrement(toMove)
//
// slot.markDirty()
// slotsInsertedInto.add(index)
// }
// }
// return slotsInsertedInto
// }
//
// val bufferSlotsStart = 54
// val playerSlotsStart = 72
// val playerSlotsEnd = 72 + 36
// fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
// fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
// fun isPlayerSlot(id: Int) = id >= playerSlotsStart
//}

View File

@ -0,0 +1,51 @@
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.api.Interface
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 getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)!!.interfaces.first()
}
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,38 @@
package net.shadowfacts.phycon.network.block.test
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.BaseInterface
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil
/**
* @author shadowfacts
*/
class DestBlockEntity: DeviceBlockEntity(PhyBlockEntities.DEST) {
override val interfaces = listOf(BaseInterface(this))
override fun handle(packet: Packet, itf: Interface) {
println("$this ($ipAddress) received packet: $packet")
}
override fun handle(frame: EthernetFrame, fromItf: Interface) {
// println("dest ${fromItf.macAddress} received frame from ${frame.source}")
super.handle(frame, fromItf)
}
override fun findDestination(fromItf: Interface): 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,36 @@
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.api.Interface
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)
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: World, pos: BlockPos): Interface? {
return getBlockEntity(world, pos)!!.interfaces.first()
}
}

View File

@ -0,0 +1,50 @@
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.frame.EthernetFrame
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.BaseInterface
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.NetworkUtil
import net.shadowfacts.phycon.network.frame.BaseFrame
/**
* @author shadowfacts
*/
class SourceBlockEntity: DeviceBlockEntity(PhyBlockEntities.SOURCE), Tickable {
override val interfaces = listOf(BaseInterface(this))
override fun handle(packet: Packet, itf: Interface) {
TODO("Not yet implemented")
}
override fun handle(frame: EthernetFrame, fromItf: Interface) {
// println("${fromItf.macAddress} received frame from ${frame.source}")
super.handle(frame, fromItf)
}
// var counter = 0
override fun tick() {
super.tick()
if (!world!!.isClient && counter % 40 == 0L) {
sendPacket(TestPacket(ipAddress, IPAddress(67, 237, 255, 168)))
}
}
override fun findDestination(fromItf: Interface): 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,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,11 @@
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.block.netinterface.InterfaceBlockEntity
/**
* @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 receivingInterface: InterfaceBlockEntity, 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 net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ReadInventoryPacket(
val inventory: GroupedItemInv,
source: MACAddress,
destination: MACAddress
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,18 +1,18 @@
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.block.netinterface.InterfaceBlockEntity
/**
* @author shadowfacts
*/
// todo: better name with LocateStackPacket
class StackLocationPacket(
val stack: ItemStack,
val amount: Int,
val sourceInterface: InterfaceBlockEntity,
source: MACAddress,
destination: MACAddress
): BasePacket(source, destination) {
}
//class StackLocationPacket(
// val stack: ItemStack,
// val amount: Int,
// val sourceInterface: InterfaceBlockEntity,
// source: IPAddress,
// destination: IPAddress
//): BasePacket(source, destination) {
//}