Compare commits

...

11 Commits
main ... future

186 changed files with 9357 additions and 8291 deletions

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# reformat
f6f4c12d0304c945c03ab70556048ee8d78e4019

View File

@ -1,7 +1,7 @@
plugins { plugins {
id "fabric-loom" version "0.12.9" id "fabric-loom" version "0.12.9"
id "maven-publish" id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.6.10" id "org.jetbrains.kotlin.jvm" version "1.7.10"
} }
archivesBaseName = project.archives_base_name archivesBaseName = project.archives_base_name
@ -79,9 +79,9 @@ repositories {
dependencies { dependencies {
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them. // You may need to force-disable transitiveness on them.
modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}" // modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}" // include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}" // include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}"
implementation project(":kiwi-java") implementation project(":kiwi-java")
include project(":kiwi-java") include project(":kiwi-java")

View File

@ -17,7 +17,7 @@ import java.lang.invoke.MethodHandles
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener { object PhyConPluginClient : ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener {
private val logger = LogManager.getLogger() private val logger = LogManager.getLogger()
private var isHighlightingHandle: MethodHandle? = null private var isHighlightingHandle: MethodHandle? = null
@ -39,7 +39,8 @@ object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTermin
AbstractTerminalScreen.searchQueryListener = this AbstractTerminalScreen.searchQueryListener = this
try { try {
val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField") val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField")
isHighlightingHandle = MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java) isHighlightingHandle =
MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java)
} catch (e: ReflectiveOperationException) { } catch (e: ReflectiveOperationException) {
logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e) logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e)
} }

View File

