Convert console screen to use Cacao

This commit is contained in:
Shadowfacts 2021-02-27 21:48:40 -05:00
parent 3926da0c3c
commit 500ad94442
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
15 changed files with 307 additions and 231 deletions

View File

@ -39,6 +39,9 @@ public final class IPAddress {
int b = Integer.parseInt(matcher.group(2));
int c = Integer.parseInt(matcher.group(3));
int d = Integer.parseInt(matcher.group(4));
if (a > 255 || b > 255 || c > 255 || d > 255) {
return null;
}
return new IPAddress(a, b, c, d);
}

View File

@ -1,16 +1,15 @@
package net.shadowfacts.cacao
import com.mojang.blaze3d.platform.GlStateManager
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvents
import net.minecraft.text.LiteralText
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.Window
import org.lwjgl.glfw.GLFW
import java.util.*
/**
@ -19,7 +18,7 @@ import java.util.*
*
* @author shadowfacts
*/
open class CacaoScreen: Screen(LiteralText("CacaoScreen")), AbstractCacaoScreen {
open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), AbstractCacaoScreen {
// _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods.
private val _windows = LinkedList<Window>()
@ -147,4 +146,4 @@ open class CacaoScreen: Screen(LiteralText("CacaoScreen")), AbstractCacaoScreen
return false
}
}
}

View File

@ -29,6 +29,8 @@ 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 TEXT = Color(0x404040)
}
}
}

View File

