Merge branch 'main' into cacao

This commit is contained in:
Shadowfacts 2021-02-26 18:52:23 -05:00
commit 2c8b7cbb63
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
51 changed files with 1786 additions and 256 deletions

8
PhyConDebugLogging.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Loggers>
<Logger name="PhyNet">
<AppenderRef ref="SysOut" level="debug"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -1,5 +1,5 @@
plugins {
id "fabric-loom" version "0.5-SNAPSHOT"
id "fabric-loom" version "0.6.49"
id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id "com.github.johnrengelman.shadow" version "4.0.4"
@ -13,6 +13,7 @@ version = project.mod_version
group = project.maven_group
minecraft {
log4jConfigs.from "PhyConDebugLogging.xml"
}
repositories {

View File

@ -1,8 +1,11 @@
package net.shadowfacts.phycon.api.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author shadowfacts
@ -25,6 +28,20 @@ public final class IPAddress {
return random(ipAddressRandom);
}
private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
@Nullable
public static IPAddress parse(String s) {
Matcher matcher = IP_PATTERN.matcher(s);
if (!matcher.matches()) {
return null;
}
int a = Integer.parseInt(matcher.group(1));
int b = Integer.parseInt(matcher.group(2));
int c = Integer.parseInt(matcher.group(3));
int d = Integer.parseInt(matcher.group(4));
return new IPAddress(a, b, c, d);
}
public static final IPAddress BROADCAST = new IPAddress(0xff_ff_ff_ff);
public final int address;

View File

@ -6,8 +6,8 @@ import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.ServerReceiver
import net.shadowfacts.phycon.networking.*
import org.apache.logging.log4j.LogManager
/**
* @author shadowfacts
@ -16,6 +16,8 @@ object PhysicalConnectivity: ModInitializer {
val MODID = "phycon"
val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
override fun onInitialize() {
PhyBlocks.init()
PhyBlockEntities.init()
@ -23,6 +25,9 @@ object PhysicalConnectivity: ModInitializer {
PhyScreens.init()
registerGlobalReceiver(C2STerminalRequestItem)
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
registerGlobalReceiver(C2SConfigureActivationMode)
registerGlobalReceiver(C2SConfigureRedstoneController)
}
private fun registerGlobalReceiver(receiver: ServerReceiver) {

View File

@ -2,19 +2,30 @@ package net.shadowfacts.phycon
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
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.networking.ClientReceiver
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
/**
* @author shadowfacts
*/
object PhysicalConnectivityClient: ClientModInitializer {
override fun onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent())
ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
}
private fun registerGlobalReceiver(receiver: ClientReceiver) {
ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
}
}

View File

@ -13,6 +13,8 @@ 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.redstone.RedstoneControllerBlock
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.test.DestBlock
@ -30,6 +32,7 @@ object PhyBlockEntities {
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
val MINER = create(::MinerBlockEntity, PhyBlocks.MINER)
val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER)
val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE)
val DEST = create(::DestBlockEntity, PhyBlocks.DEST)
@ -44,6 +47,7 @@ object PhyBlockEntities {
register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)

View File

@ -8,6 +8,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
@ -23,6 +24,7 @@ object PhyBlocks {
val CABLE = CableBlock()
val EXTRACTOR = ExtractorBlock()
val MINER = MinerBlock()
val REDSTONE_CONTROLLER = RedstoneControllerBlock()
val SOURCE = SourceBlock()
val DEST = DestBlock()
@ -34,6 +36,7 @@ object PhyBlocks {
register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)

View File

@ -11,6 +11,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
@ -26,6 +27,7 @@ object PhyItems {
val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings())
val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
val MINER = BlockItem(PhyBlocks.MINER, Item.Settings())
val REDSTONE_CONTROLLER = BlockItem(PhyBlocks.REDSTONE_CONTROLLER, Item.Settings())
val SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings())
val DEST = BlockItem(PhyBlocks.DEST , Item.Settings())
@ -40,6 +42,7 @@ object PhyItems {
register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)

View File

@ -8,7 +8,11 @@ import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.screen.ActivatableDeviceConsoleScreen
import net.shadowfacts.phycon.screen.DeviceConsoleScreen
import net.shadowfacts.phycon.screen.RedstoneControllerConsoleScreen
import net.shadowfacts.phycon.screen.TestCacaoScreen
/**
@ -37,8 +41,12 @@ class ConsoleItem: Item(Settings()) {
}
private fun openScreen(be: DeviceBlockEntity) {
// val screen = DeviceConsoleScreen(be)
val screen = TestCacaoScreen()
// val screen = when (be) {
// is ActivationController.ActivatableDevice -> ActivatableDeviceConsoleScreen(be)
// is RedstoneControllerBlockEntity -> RedstoneControllerConsoleScreen(be)
// else -> DeviceConsoleScreen(be)
// }
MinecraftClient.getInstance().openScreen(screen)
}

View File

@ -6,6 +6,7 @@ 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.PhysicalConnectivity
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface
@ -18,8 +19,7 @@ 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 net.shadowfacts.phycon.network.packet.*
import java.util.*
/**
@ -52,36 +52,45 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
abstract override fun handle(packet: Packet)
private fun doHandlePacket(packet: Packet) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
when (packet) {
is DeviceRemovedPacket -> {
arpTable.remove(packet.source)
}
}
handle(packet)
}
override fun send(frame: EthernetFrame) {
findDestination()?.receive(frame)
}
override fun receive(frame: EthernetFrame) {
println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame")
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame)
when (frame) {
is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame)
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
println("$this ($ipAddress) received packet: ${frame.packet}")
handle(frame.packet)
doHandlePacket(frame.packet)
}
}
}
}
private fun handleARPQuery(frame: ARPQueryFrame) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}")
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) {
println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress")
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress)
send(ARPResponseFrame(ipAddress, macAddress, frame.source))
}
}
private fun handleARPResponse(frame: ARPResponseFrame) {
arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}")
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source)
val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) {
@ -102,7 +111,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} else {
packetQueue.add(PendingPacket(packet, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}")
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination)
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
}
}

View File

@ -0,0 +1,107 @@
package net.shadowfacts.phycon.network
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.ShapeContext
import net.minecraft.item.ItemPlacementContext
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.Properties
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.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
/**
* @author shadowfacts
*/
abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): DeviceBlock<T>(settings) {
companion object {
val FACING = Properties.FACING
val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
}
protected abstract val faceThickness: Double
protected abstract val faceShapes: Map<Direction, VoxelShape>
private val centerShapes: Map<Direction, VoxelShape> by lazy {
mapOf(
Direction.DOWN to createCuboidShape(6.0, faceThickness, 6.0, 10.0, 10.0, 10.0),
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 16.0 - faceThickness, 10.0),
Direction.NORTH to createCuboidShape(6.0, 6.0, faceThickness, 10.0, 10.0, 10.0),
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 16.0 - faceThickness),
Direction.WEST to createCuboidShape(faceThickness, 6.0, 6.0, 10.0, 10.0, 10.0),
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 16.0 - faceThickness, 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(
faceShapes[facing],
centerShapes[facing],
CableBlock.SIDE_SHAPES[cableConnection]
)
}
}
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[CABLE_CONNECTION])
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {
getBlockEntity(world, pos)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
builder.add(CABLE_CONNECTION)
}
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
val cableConnection = getCableConnectedSide(context.world, context.blockPos, facing) ?: facing.opposite
return defaultState
.with(FACING, facing)
.with(CABLE_CONNECTION, cableConnection)
}
protected fun getCableConnectedSide(world: World, pos: BlockPos, facing: Direction): Direction? {
for (side in Direction.values()) {
if (side == facing) {
continue
}
val offsetPos = pos.offset(side)
val state = world.getBlockState(offsetPos)
val block = state.block
if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) {
return side
}
}
return null
}
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 getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION])
}
}