@ -18,23 +18,27 @@ import java.util.stream.IntStream
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhyConPluginCommon: REIServerPlugin, PhyConPlugin { object PhyConPluginCommon : REIServerPlugin, PhyConPlugin {
const val MODID = "phycon_rei" const val MODID = "phycon_rei"
lateinit var REI_SYNC_KEY: TerminalSettingKey<REISyncMode> lateinit var REI_SYNC_KEY: TerminalSettingKey<REISyncMode>
private set private set
override fun registerMenuInfo(registry: MenuInfoRegistry) { override fun registerMenuInfo(registry: MenuInfoRegistry) {
registry.register(CategoryIdentifier.of("minecraft", "plugins/crafting"), CraftingTerminalScreenHandler::class.java, SimpleMenuInfoProvider.of(::TerminalInfo)) registry.register(
CategoryIdentifier.of("minecraft", "plugins/crafting"),
CraftingTerminalScreenHandler::class.java,
SimpleMenuInfoProvider.of(::TerminalInfo)
)
} }
override fun initializePhyCon(api: PhyConAPI) { override fun initializePhyCon(api: PhyConAPI) {
REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF) REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF)
} }
class TerminalInfo<D: SimpleGridMenuDisplay>( class TerminalInfo<D : SimpleGridMenuDisplay>(
private val display: D, private val display: D,
): SimpleGridMenuInfo<CraftingTerminalScreenHandler, D> { ) : SimpleGridMenuInfo<CraftingTerminalScreenHandler, D> {
override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int { override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int {
return menu.resultSlot.id return menu.resultSlot.id

View File

@ -8,7 +8,7 @@ import net.shadowfacts.phycon.api.TerminalSetting
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
enum class REISyncMode: TerminalSetting { enum class REISyncMode : TerminalSetting {
OFF, OFF,
ON, ON,
HIGHLIGHT_ONLY; HIGHLIGHT_ONLY;

View File

@ -14,15 +14,24 @@ import techreborn.init.TRContent
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhyConTR: ModInitializer { object PhyConTR : ModInitializer {
override fun onInitialize() { override fun onInitialize() {
TRContent.StorageUnit.values().forEach { TRContent.StorageUnit.values().forEach {
ItemAttributes.GROUPED_INV.setBlockAdder(AttributeSourceType.COMPAT_WRAPPER, it.block, ::addStorageUnitGroupedInv) ItemAttributes.GROUPED_INV.setBlockAdder(
AttributeSourceType.COMPAT_WRAPPER,
it.block,
::addStorageUnitGroupedInv
)
} }
} }
private fun addStorageUnitGroupedInv(world: World, pos: BlockPos, state: BlockState, to: AttributeList<GroupedItemInv>) { private fun addStorageUnitGroupedInv(
world: World,
pos: BlockPos,
state: BlockState,
to: AttributeList<GroupedItemInv>
) {
(world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su -> (world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su ->
to.offer(StorageUnitWrapper(su)) to.offer(StorageUnitWrapper(su))
} }

View File

@ -16,7 +16,7 @@ import kotlin.math.min
*/ */
class StorageUnitWrapper( class StorageUnitWrapper(
val be: StorageUnitBaseBlockEntity, val be: StorageUnitBaseBlockEntity,
): GroupedItemInv { ) : GroupedItemInv {
override fun getStoredStacks(): Set<ItemStack> { override fun getStoredStacks(): Set<ItemStack> {
val set = ItemStackCollections.set() val set = ItemStackCollections.set()

View File

@ -16,6 +16,7 @@ public interface Interface {
void send(@NotNull EthernetFrame frame); void send(@NotNull EthernetFrame frame);
default void cableDisconnected() {} default void cableDisconnected() {
}
} }

View File

@ -10,9 +10,9 @@ public interface NetworkDevice {
/** /**
* The IP address of this device. * The IP address of this device.
* * <p>
* If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one. * If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one.
* * <p>
* The address of a network device should never be the broadcast address. * The address of a network device should never be the broadcast address.
* *
* @return The IP address of this device. * @return The IP address of this device.

View File

@ -1,13 +0,0 @@
package net.shadowfacts.phycon.api;
import alexiil.mc.lib.attributes.Attribute;
import alexiil.mc.lib.attributes.Attributes;
/**
* @author shadowfacts
*/
public class PhyAttributes {
public static final Attribute<PacketSink> PACKET_SINK = Attributes.create(PacketSink.class);
}

View File

@ -13,6 +13,7 @@ public interface TerminalSetting {
int[] getUV(); int[] getUV();
@Nullable Text getTooltip(); @Nullable
Text getTooltip();
} }

View File

@ -23,12 +23,14 @@ public final class IPAddress {
} }
private static final Random ipAddressRandom = new Random(); private static final Random ipAddressRandom = new Random();
@NotNull @NotNull
public static IPAddress random() { public static IPAddress random() {
return random(ipAddressRandom); return random(ipAddressRandom);
} }
private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"); private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
@Nullable @Nullable
public static IPAddress parse(String s) { public static IPAddress parse(String s) {
Matcher matcher = IP_PATTERN.matcher(s); Matcher matcher = IP_PATTERN.matcher(s);

View File

@ -22,6 +22,7 @@ public final class MACAddress {
} }
private static final Random macAddressRandom = new Random(); private static final Random macAddressRandom = new Random();
@NotNull @NotNull
public static MACAddress random() { public static MACAddress random() {
return random(macAddressRandom); return random(macAddressRandom);
@ -61,7 +62,7 @@ public final class MACAddress {
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
MACAddress that = (MACAddress)o; MACAddress that = (MACAddress) o;
return address == that.address; return address == that.address;
} }

View File

@ -26,7 +26,7 @@ public class MixinHandledScreen {
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V") at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V")
) )
private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) { private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
if ((Object)this instanceof AbstractTerminalScreen<?, ?> self) { if ((Object) this instanceof AbstractTerminalScreen<?, ?> self) {
self.drawSlotUnderlay(matrixStack, slot); self.drawSlotUnderlay(matrixStack, slot);
} }
} }
@ -36,7 +36,7 @@ public class MixinHandledScreen {
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V") at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
) )
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) { private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
if ((Object)this instanceof AbstractTerminalScreen<?, ?> self) { if ((Object) this instanceof AbstractTerminalScreen<?, ?> self) {
AbstractTerminalScreenHandler<?> handler = self.getScreenHandler(); AbstractTerminalScreenHandler<?> handler = self.getScreenHandler();
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) { if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
self.drawNetworkSlotAmount(stack, x, y); self.drawNetworkSlotAmount(stack, x, y);

View File

@ -9,8 +9,8 @@ interface AbstractCacaoScreen {
val windows: List<Window> val windows: List<Window>
fun <T: Window> addWindow(window: T, index: Int): T fun <T : Window> addWindow(window: T, index: Int): T
fun <T: Window> addWindow(window: T): T fun <T : Window> addWindow(window: T): T
fun removeWindow(window: Window) fun removeWindow(window: Window)

View File

@ -20,11 +20,11 @@ import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
open class CacaoHandledScreen<Handler: ScreenHandler>( open class CacaoHandledScreen<Handler : ScreenHandler>(
handler: Handler, handler: Handler,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text, title: Text,
): HandledScreen<Handler>(handler, playerInv, title), AbstractCacaoScreen { ) : HandledScreen<Handler>(handler, playerInv, title), AbstractCacaoScreen {
private val _windows = LinkedList<Window>() private val _windows = LinkedList<Window>()
@ -33,7 +33,7 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
private var hasAppeared = false private var hasAppeared = false
override fun <T: Window> addWindow(window: T, index: Int): T { override fun <T : Window> addWindow(window: T, index: Int): T {
if (window is ScreenHandlerWindow && window.screenHandler != handler) { if (window is ScreenHandlerWindow && window.screenHandler != handler) {
throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported") throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported")
} }

View File

@ -20,10 +20,11 @@ import java.util.*
* *
* @author shadowfacts * @author shadowfacts
*/ */
open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), 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. // _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods.
private val _windows = LinkedList<Window>() private val _windows = LinkedList<Window>()
/** /**
* The list of windows that belong to this screen. * The list of windows that belong to this screen.
* *
@ -42,7 +43,7 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
* @param index The index to insert the window into the window list at. * @param index The index to insert the window into the window list at.
* @return The window that was added, as a convenience. * @return The window that was added, as a convenience.
*/ */
override fun <T: Window> addWindow(window: T, index: Int): T { override fun <T : Window> addWindow(window: T, index: Int): T {
if (hasAppeared) { if (hasAppeared) {
window.viewController.viewWillAppear() window.viewController.viewWillAppear()
} }

View File

@ -14,10 +14,10 @@ class LayoutVariable(
val view: View?, val view: View?,
val layoutGuide: LayoutGuide?, val layoutGuide: LayoutGuide?,
val property: String, val property: String,
): Variable("LayoutVariable") { ) : Variable("LayoutVariable") {
constructor(view: View, property: String): this(view, null, property) constructor(view: View, property: String) : this(view, null, property)
constructor(layoutGuide: LayoutGuide, property: String): this(null, layoutGuide, property) constructor(layoutGuide: LayoutGuide, property: String) : this(null, layoutGuide, property)
init { init {
if ((view == null) == (layoutGuide == null)) { if ((view == null) == (layoutGuide == null)) {

View File

@ -1,9 +1,12 @@
# Cacao # Cacao
Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API) Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API)
UI toolkit. UI toolkit.
## Architecture ## Architecture
### Screen ### Screen
A [CacaoScreen][] is the object that acts as the interface between Minecraft GUI code and the Cacao framework. A [CacaoScreen][] is the object that acts as the interface between Minecraft GUI code and the Cacao framework.
The CacaoScreen draws Cacao views on screen and passes Minecraft input events to the appropriate Views. The CacaoScreen The CacaoScreen draws Cacao views on screen and passes Minecraft input events to the appropriate Views. The CacaoScreen
@ -12,6 +15,7 @@ owns a group of [Window](#window) objects which are displayed on screen, one on
[CacaoScreen]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt [CacaoScreen]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt
### Window ### Window
A [Window][] object has a root [View Controller](#view-controller) that it displays on screen. A [Window][] object has a root [View Controller](#view-controller) that it displays on screen.
The Window occupies the entire screen space and translates events from the screen to the root View Controller's View. The Window occupies the entire screen space and translates events from the screen to the root View Controller's View.
@ -21,6 +25,7 @@ view hierarchy.
[Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt [Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt
### View Controller ### View Controller
A [ViewController][] object owns a view, receives lifecycle events for it, and is generally used to control the view. A [ViewController][] object owns a view, receives lifecycle events for it, and is generally used to control the view.
Each View Controller has a single root [View](#view) which in turn may have subviews. Each View Controller has a single root [View](#view) which in turn may have subviews.
@ -28,6 +33,7 @@ Each View Controller has a single root [View](#view) which in turn may have subv
[ViewController]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/viewcontroller/ViewController.kt [ViewController]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/viewcontroller/ViewController.kt
### View ### View
A [View][] object represents a single view on screen. It handles drawing, positioning, and directly handles input. A [View][] object represents a single view on screen. It handles drawing, positioning, and directly handles input.
[View]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/view/View.kt [View]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/view/View.kt

View File

@ -10,10 +10,12 @@ enum class AxisPosition {
* Top for vertical, left for horizontal. * Top for vertical, left for horizontal.
*/ */
LEADING, LEADING,
/** /**
* Center X/Y. * Center X/Y.
*/ */
CENTER, CENTER,
/** /**
* Bottom for vertical, right for horizontal. * Bottom for vertical, right for horizontal.
*/ */

View File

@ -7,7 +7,7 @@ package net.shadowfacts.cacao.geometry
*/ */
data class Point(val x: Double, val y: Double) { data class Point(val x: Double, val y: Double) {
constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble()) constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
companion object { companion object {
val ORIGIN = Point(0.0, 0.0) val ORIGIN = Point(0.0, 0.0)

View File

@ -7,7 +7,7 @@ package net.shadowfacts.cacao.geometry
*/ */
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) { data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
constructor(origin: Point, size: Size): this(origin.x, origin.y, size.width, size.height) constructor(origin: Point, size: Size) : this(origin.x, origin.y, size.width, size.height)
val right: Double by lazy { val right: Double by lazy {
left + width left + width

View File

@ -14,7 +14,7 @@ data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 2
/** /**
* Constructs a color from the packed RGB color. * Constructs a color from the packed RGB color.
*/ */
constructor(rgb: Int, alpha: Int = 255): this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha) constructor(rgb: Int, alpha: Int = 255) : this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha)
/** /**
* The ARGB packed representation of this color. * The ARGB packed representation of this color.

View File

@ -5,13 +5,13 @@ package net.shadowfacts.cacao.util
*/ */
object EnumHelper { object EnumHelper {
fun <E: Enum<E>> next(value: E): E { fun <E : Enum<E>> next(value: E): E {
val constants = value.declaringClass.enumConstants val constants = value.declaringClass.enumConstants
val index = constants.indexOf(value) + 1 val index = constants.indexOf(value) + 1
return if (index < constants.size) constants[index] else constants.first() return if (index < constants.size) constants[index] else constants.first()
} }
fun <E: Enum<E>> previous(value: E): E { fun <E : Enum<E>> previous(value: E): E {
val constants = value.declaringClass.enumConstants val constants = value.declaringClass.enumConstants
val index = constants.indexOf(value) - 1 val index = constants.indexOf(value) - 1
return if (index >= 0) constants[index] else constants.last() return if (index >= 0) constants[index] else constants.last()

View File

@ -28,6 +28,11 @@ class LayoutGuide(
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY") val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
val frame: Rect val frame: Rect
get() = Rect(leftAnchor.value - owningView.leftAnchor.value, topAnchor.value - owningView.topAnchor.value, widthAnchor.value, heightAnchor.value) get() = Rect(
leftAnchor.value - owningView.leftAnchor.value,
topAnchor.value - owningView.topAnchor.value,
widthAnchor.value,
heightAnchor.value
)
} }

View File

@ -23,7 +23,7 @@ import kotlin.math.roundToInt
* *
* @author shadowfacts * @author shadowfacts
*/ */
object RenderHelper: DrawableHelper() { object RenderHelper : DrawableHelper() {
val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean() val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean()
@ -48,13 +48,27 @@ object RenderHelper: DrawableHelper() {
if (disabled) return if (disabled) return
RenderSystem.setShader(GameRenderer::getPositionTexShader) RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.setShaderTexture(0, texture.location) RenderSystem.setShaderTexture(0, texture.location)
draw(matrixStack, rect.left, rect.top, texture.u, texture.v, rect.width, rect.height, texture.width, texture.height) draw(
matrixStack,
rect.left,
rect.top,
texture.u,
texture.v,
rect.width,
rect.height,
texture.width,
texture.height
)
} }
fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) { fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) {
if (disabled) return if (disabled) return
RenderSystem.lineWidth(width) RenderSystem.lineWidth(width)
RenderSystem.enableBlend()
RenderSystem.disableTexture()
RenderSystem.defaultBlendFunc()
RenderSystem.setShader(GameRenderer::getPositionColorShader)
val tessellator = Tessellator.getInstance() val tessellator = Tessellator.getInstance()
val buffer = tessellator.buffer val buffer = tessellator.buffer
buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR) buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR)
@ -66,18 +80,50 @@ object RenderHelper: DrawableHelper() {
/** /**
* Draws the bound texture with the given screen and texture position and size. * Draws the bound texture with the given screen and texture position and size.
*/ */
fun draw(matrixStack: MatrixStack, x: Double, y: Double, u: Int, v: Int, width: Double, height: Double, textureWidth: Int, textureHeight: Int) { fun draw(
matrixStack: MatrixStack,
x: Double,
y: Double,
u: Int,
v: Int,
width: Double,
height: Double,
textureWidth: Int,
textureHeight: Int
) {
if (disabled) return if (disabled) return
val uStart = u.toFloat() / textureWidth val uStart = u.toFloat() / textureWidth
val uEnd = (u + width).toFloat() / textureWidth val uEnd = (u + width).toFloat() / textureWidth
val vStart = v.toFloat() / textureHeight val vStart = v.toFloat() / textureHeight
val vEnd = (v + height).toFloat() / textureHeight val vEnd = (v + height).toFloat() / textureHeight
drawTexturedQuad(matrixStack.peek().positionMatrix, x, x + width, y, y + height, 0.0, uStart, uEnd, vStart, vEnd) drawTexturedQuad(
matrixStack.peek().positionMatrix,
x,
x + width,
y,
y + height,
0.0,
uStart,
uEnd,
vStart,
vEnd
)
} }
// Copied from net.minecraft.client.gui.DrawableHelper // Copied from net.minecraft.client.gui.DrawableHelper
// TODO: use an access transformer to just call minecraft's impl // TODO: use an access transformer to just call minecraft's impl
private fun drawTexturedQuad(matrix: Matrix4f, x0: Double, x1: Double, y0: Double, y1: Double, z: Double, u0: Float, u1: Float, v0: Float, v1: Float) { private fun drawTexturedQuad(
matrix: Matrix4f,
x0: Double,
x1: Double,
y0: Double,
y1: Double,
z: Double,
u0: Float,
u1: Float,
v0: Float,
v1: Float
) {
val bufferBuilder = Tessellator.getInstance().buffer val bufferBuilder = Tessellator.getInstance().buffer
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE) bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE)
bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next() bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next()
@ -96,7 +142,7 @@ object RenderHelper: DrawableHelper() {
drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse) drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse)
} }
private val dummyScreen = object: Screen(LiteralText("")) { private val dummyScreen = object : Screen(LiteralText("")) {
init { init {
textRenderer = MinecraftClient.getInstance().textRenderer textRenderer = MinecraftClient.getInstance().textRenderer
itemRenderer = MinecraftClient.getInstance().itemRenderer itemRenderer = MinecraftClient.getInstance().itemRenderer

View File

@ -5,7 +5,7 @@ import kotlin.reflect.KProperty
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ObservableLateInitProperty<T: Any>(val observer: (T) -> Unit) { class ObservableLateInitProperty<T : Any>(val observer: (T) -> Unit) {
lateinit var storage: T lateinit var storage: T

View File

@ -15,7 +15,13 @@ import net.minecraft.util.Identifier
* @param centerWidth The width of the center patch. * @param centerWidth The width of the center patch.
* @param centerHeight The height of the center patch. * @param centerHeight The height of the center patch.
*/ */
data class NinePatchTexture(val texture: Texture, val cornerWidth: Int, val cornerHeight: Int, val centerWidth: Int, val centerHeight: Int) { data class NinePatchTexture(
val texture: Texture,
val cornerWidth: Int,
val cornerHeight: Int,
val centerWidth: Int,
val centerHeight: Int
) {
companion object { companion object {
val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156) val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156)

View File

@ -9,7 +9,7 @@ import net.shadowfacts.cacao.util.RenderHelper
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class BezierCurveView(val curve: BezierCurve): View() { class BezierCurveView(val curve: BezierCurve) : View() {
private val points by lazy { private val points by lazy {
val step = 0.05 val step = 0.05

View File

@ -19,13 +19,13 @@ class DialogView(
val buttonTypes: Array<ButtonType>, val buttonTypes: Array<ButtonType>,
val iconTexture: Texture?, val iconTexture: Texture?,
val buttonCallback: (ButtonType, Window) -> Unit val buttonCallback: (ButtonType, Window) -> Unit
): View() { ) : View() {
interface ButtonType { interface ButtonType {
val localizedName: Text val localizedName: Text
} }
enum class DefaultButtonType: ButtonType { enum class DefaultButtonType : ButtonType {
CANCEL, CONFIRM, OK, CLOSE; CANCEL, CONFIRM, OK, CLOSE;
override val localizedName: Text override val localizedName: Text

View File

@ -31,7 +31,7 @@ class Label(
val maxLines: Int = 0, val maxLines: Int = 0,
val wrappingMode: WrappingMode = WrappingMode.WRAP, val wrappingMode: WrappingMode = WrappingMode.WRAP,
var textAlignment: TextAlignment = TextAlignment.LEFT var textAlignment: TextAlignment = TextAlignment.LEFT
): View() { ) : View() {
companion object { companion object {
private val textRenderer: TextRenderer private val textRenderer: TextRenderer
@ -52,7 +52,7 @@ class Label(
maxLines: Int = 0, maxLines: Int = 0,
wrappingMode: WrappingMode = WrappingMode.WRAP, wrappingMode: WrappingMode = WrappingMode.WRAP,
textAlignment: TextAlignment = TextAlignment.LEFT, textAlignment: TextAlignment = TextAlignment.LEFT,
): this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment) ) : this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment)
/** /**
* The text of this label. Mutating this field will update the intrinsic content size and trigger a layout. * The text of this label. Mutating this field will update the intrinsic content size and trigger a layout.

View File

@ -17,7 +17,7 @@ import kotlin.math.roundToInt
* @author shadowfacts * @author shadowfacts
* @param ninePatch The nine patch texture that this view will draw. * @param ninePatch The nine patch texture that this view will draw.
*/ */
open class NinePatchView(val ninePatch: NinePatchTexture): View() { open class NinePatchView(val ninePatch: NinePatchTexture) : View() {
// Corners // Corners
private val topLeftDelegate = ResettableLazyProperty { private val topLeftDelegate = ResettableLazyProperty {
@ -26,24 +26,44 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
protected open val topLeft by topLeftDelegate protected open val topLeft by topLeftDelegate
private val topRightDelegate = ResettableLazyProperty { private val topRightDelegate = ResettableLazyProperty {
Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(
bounds.width - ninePatch.cornerWidth,
0.0,
ninePatch.cornerWidth.toDouble(),
ninePatch.cornerHeight.toDouble()
)
} }
protected open val topRight by topRightDelegate protected open val topRight by topRightDelegate
private val bottomLeftDelegate = ResettableLazyProperty { private val bottomLeftDelegate = ResettableLazyProperty {
Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(
0.0,
bounds.height - ninePatch.cornerHeight,
ninePatch.cornerWidth.toDouble(),
ninePatch.cornerHeight.toDouble()
)
} }
protected open val bottomLeft by bottomLeftDelegate protected open val bottomLeft by bottomLeftDelegate
private val bottomRightDelegate = ResettableLazyProperty { private val bottomRightDelegate = ResettableLazyProperty {
Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) Rect(
bounds.width - ninePatch.cornerWidth,
bounds.height - ninePatch.cornerHeight,
ninePatch.cornerWidth.toDouble(),
ninePatch.cornerHeight.toDouble()
)
} }
protected open val bottomRight by bottomRightDelegate protected open val bottomRight by bottomRightDelegate
// Edges // Edges
private val topMiddleDelegate = ResettableLazyProperty { private val topMiddleDelegate = ResettableLazyProperty {
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble()) Rect(
ninePatch.cornerWidth.toDouble(),
topLeft.top,
bounds.width - 2 * ninePatch.cornerWidth,
ninePatch.cornerHeight.toDouble()
)
} }
protected open val topMiddle by topMiddleDelegate protected open val topMiddle by topMiddleDelegate
@ -53,7 +73,12 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
protected open val bottomMiddle by bottomMiddleDelegate protected open val bottomMiddle by bottomMiddleDelegate
private val leftMiddleDelegate = ResettableLazyProperty { private val leftMiddleDelegate = ResettableLazyProperty {
Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight) Rect(
topLeft.left,
ninePatch.cornerHeight.toDouble(),
ninePatch.cornerWidth.toDouble(),
bounds.height - 2 * ninePatch.cornerHeight
)
} }
protected open val leftMiddle by leftMiddleDelegate protected open val leftMiddle by leftMiddleDelegate
@ -69,7 +94,17 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
} }
protected open val center by centerDelegate protected open val center by centerDelegate
private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate) private val delegates = listOf(
topLeftDelegate,
topRightDelegate,
bottomLeftDelegate,
bottomRightDelegate,
topMiddleDelegate,
bottomMiddleDelegate,
leftMiddleDelegate,
rightMiddleDelegate,
centerDelegate
)
override fun didLayout() { override fun didLayout() {
super.didLayout() super.didLayout()
@ -93,30 +128,114 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
private fun drawEdges(matrixStack: MatrixStack) { private fun drawEdges(matrixStack: MatrixStack) {
// Horizontal // Horizontal
for (i in 0 until (topMiddle.width.roundToInt() / ninePatch.centerWidth)) { for (i in 0 until (topMiddle.width.roundToInt() / ninePatch.centerWidth)) {
RenderHelper.draw(matrixStack, topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), topMiddle.height, ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
RenderHelper.draw(matrixStack, bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), bottomMiddle.height, ninePatch.texture.width, ninePatch.texture.height) matrixStack,
topMiddle.left + i * ninePatch.centerWidth,
topMiddle.top,
ninePatch.topMiddle.u,
ninePatch.topMiddle.v,
ninePatch.centerWidth.toDouble(),
topMiddle.height,
ninePatch.texture.width,
ninePatch.texture.height
)
RenderHelper.draw(
matrixStack,
bottomMiddle.left + i * ninePatch.centerWidth,
bottomMiddle.top,
ninePatch.bottomMiddle.u,
ninePatch.bottomMiddle.v,
ninePatch.centerWidth.toDouble(),
bottomMiddle.height,
ninePatch.texture.width,
ninePatch.texture.height
)
} }
val remWidth = topMiddle.width.roundToInt() % ninePatch.centerWidth val remWidth = topMiddle.width.roundToInt() % ninePatch.centerWidth
if (remWidth > 0) { if (remWidth > 0) {
RenderHelper.draw(matrixStack, topMiddle.right - remWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
RenderHelper.draw(matrixStack, bottomMiddle.right - remWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) matrixStack,
topMiddle.right - remWidth,
topMiddle.top,
ninePatch.topMiddle.u,
ninePatch.topMiddle.v,
remWidth.toDouble(),
ninePatch.cornerHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
RenderHelper.draw(
matrixStack,
bottomMiddle.right - remWidth,
bottomMiddle.top,
ninePatch.bottomMiddle.u,
ninePatch.bottomMiddle.v,
remWidth.toDouble(),
ninePatch.cornerHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
} }
// Vertical // Vertical
for (i in 0 until (leftMiddle.height.roundToInt() / ninePatch.centerHeight)) { for (i in 0 until (leftMiddle.height.roundToInt() / ninePatch.centerHeight)) {
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.top + i * ninePatch.centerHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.top + i * ninePatch.centerHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) matrixStack,
leftMiddle.left,
leftMiddle.top + i * ninePatch.centerHeight,
ninePatch.leftMiddle.u,
ninePatch.leftMiddle.v,
ninePatch.cornerWidth.toDouble(),
ninePatch.centerHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
RenderHelper.draw(
matrixStack,
rightMiddle.left,
rightMiddle.top + i * ninePatch.centerHeight,
ninePatch.rightMiddle.u,
ninePatch.rightMiddle.v,
ninePatch.cornerWidth.toDouble(),
ninePatch.centerHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
} }
val remHeight = leftMiddle.height.roundToInt() % ninePatch.centerHeight val remHeight = leftMiddle.height.roundToInt() % ninePatch.centerHeight
if (remHeight > 0) { if (remHeight > 0) {
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.bottom - remHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.bottom - remHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) matrixStack,
leftMiddle.left,
leftMiddle.bottom - remHeight,
ninePatch.leftMiddle.u,
ninePatch.leftMiddle.v,
ninePatch.cornerWidth.toDouble(),
remHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
RenderHelper.draw(
matrixStack,
rightMiddle.left,
rightMiddle.bottom - remHeight,
ninePatch.rightMiddle.u,
ninePatch.rightMiddle.v,
ninePatch.cornerWidth.toDouble(),
remHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
} }
} }
private fun drawCenter(matrixStack: MatrixStack) { private fun drawCenter(matrixStack: MatrixStack) {
for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) { for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) {
drawCenterRow(matrixStack, center.top + i * ninePatch.centerHeight.toDouble(), ninePatch.centerHeight.toDouble()) drawCenterRow(
matrixStack,
center.top + i * ninePatch.centerHeight.toDouble(),
ninePatch.centerHeight.toDouble()
)
} }
val remHeight = center.height.roundToInt() % ninePatch.centerHeight val remHeight = center.height.roundToInt() % ninePatch.centerHeight
if (remHeight > 0) { if (remHeight > 0) {
@ -126,11 +245,31 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) { private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) {
for (i in 0 until (center.width.roundToInt() / ninePatch.centerWidth)) { for (i in 0 until (center.width.roundToInt() / ninePatch.centerWidth)) {
RenderHelper.draw(matrixStack, center.left + i * ninePatch.centerWidth, y, ninePatch.center.u, ninePatch.center.v, ninePatch.centerWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
matrixStack,
center.left + i * ninePatch.centerWidth,
y,
ninePatch.center.u,
ninePatch.center.v,
ninePatch.centerWidth.toDouble(),
height,
ninePatch.texture.width,
ninePatch.texture.height
)
} }
val remWidth = center.width.roundToInt() % ninePatch.centerWidth val remWidth = center.width.roundToInt() % ninePatch.centerWidth
if (remWidth > 0) { if (remWidth > 0) {
RenderHelper.draw(matrixStack, center.right - remWidth, y, ninePatch.center.u, ninePatch.center.v, remWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height) RenderHelper.draw(
matrixStack,
center.right - remWidth,
y,
ninePatch.center.u,
ninePatch.center.v,
remWidth.toDouble(),
height,
ninePatch.texture.width,
ninePatch.texture.height
)
} }
} }

View File

@ -25,10 +25,11 @@ open class StackView(
val axis: Axis, val axis: Axis,
val distribution: Distribution = Distribution.FILL, val distribution: Distribution = Distribution.FILL,
val spacing: Double = 0.0 val spacing: Double = 0.0
): View() { ) : View() {
// the internal, mutable list of arranged subviews // the internal, mutable list of arranged subviews
private val _arrangedSubviews = LinkedList<View>() private val _arrangedSubviews = LinkedList<View>()
/** /**
* The list of arranged subviews belonging to this stack view. * The list of arranged subviews belonging to this stack view.
* This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview] * This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview]
@ -50,7 +51,7 @@ open class StackView(
* By default, adds the view to the end of the stack. * By default, adds the view to the end of the stack.
* @return The view that was added, as a convenience. * @return The view that was added, as a convenience.
*/ */
fun <T: View> addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T { fun <T : View> addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T {
addSubview(view) addSubview(view)
_arrangedSubviews.add(index, view) _arrangedSubviews.add(index, view)
@ -139,10 +140,16 @@ open class StackView(
val previous = arrangedSubviews.getOrNull(index - 1) val previous = arrangedSubviews.getOrNull(index - 1)
val next = arrangedSubviews.getOrNull(index + 1) val next = arrangedSubviews.getOrNull(index + 1)
if (next != null) { if (next != null) {
arrangedSubviewConnections.add(index, anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing)) arrangedSubviewConnections.add(
index,
anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing)
)
} }
if (previous != null) { if (previous != null) {
arrangedSubviewConnections.add(index - 1, anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing)) arrangedSubviewConnections.add(
index - 1,
anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing)
)
} }
} }
} }
@ -150,12 +157,15 @@ open class StackView(
when (distribution) { when (distribution) {
Distribution.LEADING -> Distribution.LEADING ->
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view) perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
Distribution.TRAILING -> Distribution.TRAILING ->
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view) perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
Distribution.FILL -> { Distribution.FILL -> {
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view) perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view) perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
} }
Distribution.CENTER -> Distribution.CENTER ->
perpAnchor(CENTER) equalTo perpAnchor(CENTER, view) perpAnchor(CENTER) equalTo perpAnchor(CENTER, view)
} }
@ -165,6 +175,7 @@ open class StackView(
private fun anchor(position: AxisPosition, view: View = this): LayoutVariable { private fun anchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis, position) return view.getAnchor(axis, position)
} }
private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable { private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis.perpendicular, position) return view.getAnchor(axis.perpendicular, position)
} }
@ -199,6 +210,7 @@ open class StackView(
* ``` * ```
*/ */
LEADING, LEADING,
/** /**
* The centers of the arranged subviews are pinned to the center of the stack view. * The centers of the arranged subviews are pinned to the center of the stack view.
* ``` * ```
@ -222,6 +234,7 @@ open class StackView(
* ``` * ```
*/ */
CENTER, CENTER,
/** /**
* The trailing edges of arranged subviews are pinned to the leading edge of the stack view. * The trailing edges of arranged subviews are pinned to the leading edge of the stack view.
* ``` * ```
@ -245,6 +258,7 @@ open class StackView(
* ``` * ```
*/ */
TRAILING, TRAILING,
/** /**
* The arranged subviews fill the perpendicular axis of the stack view. * The arranged subviews fill the perpendicular axis of the stack view.
* ``` * ```

View File

@ -11,7 +11,7 @@ import net.shadowfacts.cacao.util.texture.Texture
* *
* @author shadowfacts * @author shadowfacts
*/ */
class TextureView(var texture: Texture?): View() { class TextureView(var texture: Texture?) : View() {
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
texture?.also { texture?.also {

View File

@ -20,7 +20,7 @@ import kotlin.math.floor
* *
* @author shadowfacts * @author shadowfacts
*/ */
open class View(): Responder { open class View() : Responder {
/** /**
* The window whose view hierarchy this view belongs to. * The window whose view hierarchy this view belongs to.
@ -42,6 +42,7 @@ open class View(): Responder {
v.solver = it v.solver = it
} }
} }
/** /**
* The constraint solver used by the [Window] this view belongs to. * The constraint solver used by the [Window] this view belongs to.
* Not initialized until [wasAdded] called, using it before that will throw a runtime exception. * Not initialized until [wasAdded] called, using it before that will throw a runtime exception.
@ -55,30 +56,37 @@ open class View(): Responder {
* Layout anchor for the left edge of this view in the window's coordinate system. * Layout anchor for the left edge of this view in the window's coordinate system.
*/ */
val leftAnchor = LayoutVariable(this, "left") val leftAnchor = LayoutVariable(this, "left")
/** /**
* Layout anchor for the right edge of this view in the window's coordinate system. * Layout anchor for the right edge of this view in the window's coordinate system.
*/ */
val rightAnchor = LayoutVariable(this, "right") val rightAnchor = LayoutVariable(this, "right")
/** /**
* Layout anchor for the top edge of this view in the window's coordinate system. * Layout anchor for the top edge of this view in the window's coordinate system.
*/ */
val topAnchor = LayoutVariable(this, "top") val topAnchor = LayoutVariable(this, "top")
/** /**
* Layout anchor for the bottom edge of this view in the window's coordinate system. * Layout anchor for the bottom edge of this view in the window's coordinate system.
*/ */
val bottomAnchor = LayoutVariable(this, "bottom") val bottomAnchor = LayoutVariable(this, "bottom")
/** /**
* Layout anchor for the width of this view in the window's coordinate system. * Layout anchor for the width of this view in the window's coordinate system.
*/ */
val widthAnchor = LayoutVariable(this, "width") val widthAnchor = LayoutVariable(this, "width")
/** /**
* Layout anchor for the height of this view in the window's coordinate system. * Layout anchor for the height of this view in the window's coordinate system.
*/ */
val heightAnchor = LayoutVariable(this, "height") val heightAnchor = LayoutVariable(this, "height")
/** /**
* Layout anchor for the center X position of this view in the window's coordinate system. * Layout anchor for the center X position of this view in the window's coordinate system.
*/ */
val centerXAnchor = LayoutVariable(this, "centerX") val centerXAnchor = LayoutVariable(this, "centerX")
/** /**
* Layout anchor for the center Y position of this view in the window's coordinate system. * Layout anchor for the center Y position of this view in the window's coordinate system.
*/ */
@ -155,6 +163,7 @@ open class View(): Responder {
* This view's parent view. If `null`, this view is a top-level view in the [Window]. * This view's parent view. If `null`, this view is a top-level view in the [Window].
*/ */
var superview: View? = null var superview: View? = null
// _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods // _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods
private val _subviews = LinkedList<View>() private val _subviews = LinkedList<View>()
private var subviewsSortedByZIndex: List<View> = listOf() private var subviewsSortedByZIndex: List<View> = listOf()
@ -165,7 +174,7 @@ open class View(): Responder {
*/ */
val subviews: List<View> = _subviews val subviews: List<View> = _subviews
constructor(frame: Rect): this() { constructor(frame: Rect) : this() {
this.usesConstraintBasedLayout = false this.usesConstraintBasedLayout = false
this.frame = frame this.frame = frame
} }
@ -181,6 +190,7 @@ open class View(): Responder {
AxisPosition.CENTER -> centerXAnchor AxisPosition.CENTER -> centerXAnchor
AxisPosition.TRAILING -> rightAnchor AxisPosition.TRAILING -> rightAnchor
} }
Axis.VERTICAL -> Axis.VERTICAL ->
when (position) { when (position) {
AxisPosition.LEADING -> topAnchor AxisPosition.LEADING -> topAnchor
@ -196,7 +206,7 @@ open class View(): Responder {
* @param view The view to add. * @param view The view to add.
* @return The view that was added, as a convenience. * @return The view that was added, as a convenience.
*/ */
fun <T: View> addSubview(view: T): T { fun <T : View> addSubview(view: T): T {
_subviews.add(view) _subviews.add(view)
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex) subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
@ -240,7 +250,11 @@ open class View(): Responder {
for (b in a + 1 until variables.size) { for (b in a + 1 until variables.size) {
// if the variable views have no common ancestor after the removed view's superview is unset, // if the variable views have no common ancestor after the removed view's superview is unset,
// the constraint crossed the this<->view boundary and should be removed // the constraint crossed the this<->view boundary and should be removed
val ancestor = LowestCommonAncestor.find(variables[a].viewOrLayoutGuideView, variables[b].viewOrLayoutGuideView, View::superview) val ancestor = LowestCommonAncestor.find(
variables[a].viewOrLayoutGuideView,
variables[b].viewOrLayoutGuideView,
View::superview
)
if (ancestor == null) { if (ancestor == null) {
return@filter true return@filter true
} }
@ -350,7 +364,12 @@ open class View(): Responder {
if (usesConstraintBasedLayout) { if (usesConstraintBasedLayout) {
val superviewLeft = superview?.leftAnchor?.value ?: 0.0 val superviewLeft = superview?.leftAnchor?.value ?: 0.0
val superviewTop = superview?.topAnchor?.value ?: 0.0 val superviewTop = superview?.topAnchor?.value ?: 0.0
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value) frame = Rect(
leftAnchor.value - superviewLeft,
topAnchor.value - superviewTop,
widthAnchor.value,
heightAnchor.value
)
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value) bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
} }

View File

@ -22,7 +22,7 @@ import kotlin.math.floor
* Will be added as a subview of the button and laid out using constraints. * Will be added as a subview of the button and laid out using constraints.
* @param padding The padding between the [content] and the edges of the button. * @param padding The padding between the [content] and the edges of the button.
*/ */
abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val padding: Double = 4.0): View() { abstract class AbstractButton<Impl : AbstractButton<Impl>>(val content: View, val padding: Double = 4.0) : View() {
/** /**
* The function that handles when this button is clicked. * The function that handles when this button is clicked.
@ -51,6 +51,7 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
field = value field = value
value?.also(::addBackground) value?.also(::addBackground)
} }
/** /**
* The background to draw when the button is hovered over by the mouse. * The background to draw when the button is hovered over by the mouse.
* If `null`, the normal [background] will be used. * If `null`, the normal [background] will be used.
@ -62,6 +63,7 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
field = value field = value
value?.also(::addBackground) value?.also(::addBackground)
} }
/** /**
* The background to draw when the button is [disabled]. * The background to draw when the button is [disabled].
* If `null`, the normal [background] will be used. * If `null`, the normal [background] will be used.

View File

@ -14,7 +14,7 @@ class Button(
content: View, content: View,
padding: Double = 4.0, padding: Double = 4.0,
handler: ((Button) -> Unit)? = null handler: ((Button) -> Unit)? = null
): AbstractButton<Button>(content, padding) { ) : AbstractButton<Button>(content, padding) {
init { init {
this.handler = handler this.handler = handler
} }

View File

@ -33,13 +33,13 @@ import net.shadowfacts.kiwidsl.dsl
* Positioning of content views is handled by the dropdown. * Positioning of content views is handled by the dropdown.
* @param updateView A function for updating the view used as the button's 'label' that's visible even when the dropdown isn't. * @param updateView A function for updating the view used as the button's 'label' that's visible even when the dropdown isn't.
*/ */
class DropdownButton<Value, ContentView: View>( class DropdownButton<Value, ContentView : View>(
val initialValue: Value, val initialValue: Value,
val allValues: Iterable<Value>, val allValues: Iterable<Value>,
val createView: (Value) -> ContentView, val createView: (Value) -> ContentView,
val updateView: (newValue: Value, view: ContentView) -> Unit, val updateView: (newValue: Value, view: ContentView) -> Unit,
padding: Double = 4.0 padding: Double = 4.0
): AbstractButton<DropdownButton<Value, ContentView>>( ) : AbstractButton<DropdownButton<Value, ContentView>>(
StackView(Axis.HORIZONTAL), StackView(Axis.HORIZONTAL),
padding padding
) { ) {
@ -144,7 +144,7 @@ private class DropdownItemBackgroundView(
private val first: Boolean, private val first: Boolean,
private val last: Boolean, private val last: Boolean,
ninePatch: NinePatchTexture ninePatch: NinePatchTexture
): NinePatchView(ninePatch) { ) : NinePatchView(ninePatch) {
// Corners // Corners
private val topLeftDelegate = ResettableLazyProperty { private val topLeftDelegate = ResettableLazyProperty {
@ -159,7 +159,12 @@ private class DropdownItemBackgroundView(
override val topRight by topRightDelegate override val topRight by topRightDelegate
private val bottomLeftDelegate = ResettableLazyProperty { private val bottomLeftDelegate = ResettableLazyProperty {
Rect(topLeft.left, bounds.height - ninePatch.cornerHeight, topLeft.width, if (last) ninePatch.cornerHeight.toDouble() else 0.0) Rect(
topLeft.left,
bounds.height - ninePatch.cornerHeight,
topLeft.width,
if (last) ninePatch.cornerHeight.toDouble() else 0.0
)
} }
override val bottomLeft by bottomLeftDelegate override val bottomLeft by bottomLeftDelegate
@ -180,7 +185,12 @@ private class DropdownItemBackgroundView(
override val bottomMiddle by bottomMiddleDelegate override val bottomMiddle by bottomMiddleDelegate
private val leftMiddleDelegate = ResettableLazyProperty { private val leftMiddleDelegate = ResettableLazyProperty {
Rect(topLeft.left, topLeft.bottom, topLeft.width, bounds.height - (if (first && last) 2 else if (first || last) 1 else 0) * ninePatch.cornerHeight) Rect(
topLeft.left,
topLeft.bottom,
topLeft.width,
bounds.height - (if (first && last) 2 else if (first || last) 1 else 0) * ninePatch.cornerHeight
)
} }
override val leftMiddle by leftMiddleDelegate override val leftMiddle by leftMiddleDelegate
@ -195,7 +205,17 @@ private class DropdownItemBackgroundView(
} }
override val center by centerDelegate override val center by centerDelegate
private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate) private val delegates = listOf(
topLeftDelegate,
topRightDelegate,
bottomLeftDelegate,
bottomRightDelegate,
topMiddleDelegate,
bottomMiddleDelegate,
leftMiddleDelegate,
rightMiddleDelegate,
centerDelegate
)
override fun didLayout() { override fun didLayout() {
super.didLayout() super.didLayout()

View File

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

View File

@ -19,7 +19,7 @@ import net.shadowfacts.cacao.view.View
class ToggleButton( class ToggleButton(
initialState: Boolean, initialState: Boolean,
handler: ((ToggleButton) -> Unit)? = null, handler: ((ToggleButton) -> Unit)? = null,
): AbstractButton<ToggleButton>(TextureView(if (initialState) ON else OFF), padding = 0.0) { ) : AbstractButton<ToggleButton>(TextureView(if (initialState) ON else OFF), padding = 0.0) {
companion object { companion object {
val OFF = Texture(Identifier("textures/gui/checkbox.png"), 0, 0, 64, 64) val OFF = Texture(Identifier("textures/gui/checkbox.png"), 0, 0, 64, 64)

View File

@ -20,9 +20,9 @@ import org.lwjgl.glfw.GLFW
* the exact type of text field. * the exact type of text field.
* @param initialText The initial value of the text field. * @param initialText The initial value of the text field.
*/ */
abstract class AbstractTextField<Impl: AbstractTextField<Impl>>( abstract class AbstractTextField<Impl : AbstractTextField<Impl>>(
initialText: String initialText: String
): View() { ) : View() {
/** /**
* A function that is invoked when the text in this text field changes. * A function that is invoked when the text in this text field changes.
@ -166,7 +166,8 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
} }
// todo: label for the TextFieldWidget? // todo: label for the TextFieldWidget?
private class ProxyWidget: TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) { private class ProxyWidget :
TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) {
// AbstractButtonWidget.height is protected // AbstractButtonWidget.height is protected
fun setHeight(height: Int) { fun setHeight(height: Int) {
this.height = height this.height = height

View File

@ -6,7 +6,7 @@ package net.shadowfacts.cacao.view.textfield
open class NumberField( open class NumberField(
initialValue: Int, initialValue: Int,
handler: ((NumberField) -> Unit)? = null, handler: ((NumberField) -> Unit)? = null,
): AbstractTextField<NumberField>(initialValue.toString()) { ) : AbstractTextField<NumberField>(initialValue.toString()) {
var number: Int? var number: Int?
get() { get() {

View File

@ -10,7 +10,7 @@ package net.shadowfacts.cacao.view.textfield
open class TextField( open class TextField(
initialText: String, initialText: String,
handler: ((TextField) -> Unit)? = null handler: ((TextField) -> Unit)? = null
): AbstractTextField<TextField>(initialText) { ) : AbstractTextField<TextField>(initialText) {
init { init {
this.handler = handler this.handler = handler
} }

View File

@ -34,11 +34,11 @@ import java.lang.RuntimeException
* @param onTabChange A function invoked immediately after the active tab has changed (and the content view has been * @param onTabChange A function invoked immediately after the active tab has changed (and the content view has been
* updated). * updated).
*/ */
class TabViewController<T: TabViewController.Tab>( class TabViewController<T : TabViewController.Tab>(
val tabs: List<T>, val tabs: List<T>,
initialTab: T = tabs.first(), initialTab: T = tabs.first(),
val onTabChange: ((T) -> Unit)? = null val onTabChange: ((T) -> Unit)? = null
): ViewController() { ) : ViewController() {
/** /**
* The Tab interface defines the requirements for tab objects that can be used with this view controller. * The Tab interface defines the requirements for tab objects that can be used with this view controller.
@ -83,7 +83,7 @@ class TabViewController<T: TabViewController.Tab>(
override val tooltip: Text? = null, override val tooltip: Text? = null,
override val controller: ViewController, override val controller: ViewController,
private val visible: (() -> Boolean)? = null private val visible: (() -> Boolean)? = null
): Tab { ) : Tab {
override val isVisible: Boolean override val isVisible: Boolean
get() = visible?.invoke() ?: true get() = visible?.invoke() ?: true
} }
@ -99,6 +99,7 @@ class TabViewController<T: TabViewController.Tab>(
private lateinit var outerStack: StackView private lateinit var outerStack: StackView
private lateinit var tabStack: StackView private lateinit var tabStack: StackView
private lateinit var currentTabController: ViewController private lateinit var currentTabController: ViewController
// todo: this shouldn't be public, use layout guides // todo: this shouldn't be public, use layout guides
lateinit var tabVCContainer: View lateinit var tabVCContainer: View
private set private set
@ -227,9 +228,9 @@ class TabViewController<T: TabViewController.Tab>(
window!!.layout() window!!.layout()
} }
private class TabButton<T: Tab>( private class TabButton<T : Tab>(
val tab: T, val tab: T,
): AbstractButton<TabButton<T>>( ) : AbstractButton<TabButton<T>>(
tab.tabView, tab.tabView,
padding = 2.0 padding = 2.0
) { ) {

View File

@ -58,6 +58,7 @@ abstract class ViewController {
// _children is the internal, mutable object since we only want it to be mutated by the embed/removeChild methods // _children is the internal, mutable object since we only want it to be mutated by the embed/removeChild methods
private var _children = LinkedList<ViewController>() private var _children = LinkedList<ViewController>()
/** /**
* The list of all the child VCs of this view controller. * The list of all the child VCs of this view controller.
* This list should never be mutated directly, only by the [embedChild]/[removeChild] methods. * This list should never be mutated directly, only by the [embedChild]/[removeChild] methods.

View File

@ -9,6 +9,6 @@ import net.shadowfacts.cacao.viewcontroller.ViewController
class ScreenHandlerWindow( class ScreenHandlerWindow(
val screenHandler: ScreenHandler, val screenHandler: ScreenHandler,
viewController: ViewController viewController: ViewController
): Window(viewController) { ) : Window(viewController) {
} }

View File

@ -48,30 +48,37 @@ open class Window(
* Layout anchor for the left edge of this view in the window's coordinate system. * Layout anchor for the left edge of this view in the window's coordinate system.
*/ */
val leftAnchor = Variable("left") val leftAnchor = Variable("left")
/** /**
* Layout anchor for the right edge of this view in the window's coordinate system. * Layout anchor for the right edge of this view in the window's coordinate system.
*/ */
val rightAnchor = Variable("right") val rightAnchor = Variable("right")
/** /**
* Layout anchor for the top edge of this view in the window's coordinate system. * Layout anchor for the top edge of this view in the window's coordinate system.
*/ */
val topAnchor = Variable("top") val topAnchor = Variable("top")
/** /**
* Layout anchor for the bottom edge of this view in the window's coordinate system. * Layout anchor for the bottom edge of this view in the window's coordinate system.
*/ */
val bottomAnchor = Variable("bottom") val bottomAnchor = Variable("bottom")
/** /**
* Layout anchor for the width of this view in the window's coordinate system. * Layout anchor for the width of this view in the window's coordinate system.
*/ */
val widthAnchor = Variable("width") val widthAnchor = Variable("width")
/** /**
* Layout anchor for the height of this view in the window's coordinate system. * Layout anchor for the height of this view in the window's coordinate system.
*/ */
val heightAnchor = Variable("height") val heightAnchor = Variable("height")
/** /**
* Layout anchor for the center X position of this view in the window's coordinate system. * Layout anchor for the center X position of this view in the window's coordinate system.
*/ */
val centerXAnchor = Variable("centerX") val centerXAnchor = Variable("centerX")
/** /**
* Layout anchor for the center Y position of this view in the window's coordinate system. * Layout anchor for the center Y position of this view in the window's coordinate system.
*/ */

View File

@ -17,7 +17,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.equalTo(other: ExpressionConvertible, strength: Double): Constraint { fun ExpressionConvertible.equalTo(other: ExpressionConvertible, strength: Double): Constraint {
return Symbolics.equals(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint) return Symbolics.equals(this.toExpression(), other.toExpression()).setStrength(strength)
.apply(solver::addConstraint)
} }
infix fun ExpressionConvertible.equalTo(constant: Number): Constraint { infix fun ExpressionConvertible.equalTo(constant: Number): Constraint {
@ -25,7 +26,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.equalTo(constant: Number, strength: Double): Constraint { fun ExpressionConvertible.equalTo(constant: Number, strength: Double): Constraint {
return Symbolics.equals(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint) return Symbolics.equals(this.toExpression(), constant.toDouble()).setStrength(strength)
.apply(solver::addConstraint)
} }
infix fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible): Constraint { infix fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible): Constraint {
@ -33,7 +35,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint { fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint) return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength)
.apply(solver::addConstraint)
} }
infix fun ExpressionConvertible.lessThanOrEqualTo(constant: Number): Constraint { infix fun ExpressionConvertible.lessThanOrEqualTo(constant: Number): Constraint {
@ -41,7 +44,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.lessThanOrEqualTo(constant: Number, strength: Double): Constraint { fun ExpressionConvertible.lessThanOrEqualTo(constant: Number, strength: Double): Constraint {
return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint) return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength)
.apply(solver::addConstraint)
} }
infix fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible): Constraint { infix fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible): Constraint {
@ -49,7 +53,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint { fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint) return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength)
.apply(solver::addConstraint)
} }
infix fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number): Constraint { infix fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number): Constraint {
@ -57,7 +62,8 @@ class KiwiContext(val solver: Solver) {
} }
fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number, strength: Double): Constraint { fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number, strength: Double): Constraint {
return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint) return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength)
.apply(solver::addConstraint)
} }
// Addition // Addition

View File

@ -9,13 +9,14 @@ import net.shadowfacts.phycon.util.SortMode
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object DefaultPlugin: PhyConPlugin { object DefaultPlugin : PhyConPlugin {
lateinit var SORT_MODE: TerminalSettingKey<SortMode> lateinit var SORT_MODE: TerminalSettingKey<SortMode>
private set private set
override fun initializePhyCon(api: PhyConAPI) { override fun initializePhyCon(api: PhyConAPI) {
SORT_MODE = api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST) SORT_MODE =
api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST)
SORT_MODE.setPriority(Int.MAX_VALUE) SORT_MODE.setPriority(Int.MAX_VALUE)
} }

View File

@ -9,9 +9,12 @@ import net.shadowfacts.phycon.util.TerminalSettings
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhyConAPIImpl: PhyConAPI { object PhyConAPIImpl : PhyConAPI {
override fun <E> registerTerminalSetting(id: Identifier, defaultValue: E): TerminalSettingKey<E> where E: Enum<E>, E: TerminalSetting? { override fun <E> registerTerminalSetting(
id: Identifier,
defaultValue: E
): TerminalSettingKey<E> where E : Enum<E>, E : TerminalSetting? {
return TerminalSettings.register(id, defaultValue) return TerminalSettings.register(id, defaultValue)
} }

View File

@ -16,7 +16,7 @@ import org.apache.logging.log4j.LogManager
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhysicalConnectivity: ModInitializer { object PhysicalConnectivity : ModInitializer {
val MODID = "phycon" val MODID = "phycon"
@ -33,7 +33,10 @@ object PhysicalConnectivity: ModInitializer {
registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalRequestItem)
registerGlobalReceiver(C2STerminalUpdateDisplayedItems) registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
ItemStorage.SIDED.registerForBlockEntity(P2PReceiverBlockEntity::provideItemStorage, PhyBlockEntities.P2P_RECEIVER) ItemStorage.SIDED.registerForBlockEntity(
P2PReceiverBlockEntity::provideItemStorage,
PhyBlockEntities.P2P_RECEIVER
)
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) { for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
it.initializePhyCon(PhyConAPIImpl) it.initializePhyCon(PhyConAPIImpl)

View File

@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
import net.fabricmc.fabric.api.renderer.v1.RendererAccess import net.fabricmc.fabric.api.renderer.v1.RendererAccess
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
import net.shadowfacts.phycon.block.inserter.InserterScreen import net.shadowfacts.phycon.block.inserter.InserterScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreen
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
@ -20,7 +21,7 @@ import net.shadowfacts.phycon.util.TerminalSettings
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhysicalConnectivityClient: ClientModInitializer { object PhysicalConnectivityClient : ClientModInitializer {
val terminalSettings = TerminalSettings() val terminalSettings = TerminalSettings()
@ -44,6 +45,7 @@ object PhysicalConnectivityClient: ClientModInitializer {
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen) ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen) ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen) ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
ScreenRegistry.register(PhyScreens.SWITCH_CONSOLE, ::SwitchConsoleScreen)
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems) registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
} }

View File

@ -13,7 +13,7 @@ import net.minecraft.world.World
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class BlockWithEntity<T: BlockEntity>(settings: Settings): Block(settings), BlockEntityProvider { abstract class BlockWithEntity<T : BlockEntity>(settings: Settings) : Block(settings), BlockEntityProvider {
abstract override fun createBlockEntity(pos: BlockPos, state: BlockState): T? abstract override fun createBlockEntity(pos: BlockPos, state: BlockState): T?
fun getBlockEntity(world: BlockView, pos: BlockPos): T? { fun getBlockEntity(world: BlockView, pos: BlockPos): T? {

View File

@ -15,11 +15,21 @@ import net.shadowfacts.phycon.api.NetworkComponentBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithEntity<T>(settings), NetworkComponentBlock { abstract class DeviceBlock<T : DeviceBlockEntity>(settings: Settings) : BlockWithEntity<T>(settings),
NetworkComponentBlock {
abstract override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> abstract override fun getNetworkConnectedSides(
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Collection<Direction>
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { override fun getNetworkInterfaceForSide(
side: Direction,
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Interface? {
return getBlockEntity(world, pos)!! return getBlockEntity(world, pos)!!
} }
@ -28,7 +38,11 @@ abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithE
getBlockEntity(world, pos)!!.onBreak() getBlockEntity(world, pos)!!.onBreak()
} }
override fun <T: BlockEntity> getTicker(world: World, state: BlockState, type: BlockEntityType<T>): BlockEntityTicker<T>? { override fun <T : BlockEntity> getTicker(
world: World,
state: BlockState,
type: BlockEntityType<T>
): BlockEntityTicker<T>? {
return if (world.isClient) { return if (world.isClient) {
null null
} else { } else {

View File

@ -28,7 +28,8 @@ import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): BlockEntity(type, pos, state), abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState) :
BlockEntity(type, pos, state),
PacketSink, PacketSink,
PacketSource, PacketSource,
Interface { Interface {
@ -60,6 +61,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
is DeviceRemovedPacket -> { is DeviceRemovedPacket -> {
arpTable.remove(packet.source) arpTable.remove(packet.source)
} }
is PingPacket -> { is PingPacket -> {
sendPacket(PongPacket(ipAddress, packet.source)) sendPacket(PongPacket(ipAddress, packet.source))
} }
@ -72,7 +74,14 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
} }
override fun receive(frame: EthernetFrame) { override fun receive(frame: EthernetFrame) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{} ({}, {}) received frame from {}: {}",
this,
ipAddress,
macAddress,
frame.source,
frame
)
when (frame) { when (frame) {
is ARPQueryFrame -> handleARPQuery(frame) is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame) is ARPResponseFrame -> handleARPResponse(frame)
@ -89,14 +98,26 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP) PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
arpTable[frame.sourceIP] = frame.source arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) { if (frame.queryIP == ipAddress) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{} ({}) sending ARP response to {} with {}",
this,
ipAddress,
frame.sourceIP,
macAddress
)
send(ARPResponseFrame(ipAddress, macAddress, frame.source)) send(ARPResponseFrame(ipAddress, macAddress, frame.source))
} }
} }
private fun handleARPResponse(frame: ARPResponseFrame) { private fun handleARPResponse(frame: ARPResponseFrame) {
arpTable[frame.query] = frame.source arpTable[frame.query] = frame.source
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{}, ({}) received ARP response for {} with {}",
this,
ipAddress,
frame.query,
frame.source
)
val toRemove = packetQueue.filter { (packet, _) -> val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) { if (packet.destination == frame.query) {
@ -122,7 +143,12 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
} else { } else {
packetQueue.add(PendingPacket(packet, counter)) packetQueue.add(PendingPacket(packet, counter))
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{} ({}) sending ARP query for {}",
this,
ipAddress,
packet.destination
)
send(ARPQueryFrame(packet.destination, ipAddress, macAddress)) send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
} }
} }
@ -141,6 +167,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
this.cachedDestination = WeakReference(it) this.cachedDestination = WeakReference(it)
} }
} }
else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side") else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
} }
} }

View File

@ -27,7 +27,7 @@ import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): DeviceBlock<T>(settings) { abstract class FaceDeviceBlock<T : DeviceBlockEntity>(settings: Settings) : DeviceBlock<T>(settings) {
companion object { companion object {
val FACING = Properties.FACING val FACING = Properties.FACING
val CABLE_CONNECTION = EnumProperty.of("cable_connection", FaceCableConnection::class.java) val CABLE_CONNECTION = EnumProperty.of("cable_connection", FaceCableConnection::class.java)
@ -82,7 +82,11 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
if (cableConnection == FaceCableConnection.NONE) { if (cableConnection == FaceCableConnection.NONE) {
VoxelShapes.union(faceShapes[facing], centerShapes[facing]) VoxelShapes.union(faceShapes[facing], centerShapes[facing])
} else { } else {
VoxelShapes.union(faceShapes[facing], centerShapes[facing], CableBlock.SIDE_SHAPES[cableConnection.direction]) VoxelShapes.union(
faceShapes[facing],
centerShapes[facing],
CableBlock.SIDE_SHAPES[cableConnection.direction]
)
} }
} }
} }
@ -92,7 +96,12 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
return if (direction != null) EnumSet.of(direction) else setOf() return if (direction != null) EnumSet.of(direction) else setOf()
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { override fun getNetworkInterfaceForSide(
side: Direction,
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Interface? {
return if (side == state[FACING]) { return if (side == state[FACING]) {
null null
} else { } else {
@ -108,15 +117,22 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
} }
override fun getPlacementState(context: ItemPlacementContext): BlockState { override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite val facing =
if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
// todo: this should never be called // todo: this should never be called
val cableConnection = FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing, DyeColor.BLUE)) val cableConnection =
FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing, DyeColor.BLUE))
return defaultState return defaultState
.with(FACING, facing) .with(FACING, facing)
.with(CABLE_CONNECTION, cableConnection) .with(CABLE_CONNECTION, cableConnection)
} }
private fun getCableConnectedSide(world: WorldAccess, pos: BlockPos, facing: Direction, color: DyeColor): Direction? { private fun getCableConnectedSide(
world: WorldAccess,
pos: BlockPos,
facing: Direction,
color: DyeColor
): Direction? {
for (side in Direction.values()) { for (side in Direction.values()) {
if (side == facing) { if (side == facing) {
continue continue
@ -130,18 +146,32 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
return null return null
} }
private fun canConnectTo(world: WorldAccess, side: Direction, candidateState: BlockState, candidatePos: BlockPos, myColor: DyeColor): Boolean { private fun canConnectTo(
world: WorldAccess,
side: Direction,
candidateState: BlockState,
candidatePos: BlockPos,
myColor: DyeColor
): Boolean {
val block = candidateState.block val block = candidateState.block
return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) { return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) {
true true
} else if (block is CableBlock && block.color == myColor) { } else if (block is CableBlock && block.color == myColor) {
true true
} else { } else {
block is NetworkComponentBlock && block.getNetworkConnectedSides(candidateState, world, candidatePos).contains(side.opposite) block is NetworkComponentBlock && block.getNetworkConnectedSides(candidateState, world, candidatePos)
.contains(side.opposite)
} }
} }
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState { override fun getStateForNeighborUpdate(
state: BlockState,
side: Direction,
neighborState: BlockState,
world: WorldAccess,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
val current = state[CABLE_CONNECTION] val current = state[CABLE_CONNECTION]
var newConnection = current var newConnection = current
@ -159,7 +189,12 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
return state.with(CABLE_CONNECTION, newConnection) return state.with(CABLE_CONNECTION, newConnection)
} }
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape { override fun getOutlineShape(
state: BlockState,
world: BlockView,
pos: BlockPos,
context: ShapeContext
): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION]) return getShape(state[FACING], state[CABLE_CONNECTION])
} }

View File

@ -38,7 +38,7 @@ import java.util.*
*/ */
class CableBlock( class CableBlock(
val color: DyeColor, val color: DyeColor,
): Block( ) : Block(
FabricBlockSettings.of(CABLE_MATERIAL) FabricBlockSettings.of(CABLE_MATERIAL)
.strength(0.3f) .strength(0.3f)
.nonOpaque() .nonOpaque()
@ -63,7 +63,8 @@ class CableBlock(
VoxelShapes.union(acc, SIDE_SHAPES[side]) VoxelShapes.union(acc, SIDE_SHAPES[side])
} }
} }
val CONNECTIONS: Map<Direction, EnumProperty<CableConnection>> = Direction.values().associate { it to EnumProperty.of(it.name.toLowerCase(), CableConnection::class.java) } val CONNECTIONS: Map<Direction, EnumProperty<CableConnection>> =
Direction.values().associate { it to EnumProperty.of(it.name.toLowerCase(), CableConnection::class.java) }
fun getShape(state: BlockState): VoxelShape { fun getShape(state: BlockState): VoxelShape {
val key = Direction.values().foldIndexed(0) { i, acc, dir -> val key = Direction.values().foldIndexed(0) { i, acc, dir ->
@ -110,7 +111,14 @@ class CableBlock(
return getInitialState(context.world, context.blockPos) return getInitialState(context.world, context.blockPos)
} }
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, blockPos_1: BlockPos, blockPos_2: BlockPos): BlockState { override fun getStateForNeighborUpdate(
state: BlockState,
side: Direction,
neighborState: BlockState,
world: WorldAccess,
blockPos_1: BlockPos,
blockPos_2: BlockPos
): BlockState {
val prop = CONNECTIONS[side] val prop = CONNECTIONS[side]
val current = state[prop] val current = state[prop]
return when (current) { return when (current) {
@ -119,7 +127,11 @@ class CableBlock(
} }
} }
private fun getConnectionStateInDirection(world: WorldAccess, pos: BlockPos, direction: Direction): CableConnection { private fun getConnectionStateInDirection(
world: WorldAccess,
pos: BlockPos,
direction: Direction
): CableConnection {
val offsetPos = pos.offset(direction) val offsetPos = pos.offset(direction)
val state = world.getBlockState(offsetPos) val state = world.getBlockState(offsetPos)
val block = state.block val block = state.block
@ -140,7 +152,12 @@ class CableBlock(
} }
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { override fun getNetworkInterfaceForSide(
side: Direction,
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Interface? {
// cables don't have network interfaces // cables don't have network interfaces
return null return null
} }
@ -169,11 +186,18 @@ class CableBlock(
val connectedToPos = pos.offset(side) val connectedToPos = pos.offset(side)
val connectedTo = world.getBlockState(connectedToPos) val connectedTo = world.getBlockState(connectedToPos)
if (connectedTo.block == this && connectedTo[CONNECTIONS[side.opposite]] == CableConnection.DISABLED) { if (connectedTo.block == this && connectedTo[CONNECTIONS[side.opposite]] == CableConnection.DISABLED) {
world.setBlockState(connectedToPos, connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON)) world.setBlockState(
connectedToPos,
connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON)
)
} }
state.with(prop, if (connectedTo.block is NetworkComponentBlock) CableConnection.ON else CableConnection.OFF) state.with(
prop,
if (connectedTo.block is NetworkComponentBlock) CableConnection.ON else CableConnection.OFF
)
} }
else -> state.with(prop, CableConnection.DISABLED) else -> state.with(prop, CableConnection.DISABLED)
} }
world.setBlockState(pos, newState) world.setBlockState(pos, newState)
@ -195,7 +219,12 @@ class CableBlock(
// return false // return false
// } // }
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape { override fun getOutlineShape(
state: BlockState,
world: BlockView,
pos: BlockPos,
context: ShapeContext
): VoxelShape {
return getShape(state) return getShape(state)
} }

View File

@ -21,7 +21,7 @@ import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>( class ExtractorBlock : FaceDeviceBlock<ExtractorBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
@ -59,7 +59,14 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
arr[i] = 16.0 - arr[i] arr[i] = 16.0 - arr[i]
} }
} }
createCuboidShape(min(arr[0], arr[3]), min(arr[1], arr[4]), min(arr[2], arr[5]), max(arr[0], arr[3]), max(arr[1], arr[4]), max(arr[2], arr[5])) createCuboidShape(
min(arr[0], arr[3]),
min(arr[1], arr[4]),
min(arr[2], arr[5]),
max(arr[0], arr[3]),
max(arr[1], arr[4]),
max(arr[2], arr[5])
)
} }
EXTRACTOR_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) } EXTRACTOR_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
} }
@ -71,16 +78,4 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
override fun createBlockEntity(pos: BlockPos, state: BlockState) = ExtractorBlockEntity(pos, state) override fun createBlockEntity(pos: BlockPos, state: BlockState) = ExtractorBlockEntity(pos, state)
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
} }

View File

@ -1,13 +1,14 @@
package net.shadowfacts.phycon.block.extractor package net.shadowfacts.phycon.block.extractor
import alexiil.mc.lib.attributes.SearchOptions import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
import alexiil.mc.lib.attributes.Simulation import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import alexiil.mc.lib.attributes.item.FixedItemInv import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import alexiil.mc.lib.attributes.item.ItemAttributes import net.fabricmc.fabric.api.transfer.v1.storage.Storage
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
@ -23,12 +24,15 @@ import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.packet.RemoteActivationPacket import net.shadowfacts.phycon.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.copyWithCount
import kotlin.math.min
import kotlin.properties.Delegates import kotlin.properties.Delegates
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.EXTRACTOR, pos, state), class ExtractorBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.EXTRACTOR, pos, state),
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>, NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice, ActivationController.ActivatableDevice,
ClientConfigurableDevice { ClientConfigurableDevice {
@ -40,20 +44,19 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
private val facing: Direction private val facing: Direction
get() = cachedState[FaceDeviceBlock.FACING] get() = cachedState[FaceDeviceBlock.FACING]
private var inventory: FixedItemInv? = null private var inventory: Pair<BlockState, BlockApiCache<Storage<ItemVariant>, Direction>>? = null
override val pendingInsertions = mutableListOf<PendingInsertion>() override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = 1L override val dispatchStackTimeout = 1L
override val controller = ActivationController(SLEEP_TIME, this) override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() { private fun getInventory(): Storage<ItemVariant>? {
if (inventory == null) {
val offsetPos = pos.offset(facing) val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing) val cachedFacedBlock = world!!.getBlockState(offsetPos)
inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option) val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos)
this.inventory = Pair(cachedFacedBlock, inventory)
} }
return inventory!!.second.find(inventory!!.first, facing.opposite)
private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory()
return inventory
} }
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
@ -75,10 +78,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
val inventory = getInventory() ?: return insertion.stack val inventory = getInventory() ?: return insertion.stack
// if the inventory has changed, the old slot index is meaningless // if the inventory has changed, the old slot index is meaningless
if (inventory !== insertion.inventory) return insertion.stack if (inventory !== insertion.inventory) return insertion.stack
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION) val transaction = Transaction.openOuter()
if (extracted.isEmpty) return insertion.stack val extractedAmount = inventory.extract(ItemVariant.of(insertion.stack), insertion.stack.count.toLong(), transaction).toInt()
transaction.commit()
if (extractedAmount == 0) {
return insertion.stack
} else if (extractedAmount != insertion.stack.count) {
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have // if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
insertion.stack = extracted insertion.stack = insertion.stack.copyWithCount(extractedAmount)
}
return super.finishInsertion(insertion) return super.finishInsertion(insertion)
} }
@ -94,14 +102,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
override fun activate(): Boolean { override fun activate(): Boolean {
val inventory = getInventory() ?: return false val inventory = getInventory() ?: return false
for (slot in 0 until inventory.slotCount) { for (view in inventory.iterator(null)) {
val slotStack = inventory.getInvStack(slot) if (view.amount <= 0) continue
if (slotStack.isEmpty) continue val transaction = Transaction.openOuter()
val extractable = inventory.extractStack(slot, ExactItemStackFilter(slotStack), ItemStack.EMPTY, slotStack.count, Simulation.SIMULATE) var extractableAmount = inventory.simulateExtract(view.resource, view.amount, transaction).toInt()
if (extractable.isEmpty) continue transaction.close()
dispatchItemStack(extractable) { insertion -> if (extractableAmount <= 0) continue
extractableAmount = min(extractableAmount, view.resource.item.maxCount)
dispatchItemStack(view.resource.toStack(extractableAmount)) { insertion ->
insertion.inventory = inventory insertion.inventory = inventory
insertion.inventorySlot = slot
} }
return true return true
} }
@ -126,8 +135,9 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
} }
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) { class PendingInsertion(stack: ItemStack, timestamp: Long) :
lateinit var inventory: FixedItemInv NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: Storage<ItemVariant>
var inventorySlot by Delegates.notNull<Int>() var inventorySlot by Delegates.notNull<Int>()
} }
} }

View File

@ -31,7 +31,7 @@ import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InserterBlock: FaceDeviceBlock<InserterBlockEntity>( class InserterBlock : FaceDeviceBlock<InserterBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
@ -68,7 +68,14 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
arr[i] = 16.0 - arr[i] arr[i] = 16.0 - arr[i]
} }
} }
createCuboidShape(min(arr[0], arr[3]), min(arr[1], arr[4]), min(arr[2], arr[5]), max(arr[0], arr[3]), max(arr[1], arr[4]), max(arr[2], arr[5])) createCuboidShape(
min(arr[0], arr[3]),
min(arr[1], arr[4]),
min(arr[2], arr[5]),
max(arr[0], arr[3]),
max(arr[1], arr[4]),
max(arr[2], arr[5])
)
} }
INSERTER_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) } INSERTER_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
} }
@ -80,25 +87,20 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(pos, state) override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(pos, state)
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) { override fun onUse(
if (!world.isClient) { state: BlockState,
getBlockEntity(world, pos)!!.updateInventory() world: World,
} pos: BlockPos,
} player: PlayerEntity,
hand: Hand,
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) { hitResult: BlockHitResult
if (!world.isClient) { ): ActionResult {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
if (!world.isClient) { if (!world.isClient) {
val be = getBlockEntity(world, pos)!! val be = getBlockEntity(world, pos)!!
be.markUpdate() be.markUpdate()
val factory = object: ExtendedScreenHandlerFactory { val factory = object : ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return InserterScreenHandler(syncId, playerInv, be) return InserterScreenHandler(syncId, playerInv, be)
} }

View File

@ -1,13 +1,14 @@
package net.shadowfacts.phycon.block.inserter package net.shadowfacts.phycon.block.inserter
import alexiil.mc.lib.attributes.SearchOptions import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
import alexiil.mc.lib.attributes.Simulation import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import alexiil.mc.lib.attributes.item.ItemAttributes import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import alexiil.mc.lib.attributes.item.ItemInsertable import net.fabricmc.fabric.api.transfer.v1.storage.Storage
import alexiil.mc.lib.attributes.item.ItemStackUtil import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
@ -19,15 +20,13 @@ import net.shadowfacts.phycon.component.NetworkStackProvider
import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.component.handleItemStack
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.*
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.GhostInv
import kotlin.math.min import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state), class InserterBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state),
ItemStackPacketHandler, ItemStackPacketHandler,
ActivationController.ActivatableDevice, ActivationController.ActivatableDevice,
ClientConfigurableDevice, ClientConfigurableDevice,
@ -41,24 +40,25 @@ class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(P
private val facing: Direction private val facing: Direction
get() = cachedState[FaceDeviceBlock.FACING] get() = cachedState[FaceDeviceBlock.FACING]
private var inventory: ItemInsertable? = null private var inventory: Pair<BlockState, BlockApiCache<Storage<ItemVariant>, Direction>>? = null
private var currentRequest: PendingExtractRequest? = null private var currentRequest: PendingExtractRequest? = null
var stackToExtract: ItemStack = ItemStack.EMPTY var stackToExtract: ItemStack = ItemStack.EMPTY
override var ghostSlotStack: ItemStack override var ghostSlotStack: ItemStack
get() = stackToExtract get() = stackToExtract
set(value) { stackToExtract = value } set(value) {
stackToExtract = value
}
var amountToExtract = 1 var amountToExtract = 1
override val controller = ActivationController(SLEEP_TIME, this) override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() { private fun getInventory(): Storage<ItemVariant>? {
if (inventory == null) {
val offsetPos = pos.offset(facing) val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing) val cachedFacedBlock = world!!.getBlockState(offsetPos)
inventory = ItemAttributes.INSERTABLE.getFirstOrNull(world, offsetPos, option) val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos)
this.inventory = Pair(cachedFacedBlock, inventory)
} }
return inventory!!.second.find(inventory!!.first, facing.opposite)
private fun getInventory(): ItemInsertable? {
if (inventory == null) updateInventory()
return inventory
} }
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
@ -71,17 +71,26 @@ class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(P
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory() val inventory = getInventory()
return if (inventory != null) { if (inventory != null) {
inventory.attemptInsertion(packet.stack, Simulation.ACTION) val transaction = Transaction.openOuter()
val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction).toInt()
transaction.commit()
return if (inserted == 0) {
packet.stack
} else if (inserted == packet.stack.count) {
ItemStack.EMPTY
} else {
packet.stack.copyWithCount(packet.stack.count - inserted)
}
} else { } else {
// no inventory, entire stack remains // no inventory, entire stack remains
packet.stack return packet.stack
} }
} }
private fun handleStackLocation(packet: StackLocationPacket) { private fun handleStackLocation(packet: StackLocationPacket) {
val request = currentRequest val request = currentRequest
if (request != null && ItemStackUtil.areEqualIgnoreAmounts(request.stack, packet.stack)) { if (request != null && request.stack.equalsIgnoringAmount(packet.stack)) {
request.results.add(packet.amount to packet.stackProvider) request.results.add(packet.amount to packet.stackProvider)
if (request.isFinishable(counter)) { if (request.isFinishable(counter)) {
finishRequest() finishRequest()

View File

@ -22,7 +22,7 @@ class InserterScreen(
handler: InserterScreenHandler, handler: InserterScreenHandler,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text, title: Text,
): HandledScreen<InserterScreenHandler>( ) : HandledScreen<InserterScreenHandler>(
handler, handler,
playerInv, playerInv,
title title

View File

@ -22,13 +22,13 @@ class InserterScreenHandler(
syncId: Int, syncId: Int,
playerInv: PlayerInventory, playerInv: PlayerInventory,
val inserter: InserterBlockEntity, val inserter: InserterBlockEntity,
): ScreenHandler(PhyScreens.INSERTER, syncId) { ) : ScreenHandler(PhyScreens.INSERTER, syncId) {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "inserter") val ID = Identifier(PhysicalConnectivity.MODID, "inserter")
} }
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
this( this(
syncId, syncId,
playerInv, playerInv,

View File

@ -23,7 +23,7 @@ import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class MinerBlock: DeviceBlock<MinerBlockEntity>( class MinerBlock : DeviceBlock<MinerBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
@ -38,7 +38,12 @@ class MinerBlock: DeviceBlock<MinerBlockEntity>(
return EnumSet.of(state[FACING].opposite) return EnumSet.of(state[FACING].opposite)
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { override fun getNetworkInterfaceForSide(
side: Direction,
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Interface? {
return if (side == state[FACING]) { return if (side == state[FACING]) {
null null
} else { } else {
@ -64,7 +69,14 @@ class MinerBlock: DeviceBlock<MinerBlockEntity>(
} }
} }
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) { override fun neighborUpdate(
state: BlockState,
world: World,
pos: BlockPos,
neighbor: Block,
neighborPos: BlockPos,
bl: Boolean
) {
if (!world.isClient) { if (!world.isClient) {
// getBlockEntity(world, pos)!!.updateBlockToMine() // getBlockEntity(world, pos)!!.updateBlockToMine()
} }

View File

@ -1,8 +1,10 @@
package net.shadowfacts.phycon.block.miner package net.shadowfacts.phycon.block.miner
import alexiil.mc.lib.attributes.item.GroupedItemInvView import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import alexiil.mc.lib.attributes.item.ItemStackUtil import net.fabricmc.fabric.api.transfer.v1.storage.StorageView
import alexiil.mc.lib.attributes.item.filter.ItemFilter import net.fabricmc.fabric.api.transfer.v1.storage.base.ExtractionOnlyStorage
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
@ -22,12 +24,13 @@ import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.copyWithCount import net.shadowfacts.phycon.util.copyWithCount
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import kotlin.math.min import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.MINER, pos, state), class MinerBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity(PhyBlockEntities.MINER, pos, state),
NetworkStackProvider, NetworkStackProvider,
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>, NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice, ActivationController.ActivatableDevice,
@ -36,7 +39,7 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
private val facing: Direction private val facing: Direction
get() = cachedState[MinerBlock.FACING] get() = cachedState[MinerBlock.FACING]
private val invProxy = MinerInvProxy(this) private val invProxy = MinerStorageProxy(this)
override val pendingInsertions = mutableListOf<PendingInsertion>() override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT
@ -60,16 +63,16 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
if (minerMode != MinerMode.ON_DEMAND || packet.kind != RequestInventoryPacket.Kind.GROUPED) { if (minerMode != MinerMode.ON_DEMAND || packet.kind != RequestInventoryPacket.Kind.GROUPED) {
return return
} }
sendPacket(ReadGroupedInventoryPacket(invProxy, ipAddress, packet.source)) sendPacket(ReadItemStoragePacket(invProxy, ipAddress, packet.source))
} }
private fun handleLocateStack(packet: LocateStackPacket) { private fun handleLocateStack(packet: LocateStackPacket) {
if (minerMode != MinerMode.ON_DEMAND) { if (minerMode != MinerMode.ON_DEMAND) {
return return
} }
val amount = invProxy.getAmount(packet.stack) val amount = invProxy.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, null)
if (amount > 0) { if (amount > 0) {
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source)) sendPacket(StackLocationPacket(packet.stack, amount.toInt(), this, ipAddress, packet.source))
} }
} }
@ -80,7 +83,8 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
// always recalculate immediately before breaking // always recalculate immediately before breaking
val drops = invProxy.getDrops(recalculate = true) val drops = invProxy.getDrops(recalculate = true)
if (invProxy.getAmount(packet.stack) > 0) { val amount = invProxy.simulateExtract(ItemVariant.of(packet.stack), packet.amount.toLong(), null)
if (amount > 0) {
world!!.breakBlock(pos.offset(facing), false) world!!.breakBlock(pos.offset(facing), false)
// send the requested amount back to the requester // send the requested amount back to the requester
@ -89,7 +93,7 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
if (remaining <= 0) { if (remaining <= 0) {
break break
} }
if (!ItemStackUtil.areEqualIgnoreAmounts(droppedStack, packet.stack)) { if (!droppedStack.equalsIgnoringAmount(packet.stack)) {
continue continue
} }
@ -184,13 +188,15 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}") val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}")
} }
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView { class MinerStorageProxy(val miner: MinerBlockEntity) :
SnapshotParticipant<Pair<BlockState, List<ItemStack>>?>(),
ExtractionOnlyStorage<ItemVariant>
{
companion object { companion object {
val TOOL = ItemStack(Items.DIAMOND_PICKAXE) val TOOL = ItemStack(Items.DIAMOND_PICKAXE)
} }
private var cachedState: BlockState? = null private var cache: Pair<BlockState, List<ItemStack>>? = null
private var cachedDrops: List<ItemStack>? = null
private val world: World private val world: World
get() = miner.world!! get() = miner.world!!
@ -202,39 +208,78 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
fun getDrops(recalculate: Boolean = false): List<ItemStack> { fun getDrops(recalculate: Boolean = false): List<ItemStack> {
val targetPos = pos.offset(facing) val targetPos = pos.offset(facing)
val realState = world.getBlockState(targetPos) val realState = world.getBlockState(targetPos)
val cache = this.cache
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates? // todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
if (cachedDrops == null || realState != cachedState || recalculate) { if (cache == null || realState != cache.first || recalculate) {
cachedState = realState
val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null
cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL) val drops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL)
this.cache = Pair(realState, drops)
return drops
} }
return cachedDrops!! return cache.second
} }
override fun getStoredStacks(): Set<ItemStack> { override fun createSnapshot(): Pair<BlockState, List<ItemStack>>? {
if (miner.minerMode != MinerMode.ON_DEMAND) { return cache
return setOf()
}
return getDrops().toSet()
} }
override fun getTotalCapacity(): Int { override fun readSnapshot(snapshot: Pair<BlockState, List<ItemStack>>?) {
return Int.MAX_VALUE cache = snapshot
} }
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic { override fun extract(resource: ItemVariant, maxAmount: Long, transaction: TransactionContext): Long {
var totalCount = 0 val drops = getDrops()
for (s in storedStacks) { val (matched, unmatched) = drops.partition { resource.matches(it) }
if (filter.matches(s)) { if (matched.isEmpty()) {
totalCount += s.count return 0
}
val matchedCount = matched.sumOf { it.count }.toLong()
val extracted = min(maxAmount, matchedCount)
transaction.addCloseCallback { context, result ->
if (result.wasCommitted()) {
world.breakBlock(pos.offset(facing), false)
// send any un-extracted drops to the network
if (matchedCount > extracted) {
miner.dispatchItemStack(resource.toStack().copyWithCount((matchedCount - extracted).toInt()))
}
for (stack in unmatched) {
miner.dispatchItemStack(stack)
} }
} }
return GroupedItemInvView.ItemInvStatistic(filter, totalCount, 0, Int.MAX_VALUE) }
return extracted
}
override fun iterator(transaction: TransactionContext): Iterator<StorageView<ItemVariant>> {
return getDrops().map { View(it, this) }.iterator()
}
class View(val stack: ItemStack, val proxy: MinerStorageProxy) : StorageView<ItemVariant> {
override fun extract(resource: ItemVariant, maxAmount: Long, transaction: TransactionContext): Long {
return proxy.extract(resource, maxAmount, transaction)
}
override fun isResourceBlank(): Boolean {
return false
}
override fun getResource(): ItemVariant {
return ItemVariant.of(stack)
}
override fun getAmount(): Long {
return stack.count.toLong()
}
override fun getCapacity(): Long {
// TODO: ehh? we have stuff stored, but nothing can be inserted, so...
return 0
}
} }
} }
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) { class PendingInsertion(stack: ItemStack, timestamp: Long) :
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
} }
} }

View File

@ -1,7 +1,5 @@
package net.shadowfacts.phycon.block.netinterface package net.shadowfacts.phycon.block.netinterface
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.* import net.minecraft.block.*
import net.minecraft.entity.LivingEntity import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
@ -18,13 +16,12 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>( class InterfaceBlock : FaceDeviceBlock<InterfaceBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
), ),
NetworkComponentBlock, NetworkComponentBlock {
AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface") val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
@ -42,20 +39,4 @@ class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>(
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InterfaceBlockEntity(pos, state) override fun createBlockEntity(pos: BlockPos, state: BlockState) = InterfaceBlockEntity(pos, state)
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, boolean_1: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
} }

View File

@ -1,12 +1,14 @@
package net.shadowfacts.phycon.block.netinterface package net.shadowfacts.phycon.block.netinterface
import alexiil.mc.lib.attributes.SearchOptions import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
import alexiil.mc.lib.attributes.Simulation import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import alexiil.mc.lib.attributes.item.GroupedItemInv import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import alexiil.mc.lib.attributes.item.ItemAttributes import net.fabricmc.fabric.api.transfer.v1.storage.Storage
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
@ -19,13 +21,14 @@ import net.shadowfacts.phycon.component.NetworkStackReceiver
import net.shadowfacts.phycon.component.handleItemStack import net.shadowfacts.phycon.component.handleItemStack
import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ClientConfigurableDevice import net.shadowfacts.phycon.util.ClientConfigurableDevice
import java.lang.ref.WeakReference import net.shadowfacts.phycon.util.copyWithCount
import kotlin.math.min import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INTERFACE, pos, state), class InterfaceBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.INTERFACE, pos, state),
ItemStackPacketHandler, ItemStackPacketHandler,
NetworkStackProvider, NetworkStackProvider,
NetworkStackReceiver, NetworkStackReceiver,
@ -38,21 +41,17 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
override var receiverPriority = 0 override var receiverPriority = 0
var syncPriorities = true var syncPriorities = true
private var inventory: WeakReference<GroupedItemInv>? = null private var cachedFacedBlock: BlockState? = null
private var inventoryCache: BlockApiCache<Storage<ItemVariant>, Direction>? = null
fun updateInventory() { private fun getInventory(): Storage<ItemVariant>? {
val offsetPos = pos.offset(facing) if (cachedFacedBlock == null) {
val option = SearchOptions.inDirection(facing) cachedFacedBlock = world!!.getBlockState(pos.offset(facing))
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option).let {
WeakReference(it)
} }
if (inventoryCache == null) {
inventoryCache = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, pos.offset(facing))
} }
return inventoryCache!!.find(cachedFacedBlock!!, facing.opposite)
private fun getInventory(): GroupedItemInv? {
// if we don't have an inventory, try to get one
// this happens when readAll is called before a neighbor state changes, such as immediately after world load
if (inventory?.get() == null) updateInventory()
return inventory?.get()
} }
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
@ -70,46 +69,50 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
return return
} }
getInventory()?.also { inv -> getInventory()?.also { inv ->
sendPacket(ReadGroupedInventoryPacket(inv, ipAddress, packet.source)) sendPacket(ReadItemStoragePacket(inv, ipAddress, packet.source))
} }
} }
private fun handleLocateStack(packet: LocateStackPacket) { private fun handleLocateStack(packet: LocateStackPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
val amount = inv.getAmount(packet.stack) val transaction = Transaction.openOuter()
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source)) val amount = inv.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, transaction);
transaction.close()
sendPacket(StackLocationPacket(packet.stack, amount.toInt(), this, ipAddress, packet.source))
} }
} }
private fun handleExtractStack(packet: ExtractStackPacket) { private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
var amount = packet.amount var remaining = packet.amount
while (amount > 0) { while (remaining > 0) {
val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount)) val toExtract = min(remaining, packet.stack.maxCount)
if (extracted.isEmpty) { val transaction = Transaction.openOuter()
break val extracted = inv.extract(ItemVariant.of(packet.stack), toExtract.toLong(), transaction)
} else { transaction.commit()
amount -= extracted.count remaining -= extracted.toInt()
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source)) sendPacket(ItemStackPacket(packet.stack.copyWithCount(extracted.toInt()), ipAddress, packet.source))
}
} }
} }
} }
private fun handleCheckCapacity(packet: CheckCapacityPacket) { private fun handleCheckCapacity(packet: CheckCapacityPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE) val transaction = Transaction.openOuter()
val couldAccept = packet.stack.count - remaining.count val inserted = inv.simulateInsert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source)) transaction.close()
sendPacket(CapacityPacket(packet.stack, inserted.toInt(), this, ipAddress, packet.source))
} }
} }
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory() val inventory = getInventory()
if (inventory != null) { if (inventory != null) {
val remaining = inventory.insert(packet.stack) val transaction = Transaction.openOuter()
// whatever could not be inserted will be sent back to the packet's source val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
return remaining transaction.commit()
val remaining = packet.stack.count - inserted.toInt()
return packet.stack.copyWithCount(remaining)
} else { } else {
return packet.stack return packet.stack
} }

