diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt index 18352f8..dfe9f40 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt @@ -7,6 +7,7 @@ import net.minecraft.screen.ScreenHandler import net.minecraft.sound.SoundEvents import net.minecraft.text.Text import net.shadowfacts.cacao.geometry.Point +import net.shadowfacts.cacao.util.KeyModifiers import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.RenderHelper import net.shadowfacts.cacao.window.ScreenHandlerWindow @@ -57,15 +58,16 @@ open class CacaoHandledScreen( } override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) { + renderBackground(matrixStack) + } + + override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { // no-op } override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { - renderBackground(matrixStack) - val mouse = Point(mouseX, mouseY) windows.forEachIndexed { index, it -> - it.draw(matrixStack, mouse, delta) if (it is ScreenHandlerWindow) { if (index == windows.size - 1) { super.render(matrixStack, mouseX, mouseY, delta) @@ -74,7 +76,10 @@ open class CacaoHandledScreen( super.render(matrixStack, -1, -1, delta) } } + it.draw(matrixStack, mouse, delta) } + + drawMouseoverTooltip(matrixStack, mouseX, mouseY) } override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { @@ -90,4 +95,20 @@ open class CacaoHandledScreen( } } + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + val modifiersSet by lazy { KeyModifiers(modifiers) } + if (findResponder { it.keyPressed(keyCode, modifiersSet) }) { + return true + } + return super.keyPressed(keyCode, scanCode, modifiers) + } + + override fun charTyped(char: Char, modifiers: Int): Boolean { + val modifiersSet by lazy { KeyModifiers(modifiers) } + if (findResponder { it.charTyped(char, modifiersSet) }) { + return true + } + return super.charTyped(char, modifiers) + } + } diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt index 36fe89e..3af11b4 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt @@ -139,15 +139,15 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), return super.charTyped(char, modifiers) } - private fun findResponder(fn: (Responder) -> Boolean): Boolean { - var responder = windows.lastOrNull()?.firstResponder - while (responder != null) { - if (fn(responder)) { - return true - } - responder = responder.nextResponder - } - return false - } - +} + +fun AbstractCacaoScreen.findResponder(fn: (Responder) -> Boolean): Boolean { + var responder = windows.lastOrNull()?.firstResponder + while (responder != null) { + if (fn(responder)) { + return true + } + responder = responder.nextResponder + } + return false } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt index 0b7a863..f9fdc45 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt @@ -29,6 +29,7 @@ data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 2 val RED = Color(0xff0000) val GREEN = Color(0x00ff00) val BLUE = Color(0x0000ff) + val MAGENTA = Color(0xfc46e4) val TEXT = Color(0x404040) } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt index f45d6f7..a9a5ca6 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt @@ -94,7 +94,7 @@ class Label( for (i in 0 until lines.size) { val x = when (textAlignment) { TextAlignment.LEFT -> 0.0 - TextAlignment.CENTER -> (bounds.width + textRenderer.getWidth(lines[i])) / 2 + TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2 TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i]) } val y = i * textRenderer.fontHeight diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/textfield/AbstractTextField.kt b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/AbstractTextField.kt index 774ac3f..a78aaae 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/textfield/AbstractTextField.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/AbstractTextField.kt @@ -62,12 +62,22 @@ abstract class AbstractTextField>( minecraftWidget.setMaxLength(value) } + /** + * Whether the Minecraft builtin black background and border are drawn. Defaults to true. + */ + var drawBackground = true + set(value) { + field = value + minecraftWidget.setHasBorder(value) + } + private lateinit var originInWindow: Point private var minecraftWidget = ProxyWidget() init { minecraftWidget.text = initialText minecraftWidget.setTextPredicate { this.validate(it) } + minecraftWidget.setHasBorder(drawBackground) } /** diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt new file mode 100644 index 0000000..7efee2d --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt @@ -0,0 +1,42 @@ +package net.shadowfacts.cacao.view.textfield + +/** + * @author shadowfacts + */ +class NumberField( + initialValue: Int, + handler: ((NumberField) -> Unit)? = null, +): AbstractTextField(initialValue.toString()) { + + var number: Int? + get() { + return if (text.isEmpty()) { + null + } else { + try { + Integer.parseInt(text) + } catch (e: NumberFormatException) { + null + } + } + } + set(value) { + text = value?.toString() ?: "" + } + + var validator: ((Int) -> Boolean)? = null + + init { + this.handler = handler + } + + override fun validate(proposedText: String): Boolean { + return proposedText.isEmpty() || try { + val value = Integer.parseInt(proposedText) + validator?.invoke(value) ?: true + } catch (e: NumberFormatException) { + false + } + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 653ef3c..06d2265 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -29,6 +29,7 @@ object PhysicalConnectivity: ModInitializer { registerGlobalReceiver(C2SConfigureActivationMode) registerGlobalReceiver(C2SConfigureRedstoneController) registerGlobalReceiver(C2SConfigureInserterAmount) + registerGlobalReceiver(C2SConfigureRedstoneEmitterAmount) } private fun registerGlobalReceiver(receiver: ServerReceiver) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt index c8e55ec..4a5f391 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt @@ -4,6 +4,7 @@ import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry import net.shadowfacts.phycon.block.inserter.InserterScreen +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.block.terminal.TerminalScreen import net.shadowfacts.phycon.networking.ClientReceiver @@ -17,6 +18,7 @@ object PhysicalConnectivityClient: ClientModInitializer { override fun onInitializeClient() { ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen) ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen) + ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen) registerGlobalReceiver(S2CTerminalUpdateDisplayedItems) } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlock.kt new file mode 100644 index 0000000..292085f --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlock.kt @@ -0,0 +1,88 @@ +package net.shadowfacts.phycon.block.redstone_emitter + +import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory +import net.minecraft.block.BlockState +import net.minecraft.block.Material +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.network.PacketByteBuf +import net.minecraft.screen.ScreenHandler +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.sound.BlockSoundGroup +import net.minecraft.text.Text +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.block.FaceDeviceBlock + +/** + * @author shadowfacts + */ +class RedstoneEmitterBlock: FaceDeviceBlock( + Settings.of(Material.METAL) + .strength(1.5f) + .sounds(BlockSoundGroup.METAL) +) { + + companion object { + val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter") + } + + // todo: don't just copy the redstone controller + 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 createBlockEntity(world: BlockView) = RedstoneEmitterBlockEntity() + + override fun emitsRedstonePower(state: BlockState): Boolean { + return true + } + + override fun getStrongRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int { + return if (receivingSide.opposite == state[FACING]) { + getBlockEntity(world, pos)!!.cachedEmittedPower + } else { + 0 + } + } + + override fun getWeakRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int { + return getStrongRedstonePower(state, world, pos, receivingSide) + } + + override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, result: BlockHitResult): ActionResult { + if (!world.isClient) { + val be = getBlockEntity(world, pos)!! + + be.sync() + + val factory = object: ExtendedScreenHandlerFactory { + override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler { + return RedstoneEmitterScreenHandler(syncId, playerInv, be) + } + + override fun getDisplayName() = this@RedstoneEmitterBlock.name + + override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) { + buf.writeBlockPos(be.pos) + } + } + player.openHandledScreen(factory) + } + return ActionResult.SUCCESS + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlockEntity.kt new file mode 100644 index 0000000..5ca0ae9 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlockEntity.kt @@ -0,0 +1,97 @@ +package net.shadowfacts.phycon.block.redstone_emitter + +import alexiil.mc.lib.attributes.item.GroupedItemInvView +import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompoundTag +import net.shadowfacts.phycon.api.packet.Packet +import net.shadowfacts.phycon.api.util.IPAddress +import net.shadowfacts.phycon.block.DeviceBlockEntity +import net.shadowfacts.phycon.block.FaceDeviceBlock +import net.shadowfacts.phycon.init.PhyBlockEntities +import net.shadowfacts.phycon.packet.DeviceRemovedPacket +import net.shadowfacts.phycon.packet.ReadInventoryPacket +import net.shadowfacts.phycon.packet.RequestInventoryPacket +import kotlin.math.round + +/** + * @author shadowfacts + */ +class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER) { + + private val inventoryCache = mutableMapOf() + var cachedEmittedPower: Int = 0 + private set + + var stackToMonitor: ItemStack = ItemStack.EMPTY + var maxAmount = 64 + + override fun handle(packet: Packet) { + when (packet) { + is ReadInventoryPacket -> handleReadInventory(packet) + is DeviceRemovedPacket -> handleDeviceRemoved(packet) + } + } + + private fun handleReadInventory(packet: ReadInventoryPacket) { + inventoryCache[packet.source] = packet.inventory + recalculateRedstone() + } + + private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { + inventoryCache.remove(packet.source) + recalculateRedstone() + } + + override fun tick() { + super.tick() + + if (!world!!.isClient && counter % 20 == 0L) { + if (counter % 80 == 0L) { + updateInventories() + } else if (counter % 20 == 0L) { + recalculateRedstone() + } + } + } + + private fun updateInventories() { + sendPacket(RequestInventoryPacket(ipAddress)) + } + + private fun recalculateRedstone() { + if (stackToMonitor.isEmpty) { + cachedEmittedPower = 0 + updateWorld() + return + } + val networkAmount = inventoryCache.values.fold(0) { acc, inv -> + acc + inv.getAmount(stackToMonitor) + } + cachedEmittedPower = + if (networkAmount == 0) { + 0 + } else { + 1 + round(networkAmount / maxAmount.toDouble() * 14).toInt() + } + updateWorld() + } + + private fun updateWorld() { + world!!.updateNeighborsAlways(pos, cachedState.block) + world!!.updateNeighborsAlways(pos.offset(cachedState[FaceDeviceBlock.FACING]), cachedState.block) + } + + override fun toCommonTag(tag: CompoundTag) { + super.toCommonTag(tag) + tag.putInt("CachedEmittedPower", cachedEmittedPower) + tag.put("StackToMonitor", stackToMonitor.toTag(CompoundTag())) + tag.putInt("MaxAmount", maxAmount) + } + + override fun fromCommonTag(tag: CompoundTag) { + super.fromCommonTag(tag) + cachedEmittedPower = tag.getInt("CachedEmittedPower") + stackToMonitor = ItemStack.fromTag(tag.getCompound("StackToMonitor")) + maxAmount = tag.getInt("MaxAmount") + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreen.kt new file mode 100644 index 0000000..952ad9d --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreen.kt @@ -0,0 +1,155 @@ +package net.shadowfacts.phycon.block.redstone_emitter + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.MinecraftClient +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.text.Text +import net.minecraft.text.TranslatableText +import net.minecraft.util.Identifier +import net.shadowfacts.cacao.CacaoHandledScreen +import net.shadowfacts.cacao.geometry.Axis +import net.shadowfacts.cacao.util.Color +import net.shadowfacts.cacao.view.Label +import net.shadowfacts.cacao.view.StackView +import net.shadowfacts.cacao.view.View +import net.shadowfacts.cacao.view.textfield.NumberField +import net.shadowfacts.cacao.window.ScreenHandlerWindow +import net.shadowfacts.kiwidsl.dsl +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.init.PhyBlocks +import net.shadowfacts.phycon.networking.C2SConfigureRedstoneEmitterAmount +import no.birkett.kiwi.Variable +import kotlin.math.ceil + +/** + * @author shadowfacts + */ +class RedstoneEmitterScreen( + handler: RedstoneEmitterScreenHandler, + playerInv: PlayerInventory, + title: Text +): CacaoHandledScreen( + handler, + playerInv, + title +) { + + companion object { + val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/redstone_emitter.png") + } + + init { + backgroundWidth = 176 + backgroundHeight = 166 + + addWindow(ScreenHandlerWindow(handler, ViewController(handler.emitter))) + } + + override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) { + super.drawBackground(matrixStack, delta, mouseX, mouseY) + + 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) + } + + class ViewController( + private val emitter: RedstoneEmitterBlockEntity, + ): net.shadowfacts.cacao.viewcontroller.ViewController() { + + lateinit var halfLabel: Label + lateinit var fullLabel: Label + + override fun viewDidLoad() { + super.viewDidLoad() + + val title = Label(PhyBlocks.REDSTONE_EMITTER.name) + title.textColor = Color.TEXT + view.addSubview(title) + + val inv = Label(MinecraftClient.getInstance().player!!.inventory.displayName) + inv.textColor = Color.TEXT + view.addSubview(inv) + + val field = NumberField(emitter.maxAmount) { + if (it.number != null) { + emitter.maxAmount = it.number!! + MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureRedstoneEmitterAmount(emitter)) + } + updateLabelTexts() + } + field.validator = { it >= 0 } + field.drawBackground = false + view.addSubview(field) + + val hStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL, spacing = 2.0) + view.addSubview(hStack) + + val zeroStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL)) + zeroStack.addArrangedSubview(Label(TranslatableText("gui.phycon.emitter.count", 0), textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.TEXT + } + zeroStack.addArrangedSubview(View()) + zeroStack.addArrangedSubview(Label("0", textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.RED + } + + val halfStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL)) + halfLabel = halfStack.addArrangedSubview(Label("half", textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.TEXT + } + halfStack.addArrangedSubview(View()) + halfStack.addArrangedSubview(Label("8", textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.RED + } + + val fullStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL)) + fullLabel = fullStack.addArrangedSubview(Label("full", textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.TEXT + } + fullStack.addArrangedSubview(View()) + fullStack.addArrangedSubview(Label("15", textAlignment = Label.TextAlignment.CENTER)).apply { + textColor = Color.RED + } + + updateLabelTexts() + + view.solver.dsl { + val minX = Variable("minX") + val minY = Variable("minY") + minX equalTo ((view.widthAnchor - 176) / 2) + minY equalTo ((view.heightAnchor - 166) / 2) + + title.leftAnchor equalTo (minX + 8) + title.topAnchor equalTo (minY + 6) + + inv.leftAnchor equalTo (minX + 8) + inv.topAnchor equalTo (minY + 72) + + // offset by 1 on each edge to account for MC border + field.widthAnchor equalTo 82 + field.heightAnchor equalTo 11 + field.leftAnchor equalTo (minX + 56) + field.topAnchor equalTo (minY + 23) + + hStack.centerXAnchor equalTo view.centerXAnchor + hStack.widthAnchor equalTo (176 - 4) + hStack.topAnchor equalTo (field.bottomAnchor + 8) + hStack.bottomAnchor equalTo inv.topAnchor + + zeroStack.widthAnchor equalTo halfStack.widthAnchor + halfStack.widthAnchor equalTo fullStack.widthAnchor + } + } + + private fun updateLabelTexts() { + halfLabel.text = TranslatableText("gui.phycon.emitter.count", ceil(emitter.maxAmount / 2.0).toInt()) + fullLabel.text = TranslatableText("gui.phycon.emitter.count", emitter.maxAmount) + window!!.layout() + } + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreenHandler.kt new file mode 100644 index 0000000..36905e6 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreenHandler.kt @@ -0,0 +1,76 @@ +package net.shadowfacts.phycon.block.redstone_emitter + +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.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.util.Identifier +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.init.PhyBlocks +import net.shadowfacts.phycon.init.PhyScreens +import net.shadowfacts.phycon.util.GhostSlot +import net.shadowfacts.phycon.util.copyWithCount + +/** + * @author shadowfacts + */ +class RedstoneEmitterScreenHandler( + syncId: Int, + playerInv: PlayerInventory, + val emitter: RedstoneEmitterBlockEntity, +): ScreenHandler(PhyScreens.REDSTONE_EMITTER, syncId) { + + companion object { + val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter") + } + + constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): + this( + syncId, + playerInv, + PhyBlocks.REDSTONE_EMITTER.getBlockEntity(playerInv.player.world, buf.readBlockPos())!! + ) + + init { + // fake slot + addSlot(GhostSlot(emitter::stackToMonitor, 31, 20)) + + // player inv + for (y in 0 until 3) { + for (x in 0 until 9) { + addSlot(Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 84 + y * 18)) + } + } + + // hotbar + for (x in 0 until 9) { + addSlot(Slot(playerInv, x, 8 + x * 18, 142)) + } + } + + override fun canUse(player: PlayerEntity): Boolean { + return true + } + + override fun onSlotClick(slotId: Int, clickData: Int, slotActionType: SlotActionType, player: PlayerEntity): ItemStack { + // fake slot + if (slotId == 0) { + if (player.inventory.cursorStack.isEmpty) { + emitter.stackToMonitor = ItemStack.EMPTY + } else { + emitter.stackToMonitor = player.inventory.cursorStack.copyWithCount(1) + } + } + return super.onSlotClick(slotId, clickData, slotActionType, player) + } + + override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack { + val slot = slots[slotId] + emitter.stackToMonitor = slot.stack.copyWithCount(1) + return ItemStack.EMPTY + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt index 3ddb63b..1bb1e11 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt @@ -17,6 +17,8 @@ import net.shadowfacts.phycon.block.netswitch.SwitchBlock import net.shadowfacts.phycon.block.netswitch.SwitchBlockEntity import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity import net.shadowfacts.phycon.block.terminal.TerminalBlock import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity @@ -32,6 +34,7 @@ object PhyBlockEntities { val INSERTER = create(::InserterBlockEntity, PhyBlocks.INSERTER) val MINER = create(::MinerBlockEntity, PhyBlocks.MINER) val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER) + val REDSTONE_EMITTER = create(::RedstoneEmitterBlockEntity, PhyBlocks.REDSTONE_EMITTER) private fun create(builder: () -> T, block: Block): BlockEntityType { return BlockEntityType.Builder.create(builder, block).build(null) @@ -45,6 +48,7 @@ object PhyBlockEntities { register(InserterBlock.ID, INSERTER) register(MinerBlock.ID, MINER) register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) + register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER) } private fun register(id: Identifier, type: BlockEntityType<*>) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt index da7a0ec..3664759 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt @@ -10,6 +10,7 @@ import net.shadowfacts.phycon.block.miner.MinerBlock import net.shadowfacts.phycon.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.block.netswitch.SwitchBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock import net.shadowfacts.phycon.block.terminal.TerminalBlock /** @@ -25,6 +26,7 @@ object PhyBlocks { val INSERTER = InserterBlock() val MINER = MinerBlock() val REDSTONE_CONTROLLER = RedstoneControllerBlock() + val REDSTONE_EMITTER = RedstoneEmitterBlock() fun init() { register(InterfaceBlock.ID, INTERFACE) @@ -35,6 +37,7 @@ object PhyBlocks { register(InserterBlock.ID, INSERTER) register(MinerBlock.ID, MINER) register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) + register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER) } private fun register(id: Identifier, block: Block) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt index 49eebf2..760dc8a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt @@ -13,6 +13,7 @@ import net.shadowfacts.phycon.block.miner.MinerBlock import net.shadowfacts.phycon.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.block.netswitch.SwitchBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock import net.shadowfacts.phycon.block.terminal.TerminalBlock /** @@ -28,6 +29,7 @@ object PhyItems { val INSERTER = BlockItem(PhyBlocks.INSERTER, Item.Settings()) val MINER = BlockItem(PhyBlocks.MINER, Item.Settings()) val REDSTONE_CONTROLLER = BlockItem(PhyBlocks.REDSTONE_CONTROLLER, Item.Settings()) + val REDSTONE_EMITTER = BlockItem(PhyBlocks.REDSTONE_EMITTER, Item.Settings()) val SCREWDRIVER = ScrewdriverItem() val CONSOLE = ConsoleItem() @@ -41,6 +43,7 @@ object PhyItems { register(InserterBlock.ID, INSERTER) register(MinerBlock.ID, MINER) register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) + register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER) register(ScrewdriverItem.ID, SCREWDRIVER) register(ConsoleItem.ID, CONSOLE) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt index 66bcff5..ed44f5d 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt @@ -3,6 +3,7 @@ package net.shadowfacts.phycon.init import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry import net.minecraft.screen.ScreenHandlerType import net.shadowfacts.phycon.block.inserter.InserterScreenHandler +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler object PhyScreens { @@ -11,10 +12,13 @@ object PhyScreens { private set lateinit var INSERTER: ScreenHandlerType private set + lateinit var REDSTONE_EMITTER: ScreenHandlerType + private set fun init() { TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalScreenHandler.ID, ::TerminalScreenHandler) INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler) + REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler) } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneEmitterAmount.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneEmitterAmount.kt new file mode 100644 index 0000000..604b27b --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneEmitterAmount.kt @@ -0,0 +1,46 @@ +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.block.inserter.InserterBlockEntity +import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity + +/** + * @author shadowfacts + */ +object C2SConfigureRedstoneEmitterAmount: ServerReceiver { + override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_redstone_emitter_amount") + + operator fun invoke(be: RedstoneEmitterBlockEntity): Packet<*> { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(be.world!!.registryKey.value) + buf.writeBlockPos(be.pos) + buf.writeVarInt(be.maxAmount) + + 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 amount = buf.readVarInt() + + server.execute { + val key = RegistryKey.of(Registry.DIMENSION, dimID) + val world = server.getWorld(key) ?: return@execute + val be = world.getBlockEntity(pos) as? RedstoneEmitterBlockEntity ?: return@execute + be.maxAmount = amount + be.markDirty() + } + } +} diff --git a/src/main/resources/assets/phycon/blockstates/redstone_emitter.json b/src/main/resources/assets/phycon/blockstates/redstone_emitter.json new file mode 100644 index 0000000..2165037 --- /dev/null +++ b/src/main/resources/assets/phycon/blockstates/redstone_emitter.json @@ -0,0 +1,159 @@ +{ + "multipart": [ + { + "apply": { "model": "phycon:block/cable_center" } + }, + { + "when": { "facing": "down" }, + "apply": { "model": "phycon:block/redstone_emitter_side" } + }, + { + "when": { "facing": "up" }, + "apply": { "model": "phycon:block/redstone_emitter_side", "x": 180 } + }, + { + "when": { "facing": "north" }, + "apply": { "model": "phycon:block/redstone_emitter_side", "x": 270 } + }, + { + "when": { "facing": "south" }, + "apply": { "model": "phycon:block/redstone_emitter_side", "x": 90 } + }, + { + "when": { "facing": "west" }, + "apply": { "model": "phycon:block/redstone_emitter_side", "x": 90, "y": 90 } + }, + { + "when": { "facing": "east" }, + "apply": { "model": "phycon:block/redstone_emitter_side", "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 } + } + ] +} diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json index e558bfb..937d34f 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -7,6 +7,7 @@ "block.phycon.inserter": "Inventory Inserter", "block.phycon.miner": "Block Miner", "block.phycon.redstone_controller": "Redstone Controller", + "block.phycon.redstone_emitter": "Redstone Emitter", "item.phycon.screwdriver": "Screwdriver", "item.phycon.console": "Console", @@ -25,5 +26,6 @@ "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" + "gui.phycon.activation_mode.managed": "Managed", + "gui.phycon.emitter.count": "%d Item(s)" } diff --git a/src/main/resources/assets/phycon/models/block/redstone_emitter_side.json b/src/main/resources/assets/phycon/models/block/redstone_emitter_side.json new file mode 100644 index 0000000..05be28f --- /dev/null +++ b/src/main/resources/assets/phycon/models/block/redstone_emitter_side.json @@ -0,0 +1,33 @@ +{ + "parent": "block/block", + "textures": { + "cable": "phycon:block/cable_straight", + "front": "phycon:block/redstone_emitter_front", + "back": "phycon:block/redstone_emitter_back", + "side": "phycon:block/redstone_emitter_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" } + } + } + ] +} diff --git a/src/main/resources/assets/phycon/textures/gui/redstone_emitter.png b/src/main/resources/assets/phycon/textures/gui/redstone_emitter.png new file mode 100644 index 0000000..669b01e Binary files /dev/null and b/src/main/resources/assets/phycon/textures/gui/redstone_emitter.png differ diff --git a/src/main/resources/data/phycon/loot_tables/blocks/redstone_emitter.json b/src/main/resources/data/phycon/loot_tables/blocks/redstone_emitter.json new file mode 100644 index 0000000..f005696 --- /dev/null +++ b/src/main/resources/data/phycon/loot_tables/blocks/redstone_emitter.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "phycon:redstone_emitter" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +}