From 3cd4a7aa0d4913d0a2a1cc7f8c7893948d017466 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 2 Mar 2021 22:20:25 -0500 Subject: [PATCH] Add Redstone Emitter --- .../shadowfacts/cacao/CacaoHandledScreen.kt | 27 ++- .../net/shadowfacts/cacao/CacaoScreen.kt | 22 +-- .../net/shadowfacts/cacao/util/Color.kt | 1 + .../net/shadowfacts/cacao/view/Label.kt | 2 +- .../cacao/view/textfield/AbstractTextField.kt | 10 ++ .../cacao/view/textfield/NumberField.kt | 42 +++++ .../phycon/PhysicalConnectivity.kt | 1 + .../phycon/PhysicalConnectivityClient.kt | 2 + .../redstone_emitter/RedstoneEmitterBlock.kt | 88 ++++++++++ .../RedstoneEmitterBlockEntity.kt | 97 +++++++++++ .../redstone_emitter/RedstoneEmitterScreen.kt | 155 +++++++++++++++++ .../RedstoneEmitterScreenHandler.kt | 76 +++++++++ .../phycon/init/PhyBlockEntities.kt | 4 + .../net/shadowfacts/phycon/init/PhyBlocks.kt | 3 + .../net/shadowfacts/phycon/init/PhyItems.kt | 3 + .../net/shadowfacts/phycon/init/PhyScreens.kt | 4 + .../C2SConfigureRedstoneEmitterAmount.kt | 46 +++++ .../phycon/blockstates/redstone_emitter.json | 159 ++++++++++++++++++ .../resources/assets/phycon/lang/en_us.json | 4 +- .../models/block/redstone_emitter_side.json | 33 ++++ .../phycon/textures/gui/redstone_emitter.png | Bin 0 -> 8808 bytes .../loot_tables/blocks/redstone_emitter.json | 19 +++ 22 files changed, 782 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/cacao/view/textfield/NumberField.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlock.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterBlockEntity.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreen.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/block/redstone_emitter/RedstoneEmitterScreenHandler.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneEmitterAmount.kt create mode 100644 src/main/resources/assets/phycon/blockstates/redstone_emitter.json create mode 100644 src/main/resources/assets/phycon/models/block/redstone_emitter_side.json create mode 100644 src/main/resources/assets/phycon/textures/gui/redstone_emitter.png create mode 100644 src/main/resources/data/phycon/loot_tables/blocks/redstone_emitter.json 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 0000000000000000000000000000000000000000..669b01e4df4be243202569ba6982d9b210694fce GIT binary patch literal 8808 zcma(%Wl$X5mNR&80)*fe+=4p{E(z}L9)i0K65N7&AjlxWCAb845-dP)8wLgl5`0*) zueNHp_N%wsU0r?exyPk{oYOJtstP!mWS9T|07pqtRucdKJ}rR&478`0v3t2K06_BE zUt8Z()53?w)!oI`;hhbQr=P10jg7Cv)1mM3Zmxm5by14!qk$Y(sIa+kysZm**S$nv zSA~H=CFJ%nMTun>1j42|24!FNJTC2pTr;s45~ijZd$iog@$0T%m;O976PL_S*x%p& z)DhAhu-17G^TQU4JVbT4f{;x^7V*U1wIUFHTE}{UZRq~`e->rb$8YZfMU@J!7SB1? zkmtP~Qk*`1)q-Dvwu85`#~vuhJLC#_COeo=xF#ctHXblJf1dN2@?zY=rMJ2BK21p$ z#L(uRZ=CeGj&$sHiCkBQcYAiPF5ZQuQ$f;qYV+asF@OC)TR^HUnBd+$Yd0&; zWo^hwRy`-3zfNtthO@}`be=*VXxML!pKVo9ld*;MXReq^G;KTWg$lIM015g?J|iB_`!noZ?5;>WJrWSlbj?N;7nWaLmms+mtz)7Zy4l zUOL9l`-W_19|u6|Aq5>5Kb%~`FG$@=K1Rj!!0czIUx&tILne3*-;HOZYJO?`G4@8+ zz^hrM241(VD(bRiv$$&d*<0aqcF|0Ba#iD;N%L01FNSQI?Bg)L>gFqliRi4P#OOGA zaKs-nUL)gpQ+fz@4`0EUI}6a;Q+c}3PnJvdo%R@{0cDK}*gMab=7 z7u`{O<@zkpJe@#TUxhgj5l|1Rx+DlQsCh4{xiUCze%nz(nXTZ=Bhzx9;^89R5IZV4 z)_23rzPFV@9Q#7g($98#F6GXYVaYiYTp}J-?WGzz&?VI?em`di9-rb(-Qa7_P;saw z_?q7Up|ve+>UI{QsLPBydv~guwT9=GU>+R8Geic87v&MvV{SbOX4gtFJ} zjps1rFl{r@0(L4ONfy5~NdCmuyR2k;yZIH!4Qji-RhCDnt8uclXpM9z)$~DtZY;S@Kle?Nse!!%Yj6T)*YZu0|tjI3LZ%9+x*Bj!bx(IKPq|v;# ztaZOV8|iSduy#$edSbmm7Y2y1VeGuJj^>xufr2GILZm*Y%>$=P7&q+R9GFNHoTyJaWK`IlI)`(sth;U!>%lH7ljC9Bwo57%lRNzHzCHnc!zmu57stDj7DelXs~& zl6v}N_Vq)Z&-L%Er#rc?!x@yJI=Fp!3Y%Y@e9S3y<4CG&#TnC2l_+G-c4GDjng#W` zwX7jadp@<~LCB+Z;jd@-7tR$FKJK{t$3ldO!S9NW-`40^0n5pceuqu7j#G-R`cLw( zVm<#=&`iD^GM8jZ-E-)+XO%DE6gK@XtH&X1+P_e^xzhB0w^+ER*s;qtWobIaqhxf^z;U|aRLKh(Y}{3eOn}L);0jD0Pj-yjtKQsT?_fw?!r%G-X_V7*0rei_T zV-{~l8#T7)+$aqMKZAocdN_NBDTb*6Sw!iFdr<^GxP7-Wy?K9=SiDLE4_*zti_}$H zOxf26dDb2?Jn!@CX4^odMFR3oCxwWsiS>4X)hLGrjIw*b=!VB-A#M$EFzX2XV&d4; zGA~Kvl7Yp%P%j=-6wF!ti7o`mr9fUnY`Ux^y^Of_6AyFLELhjC`6lzDWC8!Q@i+yV z@UJ{g62OgN2~=Ph!Xs!l@24Lut77(Cw=wR`cT?%YK`?f4$g!s{~jrI%xhj$5`7IIDtwQ&`10-Zk|3H z#^@|E-N1-09`Cza7F~WLkvjUrP2Y<_@^#NQPv7yq`bG<%XG)RT$36jkOp&WL$ zh{5|J1v{%@g%~5jCPT5{(wBH)nN|YwWNTfD=j~&7ah|X_gGOcIrj6Lf5{Do|eX)RO zp{5jT`}2*8U_Ng@TxaS|e*-NeO}lPp)`xN9&y^8Qg`=9+?7yiAYgHVJlIJak2=tAj z_(h>HG9}Mft38E5Ei#1RJAr~{QK9+9(o3b;o;4CsLXHV9SanUh2cDS2nW75LvI@_# z^?U)-_rk9z_`jNV0*SW5a&Xq=>OJ9fHj7l8P@?6@N;2#P3`%Ok%*6^OvG(q26bruy zItPX#IwtG9R|b(_6ck}GWWKwpYF1rZYFU&ls!dDxU=vqq=qW_16*N<6q{1vQELLg% zg8ENq6$we!OS!k-n@&{x&SOoOYKgl{>#KtI30Z3|7}UUHipbl> z68ME!$AMu-T;4k=bfM>d5c$ROMbLU8(Aw%xJ~!Mdt792yVUKx`!?lsxikJZjt*|=fy9X~GSXYEtn<4|o zSK;1Kk!9JpX(>oZAI{17XKjlovv2q;Qo$lghf!IuDIOnCt+-JLdFM}siJ3$;(y7k- z(v5rq-LYrOVwyxhFShK^1z%}vWbN(b-d4)8XKb48)c2uZGf@&#MCPNb2p8ks+ZUFe?SCIq#tVsJEovx~qJ`Be+aj=ujX znuuH--$%|D)Lumdb6PSQ`;{o=QzpMaunUs7^_PKCtmpjN20BSm&kuEhq{PO@Ei!N; zaT_d;x=jk_aBkN+M-dhbKCn@|t~8XS@4QMDb<^izba}Q27xND9JO9Zyqk24p`20xZ z>+9t2^;x<6^NK=Bt3nh8>(5o$7l+1|_)#6}y_^|<4GQ#jH$~n|>C0GBn*M>a#oG-2 z&j}B^=`omuB28LDGIqYkUE>+x@LH196N3SaY>h0gtnJgy0Hr7Pa z)`@tX_l`dHKjcz4WBD?Vf7CcV!XHYwoMnqglc&r_C?L9mev8JHeI0UyWP}tEHN8Zd z&qWqTl1gF5Yjo3n?%cRIUe1lUsP}c1-gPlNtsCz(mS$j%Q76*B66N+k z8%orj%)i+h<{n|b%Bi_nrgV3bPOm3l4mD`l%M4?Ay zmY`hjVa}zxG%+xaU4XB=6w+M_bZxqE5Y&0tSK}^({hg!O{>z?^I;Tnu=Q*06c*p2o z^r*y3YuqZF2JuSbBB(PQJ*&;^B9U)B@eY~#KD}-c>F zN^;BvVzX9AVQDLMTRJ}I54NOetJB+b$(-tdCn$@wuRUQh9TUe{iBd7^>M@$S>=vcn z1N-5D+gK&MArTTcuW)`xT5Ue}%;Z-7&3WrA7C3*Il1EQX{81wr?kKub$O!+5h^DPw zE15PQ*;f-a-AlJFZM@Un^JBrb!RcSKGeK>IbVRPQHaLypQWdc1-2Zx}X_VrCb;hfh z6t^&0IVo+jnZ6tZ!SWnDv3q95SW5d zK`3eC3YO6>+A+*-ST@7Chl*Ba>Dr2MtHvrFt~V`qp7jBE%+DElZMK)MSiH0_5S=v!1Z!HBUI3)(esBGY)He+9$ zHQSPL94_4?8Z%sbj^KAZG%%V*nUF~B1GAnwAOczAb+-sCfD@wz4f}V>s?un%%*u1M!M)><%chH-sk&4t7~#N zVi{gkf^wg4p2?gr8zY@be1h(vTK)GtOV^FgVx}TBJR`ge_4Go7kQ2qlZJW_RO$9 zTV;iCM|WEz25CehZ%D!iswf{k@d54S{*eQz%1xEW1n3)R)iX%+oG=R_l}1?p>s7{9 zP9!DqOzk5)ywwDH$)K9VU@m_ahpAQ+m1J-jcH~_r8L63vTA?;4>)7ZUiPBX|j4JU= z&WTrpjWP$)6!@6Y!Cm9uQNXhtKXw5d=JXt2lc+@1QNs(wfouJd?&Zp5OF5(H+T42j zwN9izp``2V^G9qtneOo{E566?aqxrm9p5CS*`D~;&*Cb8sjE}OxL#afO(q^o1-=>)nv1C!riYSc^VBW5?o}(?ZaUA%K!YSIF`Jz35g_1Q(W*D?pvO zZ}IXAwr4iT)~HXGO!gR6ZK0tleHH3~XmUW|p9bEH@3 zmZ`mk!sL_RiY(d16Kc4@Y$*9BsxFWdf@0~+_QWrZ3|bgcZ!w%@!LG*?w4XoNhJO+V zo)YWscP8(6s{IVjWtFD;PrzxKV#<$ zC29~R`u_6ut~{(?9V?p{bGhuL-f&`#f=KgQ=dYvb9~qUadEiBtd!wP`rCCW(G;8Uh$Ui};(u24o~P2_9sbgNE9iSk)Z(_Xyp1k{sWT*%pAo?^HFQ`xckuwkBF zwmTMj;(OV>K*?r0aa`bVA;=%I*P2$<->JuV$By-ewDThxfSN*arsXn{k+bw(aj;x^ zm7c~>52;-AK-{Q#bLY23;zqq4uu^Bj!L@6XPpCY*RQ`-4-RcKoe9jKTXE!9rNwWbl84JEepU)?Ttdt5V z<9xRBqq6pB(TSRn&)2N-ahlZ{`V5^Gt*SN50*Cao>J*CxIS`jxl(4*BQ%(I&r4@z2 zu>f1JDgN442w4%q88+-=C=W-FM0U~7V|zi1>?ZY%nqtG0(%v{7&jd2UoO{2iZP(V-FD!i1#Yd5(@%Gk@6`(uJeU(fz6l#I%vnR3E zfsE*ncWCoMHeV8EBExm~JzevIn={)sHGYfp264eE&!zl`Y|BUtGBiKk(d1$?RU@Lz z@?TMo*P^kA=vHjEYRg!w6n^%L&(xRbJ&j&kGU27lleFa_e>$(I?0-g{gzX1W`mJ4Y zqZDlXsJULmwYZSEkd`}?057Nr^;Fsq;Tpe_y0g;tx%fwVzhwn#@)YCu%&GL`l|xVOC<%Gty=R|33MQSsA; z+7}lW6A);yrca>oWC59j@d01}00ltvpHm$G2mn9={7+CJJ^bevWISx_5~||bS}x2F zVRi&9hJaV7c|#i;h2q#CEWo=qQjqAY6@~92=(rR!vCqI$2;@?)CQHMA;%83yuwIKI z=DGpG#>Mq7vk*lC6{**~C2j(Mh9>~Gx3_O6EjrM^6~gwwzk{ZKV(|}J{!g9%0pZ_* zcp9y$U2vaFlmMBTnZ+k1kq-;0Bxx}dX~S$$KxZf=+(JSxx3{;4xK(GjpZr*%&GBzM z2Ktvv>sG(CQUB%lWM`Q~v|GH4-I$zgsb+<&Ec=RYUm|GeAi@pigF;40nGhYVS&?>+ zGaF0#SDM8z`F08Aa3W|5_`kyO-|fHv-~llJ=Hw0Z$!;RS6oyX}LkHJxM;J{?yx^T@ ziv5X#LrS8Fl%aXZ|2D+<04gMWK(Bzc0?oGPy-?Fsjwq58(-YSh|H_4&hCL-v2NF4i zaHwLPI7j-lq!%ENmUsSyK05+=UEzosN0O0VGybf#n$ZkWn#%fQ9EpXzNhSdWpZ}EXN=stA)7DH&S7w;+$qcCzvaQ#Tx2$j7 z``u8t%ninw0+4YaA4@h|!|r~s?$$0pRRxY&Olr2gE^YGo?q_}W93Uu_tXtS#^ZJgH zBC+PUCM!UMV zFuq)j{5&Qnxg>>&_F?()8hGY(a|S}OA}eyBgD-y6m}eYUyUfULppSG zzSh3nmdx+k$_~>->-}vNwKW(=^08;$jN>-CT=mW@XnX!I`tT?5#HCuh;(dFyH_|jL z_sbdE%Y$0d07LW9Nl?J!wc+LVE0n#0DIEx6bkRTYk9LpV%|X)krOeG+%*W-^ijj3# z(v!%n2mcl_(35$lJoEs=IV7>WGlR~vaXmUZOLuD?OqmbF;Tzu-lAg9l_xhc`@7BwL z2hG&R-QW?~fF?db53&el#1-ML+?CPzT-J<3= z2)|$NhG&O=F#-n=?%L4eW9?t*vfT#|p6dZ}R^OJGaG=*EdH^nIUvqOmeE{=eizLB~ z8BG-RFOix#6yOuZExTEFuoWmAet9Id092<&Qo>5s*jxvRR z&cB$ndfFIZ@Hq-|8oQ$ccyPcwHSPWi!C$YN<;4-JYHw7k$7}II=M8Z`;Bn*uOS+26 z^x212)pE&;1yEo{f1%jmCKs%XUx`Mn>2#9|TS^#n!MIIAfRuoX^B&>VUl^izQ`3M% zxavbUQY@8#1zPfo7c=WLlO|5u(;{}*yY_*%%{Xm**Y|Wo%JvZ$00iZb_DcUwy0?LU z?hJnayZvtEM2+hXfmmB(Ud>LtM#RB{pA?Gx^5;zJKA8tV_5jknZob+#lU(eu#LzYh zJL%DH6e8yI^aZ^7(6gLj?i2~A<3}#+K3!~=e7ucGm!Zkts%>F1UuMH4SR(;6B**2W z7oP>v1)mjDbGAuJwvj>vP*U#B>0h{f=lFCjv=$s9GBiBAy?GJ%@)Z;E8z0-MeUy|1 z8sUSi55!5xe1gd3J(7|KH>t1n;fVXq)UMx+W*nydT&vk8!E%LV*R!T>cGba$XG{E|t5F_S3dslqNO;4y6w5ALTNN|Nv=vf3Zcz%fMJpWRovSLdViav*e` zM1oD7?Rp`YxE{@p}tg6+%YM zRE4XDxL_|7P>@@Tqc})33HjYqI>$BsIoGvcbOYm{fJEaozRLvcKGpkvA9F~s@Y<8- zY>_4N3QW6I^g0s%;=hRXD4hpqli2*%mX|Ogw!vih?_VMRuM7MLyrSi?uZ|8-`HQb4 Lrz%?`Z65X?JlZ{j literal 0 HcmV?d00001 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" + } + ] + } + ] +}