View File

@ -1,7 +1,5 @@
package net.shadowfacts.phycon.block.netswitch package net.shadowfacts.phycon.block.netswitch
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Material import net.minecraft.block.Material
import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntity
@ -11,7 +9,6 @@ import net.minecraft.sound.BlockSoundGroup
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldAccess import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
@ -23,13 +20,12 @@ import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class SwitchBlock: BlockWithEntity<SwitchBlockEntity>( class SwitchBlock : BlockWithEntity<SwitchBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
), ),
NetworkComponentBlock, NetworkComponentBlock {
AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch") val ID = Identifier(PhysicalConnectivity.MODID, "switch")
@ -39,13 +35,22 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
return EnumSet.allOf(Direction::class.java) return EnumSet.allOf(Direction::class.java)
} }
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { override fun getNetworkInterfaceForSide(
side: Direction,
state: BlockState,
world: WorldAccess,
pos: BlockPos
): Interface? {
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side } return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
} }
override fun createBlockEntity(pos: BlockPos, state: BlockState) = SwitchBlockEntity(pos, state) override fun createBlockEntity(pos: BlockPos, state: BlockState) = SwitchBlockEntity(pos, state)
override fun <T: BlockEntity> getTicker(world: World, state: BlockState, type: BlockEntityType<T>): BlockEntityTicker<T>? { override fun <T : BlockEntity> getTicker(
world: World,
state: BlockState,
type: BlockEntityType<T>
): BlockEntityTicker<T>? {
return if (world.isClient) { return if (world.isClient) {
null null
} else { } else {
@ -54,8 +59,4 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
} }
} }
} }
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
} }