View File

@ -1,59 +1,126 @@
package net.shadowfacts.phycon.network.block.extractor
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.FixedItemInv
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.network.component.NetworkStackDispatcher
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.CapacityPacket
import net.shadowfacts.phycon.network.packet.CheckCapacityPacket
import net.shadowfacts.phycon.network.packet.ItemStackPacket
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode
import kotlin.properties.Delegates
/**
* @author shadowfacts
*/
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) {
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice {
companion object {
val SLEEP_TIME = 40L
}
private val facing: Direction
get() = cachedState[ExtractorBlock.FACING]
private var inventory: GroupedItemInv? = null
private var shouldExtract = false
private var inventory: FixedItemInv? = null
override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = 40L
override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option)
inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
}
private fun getInventory(): GroupedItemInv? {
private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory()
return inventory
}
override fun handle(packet: Packet) {
if (packet is CapacityPacket && shouldExtract) {
getInventory()?.also { inv ->
shouldExtract = false
val extracted = inv.extract(packet.stack, packet.capacity)
sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress))
}
when (packet) {
is CapacityPacket -> handleCapacity(packet)
is ItemStackPacket -> handleItemStack(packet)
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
// we can't insert things back into the inventory, so just let them spawn
return packet.stack
}
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
val inventory = getInventory() ?: return insertion.stack
// if the inventory has changed, the old slot index is meaningless
if (inventory !== insertion.inventory) return insertion.stack
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
if (extracted.isEmpty) return insertion.stack
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
insertion.stack = extracted
return super.finishInsertion(insertion)
}
override fun tick() {
super.tick()
if (!world!!.isClient && counter % 40 == 0L) {
getInventory()?.also {
val stack = it.storedStacks.firstOrNull() ?: return
shouldExtract = true
sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST))
}
if (!world!!.isClient) {
controller.tick()
}
}
override fun activate(): Boolean {
val inventory = getInventory() ?: return false
for (slot in 0 until inventory.slotCount) {
val slotStack = inventory.getInvStack(slot)
if (slotStack.isEmpty) continue
dispatchItemStack(slotStack) { insertion ->
insertion.inventory = inventory
insertion.inventorySlot = slot
}
return true
}
return false
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putString("ActivationMode", controller.activationMode.name)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putString("ActivationMode", controller.activationMode.name)
return super.toClientTag(tag)
}
override fun fromClientTag(tag: CompoundTag) {
super.fromClientTag(tag)
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: FixedItemInv
var inventorySlot by Delegates.notNull<Int>()
}
}

View File

@ -4,103 +4,39 @@ import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.*
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.network.DeviceBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
import net.shadowfacts.phycon.network.FaceDeviceBlock
/**
* @author shadowfacts
*/
class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.METAL)),
class InterfaceBlock: FaceDeviceBlock<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: WorldAccess, pos: BlockPos): Collection<Direction> {
val set = EnumSet.of(state[CABLE_CONNECTION])
set.remove(state[FACING])
return set
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {
getBlockEntity(world, pos)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
builder.add(CABLE_CONNECTION)
}
override val faceThickness = 2.0
override val faceShapes = mapOf(
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)
)
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()
@ -113,19 +49,8 @@ class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.MET
}
}
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