@ -26,7 +26,7 @@ import net.shadowfacts.cacao.util.RenderHelper
*/
class Label(
text: Text,
val shadow: Boolean = true,
val shadow: Boolean = false,
val maxLines: Int = 0,
val wrappingMode: WrappingMode = WrappingMode.WRAP,
val textAlignment: TextAlignment = TextAlignment.LEFT
@ -47,7 +47,7 @@ class Label(
constructor(
text: String,
shadow: Boolean = true,
shadow: Boolean = false,
maxLines: Int = 0,
wrappingMode: WrappingMode = WrappingMode.WRAP,
textAlignment: TextAlignment = TextAlignment.LEFT,

View File

@ -16,7 +16,12 @@ import net.shadowfacts.cacao.view.Label
* @param initialValue The initial enum value for this button.
* @param localizer A function that takes an enum value and converts into a string for the button's label.
*/
class EnumButton<E: Enum<E>>(initialValue: E, val localizer: (E) -> Text): AbstractButton<EnumButton<E>>(Label(localizer(initialValue))) {
class EnumButton<E: Enum<E>>(
initialValue: E,
val localizer: (E) -> Text
): AbstractButton<EnumButton<E>>(
Label(localizer(initialValue), shadow = true)
) {
private val label: Label
get() = content as Label

View File

@ -7,6 +7,7 @@ import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.util.texture.NinePatchTexture
import net.shadowfacts.cacao.util.texture.Texture
@ -21,7 +22,7 @@ import net.shadowfacts.kiwidsl.dsl
* @author shadowfacts
*/
class TabViewController<T: TabViewController.Tab>(
val tabs: Array<T>,
val tabs: List<T>,
initalTab: T = tabs.first()
): ViewController() {
@ -38,11 +39,14 @@ class TabViewController<T: TabViewController.Tab>(
private lateinit var outerStack: StackView
private lateinit var tabStack: StackView
private lateinit var tabVCContainer: View
// todo: this shouldn't be public, use layout guides
lateinit var tabVCContainer: View
private set
override fun viewDidLoad() {
super.viewDidLoad()
// todo: might be simpler to just not use a stack view
// padding is -4 so tab button texture overlaps with panel BG as expected
outerStack = StackView(Axis.VERTICAL, StackView.Distribution.FILL, -4.0)
view.addSubview(outerStack)
@ -51,9 +55,6 @@ class TabViewController<T: TabViewController.Tab>(
tabStack.zIndex = 1.0
outerStack.addArrangedSubview(tabStack)
tabVCContainer = View()
outerStack.addArrangedSubview(tabVCContainer)
tabButtons = tabs.mapIndexed { index, tab ->
val btn = TabButton(tab)
btn.handler = this::selectTab
@ -69,8 +70,11 @@ class TabViewController<T: TabViewController.Tab>(
tabStack.addArrangedSubview(View())
val background = NinePatchView(NinePatchTexture.PANEL_BG)
background.zIndex = -1.0
tabVCContainer.addSubview(background)
outerStack.addArrangedSubview(background)
tabVCContainer = View()
tabVCContainer.zIndex = 1.0
view.addSubview(tabVCContainer)
embedChild(currentTab.controller, tabVCContainer)
@ -80,10 +84,10 @@ class TabViewController<T: TabViewController.Tab>(
outerStack.topAnchor equalTo view.topAnchor
outerStack.bottomAnchor equalTo view.bottomAnchor
background.leftAnchor equalTo tabVCContainer.leftAnchor
background.rightAnchor equalTo tabVCContainer.rightAnchor
background.topAnchor equalTo tabVCContainer.topAnchor
background.bottomAnchor equalTo tabVCContainer.bottomAnchor
tabVCContainer.leftAnchor equalTo (background.leftAnchor + 6)
tabVCContainer.rightAnchor equalTo (background.rightAnchor - 6)
tabVCContainer.topAnchor equalTo (background.topAnchor + 6)
tabVCContainer.bottomAnchor equalTo (background.bottomAnchor - 6)
}
}
@ -125,7 +129,11 @@ class TabViewController<T: TabViewController.Tab>(
super.wasAdded()
backgroundView.usesConstraintBasedLayout = false
backgroundView.frame = Rect(0.0, 0.0, 28.0, 32.0)
backgroundView.zIndex = -1.0
addSubview(backgroundView)
solver.dsl {
content.bottomAnchor lessThanOrEqualTo (bottomAnchor - 4)
}
}
override fun didLayout() {

View File

@ -8,12 +8,7 @@ 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
import net.shadowfacts.phycon.screen.console.DeviceConsoleScreen
/**
* @author shadowfacts
@ -41,12 +36,8 @@ class ConsoleItem: Item(Settings()) {
}
private fun openScreen(be: DeviceBlockEntity) {
val screen = TestCacaoScreen()
// val screen = when (be) {
// is ActivationController.ActivatableDevice -> ActivatableDeviceConsoleScreen(be)
// is RedstoneControllerBlockEntity -> RedstoneControllerConsoleScreen(be)
// else -> DeviceConsoleScreen(be)
// }
// val screen = TestCacaoScreen()
val screen = DeviceConsoleScreen(be)
MinecraftClient.getInstance().openScreen(screen)
}

View File

@ -1,64 +0,0 @@
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

@ -1,40 +0,0 @@
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
/**
* @author shadowfacts
*/
class DeviceConsoleScreen(
val device: DeviceBlockEntity,
): Screen(TranslatableText("item.phycon.console")) {
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
}
return super.keyPressed(key, j, k)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
super.render(matrixStack, mouseX, mouseY, delta)
drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff)
drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff)
}
}

View File

@ -1,96 +0,0 @@
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,50 @@
package net.shadowfacts.phycon.screen.console
import net.minecraft.client.MinecraftClient
import net.minecraft.text.TranslatableText
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.button.EnumButton
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
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
/**
* @author shadowfacts
*/
class ActivatableDeviceViewController<T>(
val device: T,
): ViewController() where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice {
override fun viewDidLoad() {
super.viewDidLoad()
val label = Label(TranslatableText("gui.phycon.console.remote.mode")).apply {
textColor = Color.TEXT
}
view.addSubview(label)
val mode = EnumButton(device.controller.activationMode, ActivationMode::friendlyName)
mode.handler = {
device.controller.activationMode = it.value
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device))
}
view.addSubview(mode)
view.solver.dsl {
mode.widthAnchor equalTo 100
mode.heightAnchor equalTo 20
mode.topAnchor equalTo view.topAnchor
mode.rightAnchor equalTo view.rightAnchor
label.centerYAnchor equalTo mode.centerYAnchor
label.rightAnchor equalTo (mode.leftAnchor - 4)
}
}
}

View File

@ -0,0 +1,94 @@
package net.shadowfacts.phycon.screen.console
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.TextureView
import net.shadowfacts.cacao.view.View
import net.shadowfacts.cacao.viewcontroller.TabViewController
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.cacao.window.Window
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class DeviceConsoleScreen(
val device: DeviceBlockEntity,
): CacaoScreen(TranslatableText("item.phycon.console")) {
private val tabController: TabViewController<Tab>
init {
val tabs = mutableListOf(
Tab(
Label("IP").apply { textColor = Color.TEXT },
TranslatableText("gui.phycon.console.details"),
DeviceDetailsViewController(device)
)
)
if (device is ActivationController.ActivatableDevice) {
tabs.add(Tab(
TextureView(Texture(Identifier("textures/item/ender_pearl.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("gui.phycon.console.remote"),
ActivatableDeviceViewController(device)
))
}
if (device is RedstoneControllerBlockEntity) {
tabs.add(Tab(
TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.redstone_controller"),
RedstoneControllerViewController(device)
))
}
tabController = TabViewController(tabs)
val root = object: ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
embedChild(tabController, pinEdges = false)
view.solver.dsl {
tabController.view.centerXAnchor equalTo view.centerXAnchor
tabController.view.centerYAnchor equalTo view.centerYAnchor
tabController.tabVCContainer.widthAnchor equalTo 200
tabController.tabVCContainer.heightAnchor equalTo 150
}
}
}
addWindow(Window(root))
}
override fun isPauseScreen() = false
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == GLFW.GLFW_KEY_E) {
onClose()
return true
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
data class Tab(
override val tabView: View,
override val tooltip: Text?,
override val controller: ViewController,
): TabViewController.Tab
}

View File

@ -0,0 +1,44 @@
package net.shadowfacts.phycon.screen.console
import net.minecraft.text.TranslatableText
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.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.network.DeviceBlockEntity
/**
* @author shadowfacts
*/
class DeviceDetailsViewController(val device: DeviceBlockEntity): ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val stack = StackView(Axis.VERTICAL, StackView.Distribution.LEADING, spacing = 4.0)
view.addSubview(stack)
val name = device.cachedState.block.name.styled { it.withBold(true) }
val deviceNameLabel = Label(name).apply {
textColor = Color.TEXT
}
stack.addArrangedSubview(deviceNameLabel)
val ipLabel = Label(TranslatableText("gui.phycon.console.details.ip", device.ipAddress.toString())).apply {
textColor = Color.TEXT
}
stack.addArrangedSubview(ipLabel)
val macLabel = Label(TranslatableText("gui.phycon.console.details.mac", device.macAddress.toString())).apply {
textColor = Color.TEXT
}
stack.addArrangedSubview(macLabel)
view.solver.dsl {
stack.leftAnchor equalTo view.leftAnchor
stack.topAnchor equalTo view.topAnchor
}
}
}

View File

@ -0,0 +1,73 @@
package net.shadowfacts.phycon.screen.console
import net.minecraft.client.MinecraftClient
import net.minecraft.text.TranslatableText
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.button.EnumButton
import net.shadowfacts.cacao.view.textfield.TextField
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
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.RedstoneMode
/**
* @author shadowfacts
*/
class RedstoneControllerViewController(val device: RedstoneControllerBlockEntity): ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val modeLabel = Label(TranslatableText("gui.phycon.console.redstone.mode")).apply {
textColor = Color.TEXT
}
view.addSubview(modeLabel)
val managedDevicesLabel = Label(TranslatableText("gui.phycon.console.redstone.devices")).apply {
textColor = Color.TEXT
}
view.addSubview(managedDevicesLabel)
val controls = StackView(Axis.VERTICAL, StackView.Distribution.FILL, spacing = 4.0)
view.addSubview(controls)
val mode = EnumButton(device.redstoneMode, RedstoneMode::friendlyName)
mode.handler = {
device.redstoneMode = it.value
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
controls.addArrangedSubview(mode)
val textFields = (0 until 5).map { i ->
TextField(device.managedDevices[i]?.toString() ?: "") {
device.managedDevices[i] = IPAddress.parse(it.text)
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
}
textFields.forEach(controls::addArrangedSubview)
view.solver.dsl {
controls.widthAnchor equalTo 100
controls.rightAnchor equalTo view.rightAnchor
controls.topAnchor equalTo view.topAnchor
mode.heightAnchor equalTo 20
textFields.forEach {
it.heightAnchor equalTo 20
}
modeLabel.centerYAnchor equalTo mode.centerYAnchor
modeLabel.rightAnchor equalTo (mode.leftAnchor - 4)
managedDevicesLabel.centerYAnchor equalTo textFields.first().centerYAnchor
managedDevicesLabel.rightAnchor equalTo (textFields.first().leftAnchor - 4)
}
}
}

View File

@ -11,6 +11,13 @@
"item.phycon.console": "Console",
"gui.phycon.terminal_buffer": "Buffer",
"gui.phycon.console.details": "Device Details",
"gui.phycon.console.details.ip": "IP Address: %s",
"gui.phycon.console.details.mac": "MAC Address: %s",
"gui.phycon.console.redstone.mode": "Redstone Mode",
"gui.phycon.console.redstone.devices": "Managed Devices",
"gui.phycon.console.remote": "Remote Management",
"gui.phycon.console.remote.mode": "Activation Mode",
"gui.phycon.redstone_mode.high": "High",
"gui.phycon.redstone_mode.low": "Low",
"gui.phycon.redstone_mode.toggle": "Toggle",