View File

@ -20,6 +20,7 @@ import net.shadowfacts.phycon.frame.BasePacketFrame
import net.shadowfacts.phycon.frame.NetworkSplitFrame import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.ItemStackPacket import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.util.IntRingBuffer
import net.shadowfacts.phycon.util.NetworkUtil import net.shadowfacts.phycon.util.NetworkUtil
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Deque import java.util.Deque
@ -28,7 +29,7 @@ import java.util.LinkedList
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockEntities.SWITCH, pos, state) { class SwitchBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(PhyBlockEntities.SWITCH, pos, state) {
companion object { companion object {
var SWITCHING_CAPACITY = 256 // 256 packets/tick var SWITCHING_CAPACITY = 256 // 256 packets/tick
@ -38,9 +39,13 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
private val macTable = mutableMapOf<MACAddress, Direction>() private val macTable = mutableMapOf<MACAddress, Direction>()
private val destinationCache = Array<WeakReference<Interface>?>(6) { null } private val destinationCache = Array<WeakReference<Interface>?>(6) { null }
val packetStatistics = IntRingBuffer(60) // 1 minute's worth
private val currentSecondPacketStatistics = IntRingBuffer(20)
private var packetsHandledThisTick = 0 private var packetsHandledThisTick = 0
private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList() private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
var statisticsObserver: (() -> Unit)? = null
fun interfaceForSide(side: Direction): SwitchInterface { fun interfaceForSide(side: Direction): SwitchInterface {
return interfaces.find { it.side == side }!! return interfaces.find { it.side == side }!!
} }
@ -64,7 +69,14 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
private fun resend(frame: EthernetFrame, fromItf: SwitchInterface) { private fun resend(frame: EthernetFrame, fromItf: SwitchInterface) {
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) { if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!! val dir = macTable[frame.destination]!!
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{} ({}, {}) forwarding {} to side {}",
this,
fromItf.side,
fromItf.macAddress,
frame,
dir
)
interfaceForSide(dir).send(frame) interfaceForSide(dir).send(frame)
} else { } else {
flood(frame, fromItf) flood(frame, fromItf)
@ -72,7 +84,13 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
} }
private fun flood(frame: EthernetFrame, source: SwitchInterface) { private fun flood(frame: EthernetFrame, source: SwitchInterface) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, source.side, source.macAddress, frame) PhysicalConnectivity.NETWORK_LOGGER.debug(
"{} ({}, {}) flooding {}",
this,
source.side,
source.macAddress,
frame
)
for (itf in interfaces) { for (itf in interfaces) {
if (source == itf) continue if (source == itf) continue
itf.send(frame) itf.send(frame)
@ -98,6 +116,16 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
} }
fun tick() { fun tick() {
if (statisticsObserver != null) {
if (currentSecondPacketStatistics.size == 20) {
packetStatistics.add(currentSecondPacketStatistics.sum())
currentSecondPacketStatistics.clear()
statisticsObserver?.invoke()
} else {
currentSecondPacketStatistics.add(packetsHandledThisTick)
}
}
packetsHandledThisTick = 0 packetsHandledThisTick = 0
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) { while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
@ -147,6 +175,11 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
delayedPackets.addLast(frame to fromItf) delayedPackets.addLast(frame to fromItf)
} }
} }
tag.getIntArray("PacketStatistics")?.also { statistics ->
if (statistics.isNotEmpty()) {
packetStatistics.replace(statistics)
}
}
} }
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? { override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
@ -156,6 +189,7 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
override fun toInitialChunkDataNbt(): NbtCompound { override fun toInitialChunkDataNbt(): NbtCompound {
val tag = NbtCompound() val tag = NbtCompound()
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address }) tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
tag.putIntArray("PacketStatistics", packetStatistics.asContiguousArray())
return tag return tag
} }
@ -163,7 +197,7 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
val side: Direction, val side: Direction,
val switch: WeakReference<SwitchBlockEntity>, val switch: WeakReference<SwitchBlockEntity>,
@JvmField var macAddress: MACAddress, @JvmField var macAddress: MACAddress,
): Interface { ) : Interface {
override fun getMACAddress() = macAddress override fun getMACAddress() = macAddress
override fun receive(frame: EthernetFrame) { override fun receive(frame: EthernetFrame) {

View File

@ -0,0 +1,100 @@
package net.shadowfacts.phycon.block.netswitch
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.shadowfacts.cacao.CacaoHandledScreen
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.RenderHelper
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.View
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.cacao.window.ScreenHandlerWindow
import net.shadowfacts.kiwidsl.dsl
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class SwitchConsoleScreen(
handler: SwitchConsoleScreenHandler,
playerInventory: PlayerInventory,
title: Text,
) : CacaoHandledScreen<SwitchConsoleScreenHandler>(
handler,
playerInventory,
title,
) {
val root = SwitchConsoleViewController(handler.switch)
init {
addWindow(ScreenHandlerWindow(handler, root))
}
override fun shouldPause() = false
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == GLFW.GLFW_KEY_E) {
close()
return true
}
return super.keyPressed(keyCode, scanCode, modifiers)
}
}
class SwitchConsoleViewController(val switch: SwitchBlockEntity) : ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val stats = SwitchPacketStatisticsView(switch)
view.addSubview(stats)
view.solver.dsl {
stats.centerXAnchor equalTo (view.centerXAnchor + 50)
stats.centerYAnchor equalTo (view.centerYAnchor + 50)
}
}
}
class SwitchPacketStatisticsView(val switch: SwitchBlockEntity) : View() {
init {
intrinsicContentSize = Size(180.0, 90.0)
}
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.fill(matrixStack, bounds, Color.BLACK)
if (switch.packetStatistics.size == 0) {
return
}
// TODO: drawLine isn't working for some reason
RenderHelper.drawLine(
Point(bounds.left, bounds.top),
Point(bounds.right, bounds.bottom),
1.0,
2f,
Color.MAGENTA
)
return
val maxPackets = switch.packetStatistics.maxOf { it }
val maxDataPointsCount = 60
var lastPoint: Point? = null
val size = Size(3.0, 3.0)
for ((index, packets) in switch.packetStatistics.withIndex()) {
val x = (1 - (switch.packetStatistics.size - index).toDouble() / maxDataPointsCount) * bounds.width
val y = (1 - (packets.toDouble() / maxPackets)) * (bounds.height)
val point = Point(x, y)
if (lastPoint != null) {
// RenderHelper.fill(matrixStack, Rect(lastPoint, 3.0, 3.0), Color.RED)
RenderHelper.drawLine(lastPoint, point, 1.0, 2f, Color.RED)
}
lastPoint = point
}
}
}