@ -9,11 +9,13 @@ 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.FaceDeviceBlock
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.*
import kotlin.math.min
/**
* @author shadowfacts
@ -24,7 +26,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
NetworkStackReceiver {
private val facing: Direction
get() = cachedState[InterfaceBlock.FACING]
get() = cachedState[FaceDeviceBlock.FACING]
// todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null
@ -67,8 +69,16 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv ->
val extracted = inv.extract(packet.stack, packet.amount)
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
var amount = packet.amount
while (amount > 0) {
val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount))
if (extracted.isEmpty) {
break
} else {
amount -= extracted.count
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
}
}
}
}

View File

@ -7,6 +7,7 @@ import net.minecraft.entity.ItemEntity
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
@ -53,10 +54,10 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!!
println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir")
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
interfaceForSide(dir).send(frame)
} else {
println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame")
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame)
flood(frame, fromItf)
}
}

View File

@ -0,0 +1,74 @@
package net.shadowfacts.phycon.network.block.redstone
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.item.ItemPlacementContext
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.FaceDeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>(Settings.of(Material.METAL)) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_controller")
val LIT = Properties.LIT
}
// todo: don't just copy this from the Interface block
override val faceThickness = 3.0
override val faceShapes = mapOf(
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
)
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(LIT)
}
override fun createBlockEntity(world: BlockView) = RedstoneControllerBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val state = super.getPlacementState(context)
return state.with(LIT, isPowered(context.world, context.blockPos, state[FACING]))
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
// this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
if (!world.isClient) {
val wasLit = state[LIT]
val isLit = isPowered(world, pos, state[FACING])
if (wasLit != isLit) {
toggleLit(state, world, pos)
}
}
}
private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean {
val offset = pos.offset(facing)
return world.getEmittedRedstonePower(offset, facing) > 0
}
private fun toggleLit(state: BlockState, world: World, pos: BlockPos) {
world.setBlockState(pos, state.cycle(LIT), 2)
getBlockEntity(world, pos)!!.redstoneStateChanged()
}
}

View File

@ -0,0 +1,75 @@
package net.shadowfacts.phycon.network.block.redstone
import net.minecraft.block.BlockState
import net.minecraft.nbt.CompoundTag
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.RedstoneMode
/**
* @author shadowfacts
*/
class RedstoneControllerBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER) {
var managedDevices = Array<IPAddress?>(5) { null }
var redstoneMode = RedstoneMode.HIGH
private var redstonePowered = false
override fun handle(packet: Packet) {
}
fun redstoneStateChanged() {
val oldPowered = redstonePowered
redstonePowered = cachedState[RedstoneControllerBlock.LIT]
val mode: RemoteActivationPacket.Mode? = when (redstoneMode) {
RedstoneMode.TOGGLE -> if (oldPowered != redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
RedstoneMode.RISING_EDGE -> if (!oldPowered && redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
RedstoneMode.FALLING_EDGE -> if (oldPowered && !redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
RedstoneMode.HIGH -> if (redstonePowered) RemoteActivationPacket.Mode.ENABLE else RemoteActivationPacket.Mode.DISABLE
RedstoneMode.LOW -> if (redstonePowered) RemoteActivationPacket.Mode.DISABLE else RemoteActivationPacket.Mode.ENABLE
}
if (mode != null) {
sendActivatePacket(mode)
}
}
private fun sendActivatePacket(mode: RemoteActivationPacket.Mode) {
for (ip in managedDevices) {
if (ip == null) continue
sendPacket(RemoteActivationPacket(mode, ipAddress, ip))
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
tag.putString("RedstoneMode", redstoneMode.name)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
val addresses = tag.getIntArray("ManagedDevices")
managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
tag.putString("RedstoneMode", redstoneMode.name)
return super.toClientTag(tag)
}
override fun fromClientTag(tag: CompoundTag) {
super.fromClientTag(tag)
val addresses = tag.getIntArray("ManagedDevices")
managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
}
}

View File

@ -56,7 +56,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var netItemObserver: WeakReference<NetItemObserver>? = null
@ -86,14 +85,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private fun handleReadInventory(packet: ReadInventoryPacket) {
inventoryCache[packet.source] = packet.inventory
updateNetItems()
sync()
updateAndSync()
}
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
inventoryCache.remove(packet.source)
updateNetItems()
sync()
updateAndSync()
}
private fun handleStackLocation(packet: StackLocationPacket) {
@ -113,12 +110,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
// 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()
updateAndSync()
return remaining
}
private fun updateAndSync() {
updateNetItems()
sync()
netItemObserver?.get()?.netItemsChanged()
}
private fun updateNetItems() {
cachedNetItems.clear()
for (inventory in inventoryCache.values) {
@ -127,12 +129,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
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() {
@ -175,21 +171,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
finishTimedOutPendingInsertions()
}
if (observers > 0) {
if (world!!.isClient) {
println(cachedNetItems)
} else {
updateNetItems()
sync()
}
if (observers > 0 && !world!!.isClient) {
updateAndSync()
}
}
}
fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
updateNetItems()
sync()
updateAndSync()
inventoryCache.clear()
sendPacket(RequestInventoryPacket(ipAddress))
@ -238,8 +228,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
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()
updateAndSync()
return remaining
}
@ -263,31 +252,11 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
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
}
netItemObserver?.get()?.netItemsChanged()
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
}
interface NetItemObserver {

View File

@ -1,18 +1,33 @@
package net.shadowfacts.phycon.network.block.terminal
import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.gui.widget.AbstractButtonWidget
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
import net.shadowfacts.phycon.util.SortMode
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import org.lwjgl.glfw.GLFW
import java.lang.NumberFormatException
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min
/**
* @author shadowfacts
@ -20,10 +35,32 @@ import org.lwjgl.glfw.GLFW
// 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")
private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
private val DIALOG = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png")
}
private lateinit var searchBox: TextFieldWidget
private lateinit var sortButton: SortButton
private lateinit var amountBox: TextFieldWidget
private var dialogStack = ItemStack.EMPTY
private var showingAmountDialog = false
set(value) {
val oldValue = field
field = value
for (e in dialogChildren) {
e.visible = value
}
amountBox.isVisible
searchBox.setSelected(!value)
amountBox.setSelected(value)
if (value && !oldValue) {
amountBox.text = "1"
}
}
private var dialogChildren = mutableListOf<AbstractButtonWidget>()
private val dialogWidth = 158
private val dialogHeight = 62
init {
backgroundWidth = 252
@ -33,6 +70,9 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
override fun init() {
super.init()
children.clear()
dialogChildren.clear()
client!!.keyboard.setRepeatEvents(true)
searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search"))
@ -42,37 +82,134 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
searchBox.isVisible = true
searchBox.setSelected(true)
searchBox.setEditableColor(0xffffff)
children.add(searchBox)
addChild(searchBox)
sortButton = SortButton(x + 256, y + 0, handler.sortMode, {
requestUpdatedItems()
}, ::renderTooltip)
addButton(sortButton)
val dialogMinX = width / 2 - dialogWidth / 2
val dialogMinY = height / 2 - dialogHeight / 2
amountBox = TextFieldWidget(textRenderer, dialogMinX + 8, dialogMinY + 27, 80, 9, LiteralText("Amount"))
amountBox.setHasBorder(false)
amountBox.isVisible = false
amountBox.setSelected(false)
amountBox.setEditableColor(0xffffff)
amountBox.setTextPredicate {
if (it.isEmpty()) {
true
} else {
try {
Integer.parseInt(it) > 0
} catch (e: NumberFormatException) {
false
}
}
}
dialogChildren.add(amountBox)
val plusOne = SmallButton(dialogMinX + 7, dialogMinY + 7, 28, LiteralText("+1")) {
amountBox.intValue += 1
}
dialogChildren.add(plusOne)
val plusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 7, 28, LiteralText("+10")) {
amountBox.intValue = ceil((amountBox.intValue + 1) / 10.0).toInt() * 10
}
dialogChildren.add(plusTen)
val plusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 7, 28, LiteralText("+100")) {
amountBox.intValue = ceil((amountBox.intValue + 1) / 100.0).toInt() * 100
}
dialogChildren.add(plusHundred)
val minusOne = SmallButton(dialogMinX + 7, dialogMinY + 39, 28, LiteralText("-1")) {
amountBox.intValue -= 1
}
dialogChildren.add(minusOne)
val minusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 39, 28, LiteralText("-10")) {
amountBox.intValue = floor((amountBox.intValue - 1) / 10.0).toInt() * 10
}
dialogChildren.add(minusTen)
val minusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 39, 28, LiteralText("-100")) {
amountBox.intValue = floor((amountBox.intValue - 1) / 100.0).toInt() * 100
}
dialogChildren.add(minusHundred)
// 101,25
val request = ButtonWidget(dialogMinX + 101, dialogMinY + 21, 50, 20, LiteralText("Request")) {
doDialogRequest()
}
dialogChildren.add(request)
requestUpdatedItems()
}
private fun requestUpdatedItems() {
val player = MinecraftClient.getInstance().player!!
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchBox.text, sortButton.mode))
}
override fun tick() {
super.tick()
searchBox.tick()
amountBox.tick()
}
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)
textRenderer.draw(matrixStack, TranslatableText("gui.phycon.terminal_buffer"), 7f, 6f, 0x404040)
}
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
renderBackground(matrixStack)
// if the dialog is open, the background gradient will be drawn in front of the main terminal gui
if (!showingAmountDialog) {
renderBackground(matrixStack)
}
GlStateManager.color4f(1f, 1f, 1f, 1f)
RenderSystem.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
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
super.render(matrixStack, mouseX, mouseY, delta)
if (showingAmountDialog) {
RenderSystem.pushMatrix()
// items are rendered at some stupidly high z offset. item amounts at an even higher one
RenderSystem.translatef(0f, 0f, -350f)
// fake the mouse x/y while showing a dialog so slot mouseover highlights aren't drawn
super.render(matrixStack, -1, -1, delta)
RenderSystem.popMatrix()
} else {
super.render(matrixStack, mouseX, mouseY, delta)
}
searchBox.render(matrixStack, mouseX, mouseY, delta)
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
if (showingAmountDialog) {
renderBackground(matrixStack)
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DIALOG)
val dialogMinX = width / 2 - dialogWidth / 2
val dialogMinY = height / 2 - dialogHeight / 2
drawTexture(matrixStack, dialogMinX, dialogMinY, 0, 0, dialogWidth, dialogHeight)
for (e in dialogChildren) {
e.render(matrixStack, mouseX, mouseY, delta)
}
} else {
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
}
}
@ExperimentalUnsignedTypes
@ -90,42 +227,208 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
}
override fun onMouseClick(slot: Slot?, i: Int, j: Int, slotActionType: SlotActionType?) {
super.onMouseClick(slot, i, j, slotActionType)
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
super.onMouseClick(slot, invSlot, clickData, type)
// don't unfocus the search box on mouse click
searchBox.setSelected(true)
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
val stack = slot.stack
if (type == SlotActionType.QUICK_MOVE) {
// shift click, request full stack
requestItem(stack, min(stack.count, stack.maxCount))
} else if (type == SlotActionType.PICKUP) {
if (clickData == 1) {
// right click, request half stack
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
} else {
dialogStack = stack
showingAmountDialog = true
searchBox.setSelected(false)
}
}
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (showingAmountDialog) {
for (e in dialogChildren) {
if (e.mouseClicked(mouseX, mouseY, button)) {
return true
}
}
return false
} else {
return super.mouseClicked(mouseX, mouseY, button)
}
}
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseDragged(d, e, i, f, g)
}
}
override fun mouseMoved(d: Double, e: Double) {
if (showingAmountDialog) {
} else {
super.mouseMoved(d, e)
}
}
override fun mouseReleased(d: Double, e: Double, i: Int): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseReleased(d, e, i)
}
}
override fun mouseScrolled(d: Double, e: Double, f: Double): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseScrolled(d, e, f)
}
}
override fun charTyped(c: Char, i: Int): Boolean {
val oldText = searchBox.text
if (searchBox.charTyped(c, i)) {
if (searchBox.text != oldText) {
search()
if (showingAmountDialog) {
return amountBox.charTyped(c, i)
} else {
val oldText = searchBox.text
if (searchBox.charTyped(c, i)) {
if (searchBox.text != oldText) {
requestUpdatedItems()
}
return true
}
return true
}
return super.charTyped(c, i)
return super.charTyped(c, i)
}
}
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
val oldText = searchBox.text
if (searchBox.keyPressed(key, j, k)) {
if (searchBox.text != oldText) {
search()
if (showingAmountDialog) {
return when (key) {
GLFW.GLFW_KEY_ESCAPE -> {
showingAmountDialog = false
true
}
GLFW.GLFW_KEY_ENTER -> {
doDialogRequest()
true
}
else -> {
amountBox.keyPressed(key, j, k)
}
}
return true
}
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
true
} else {
super.keyPressed(key, j, k)
val oldText = searchBox.text
if (searchBox.keyPressed(key, j, k)) {
if (searchBox.text != oldText) {
requestUpdatedItems()
}
return true
}
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
true
} else {
super.keyPressed(key, j, k)
}
}
}
private fun search() {
screenHandler.search(searchBox.text)
private fun doDialogRequest() {
showingAmountDialog = false
requestItem(dialogStack, amountBox.intValue)
}
private fun requestItem(stack: ItemStack, amount: Int) {
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
netHandler.sendPacket(packet)
}
private var TextFieldWidget.intValue: Int
get() = if (text.isEmpty()) 0 else Integer.parseInt(text)
set(value) {
text = value.toString()
setSelected(true)
}
class SmallButton(x: Int, y: Int, width: Int, title: Text, action: PressAction): ButtonWidget(x, y, width, 14, title, action) {
@ExperimentalUnsignedTypes
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val client = MinecraftClient.getInstance()
client.textureManager.bindTexture(DIALOG)
RenderSystem.color4f(1f, 1f, 1f, 1f)
val v = if (isHovered) 142 else 128
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.enableDepthTest()
drawTexture(matrixStack, x, y, 0, v, width / 2, height)
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, v, width / 2, height)
drawCenteredText(matrixStack, client.textRenderer, message, x + width / 2, y + (height - 8) / 2, 0xffffffffu.toInt())
}
}
class SortButton(
x: Int,
y: Int,
var mode: SortMode,
val onChange: (SortMode) -> Unit,
val doRenderTooltip: (MatrixStack, Text, Int, Int) -> Unit
): AbstractPressableButtonWidget(x, y, 20, 20, LiteralText("")) {
override fun onPress() {}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if ((button == 0 || button == 1) && clicked(mouseX, mouseY)) {
val newVal = if (button == 0) mode.next else mode.prev
mode = newVal
onChange(mode)
playDownSound(MinecraftClient.getInstance().soundManager)
return true
}
return false
}
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val client = MinecraftClient.getInstance()
RenderSystem.color4f(1f, 1f, 1f, 1f)
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.enableDepthTest()
client.textureManager.bindTexture(WIDGETS_LOCATION)
val k = getYImage(isHovered)
drawTexture(matrixStack, x, y, 0, 46 + k * 20, width / 2, height)
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, 46 + k * 20, width / 2, height)
client.textureManager.bindTexture(BACKGROUND)
val u: Int = when (mode) {
SortMode.COUNT_HIGH_FIRST -> 0
SortMode.COUNT_LOW_FIRST -> 16
SortMode.ALPHABETICAL -> 32
}
drawTexture(matrixStack, x + 2, y + 2, u, 230, 16, 16)
if (isHovered) {
renderToolTip(matrixStack, mouseX, mouseY)
}
}
override fun renderToolTip(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
val text = LiteralText("")
text.append("Sort by: ")
text.append(mode.tooltip)
doRenderTooltip(matrixStack, text, mouseX, mouseY)
}
}
}

View File

@ -8,12 +8,15 @@ import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
import net.shadowfacts.phycon.util.SortMode
import java.lang.ref.WeakReference
import kotlin.math.ceil
import kotlin.math.min
@ -21,7 +24,7 @@ import kotlin.math.min
/**
* @author shadowfacts
*/
class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
TerminalBlockEntity.NetItemObserver {
companion object {
@ -30,6 +33,19 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
private val fakeInv = FakeInventory(this)
private var searchQuery: String = ""
var sortMode = SortMode.COUNT_HIGH_FIRST
private set
private var itemEntries = listOf<Entry>()
set(value) {
field = value
if (terminal.world!!.isClient) {
itemsForDisplay = value.map {
val stack = it.stack.copy()
stack.count = it.amount
stack
}
}
}
var itemsForDisplay = listOf<ItemStack>()
private set
@ -37,8 +53,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init {
terminal.netItemObserver = WeakReference(this)
netItemsChanged()
if (!terminal.world!!.isClient) {
terminal.netItemObserver = WeakReference(this)
netItemsChanged()
}
// network
for (y in 0 until 6) {
@ -67,7 +85,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
override fun netItemsChanged() {
itemsForDisplay = terminal.cachedNetItems.object2IntEntrySet().filter {
val player = playerInv.player
assert(player is ServerPlayerEntity)
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
if (searchQuery.isBlank()) return@filter true
if (searchQuery.startsWith('@')) {
val unprefixed = searchQuery.drop(1)
@ -77,20 +98,34 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
}
it.key.name.string.contains(searchQuery, true)
}.sortedByDescending {
it.intValue
}.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
val sorted =
when (sortMode) {
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
}
itemEntries = sorted.map { Entry(it.key, it.intValue) }
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode))
}
fun search(query: String) {
searchQuery = query
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode) {
this.searchQuery = query
this.sortMode = sortMode
netItemsChanged()
}
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, sortMode: SortMode) {
assert(playerInv.player.world.isClient)
this.searchQuery = query
this.sortMode = sortMode
itemEntries = entries
}
override fun canUse(player: PlayerEntity): Boolean {
return true
}
@ -102,28 +137,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
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) {
requestItem(player, 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) {
requestItem(player, 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
if (isBufferSlot(slotId)) {
// 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
@ -134,13 +148,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
return super.onSlotClick(slotId, clickData, actionType, player)
}
private fun requestItem(player: PlayerEntity, stack: ItemStack, amount: Int) {
if (!player.world.isClient) return
val handler = (player as ClientPlayerEntity).networkHandler
val packet = C2STerminalRequestItem(terminal, stack, amount)
handler.sendPacket(packet)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY;
@ -154,7 +161,8 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
val result = slot.stack.copy()
if (isBufferSlot(slotId)) {
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, false)) {
// last boolean param is fromLast
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
return ItemStack.EMPTY
}
if (slot.stack.isEmpty) {
@ -204,4 +212,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
data class Entry(val stack: ItemStack, val amount: Int)
}

View File

@ -0,0 +1,60 @@
package net.shadowfacts.phycon.network.component
import net.minecraft.block.entity.BlockEntity
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode
/**
* @author shadowfacts
*/
class ActivationController<T>(
private val sleepInterval: Long,
private val device: T
) where T: ActivationController.ActivatableDevice, T: BlockEntity {
var activationMode = ActivationMode.AUTOMATIC
set(value) {
field = value
// when the activation mode changes, reset the remote enabled status
remotelyEnabled = false
}
private var lastActivation = -sleepInterval
private var remotelyEnabled = false
fun tick() {
if (activationMode == ActivationMode.AUTOMATIC || remotelyEnabled) {
tryActivate()
}
}
fun handleRemoteActivation(packet: RemoteActivationPacket) {
if (activationMode != ActivationMode.MANAGED) {
return
}
when (packet.mode) {
RemoteActivationPacket.Mode.SINGLE -> tryActivate()
RemoteActivationPacket.Mode.ENABLE -> remotelyEnabled = true
RemoteActivationPacket.Mode.DISABLE -> remotelyEnabled = false
}
}
private fun tryActivate() {
assert(!device.world!!.isClient)
if ((device.counter - lastActivation) < sleepInterval) {
return
}
if (device.activate()) {
lastActivation = device.counter
}
}
interface ActivatableDevice {
val controller: ActivationController<*>
val counter: Long
fun activate(): Boolean
}
}

View File

@ -33,7 +33,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
if (insertion != null) {
insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(this)) {
finishInsertion(insertion)
val remaining = finishInsertion(insertion)
// todo: do something with remaining
}
}
}
@ -43,7 +44,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
// todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
val remaining = insertion.stack
// copy the insertion stack so subclasses that override this method can still see the originally dispatched stack after the super call
val remaining = insertion.stack.copy()
while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeFirst()
if (capacity <= 0) continue
@ -56,7 +58,7 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
}
open class PendingInsertion<Self: PendingInsertion<Self>>(
val stack: ItemStack,
var stack: ItemStack,
val timestamp: Long
) {
val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>()
@ -77,5 +79,6 @@ fun <Self, Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>> Self.f
val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion)
// todo: do something with remaining?
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
}

View File

@ -0,0 +1,14 @@
package net.shadowfacts.phycon.network.packet
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class RemoteActivationPacket(val mode: Mode, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
enum class Mode {
SINGLE,
ENABLE,
DISABLE
}
}

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.minecraft.util.registry.RegistryKey
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.util.ActivationMode
/**
* @author shadowfacts
*/
object C2SConfigureActivationMode: ServerReceiver {
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_activation_mode")
operator fun <T> invoke(be: T): Packet<*> where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice {
val buf = PacketByteBufs.create()
buf.writeIdentifier(be.world!!.registryKey.value)
buf.writeBlockPos(be.pos)
buf.writeString(be.controller.activationMode.name)
return createPacket(buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val mode = ActivationMode.valueOf(buf.readString())
server.execute {
// todo: check the player is close enough
val key = RegistryKey.of(Registry.DIMENSION, dimID)
val world = server.getWorld(key) ?: return@execute
val device = world.getBlockEntity(pos) ?: return@execute
if (device !is ActivationController.ActivatableDevice) return@execute
device.controller.activationMode = mode
device.markDirty()
}
}
}

View File

@ -0,0 +1,59 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.minecraft.util.registry.RegistryKey
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.util.RedstoneMode
/**
* @author shadowfacts
*/
object C2SConfigureRedstoneController: ServerReceiver {
// todo: it would be nice if there wasn't so much duplication with C2SConfigureActivationMode
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_redstone_controller")
operator fun invoke(be: RedstoneControllerBlockEntity): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(be.world!!.registryKey.value)
buf.writeBlockPos(be.pos)
buf.writeString(be.redstoneMode.name)
be.managedDevices.forEach {
buf.writeInt(it?.address ?: 0)
}
return createPacket(buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val mode = RedstoneMode.valueOf(buf.readString())
val managedDevices = Array<IPAddress?>(5) { null }
(0..4).map {
val v = buf.readInt()
managedDevices[it] = if (v == 0) null else IPAddress(v)
}
server.execute {
// todo: check if the player is close enough
val key = RegistryKey.of(Registry.DIMENSION, dimID)
val world = server.getWorld(key) ?: return@execute
val device = world.getBlockEntity(pos) as? RedstoneControllerBlockEntity ?: return@execute
device.redstoneMode = mode
device.managedDevices = managedDevices
device.markDirty()
}
}
}

View File

@ -0,0 +1,50 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
import net.shadowfacts.phycon.util.SortMode
/**
* @author shadowfacts
*/
object C2STerminalUpdateDisplayedItems: ServerReceiver {
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed")
operator fun invoke(terminal: TerminalBlockEntity, query: String, sortMode: SortMode): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeString(query)
buf.writeVarInt(sortMode.ordinal)
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val query = buf.readString()
val sortMode = SortMode.values()[buf.readVarInt()]
server.execute {
if (player.world.registryKey.value != dimID) return@execute
val screenHandler = player.currentScreenHandler
if (screenHandler !is TerminalScreenHandler) return@execute
if (screenHandler.terminal.pos != pos) return@execute
screenHandler.sendUpdatedItemsToClient(player, query, sortMode)
}
}
}

View File

@ -0,0 +1,11 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.minecraft.util.Identifier
/**
* @author shadowfacts
*/
interface ClientReceiver: ClientPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier
}