View File

@ -0,0 +1,46 @@
package net.shadowfacts.phycon.block.netswitch
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.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
/**
* @author shadowfacts
*/
class SwitchConsoleScreenHandler(
syncId: Int,
val switch: SwitchBlockEntity,
) : ScreenHandler(PhyScreens.SWITCH_CONSOLE, syncId) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch_console")
}
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
this(
syncId,
PhyBlocks.SWITCH.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
)
init {
switch.statisticsObserver = {
switch.world!!.updateListeners(switch.pos, switch.cachedState, switch.cachedState, 3)
}
}
override fun canUse(player: PlayerEntity): Boolean {
return true
}
override fun close(player: PlayerEntity) {
super.close(player)
switch.statisticsObserver = null
}
}

View File

@ -12,7 +12,7 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class P2PInterfaceBlock: FaceDeviceBlock<P2PInterfaceBlockEntity>( class P2PInterfaceBlock : FaceDeviceBlock<P2PInterfaceBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)

View File

@ -15,7 +15,8 @@ import net.shadowfacts.phycon.packet.RequestInventoryPacket
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class P2PInterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_INTERFACE, pos, state) { class P2PInterfaceBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.P2P_INTERFACE, pos, state) {
private var inventory: Storage<ItemVariant>? = null private var inventory: Storage<ItemVariant>? = null

View File

@ -16,7 +16,7 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class P2PReceiverBlock: FaceDeviceBlock<P2PReceiverBlockEntity>( class P2PReceiverBlock : FaceDeviceBlock<P2PReceiverBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)

View File

@ -21,7 +21,8 @@ import java.lang.ref.WeakReference
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class P2PReceiverBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_RECEIVER, pos, state), class P2PReceiverBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.P2P_RECEIVER, pos, state),
ClientConfigurableDevice { ClientConfigurableDevice {
enum class Status { enum class Status {

View File

@ -18,7 +18,7 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>( class RedstoneControllerBlock : FaceDeviceBlock<RedstoneControllerBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
@ -51,7 +51,14 @@ class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>(
return state.with(POWERED, isPowered(context.world, context.blockPos, state[FACING])) return state.with(POWERED, isPowered(context.world, context.blockPos, state[FACING]))
} }
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) { override fun neighborUpdate(
state: BlockState,
world: World,
pos: BlockPos,
neighborBlock: Block,
neighborPos: BlockPos,
bl: Boolean
) {
// this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess // this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
if (!world.isClient) { if (!world.isClient) {
val wasLit = state[POWERED] val wasLit = state[POWERED]

View File

@ -14,7 +14,8 @@ import net.shadowfacts.phycon.util.RedstoneMode
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class RedstoneControllerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER, pos, state), class RedstoneControllerBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER, pos, state),
ClientConfigurableDevice { ClientConfigurableDevice {
var managedDevices = Array<IPAddress?>(5) { null } var managedDevices = Array<IPAddress?>(5) { null }

View File

@ -24,7 +24,7 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>( class RedstoneEmitterBlock : FaceDeviceBlock<RedstoneEmitterBlockEntity>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
@ -51,7 +51,12 @@ class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
return true return true
} }
override fun getStrongRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int { override fun getStrongRedstonePower(
state: BlockState,
world: BlockView,
pos: BlockPos,
receivingSide: Direction
): Int {
return if (receivingSide.opposite == state[FACING]) { return if (receivingSide.opposite == state[FACING]) {
getBlockEntity(world, pos)!!.cachedEmittedPower getBlockEntity(world, pos)!!.cachedEmittedPower
} else { } else {
@ -59,17 +64,29 @@ class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
} }
} }
override fun getWeakRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int { override fun getWeakRedstonePower(
state: BlockState,
world: BlockView,
pos: BlockPos,
receivingSide: Direction
): Int {
return getStrongRedstonePower(state, world, pos, receivingSide) return getStrongRedstonePower(state, world, pos, receivingSide)
} }
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, result: BlockHitResult): ActionResult { override fun onUse(
state: BlockState,
world: World,
pos: BlockPos,
player: PlayerEntity,
hand: Hand,
result: BlockHitResult
): ActionResult {
if (!world.isClient) { if (!world.isClient) {
val be = getBlockEntity(world, pos)!! val be = getBlockEntity(world, pos)!!
be.markUpdate() be.markUpdate()
val factory = object: ExtendedScreenHandlerFactory { val factory = object : ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return RedstoneEmitterScreenHandler(syncId, playerInv, be) return RedstoneEmitterScreenHandler(syncId, playerInv, be)
} }

View File

@ -1,6 +1,7 @@
package net.shadowfacts.phycon.block.redstone_emitter package net.shadowfacts.phycon.block.redstone_emitter
import alexiil.mc.lib.attributes.item.GroupedItemInvView import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
@ -12,27 +13,29 @@ import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.FaceDeviceBlock import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.DeviceRemovedPacket import net.shadowfacts.phycon.packet.DeviceRemovedPacket
import net.shadowfacts.phycon.packet.ReadGroupedInventoryPacket import net.shadowfacts.phycon.packet.ReadItemStoragePacket
import net.shadowfacts.phycon.packet.RequestInventoryPacket import net.shadowfacts.phycon.packet.RequestInventoryPacket
import net.shadowfacts.phycon.util.ClientConfigurableDevice import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.GhostInv import net.shadowfacts.phycon.util.GhostInv
import kotlin.math.round
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state), class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState) :
DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state),
ClientConfigurableDevice, ClientConfigurableDevice,
GhostInv { GhostInv {
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>() private val inventoryCache = mutableMapOf<IPAddress, Storage<ItemVariant>>()
var cachedEmittedPower: Int = 0 var cachedEmittedPower: Int = 0
private set private set
var stackToMonitor: ItemStack = ItemStack.EMPTY var stackToMonitor: ItemStack = ItemStack.EMPTY
override var ghostSlotStack: ItemStack override var ghostSlotStack: ItemStack
get() = stackToMonitor get() = stackToMonitor
set(value) { stackToMonitor = value } set(value) {
stackToMonitor = value
}
var maxAmount = 64 var maxAmount = 64
var mode = Mode.ANALOG var mode = Mode.ANALOG
set(value) { set(value) {
@ -42,12 +45,12 @@ class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockE
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
when (packet) { when (packet) {
is ReadGroupedInventoryPacket -> handleReadInventory(packet) is ReadItemStoragePacket -> handleReadItemStorage(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet)
} }
} }
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) { private fun handleReadItemStorage(packet: ReadItemStoragePacket) {
inventoryCache[packet.source] = packet.inventory inventoryCache[packet.source] = packet.inventory
recalculateRedstone() recalculateRedstone()
} }
@ -63,9 +66,8 @@ class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockE
if (!world!!.isClient && counter % 20 == 0L) { if (!world!!.isClient && counter % 20 == 0L) {
if (counter % 80 == 0L) { if (counter % 80 == 0L) {
updateInventories() updateInventories()
} else if (counter % 20 == 0L) {
recalculateRedstone()
} }
recalculateRedstone()
} }
} }
@ -81,16 +83,18 @@ class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockE
updateWorld() updateWorld()
return return
} }
val variant = ItemVariant.of(stackToMonitor)
val networkAmount = inventoryCache.values.fold(0) { acc, inv -> val networkAmount = inventoryCache.values.fold(0) { acc, inv ->
acc + inv.getAmount(stackToMonitor) acc + inv.simulateExtract(variant, Long.MAX_VALUE, null).toInt()
} }
cachedEmittedPower = cachedEmittedPower =
when (mode) { when (mode) {
Mode.ANALOG -> if (networkAmount == 0) { Mode.ANALOG -> if (networkAmount == 0) {
0 0
} else { } else {
1 + round(networkAmount / maxAmount.toDouble() * 14).toInt() 1 + (networkAmount / maxAmount.toDouble() * 14).toInt()
} }
Mode.DIGITAL -> if (networkAmount >= maxAmount) 15 else 0 Mode.DIGITAL -> if (networkAmount >= maxAmount) 15 else 0
} }

View File

@ -30,7 +30,7 @@ class RedstoneEmitterScreen(
handler: RedstoneEmitterScreenHandler, handler: RedstoneEmitterScreenHandler,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text title: Text
): CacaoHandledScreen<RedstoneEmitterScreenHandler>( ) : CacaoHandledScreen<RedstoneEmitterScreenHandler>(
handler, handler,
playerInv, playerInv,
title title
@ -59,7 +59,7 @@ class RedstoneEmitterScreen(
class ViewController( class ViewController(
private val emitter: RedstoneEmitterBlockEntity, private val emitter: RedstoneEmitterBlockEntity,
): net.shadowfacts.cacao.viewcontroller.ViewController() { ) : net.shadowfacts.cacao.viewcontroller.ViewController() {
lateinit var halfLabel: Label lateinit var halfLabel: Label
lateinit var fullLabel: Label lateinit var fullLabel: Label
@ -90,7 +90,12 @@ class RedstoneEmitterScreen(
view.addSubview(hStack) view.addSubview(hStack)
val zeroStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL)) val zeroStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
zeroStack.addArrangedSubview(Label(TranslatableText("gui.phycon.emitter.count", 0), textAlignment = Label.TextAlignment.CENTER)).apply { zeroStack.addArrangedSubview(
Label(
TranslatableText("gui.phycon.emitter.count", 0),
textAlignment = Label.TextAlignment.CENTER
)
).apply {
textColor = Color.TEXT textColor = Color.TEXT
} }
zeroStack.addArrangedSubview(View()) zeroStack.addArrangedSubview(View())

View File

@ -21,13 +21,13 @@ class RedstoneEmitterScreenHandler(
syncId: Int, syncId: Int,
playerInv: PlayerInventory, playerInv: PlayerInventory,
val emitter: RedstoneEmitterBlockEntity, val emitter: RedstoneEmitterBlockEntity,
): ScreenHandler(PhyScreens.REDSTONE_EMITTER, syncId) { ) : ScreenHandler(PhyScreens.REDSTONE_EMITTER, syncId) {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter") val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter")
} }
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
this( this(
syncId, syncId,
playerInv, playerInv,

View File

@ -1,7 +1,5 @@
package net.shadowfacts.phycon.block.terminal package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Material import net.minecraft.block.Material
@ -12,15 +10,11 @@ import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties import net.minecraft.state.property.Properties
import net.minecraft.util.ActionResult import net.minecraft.util.ActionResult
import net.minecraft.util.Hand import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.ItemScatterer
import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldAccess import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponentBlock import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.DeviceBlock import net.shadowfacts.phycon.block.DeviceBlock
import java.util.EnumSet import java.util.EnumSet
@ -28,13 +22,12 @@ import java.util.EnumSet
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBlock<T>( abstract class AbstractTerminalBlock<T : AbstractTerminalBlockEntity> : DeviceBlock<T>(
Settings.of(Material.METAL) Settings.of(Material.METAL)
.strength(1.5f) .strength(1.5f)
.sounds(BlockSoundGroup.METAL) .sounds(BlockSoundGroup.METAL)
), ),
NetworkComponentBlock, NetworkComponentBlock {
AttributeProvider {
companion object { companion object {
val FACING = Properties.FACING val FACING = Properties.FACING
@ -55,7 +48,14 @@ abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBloc
return defaultState.with(FACING, context.playerLookDirection.opposite) return defaultState.with(FACING, context.playerLookDirection.opposite)
} }
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult { override fun onUse(
state: BlockState,
world: World,
pos: BlockPos,
player: PlayerEntity,
hand: Hand,
hitResult: BlockHitResult
): ActionResult {
getBlockEntity(world, pos)!!.onActivate(player) getBlockEntity(world, pos)!!.onActivate(player)
return ActionResult.SUCCESS return ActionResult.SUCCESS
} }
@ -68,8 +68,4 @@ abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBloc
super.onStateReplaced(state, world, pos, newState, moved) super.onStateReplaced(state, world, pos, newState, moved)
} }
} }
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
} }

View File

@ -1,8 +1,9 @@
package net.shadowfacts.phycon.block.terminal package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.item.GroupedItemInvView import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import alexiil.mc.lib.attributes.item.ItemStackCollections import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import alexiil.mc.lib.attributes.item.ItemStackUtil import net.fabricmc.fabric.api.transfer.v1.storage.Storage
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
@ -18,9 +19,9 @@ import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.block.DeviceBlockEntity import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.component.* import net.shadowfacts.phycon.component.*
import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.packet.* import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.NetworkUtil import net.shadowfacts.phycon.util.NetworkUtil
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.* import java.util.*
import java.util.function.IntBinaryOperator import java.util.function.IntBinaryOperator
@ -30,7 +31,8 @@ import kotlin.properties.Delegates
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state), abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState) :
DeviceBlockEntity(type, pos, state),
InventoryChangedListener, InventoryChangedListener,
ItemStackPacketHandler, ItemStackPacketHandler,
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> { NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
@ -43,14 +45,14 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
} }
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>() protected val inventoryCache = mutableMapOf<IPAddress, Storage<ItemVariant>>()
val internalBuffer = TerminalBufferInventory(18) val internalBuffer = TerminalBufferInventory(18)
protected val pendingRequests = LinkedList<StackLocateRequest>() protected val pendingRequests = LinkedList<StackLocateRequest>()
override val pendingInsertions = mutableListOf<PendingInsertion>() override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = INSERTION_TIMEOUT override val dispatchStackTimeout = INSERTION_TIMEOUT
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = Object2IntOpenHashMap<ItemVariant>()
private var requestInventoryTimestamp: Long? = null private var requestInventoryTimestamp: Long? = null
// todo: multiple players could have the terminal open simultaneously // todo: multiple players could have the terminal open simultaneously
@ -77,7 +79,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
when (packet) { when (packet) {
is ReadGroupedInventoryPacket -> handleReadInventory(packet) is ReadItemStoragePacket -> handleReadItemStorage(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet) is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(packet) is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet) is ItemStackPacket -> handleItemStack(packet)
@ -85,7 +87,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
} }
} }
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) { private fun handleReadItemStorage(packet: ReadItemStoragePacket) {
inventoryCache[packet.source] = packet.inventory inventoryCache[packet.source] = packet.inventory
updateAndSync() updateAndSync()
} }
@ -97,7 +99,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
private fun handleStackLocation(packet: StackLocationPacket) { private fun handleStackLocation(packet: StackLocationPacket) {
val request = pendingRequests.firstOrNull { val request = pendingRequests.firstOrNull {
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) it.stack.equalsIgnoringAmount(packet.stack)
} }
if (request != null) { if (request != null) {
request.results.add(packet.amount to packet.stackProvider) request.results.add(packet.amount to packet.stackProvider)
@ -136,10 +138,12 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
private fun updateNetItems() { private fun updateNetItems() {
cachedNetItems.clear() cachedNetItems.clear()
for (inventory in inventoryCache.values) { for (inventory in inventoryCache.values) {
for (stack in inventory.storedStacks) { val transaction = Transaction.openOuter()
val amount = inventory.getAmount(stack) for (view in inventory.iterator(transaction)) {
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b }) val amount = view.amount.toInt()
cachedNetItems.mergeInt(view.resource, amount, IntBinaryOperator { a, b -> a + b })
} }
transaction.close()
} }
} }
@ -259,7 +263,8 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
fun netItemsChanged() fun netItemsChanged()
} }
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) { class PendingInsertion(stack: ItemStack, timestamp: Long) :
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
var bufferSlot by Delegates.notNull<Int>() var bufferSlot by Delegates.notNull<Int>()
} }

View File

@ -27,13 +27,13 @@ import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: AbstractTerminalScreenHandler<BE>>( abstract class AbstractTerminalScreen<BE : AbstractTerminalBlockEntity, T : AbstractTerminalScreenHandler<BE>>(
handler: T, handler: T,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text, title: Text,
val terminalBackgroundWidth: Int, val terminalBackgroundWidth: Int,
val terminalBackgroundHeight: Int, val terminalBackgroundHeight: Int,
): CacaoHandledScreen<T>(handler, playerInv, title) { ) : CacaoHandledScreen<T>(handler, playerInv, title) {
interface SearchQueryListener { interface SearchQueryListener {
fun terminalSearchQueryChanged(newValue: String) fun terminalSearchQueryChanged(newValue: String)
@ -80,7 +80,13 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
fun requestUpdatedItems() { fun requestUpdatedItems() {
val player = MinecraftClient.getInstance().player!! val player = MinecraftClient.getInstance().player!!
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchQuery, scrollPosition.toFloat())) player.networkHandler.sendPacket(
C2STerminalUpdateDisplayedItems(
handler.terminal,
searchQuery,
scrollPosition.toFloat()
)
)
} }
private fun showRequestAmountDialog(stack: ItemStack) { private fun showRequestAmountDialog(stack: ItemStack) {
@ -115,10 +121,12 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
format.format(amount / 1_000.0) + "K" format.format(amount / 1_000.0) + "K"
} }
amount < 1_000_000_000 -> { amount < 1_000_000_000 -> {
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
format.format(amount / 1_000_000.0) + "M" format.format(amount / 1_000_000.0) + "M"
} }
else -> { else -> {
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B" DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
} }
@ -136,7 +144,18 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer) val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer)
val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3 val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
val textY = (1 / scale * 18) - 11 val textY = (1 / scale * 18) - 11
textRenderer.draw(s, textX, textY, 0xffffff, true, matrixStack.peek().positionMatrix, immediate, false, 0, 0xF000F0) textRenderer.draw(
s,
textX,
textY,
0xffffff,
true,
matrixStack.peek().positionMatrix,
immediate,
false,
0,
0xF000F0
)
RenderSystem.enableDepthTest() RenderSystem.enableDepthTest()
immediate.draw() immediate.draw()
} }