View File

@ -0,0 +1,57 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.ClientPlayNetworkHandler
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
import net.shadowfacts.phycon.util.SortMode
/**
* @author shadowfacts
*/
object S2CTerminalUpdateDisplayedItems: ClientReceiver {
override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
operator fun invoke(terminal: TerminalBlockEntity, entries: List<TerminalScreenHandler.Entry>, query: String, sortMode: SortMode): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeVarInt(entries.size)
for (e in entries) {
buf.writeItemStack(e.stack)
buf.writeVarInt(e.amount)
}
buf.writeString(query)
buf.writeVarInt(sortMode.ordinal)
return ServerPlayNetworking.createS2CPacket(CHANNEL, buf)
}
override fun receive(client: MinecraftClient, handler: ClientPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val entryCount = buf.readVarInt()
val entries = ArrayList<TerminalScreenHandler.Entry>(entryCount)
for (i in 0 until entryCount) {
entries.add(TerminalScreenHandler.Entry(buf.readItemStack(), buf.readVarInt()))
}
val query = buf.readString()
val sortMode = SortMode.values()[buf.readVarInt()]
client.execute {
if (client.player!!.world.registryKey.value != dimID) return@execute
val screenHandler = client.player!!.currentScreenHandler
if (screenHandler !is TerminalScreenHandler) return@execute
if (screenHandler.terminal.pos != pos) return@execute
screenHandler.receivedUpdatedItemsFromServer(entries, query, sortMode)
}
}
}