View File

@ -1,24 +1,20 @@
package net.shadowfacts.phycon.block.terminal package net.shadowfacts.phycon.block.terminal
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType import net.minecraft.screen.slot.SlotActionType
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerType import net.minecraft.screen.ScreenHandlerType
import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.DefaultPlugin import net.shadowfacts.phycon.DefaultPlugin
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
import net.shadowfacts.phycon.util.SortMode import net.shadowfacts.phycon.util.SortMode
import net.shadowfacts.phycon.util.TerminalSettings import net.shadowfacts.phycon.util.TerminalSettings
import net.shadowfacts.phycon.util.copyWithCount import net.shadowfacts.phycon.util.name
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.max import kotlin.math.max
@ -28,12 +24,12 @@ import kotlin.math.roundToInt
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>( abstract class AbstractTerminalScreenHandler<T : AbstractTerminalBlockEntity>(
handlerType: ScreenHandlerType<*>, handlerType: ScreenHandlerType<*>,
syncId: Int, syncId: Int,
val playerInv: PlayerInventory, val playerInv: PlayerInventory,
val terminal: T, val terminal: T,
): ScreenHandler(handlerType, syncId), ) : ScreenHandler(handlerType, syncId),
AbstractTerminalBlockEntity.NetItemObserver { AbstractTerminalBlockEntity.NetItemObserver {
private val rowsDisplayed = 6 private val rowsDisplayed = 6
@ -49,7 +45,7 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
field = value field = value
if (terminal.world!!.isClient) { if (terminal.world!!.isClient) {
itemsForDisplay = value.map { itemsForDisplay = value.map {
it.stack.copyWithCount(it.amount) it.variant.toStack(it.amount)
} }
} }
} }
@ -106,6 +102,8 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
return@filter true return@filter true
} }
} }
// TODO: this is happening on the logical server, won't work with localization
// should filtering happen on the client?
it.key.name.string.contains(searchQuery, true) it.key.name.string.contains(searchQuery, true)
} }
@ -125,7 +123,16 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
// itemEntries = sorted.map { Entry(it.key, it.intValue) } // itemEntries = sorted.map { Entry(it.key, it.intValue) }
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries)) (player as ServerPlayerEntity).networkHandler.sendPacket(
S2CTerminalUpdateDisplayedItems(
terminal,
itemEntries,
searchQuery,
settings,
scrollPosition,
totalEntries
)
)
} }
fun totalRows(): Int { fun totalRows(): Int {
@ -144,7 +151,12 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
return currentScrollOffsetInRows() * 9 return currentScrollOffsetInRows() * 9
} }
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) { fun sendUpdatedItemsToClient(
player: ServerPlayerEntity,
query: String,
settings: TerminalSettings,
scrollPosition: Float
) {
this.searchQuery = query this.searchQuery = query
this.settings = settings this.settings = settings
this.scrollPosition = scrollPosition this.scrollPosition = scrollPosition
@ -175,7 +187,8 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
// todo: why does this think it's quick_craft sometimes? // todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !cursorStack.isEmpty) { if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !cursorStack.isEmpty) {
// placing cursor stack into buffer // placing cursor stack into buffer
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index val bufferSlot =
slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK) terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
} }
} }
@ -203,8 +216,16 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED) terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
} }
} else if (isPlayerSlot(slotId)) { } else if (isPlayerSlot(slotId)) {
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK } val slotsInsertedInto = tryInsertItem(
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) } slot.stack,
bufferSlotsStart until playerSlotsStart
) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
slotsInsertedInto.forEach {
terminal.internalBuffer.markSlot(
it - bufferSlotsStart,
TerminalBufferInventory.Mode.TO_NETWORK
)
}
if (slotsInsertedInto.isEmpty()) { if (slotsInsertedInto.isEmpty()) {
return ItemStack.EMPTY return ItemStack.EMPTY
} }
@ -250,5 +271,5 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart fun isPlayerSlot(id: Int) = id >= playerSlotsStart
data class Entry(val stack: ItemStack, val amount: Int) data class Entry(val variant: ItemVariant, val amount: Int)
} }

View File

@ -19,11 +19,11 @@ import net.shadowfacts.phycon.util.TerminalSettings
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S: AbstractTerminalScreen<BE, H>, H: AbstractTerminalScreenHandler<BE>>( abstract class AbstractTerminalViewController<BE : AbstractTerminalBlockEntity, S : AbstractTerminalScreen<BE, H>, H : AbstractTerminalScreenHandler<BE>>(
val screen: S, val screen: S,
val handler: H, val handler: H,
val terminal: BE = handler.terminal, val terminal: BE = handler.terminal,
): ViewController() { ) : ViewController() {
private lateinit var scrollTrack: ScrollTrackView private lateinit var scrollTrack: ScrollTrackView
lateinit var settingsView: View lateinit var settingsView: View
@ -162,13 +162,13 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
screen.requestUpdatedItems() screen.requestUpdatedItems()
} }
class TerminalSearchField: TextField("") { class TerminalSearchField : TextField("") {
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) { override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
// no-op // no-op
} }
} }
class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>): View() { class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>) : View() {
override fun mouseScrolled(point: Point, amount: Double): Boolean { override fun mouseScrolled(point: Point, amount: Double): Boolean {
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt() var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows()) newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())

View File

@ -9,7 +9,7 @@ import net.shadowfacts.phycon.PhysicalConnectivity
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class CraftingTerminalBlock: AbstractTerminalBlock<CraftingTerminalBlockEntity>() { class CraftingTerminalBlock : AbstractTerminalBlock<CraftingTerminalBlockEntity>() {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal") val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal")

View File

@ -1,8 +1,9 @@
package net.shadowfacts.phycon.block.terminal package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.item.ItemStackCollections import it.unimi.dsi.fastutil.ints.IntBinaryOperator
import alexiil.mc.lib.attributes.item.ItemStackUtil import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
@ -17,16 +18,16 @@ import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.ItemStackPacket import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.packet.LocateStackPacket import net.shadowfacts.phycon.packet.LocateStackPacket
import net.shadowfacts.phycon.packet.RequestInventoryPacket import net.shadowfacts.phycon.util.equalsIgnoringAmount
import net.shadowfacts.phycon.util.fromTag import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.toTag import net.shadowfacts.phycon.util.toTag
import java.util.LinkedList import java.util.LinkedList
import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL, pos, state) { class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState) :
AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL, pos, state) {
val craftingInv = SimpleInventory(9) val craftingInv = SimpleInventory(9)
@ -36,7 +37,7 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
super.onActivate(player) super.onActivate(player)
if (!world!!.isClient) { if (!world!!.isClient) {
val factory = object: ExtendedScreenHandlerFactory { val factory = object : ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity) return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity)
} }
@ -52,20 +53,23 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
} }
fun requestItemsForCrafting(maxAmount: Int) { fun requestItemsForCrafting(maxAmount: Int) {
val amounts = ItemStackCollections.map<IntArray>() // use an array map because we have at most 9 items
// values are bitfields of which slots contain the item
val stackToSlotsMap = Object2IntArrayMap<ItemVariant>()
val or = IntBinaryOperator { a, b -> a or b }
for (i in 0 until craftingInv.size()) { for (i in 0 until craftingInv.size()) {
val craftingInvStack = craftingInv.getStack(i) val craftingInvStack = craftingInv.getStack(i)
if (craftingInvStack.isEmpty) continue if (craftingInvStack.isEmpty) continue
if (craftingInvStack.count >= craftingInvStack.maxCount) continue if (craftingInvStack.count >= craftingInvStack.maxCount) continue
if (craftingInvStack !in amounts) amounts[craftingInvStack] = IntArray(9) { 0 } stackToSlotsMap.mergeInt(ItemVariant.of(craftingInvStack), 1 shl i, or)
amounts[craftingInvStack]!![i] = min(maxAmount, craftingInvStack.maxCount - craftingInvStack.count)
} }
for ((stack, amountPerSlot) in amounts) { for ((variant, slots) in stackToSlotsMap) {
val total = amountPerSlot.sum() val total = slots.countOneBits()
val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot) val stack = variant.toStack()
val request = CraftingStackLocateRequest(stack, total, counter, slots)
pendingRequests.add(request) pendingRequests.add(request)
sendPacket(LocateStackPacket(stack, ipAddress)) sendPacket(LocateStackPacket(stack, ipAddress))
} }
@ -80,27 +84,29 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
} }
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val craftingReq = completedCraftingStackRequests.find { ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) } val craftingReq =
completedCraftingStackRequests.find { it.stack.equalsIgnoringAmount(packet.stack) }
if (craftingReq != null) { if (craftingReq != null) {
var remaining = packet.stack.copy() var remaining = packet.stack.copy()
for (i in 0 until craftingInv.size()) { for (i in 0 until craftingInv.size()) {
val currentStack = craftingInv.getStack(i) val currentStack = craftingInv.getStack(i)
if (currentStack.count >= currentStack.maxCount) continue if (currentStack.count >= currentStack.maxCount) continue
if (!ItemStackUtil.areEqualIgnoreAmounts(currentStack, remaining)) continue if (!currentStack.equalsIgnoringAmount(remaining)) continue
val toInsert = minOf(remaining.count, currentStack.maxCount - currentStack.count, craftingReq.amountPerSlot[i]) currentStack.count += 1
currentStack.count += toInsert remaining.count -= 1
remaining.count -= toInsert
craftingReq.amountPerSlot[i] -= toInsert craftingReq.slots = craftingReq.slots and (1 shl i).inv()
craftingReq.received += toInsert craftingReq.received += 1
if (remaining.isEmpty) { if (remaining.isEmpty) {
break break
} }
} }
if (craftingReq.amountPerSlot.sum() == 0 || craftingReq.received >= craftingReq.totalResultAmount) { // if slots == 0, there are no more slots needing items for this request
if (craftingReq.slots == 0 || craftingReq.received >= craftingReq.totalResultAmount) {
completedCraftingStackRequests.remove(craftingReq) completedCraftingStackRequests.remove(craftingReq)
} }
@ -130,8 +136,11 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
stack: ItemStack, stack: ItemStack,
amount: Int, amount: Int,
timestamp: Long, timestamp: Long,
val amountPerSlot: IntArray, /**
): StackLocateRequest(stack, amount, timestamp) { * Values are bitfields of which slots in the crafting inventory this request is for.
*/
var slots: Int,
) : StackLocateRequest(stack, amount, timestamp) {
var received: Int = 0 var received: Int = 0
} }

View File

@ -15,7 +15,7 @@ class CraftingTerminalScreen(
handler: CraftingTerminalScreenHandler, handler: CraftingTerminalScreenHandler,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text, title: Text,
): AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>( ) : AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>(
handler, handler,
playerInv, playerInv,
title, title,

View File

@ -23,7 +23,12 @@ class CraftingTerminalScreenHandler(
syncId: Int, syncId: Int,
playerInv: PlayerInventory, playerInv: PlayerInventory,
terminal: CraftingTerminalBlockEntity, terminal: CraftingTerminalBlockEntity,
): AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) { ) : AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(
PhyScreens.CRAFTING_TERMINAL,
syncId,
playerInv,
terminal
) {
val craftingInv = CraftingInv(this) val craftingInv = CraftingInv(this)
val result = CraftingResultInventory() val result = CraftingResultInventory()
@ -36,7 +41,7 @@ class CraftingTerminalScreenHandler(
override val xOffset: Int override val xOffset: Int
get() = 5 get() = 5
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
this( this(
syncId, syncId,
playerInv, playerInv,
@ -73,7 +78,14 @@ class CraftingTerminalScreenHandler(
ItemStack.EMPTY ItemStack.EMPTY
} }
result.setStack(0, resultStack) result.setStack(0, resultStack)
player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), resultSlot.id, resultStack)) player.networkHandler.sendPacket(
ScreenHandlerSlotUpdateS2CPacket(
syncId,
nextRevision(),
resultSlot.id,
resultStack
)
)
} }
} }
@ -125,7 +137,7 @@ class CraftingTerminalScreenHandler(
} }
// RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so... // RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so...
class CraftingInv(val handler: CraftingTerminalScreenHandler): CraftingInventory(handler, 3, 3) { class CraftingInv(val handler: CraftingTerminalScreenHandler) : CraftingInventory(handler, 3, 3) {
private val backing = handler.terminal.craftingInv private val backing = handler.terminal.craftingInv
override fun isEmpty(): Boolean { override fun isEmpty(): Boolean {

View File

@ -23,7 +23,7 @@ import org.lwjgl.glfw.GLFW
class CraftingTerminalViewController( class CraftingTerminalViewController(
screen: CraftingTerminalScreen, screen: CraftingTerminalScreen,
handler: CraftingTerminalScreenHandler, handler: CraftingTerminalScreenHandler,
): AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>( ) : AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>(
screen, screen,
handler, handler,
) { ) {
@ -57,7 +57,7 @@ class CraftingTerminalViewController(
} }
val clearIcon = TextureView(CLEAR_ICON).apply { val clearIcon = TextureView(CLEAR_ICON).apply {
intrinsicContentSize = Size(3.0,3.0) intrinsicContentSize = Size(3.0, 3.0)
} }
val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply { val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply {
background = TextureView(SMALL_BUTTON) background = TextureView(SMALL_BUTTON)
@ -73,7 +73,7 @@ class CraftingTerminalViewController(
intrinsicContentSize = Size(3.0, 3.0) intrinsicContentSize = Size(3.0, 3.0)
} }
val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply { val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply {
background= TextureView(SMALL_BUTTON) background = TextureView(SMALL_BUTTON)
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED) hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
tooltip = TranslatableText("gui.phycon.terminal.more_crafting") tooltip = TranslatableText("gui.phycon.terminal.more_crafting")
} }
@ -84,7 +84,12 @@ class CraftingTerminalViewController(
} }
private fun clearPressed(button: Button) { private fun clearPressed(button: Button) {
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, C2STerminalCraftingButton.Action.CLEAR_GRID)) MinecraftClient.getInstance().player!!.networkHandler.sendPacket(
C2STerminalCraftingButton(
terminal,
C2STerminalCraftingButton.Action.CLEAR_GRID
)
)
} }
private fun plusPressed(button: Button) { private fun plusPressed(button: Button) {

View File

@ -17,12 +17,12 @@ import java.util.EnumMap
*/ */
class SettingButton<E>( class SettingButton<E>(
val key: TerminalSettings.SettingKey<E>, val key: TerminalSettings.SettingKey<E>,
): AbstractButton<SettingButton<E>>( ) : AbstractButton<SettingButton<E>>(
TextureView(null).apply { TextureView(null).apply {
intrinsicContentSize = Size(16.0, 16.0) intrinsicContentSize = Size(16.0, 16.0)
}, },
padding = 2.0 padding = 2.0
) where E: Enum<E>, E: TerminalSetting { ) where E : Enum<E>, E : TerminalSetting {
private val textureCache = EnumMap<E, Texture>(key.clazz) private val textureCache = EnumMap<E, Texture>(key.clazz)

View File

@ -8,7 +8,7 @@ import net.shadowfacts.phycon.PhysicalConnectivity
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBlock: AbstractTerminalBlock<TerminalBlockEntity>() { class TerminalBlock : AbstractTerminalBlock<TerminalBlockEntity>() {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal") val ID = Identifier(PhysicalConnectivity.MODID, "terminal")

View File

@ -10,18 +10,18 @@ import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.TranslatableText import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.RequestInventoryPacket
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL, pos, state) { class TerminalBlockEntity(pos: BlockPos, state: BlockState) :
AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL, pos, state) {
override fun onActivate(player: PlayerEntity) { override fun onActivate(player: PlayerEntity) {
super.onActivate(player) super.onActivate(player)
if (!world!!.isClient) { if (!world!!.isClient) {
val factory = object: ExtendedScreenHandlerFactory { val factory = object : ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler { override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity) return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity)
} }

View File

@ -1,10 +1,10 @@
package net.shadowfacts.phycon.block.terminal package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.minecraft.inventory.SimpleInventory import net.minecraft.inventory.SimpleInventory
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtIntArray import net.minecraft.nbt.NbtIntArray
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import net.shadowfacts.phycon.util.fromTag import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.toTag import net.shadowfacts.phycon.util.toTag
import kotlin.math.min import kotlin.math.min
@ -12,7 +12,7 @@ import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalBufferInventory(size: Int): SimpleInventory(size) { class TerminalBufferInventory(size: Int) : SimpleInventory(size) {
enum class Mode { enum class Mode {
TO_NETWORK, FROM_NETWORK, UNASSIGNED TO_NETWORK, FROM_NETWORK, UNASSIGNED
@ -54,7 +54,7 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
setStack(slot, stack) setStack(slot, stack)
markSlot(slot, mode) markSlot(slot, mode)
return ItemStack.EMPTY return ItemStack.EMPTY
} else if (ItemStackUtil.areEqualIgnoreAmounts(stack, current)) { } else if (stack.equalsIgnoringAmount(current)) {
val toTransfer = min(current.maxCount - current.count, stack.count) val toTransfer = min(current.maxCount - current.count, stack.count)
current.count += toTransfer current.count += toTransfer
stack.count -= toTransfer stack.count -= toTransfer

View File

@ -8,7 +8,7 @@ import net.minecraft.item.ItemStack
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int): Slot(fakeInv, slot, x, y) { class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int) : Slot(fakeInv, slot, x, y) {
override fun canInsert(stack: ItemStack): Boolean { override fun canInsert(stack: ItemStack): Boolean {
return false return false
@ -23,7 +23,7 @@ class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int): Slot(
} }
class FakeInventory(val screenHandler: AbstractTerminalScreenHandler<*>): Inventory { class FakeInventory(val screenHandler: AbstractTerminalScreenHandler<*>) : Inventory {
override fun getStack(slot: Int): ItemStack { override fun getStack(slot: Int): ItemStack {
if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
return screenHandler.itemsForDisplay[slot] return screenHandler.itemsForDisplay[slot]

View File

@ -21,10 +21,11 @@ import kotlin.math.floor
class TerminalRequestAmountViewController( class TerminalRequestAmountViewController(
val screen: AbstractTerminalScreen<*, *>, val screen: AbstractTerminalScreen<*, *>,
val stack: ItemStack, val stack: ItemStack,
): ViewController() { ) : ViewController() {
companion object { companion object {
private val BACKGROUND = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png"), 0, 0) private val BACKGROUND =
Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png"), 0, 0)
} }
lateinit var field: NumberField lateinit var field: NumberField
@ -137,7 +138,7 @@ class TerminalRequestAmountViewController(
screen.amountVC = null screen.amountVC = null
} }
class AmountField(val vc: TerminalRequestAmountViewController): NumberField(1) { class AmountField(val vc: TerminalRequestAmountViewController) : NumberField(1) {
override fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean { override fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
return if (keyCode == GLFW.GLFW_KEY_ENTER) { return if (keyCode == GLFW.GLFW_KEY_ENTER) {
vc.doRequest() vc.doRequest()

View File

@ -12,7 +12,7 @@ class TerminalScreen(
handler: TerminalScreenHandler, handler: TerminalScreenHandler,
playerInv: PlayerInventory, playerInv: PlayerInventory,
title: Text, title: Text,
): AbstractTerminalScreen<TerminalBlockEntity, TerminalScreenHandler>( ) : AbstractTerminalScreen<TerminalBlockEntity, TerminalScreenHandler>(
handler, handler,
playerInv, playerInv,
title, title,

View File

@ -12,9 +12,9 @@ class TerminalScreenHandler(
syncId: Int, syncId: Int,
playerInv: PlayerInventory, playerInv: PlayerInventory,
terminal: TerminalBlockEntity, terminal: TerminalBlockEntity,
): AbstractTerminalScreenHandler<TerminalBlockEntity>(PhyScreens.TERMINAL, syncId, playerInv, terminal) { ) : AbstractTerminalScreenHandler<TerminalBlockEntity>(PhyScreens.TERMINAL, syncId, playerInv, terminal) {
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
this( this(
syncId, syncId,
playerInv, playerInv,

View File

@ -6,7 +6,7 @@ package net.shadowfacts.phycon.block.terminal
class TerminalViewController( class TerminalViewController(
screen: TerminalScreen, screen: TerminalScreen,
handler: TerminalScreenHandler, handler: TerminalScreenHandler,
): AbstractTerminalViewController<TerminalBlockEntity, TerminalScreen, TerminalScreenHandler>( ) : AbstractTerminalViewController<TerminalBlockEntity, TerminalScreen, TerminalScreenHandler>(
screen, screen,
handler, handler,
) { ) {

Some files were not shown because too many files have changed in this diff Show More