View File

@ -1,6 +1,9 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
/**
@ -8,4 +11,8 @@ import net.minecraft.util.Identifier
*/
interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier
fun createPacket(buf: PacketByteBuf): Packet<*> {
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
}

View File

@ -0,0 +1,64 @@
package net.shadowfacts.phycon.screen
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.networking.C2SConfigureActivationMode
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.next
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class ActivatableDeviceConsoleScreen<T>(
val device: T
): Screen(device.cachedState.block.name) where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice {
private val backgroundWidth = 252
private val backgroundHeight = 222
override fun init() {
super.init()
buttons.clear()
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
val mode = EnumButton(device.controller::activationMode, minX + 5, minY + 25, 55, 20) {
client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device))
}
addButton(mode)
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose()
return true
}
return super.keyPressed(key, j, k)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
super.render(matrixStack, mouseX, mouseY, delta)
textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
}
}

View File

@ -3,6 +3,8 @@ package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlockEntity
import org.lwjgl.glfw.GLFW
@ -11,18 +13,18 @@ import org.lwjgl.glfw.GLFW
*/
class DeviceConsoleScreen(
val device: DeviceBlockEntity,
): Screen(TranslatableText("item.phycon.onsole")) {
): Screen(TranslatableText("item.phycon.console")) {
override fun init() {
super.init()
companion object {
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/console.png")
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose();
return true;
onClose()
return true
}
return super.keyPressed(key, j, k)
}

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.minecraft.text.Text
import net.shadowfacts.phycon.util.FriendlyNameable
import net.shadowfacts.phycon.util.RotatableEnum
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import kotlin.reflect.KMutableProperty
/**
* @author shadowfacts
*/
class EnumButton<E>(
val prop: KMutableProperty<E>,
x: Int,
y: Int,
width: Int,
height: Int,
val onChange: () -> Unit
): AbstractPressableButtonWidget(
x,
y,
width,
height,
prop.getter.call().friendlyName
) where E: Enum<E>, E: RotatableEnum, E: FriendlyNameable {
private var currentButton: Int? = null
override fun mouseClicked(d: Double, e: Double, button: Int): Boolean {
currentButton = button
val res = super.mouseClicked(d, e, button)
currentButton = null
return res
}
override fun isValidClickButton(i: Int): Boolean {
return i == 0 || i == 1
}
override fun onPress() {
val newVal = if ((currentButton ?: 0) == 0) prop.getter.call().next else prop.getter.call().prev
prop.setter.call(newVal)
message = newVal.friendlyName
onChange()
}
}

View File

@ -0,0 +1,96 @@
package net.shadowfacts.phycon.screen
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.LiteralText
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.networking.C2SConfigureRedstoneController
import net.shadowfacts.phycon.util.next
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class RedstoneControllerConsoleScreen(
val device: RedstoneControllerBlockEntity
): Screen(device.cachedState.block.name) {
private val backgroundWidth = 252
private val backgroundHeight = 222
private val ipAddressTextFields = mutableListOf<TextFieldWidget>()
override fun init() {
super.init()
buttons.clear()
ipAddressTextFields.clear()
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
val mode = EnumButton(device::redstoneMode, minX + 5, minY + 25, 75, 20) {
client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
addButton(mode)
for (i in 0 until 5) {
// todo: field name
val field = TextFieldWidget(textRenderer, minX + 5, minY + 50 + 22 * i, backgroundWidth / 2, 20, LiteralText(""))
field.setMaxLength(15)
field.setHasBorder(true)
field.isVisible = true
field.setEditableColor(0xffffff)
field.text = device.managedDevices[i]?.toString()
field.setChangedListener { newVal ->
device.managedDevices[i] = IPAddress.parse(newVal)
client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
addChild(field)
ipAddressTextFields.add(field)
}
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose()
return true
}
return super.keyPressed(key, j, k)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val clickedField = ipAddressTextFields.find { it.x <= mouseX && it.x + it.width >= mouseX && it.y <= mouseY && it.y + it.height >= mouseY }
if (clickedField != null) {
ipAddressTextFields.forEach {
if (it !== clickedField) it.setSelected(false)
}
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
super.render(matrixStack, mouseX, mouseY, delta)
ipAddressTextFields.forEach { it.render(matrixStack, mouseX, mouseY, delta) }
textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
}
}

View File

@ -0,0 +1,15 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
/**
* @author shadowfacts
*/
enum class ActivationMode: RotatableEnum, FriendlyNameable {
AUTOMATIC,
MANAGED;
override val friendlyName: Text
get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}")
}

View File

@ -0,0 +1,10 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
interface FriendlyNameable {
val friendlyName: Text
}

View File

@ -0,0 +1,30 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
/**
* @author shadowfacts
*/
enum class RedstoneMode: RotatableEnum, FriendlyNameable {
HIGH,
LOW,
TOGGLE,
RISING_EDGE,
FALLING_EDGE;
val isDiscrete: Boolean
get() = when (this) {
TOGGLE, RISING_EDGE, FALLING_EDGE -> true
else -> false
}
val isContinuous: Boolean
get() = when (this) {
HIGH, LOW -> true
else -> false
}
override val friendlyName: Text
get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}")
}

View File

@ -0,0 +1,13 @@
package net.shadowfacts.phycon.util
/**
* @author shadowfacts
*/
interface RotatableEnum {
}
val <E> E.prev: E where E: Enum<E>, E: RotatableEnum
get() = javaClass.enumConstants[(ordinal - 1 + javaClass.enumConstants.size) % javaClass.enumConstants.size]
val <E> E.next: E where E: Enum<E>, E: RotatableEnum
get() = javaClass.enumConstants[(ordinal + 1) % javaClass.enumConstants.size]

View File

@ -0,0 +1,21 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
enum class SortMode: RotatableEnum {
COUNT_HIGH_FIRST,
COUNT_LOW_FIRST,
ALPHABETICAL;
val tooltip: Text
get() = when (this) {
COUNT_HIGH_FIRST -> LiteralText("Count, highest first")
COUNT_LOW_FIRST -> LiteralText("Count, lowest first")
ALPHABETICAL -> LiteralText("Name")
}
}

View File

@ -140,7 +140,6 @@
},
{
"when": {"cable_connection": "down", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
},

View File

@ -0,0 +1,183 @@
{
"multipart": [
{
"apply": { "model": "phycon:block/cable_center" }
},
{
"when": { "facing": "down", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off" }
},
{
"when": { "facing": "up", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 180 }
},
{
"when": { "facing": "north", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 270 }
},
{
"when": { "facing": "south", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90 }
},
{
"when": { "facing": "west", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 90 }
},
{
"when": { "facing": "east", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 270 }
},
{
"when": { "facing": "down", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on" }
},
{
"when": { "facing": "up", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 180 }
},
{
"when": { "facing": "north", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 270 }
},
{
"when": { "facing": "south", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90 }
},
{
"when": { "facing": "west", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 90 }
},
{
"when": { "facing": "east", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 270 }
},
{
"when": { "cable_connection": "up", "facing": "down" },
"apply": { "model": "phycon:block/interface_cable_straight" }
},
{
"when": { "cable_connection": "down", "facing": "up" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 180 }
},
{
"when": { "cable_connection": "north", "facing": "south" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90 }
},
{
"when": { "cable_connection": "south", "facing": "north" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 270 }
},
{
"when": { "cable_connection": "west", "facing": "east" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 270 }
},
{
"when": { "cable_connection": "east", "facing": "west" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 90 }
},
{
"when": {"cable_connection": "north", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner" }
},
{
"when": {"cable_connection": "east", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 180 }
},
{
"when": {"cable_connection": "west", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 270 }
},
{
"when": {"cable_connection": "north", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "east", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 270 }
},
{
"when": {"cable_connection": "south", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180 }
},
{
"when": {"cable_connection": "west", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 90 }
},
{
"when": {"cable_connection": "down", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 180 }
},
{
"when": {"cable_connection": "up", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270 }
},
{
"when": {"cable_connection": "west", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner_2" }
},
{
"when": {"cable_connection": "east", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "down", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90 }
},
{
"when": {"cable_connection": "up", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 180 }
},
{
"when": {"cable_connection": "west", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner_3" }
},
{
"when": {"cable_connection": "east", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "down", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 90 }
},
{
"when": {"cable_connection": "up", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 270 }
},
{
"when": {"cable_connection": "north", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 270 }
},
{
"when": {"cable_connection": "down", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
},
{
"when": {"cable_connection": "up", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 90 }
},
{
"when": {"cable_connection": "north", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 270 }
}
]
}

View File

@ -5,7 +5,17 @@
"block.phycon.cable": "Cable",
"block.phycon.extractor": "Inventory Extractor",
"block.phycon.miner": "Block Miner",
"block.phycon.redstone_controller": "Redstone Controller",
"item.phycon.screwdriver": "Screwdriver",
"item.phycon.console": "Console"
"item.phycon.console": "Console",
"gui.phycon.terminal_buffer": "Buffer",
"gui.phycon.redstone_mode.high": "High",
"gui.phycon.redstone_mode.low": "Low",
"gui.phycon.redstone_mode.toggle": "Toggle",
"gui.phycon.redstone_mode.rising_edge": "Rising Edge",
"gui.phycon.redstone_mode.falling_edge": "Falling Edge",
"gui.phycon.activation_mode.automatic": "Automatic",
"gui.phycon.activation_mode.managed": "Managed"
}

View File

@ -1,6 +1,7 @@
{
"textures": {
"center": "phycon:block/cable_cap_end"
"center": "phycon:block/cable_cap_end",
"particle": "#center"
},
"elements": [
{

View File

@ -0,0 +1,33 @@
{
"parent": "block/block",
"textures": {
"cable": "phycon:block/cable_straight",
"front": "phycon:block/redstone_controller_front_off",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 3, 16],
"faces": {
"down": { "texture": "#front" },
"up": { "texture": "#back" },
"north": { "texture": "#side" },
"south": { "texture": "#side" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
},
{
"from": [6, 3, 6],
"to": [10, 6, 10],
"faces": {
"north": { "texture": "#cable" },
"south": { "texture": "#cable" },
"west": { "texture": "#cable" },
"east": { "texture": "#cable" }
}
}
]
}

View File

@ -0,0 +1,33 @@
{
"parent": "block/block",
"textures": {
"cable": "phycon:block/cable_straight",
"front": "phycon:block/redstone_controller_front_on",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 3, 16],
"faces": {
"down": { "texture": "#front" },
"up": { "texture": "#back" },
"north": { "texture": "#side" },
"south": { "texture": "#side" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
},
{
"from": [6, 3, 6],
"to": [10, 6, 10],
"faces": {
"north": { "texture": "#cable" },
"south": { "texture": "#cable" },
"west": { "texture": "#cable" },
"east": { "texture": "#cable" }
}
}
]
}

View File

@ -0,0 +1,51 @@
{
"parent": "block/block",
"textures": {
"front": "phycon:block/redstone_controller_front_off",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back",
"cable_end": "phycon:block/cable_cap_end",
"cable_side": "phycon:block/cable_straight"
},
"elements": [
{
"_comment": "cable center",
"from": [6, 6, 3],
"to": [10, 10, 16],
"faces": {
"down": { "texture": "#cable_side" },
"up": { "texture": "#cable_side" },
"south": { "texture": "#cable_end" },
"west": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] },
"east": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] }
}
},
{
"_comment": "redstone controller side",
"from": [0, 0, 0],
"to": [16, 16, 3],
"faces": {
"down": { "texture": "#side" },
"up": { "texture": "#side" },
"north": { "texture": "#front" },
"south": { "texture": "#back" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
}
],
"_test": [
{
"_comment": "cable center",
"from": [6, 6, 3],
"to": [10, 10, 16],
"faces": {
"down": { "texture": "#cable_side", "rotation": 270, "uv": [4, 6, 10, 10] },
"up": { "texture": "#cable_side", "rotation": 90, "uv": [4, 6, 10, 10] },
"south": { "texture": "#cable_end" },
"west": { "texture": "#cable_side", "uv": [4, 6, 10, 10] },
"east": { "texture": "#cable_side", "rotation": 180, "uv": [4, 6, 10, 10] }
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB