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 {
id "fabric-loom" version "0.12.9"
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
@ -79,9 +79,9 @@ repositories {
dependencies {
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
// You may need to force-disable transitiveness on them.
modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-items:${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-items:${project.libblockattributes_version}"
implementation project(":kiwi-java")
include project(":kiwi-java")

View File

@ -39,7 +39,8 @@ object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTermin
AbstractTerminalScreen.searchQueryListener = this
try {
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) {
logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e)
}

View File

@ -25,7 +25,11 @@ object PhyConPluginCommon: REIServerPlugin, PhyConPlugin {
private set
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) {

View File

@ -18,11 +18,20 @@ object PhyConTR: ModInitializer {
override fun onInitialize() {
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 ->
to.offer(StorageUnitWrapper(su))
}

View File

@ -16,6 +16,7 @@ public interface Interface {
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.
*
* <p>
* 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.
*
* @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();
@Nullable Text getTooltip();
@Nullable
Text getTooltip();
}

View File

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

View File

@ -22,6 +22,7 @@ public final class MACAddress {
}
private static final Random macAddressRandom = new Random();
@NotNull
public static MACAddress random() {
return random(macAddressRandom);

View File

@ -24,6 +24,7 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
// _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods.
private val _windows = LinkedList<Window>()
/**
* The list of windows that belong to this screen.
*

View File

@ -1,9 +1,12 @@
# Cacao
Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API)
UI toolkit.
## Architecture
### Screen
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
@ -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
### Window
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.
@ -21,6 +25,7 @@ view hierarchy.
[Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt
### View Controller
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.
@ -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
### View
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 File

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

View File

@ -28,6 +28,11 @@ class LayoutGuide(
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
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

@ -48,13 +48,27 @@ object RenderHelper: DrawableHelper() {
if (disabled) return
RenderSystem.setShader(GameRenderer::getPositionTexShader)
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) {
if (disabled) return
RenderSystem.lineWidth(width)
RenderSystem.enableBlend()
RenderSystem.disableTexture()
RenderSystem.defaultBlendFunc()
RenderSystem.setShader(GameRenderer::getPositionColorShader)
val tessellator = Tessellator.getInstance()
val buffer = tessellator.buffer
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.
*/
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
val uStart = u.toFloat() / textureWidth
val uEnd = (u + width).toFloat() / textureWidth
val vStart = v.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
// 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
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE)
bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next()

View File

@ -15,7 +15,13 @@ import net.minecraft.util.Identifier
* @param centerWidth The width 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 {
val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156)

View File

@ -26,24 +26,44 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
protected open val topLeft by topLeftDelegate
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
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
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
// Edges
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
@ -53,7 +73,12 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
protected open val bottomMiddle by bottomMiddleDelegate
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
@ -69,7 +94,17 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
}
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() {
super.didLayout()
@ -93,30 +128,114 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
private fun drawEdges(matrixStack: MatrixStack) {
// Horizontal
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(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)
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(
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
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(matrixStack, bottomMiddle.right - remWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
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(
matrixStack,
bottomMiddle.right - remWidth,
bottomMiddle.top,
ninePatch.bottomMiddle.u,
ninePatch.bottomMiddle.v,
remWidth.toDouble(),
ninePatch.cornerHeight.toDouble(),
ninePatch.texture.width,
ninePatch.texture.height
)
}
// Vertical
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(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)
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(
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
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(matrixStack, rightMiddle.left, rightMiddle.bottom - remHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
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(
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) {
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
if (remHeight > 0) {
@ -126,11 +245,31 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) {
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
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

@ -29,6 +29,7 @@ open class StackView(
// the internal, mutable list of arranged subviews
private val _arrangedSubviews = LinkedList<View>()
/**
* The list of arranged subviews belonging to this stack view.
* This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview]
@ -139,10 +140,16 @@ open class StackView(
val previous = arrangedSubviews.getOrNull(index - 1)
val next = arrangedSubviews.getOrNull(index + 1)
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) {
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) {
Distribution.LEADING ->
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
Distribution.TRAILING ->
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
Distribution.FILL -> {
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
}
Distribution.CENTER ->
perpAnchor(CENTER) equalTo perpAnchor(CENTER, view)
}
@ -165,6 +175,7 @@ open class StackView(
private fun anchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis, position)
}
private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable {
return view.getAnchor(axis.perpendicular, position)
}
@ -199,6 +210,7 @@ open class StackView(
* ```
*/
LEADING,
/**
* The centers of the arranged subviews are pinned to the center of the stack view.
* ```
@ -222,6 +234,7 @@ open class StackView(
* ```
*/
CENTER,
/**
* The trailing edges of arranged subviews are pinned to the leading edge of the stack view.
* ```
@ -245,6 +258,7 @@ open class StackView(
* ```
*/
TRAILING,
/**
* The arranged subviews fill the perpendicular axis of the stack view.
* ```

View File

@ -42,6 +42,7 @@ open class View(): Responder {
v.solver = it
}
}
/**
* 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.
@ -55,30 +56,37 @@ open class View(): Responder {
* Layout anchor for the left edge of this view in the window's coordinate system.
*/
val leftAnchor = LayoutVariable(this, "left")
/**
* Layout anchor for the right edge of this view in the window's coordinate system.
*/
val rightAnchor = LayoutVariable(this, "right")
/**
* Layout anchor for the top edge of this view in the window's coordinate system.
*/
val topAnchor = LayoutVariable(this, "top")
/**
* Layout anchor for the bottom edge of this view in the window's coordinate system.
*/
val bottomAnchor = LayoutVariable(this, "bottom")
/**
* Layout anchor for the width of this view in the window's coordinate system.
*/
val widthAnchor = LayoutVariable(this, "width")
/**
* Layout anchor for the height of this view in the window's coordinate system.
*/
val heightAnchor = LayoutVariable(this, "height")
/**
* Layout anchor for the center X position of this view in the window's coordinate system.
*/
val centerXAnchor = LayoutVariable(this, "centerX")
/**
* 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].
*/
var superview: View? = null
// _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 var subviewsSortedByZIndex: List<View> = listOf()
@ -181,6 +190,7 @@ open class View(): Responder {
AxisPosition.CENTER -> centerXAnchor
AxisPosition.TRAILING -> rightAnchor
}
Axis.VERTICAL ->
when (position) {
AxisPosition.LEADING -> topAnchor
@ -240,7 +250,11 @@ open class View(): Responder {
for (b in a + 1 until variables.size) {
// 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
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) {
return@filter true
}
@ -350,7 +364,12 @@ open class View(): Responder {
if (usesConstraintBasedLayout) {
val superviewLeft = superview?.leftAnchor?.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)
}

View File

@ -51,6 +51,7 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
field = value
value?.also(::addBackground)
}
/**
* The background to draw when the button is hovered over by the mouse.
* If `null`, the normal [background] will be used.
@ -62,6 +63,7 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
field = value
value?.also(::addBackground)
}
/**
* The background to draw when the button is [disabled].
* If `null`, the normal [background] will be used.

View File

@ -159,7 +159,12 @@ private class DropdownItemBackgroundView(
override val topRight by topRightDelegate
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
@ -180,7 +185,12 @@ private class DropdownItemBackgroundView(
override val bottomMiddle by bottomMiddleDelegate
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
@ -195,7 +205,17 @@ private class DropdownItemBackgroundView(
}
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() {
super.didLayout()

View File

@ -166,7 +166,8 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
}
// 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
fun setHeight(height: Int) {
this.height = height

View File

@ -99,6 +99,7 @@ class TabViewController<T: TabViewController.Tab>(
private lateinit var outerStack: StackView
private lateinit var tabStack: StackView
private lateinit var currentTabController: ViewController
// todo: this shouldn't be public, use layout guides
lateinit var tabVCContainer: View
private set

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
private var _children = LinkedList<ViewController>()
/**
* The list of all the child VCs of this view controller.
* This list should never be mutated directly, only by the [embedChild]/[removeChild] methods.

View File

@ -48,30 +48,37 @@ open class Window(
* Layout anchor for the left edge of this view in the window's coordinate system.
*/
val leftAnchor = Variable("left")
/**
* Layout anchor for the right edge of this view in the window's coordinate system.
*/
val rightAnchor = Variable("right")
/**
* Layout anchor for the top edge of this view in the window's coordinate system.
*/
val topAnchor = Variable("top")
/**
* Layout anchor for the bottom edge of this view in the window's coordinate system.
*/
val bottomAnchor = Variable("bottom")
/**
* Layout anchor for the width of this view in the window's coordinate system.
*/
val widthAnchor = Variable("width")
/**
* Layout anchor for the height of this view in the window's coordinate system.
*/
val heightAnchor = Variable("height")
/**
* Layout anchor for the center X position of this view in the window's coordinate system.
*/
val centerXAnchor = Variable("centerX")
/**
* 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 {
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 {
@ -25,7 +26,8 @@ class KiwiContext(val solver: Solver) {
}
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 {
@ -33,7 +35,8 @@ class KiwiContext(val solver: Solver) {
}
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 {
@ -41,7 +44,8 @@ class KiwiContext(val solver: Solver) {
}
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 {
@ -49,7 +53,8 @@ class KiwiContext(val solver: Solver) {
}
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 {
@ -57,7 +62,8 @@ class KiwiContext(val solver: Solver) {
}
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

View File

@ -15,7 +15,8 @@ object DefaultPlugin: PhyConPlugin {
private set
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)
}

View File

@ -11,7 +11,10 @@ import net.shadowfacts.phycon.util.TerminalSettings
*/
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)
}

View File

@ -33,7 +33,10 @@ object PhysicalConnectivity: ModInitializer {
registerGlobalReceiver(C2STerminalRequestItem)
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)) {
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.material.RenderMaterial
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.terminal.CraftingTerminalScreen
import net.shadowfacts.phycon.init.PhyScreens
@ -44,6 +45,7 @@ object PhysicalConnectivityClient: ClientModInitializer {
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
ScreenRegistry.register(PhyScreens.SWITCH_CONSOLE, ::SwitchConsoleScreen)
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
}

View File

@ -15,11 +15,21 @@ import net.shadowfacts.phycon.api.NetworkComponentBlock
/**
* @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)!!
}
@ -28,7 +38,11 @@ abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithE
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) {
null
} else {

View File

@ -28,7 +28,8 @@ import java.util.*
/**
* @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,
PacketSource,
Interface {
@ -60,6 +61,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
is DeviceRemovedPacket -> {
arpTable.remove(packet.source)
}
is PingPacket -> {
sendPacket(PongPacket(ipAddress, packet.source))
}
@ -72,7 +74,14 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
}
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) {
is ARPQueryFrame -> handleARPQuery(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)
arpTable[frame.sourceIP] = frame.source
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))
}
}
private fun handleARPResponse(frame: ARPResponseFrame) {
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, _) ->
if (packet.destination == frame.query) {
@ -122,7 +143,12 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
} else {
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))
}
}
@ -141,6 +167,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
this.cachedDestination = WeakReference(it)
}
}
else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
}
}

View File

@ -82,7 +82,11 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
if (cableConnection == FaceCableConnection.NONE) {
VoxelShapes.union(faceShapes[facing], centerShapes[facing])
} 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()
}
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]) {
null
} else {
@ -108,15 +117,22 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
}
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
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
.with(FACING, facing)
.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()) {
if (side == facing) {
continue
@ -130,18 +146,32 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
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
return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) {
true
} else if (block is CableBlock && block.color == myColor) {
true
} 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]
var newConnection = current
@ -159,7 +189,12 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
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])
}

View File

@ -63,7 +63,8 @@ class CableBlock(
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 {
val key = Direction.values().foldIndexed(0) { i, acc, dir ->
@ -110,7 +111,14 @@ class CableBlock(
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 current = state[prop]
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 state = world.getBlockState(offsetPos)
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
return null
}
@ -169,11 +186,18 @@ class CableBlock(
val connectedToPos = pos.offset(side)
val connectedTo = world.getBlockState(connectedToPos)
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)
}
world.setBlockState(pos, newState)
@ -195,7 +219,12 @@ class CableBlock(
// 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)
}

View File

@ -59,7 +59,14 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
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) }
}
@ -71,16 +78,4 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
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
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.FixedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
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.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
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.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.copyWithCount
import kotlin.math.min
import kotlin.properties.Delegates
/**
* @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>,
ActivationController.ActivatableDevice,
ClientConfigurableDevice {
@ -40,20 +44,19 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
private val facing: Direction
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 dispatchStackTimeout = 1L
override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() {
private fun getInventory(): Storage<ItemVariant>? {
if (inventory == null) {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
val cachedFacedBlock = world!!.getBlockState(offsetPos)
val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos)
this.inventory = Pair(cachedFacedBlock, inventory)
}
private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory()
return inventory
return inventory!!.second.find(inventory!!.first, facing.opposite)
}
override fun handle(packet: Packet) {
@ -75,10 +78,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
val inventory = getInventory() ?: return insertion.stack
// if the inventory has changed, the old slot index is meaningless
if (inventory !== insertion.inventory) return insertion.stack
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
if (extracted.isEmpty) return insertion.stack
val transaction = Transaction.openOuter()
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
insertion.stack = extracted
insertion.stack = insertion.stack.copyWithCount(extractedAmount)
}
return super.finishInsertion(insertion)
}
@ -94,14 +102,15 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
override fun activate(): Boolean {
val inventory = getInventory() ?: return false
for (slot in 0 until inventory.slotCount) {
val slotStack = inventory.getInvStack(slot)
if (slotStack.isEmpty) continue
val extractable = inventory.extractStack(slot, ExactItemStackFilter(slotStack), ItemStack.EMPTY, slotStack.count, Simulation.SIMULATE)
if (extractable.isEmpty) continue
dispatchItemStack(extractable) { insertion ->
for (view in inventory.iterator(null)) {
if (view.amount <= 0) continue
val transaction = Transaction.openOuter()
var extractableAmount = inventory.simulateExtract(view.resource, view.amount, transaction).toInt()
transaction.close()
if (extractableAmount <= 0) continue
extractableAmount = min(extractableAmount, view.resource.item.maxCount)
dispatchItemStack(view.resource.toStack(extractableAmount)) { insertion ->
insertion.inventory = inventory
insertion.inventorySlot = slot
}
return true
}
@ -126,8 +135,9 @@ class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: FixedItemInv
class PendingInsertion(stack: ItemStack, timestamp: Long) :
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: Storage<ItemVariant>
var inventorySlot by Delegates.notNull<Int>()
}
}

View File

@ -68,7 +68,14 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
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) }
}
@ -80,19 +87,14 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(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, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
}
}
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 {
if (!world.isClient) {
val be = getBlockEntity(world, pos)!!

View File

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

View File

@ -38,7 +38,12 @@ class MinerBlock: DeviceBlock<MinerBlockEntity>(
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]) {
null
} 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) {
// getBlockEntity(world, pos)!!.updateBlockToMine()
}

View File

@ -1,8 +1,10 @@
package net.shadowfacts.phycon.block.miner
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import alexiil.mc.lib.attributes.item.ItemStackUtil
import alexiil.mc.lib.attributes.item.filter.ItemFilter
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView
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.BlockState
import net.minecraft.item.ItemStack
@ -22,6 +24,7 @@ import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.copyWithCount
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import kotlin.math.min
/**
@ -36,7 +39,7 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
private val facing: Direction
get() = cachedState[MinerBlock.FACING]
private val invProxy = MinerInvProxy(this)
private val invProxy = MinerStorageProxy(this)
override val pendingInsertions = mutableListOf<PendingInsertion>()
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) {
return
}
sendPacket(ReadGroupedInventoryPacket(invProxy, ipAddress, packet.source))
sendPacket(ReadItemStoragePacket(invProxy, ipAddress, packet.source))
}
private fun handleLocateStack(packet: LocateStackPacket) {
if (minerMode != MinerMode.ON_DEMAND) {
return
}
val amount = invProxy.getAmount(packet.stack)
val amount = invProxy.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, null)
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
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)
// send the requested amount back to the requester
@ -89,7 +93,7 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
if (remaining <= 0) {
break
}
if (!ItemStackUtil.areEqualIgnoreAmounts(droppedStack, packet.stack)) {
if (!droppedStack.equalsIgnoringAmount(packet.stack)) {
continue
}
@ -184,13 +188,15 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
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 {
val TOOL = ItemStack(Items.DIAMOND_PICKAXE)
}
private var cachedState: BlockState? = null
private var cachedDrops: List<ItemStack>? = null
private var cache: Pair<BlockState, List<ItemStack>>? = null
private val world: World
get() = miner.world!!
@ -202,39 +208,78 @@ class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyB
fun getDrops(recalculate: Boolean = false): List<ItemStack> {
val targetPos = pos.offset(facing)
val realState = world.getBlockState(targetPos)
val cache = this.cache
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
if (cachedDrops == null || realState != cachedState || recalculate) {
cachedState = realState
if (cache == null || realState != cache.first || recalculate) {
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> {
if (miner.minerMode != MinerMode.ON_DEMAND) {
return setOf()
}
return getDrops().toSet()
override fun createSnapshot(): Pair<BlockState, List<ItemStack>>? {
return cache
}
override fun getTotalCapacity(): Int {
return Int.MAX_VALUE
override fun readSnapshot(snapshot: Pair<BlockState, List<ItemStack>>?) {
cache = snapshot
}
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
var totalCount = 0
for (s in storedStacks) {
if (filter.matches(s)) {
totalCount += s.count
override fun extract(resource: ItemVariant, maxAmount: Long, transaction: TransactionContext): Long {
val drops = getDrops()
val (matched, unmatched) = drops.partition { resource.matches(it) }
if (matched.isEmpty()) {
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
import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.*
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
@ -23,8 +21,7 @@ class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>(
.strength(1.5f)
.sounds(BlockSoundGroup.METAL)
),
NetworkComponentBlock,
AttributeProvider {
NetworkComponentBlock {
companion object {
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 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
import alexiil.mc.lib.attributes.SearchOptions
import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
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.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
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.packet.*
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import java.lang.ref.WeakReference
import net.shadowfacts.phycon.util.copyWithCount
import kotlin.math.min
/**
* @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,
NetworkStackProvider,
NetworkStackReceiver,
@ -38,21 +41,17 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
override var receiverPriority = 0
var syncPriorities = true
private var inventory: WeakReference<GroupedItemInv>? = null
private var cachedFacedBlock: BlockState? = null
private var inventoryCache: BlockApiCache<Storage<ItemVariant>, Direction>? = null
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option).let {
WeakReference(it)
private fun getInventory(): Storage<ItemVariant>? {
if (cachedFacedBlock == null) {
cachedFacedBlock = world!!.getBlockState(pos.offset(facing))
}
if (inventoryCache == null) {
inventoryCache = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, pos.offset(facing))
}
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()
return inventoryCache!!.find(cachedFacedBlock!!, facing.opposite)
}
override fun handle(packet: Packet) {
@ -70,46 +69,50 @@ class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(
return
}
getInventory()?.also { inv ->
sendPacket(ReadGroupedInventoryPacket(inv, ipAddress, packet.source))
sendPacket(ReadItemStoragePacket(inv, ipAddress, packet.source))
}
}
private fun handleLocateStack(packet: LocateStackPacket) {
getInventory()?.also { inv ->
val amount = inv.getAmount(packet.stack)
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
val transaction = Transaction.openOuter()
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) {
getInventory()?.also { inv ->
var amount = packet.amount
while (amount > 0) {
val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount))
if (extracted.isEmpty) {
break
} else {
amount -= extracted.count
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
}
var remaining = packet.amount
while (remaining > 0) {
val toExtract = min(remaining, packet.stack.maxCount)
val transaction = Transaction.openOuter()
val extracted = inv.extract(ItemVariant.of(packet.stack), toExtract.toLong(), transaction)
transaction.commit()
remaining -= extracted.toInt()
sendPacket(ItemStackPacket(packet.stack.copyWithCount(extracted.toInt()), ipAddress, packet.source))
}
}
}
private fun handleCheckCapacity(packet: CheckCapacityPacket) {
getInventory()?.also { inv ->
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
val couldAccept = packet.stack.count - remaining.count
sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source))
val transaction = Transaction.openOuter()
val inserted = inv.simulateInsert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
transaction.close()
sendPacket(CapacityPacket(packet.stack, inserted.toInt(), this, ipAddress, packet.source))
}
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val inventory = getInventory()
if (inventory != null) {
val remaining = inventory.insert(packet.stack)
// whatever could not be inserted will be sent back to the packet's source
return remaining
val transaction = Transaction.openOuter()
val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
transaction.commit()
val remaining = packet.stack.count - inserted.toInt()
return packet.stack.copyWithCount(remaining)
} else {
return packet.stack
}

View File

@ -1,7 +1,5 @@
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.Material
import net.minecraft.block.entity.BlockEntity
@ -11,7 +9,6 @@ import net.minecraft.sound.BlockSoundGroup
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
@ -28,8 +25,7 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
.strength(1.5f)
.sounds(BlockSoundGroup.METAL)
),
NetworkComponentBlock,
AttributeProvider {
NetworkComponentBlock {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "switch")
@ -39,13 +35,22 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
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 }
}
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) {
null
} 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.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.util.IntRingBuffer
import net.shadowfacts.phycon.util.NetworkUtil
import java.lang.ref.WeakReference
import java.util.Deque
@ -38,9 +39,13 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
private val macTable = mutableMapOf<MACAddress, Direction>()
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 delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
var statisticsObserver: (() -> Unit)? = null
fun interfaceForSide(side: Direction): SwitchInterface {
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) {
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(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)
} else {
flood(frame, fromItf)
@ -72,7 +84,13 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
}
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) {
if (source == itf) continue
itf.send(frame)
@ -98,6 +116,16 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
}
fun tick() {
if (statisticsObserver != null) {
if (currentSecondPacketStatistics.size == 20) {
packetStatistics.add(currentSecondPacketStatistics.sum())
currentSecondPacketStatistics.clear()
statisticsObserver?.invoke()
} else {
currentSecondPacketStatistics.add(packetsHandledThisTick)
}
}
packetsHandledThisTick = 0
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
@ -147,6 +175,11 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
delayedPackets.addLast(frame to fromItf)
}
}
tag.getIntArray("PacketStatistics")?.also { statistics ->
if (statistics.isNotEmpty()) {
packetStatistics.replace(statistics)
}
}
}
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
@ -156,6 +189,7 @@ class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockE
override fun toInitialChunkDataNbt(): NbtCompound {
val tag = NbtCompound()
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
tag.putIntArray("PacketStatistics", packetStatistics.asContiguousArray())
return tag
}

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

@ -15,7 +15,8 @@ import net.shadowfacts.phycon.packet.RequestInventoryPacket
/**
* @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

View File

@ -21,7 +21,8 @@ import java.lang.ref.WeakReference
/**
* @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 {
enum class Status {

View File

@ -51,7 +51,14 @@ class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>(
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
if (!world.isClient) {
val wasLit = state[POWERED]

View File

@ -14,7 +14,8 @@ import net.shadowfacts.phycon.util.RedstoneMode
/**
* @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 {
var managedDevices = Array<IPAddress?>(5) { null }

View File

@ -51,7 +51,12 @@ class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
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]) {
getBlockEntity(world, pos)!!.cachedEmittedPower
} else {
@ -59,11 +64,23 @@ 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)
}
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) {
val be = getBlockEntity(world, pos)!!

View File

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

View File

@ -90,7 +90,12 @@ class RedstoneEmitterScreen(
view.addSubview(hStack)
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
}
zeroStack.addArrangedSubview(View())

View File

@ -1,7 +1,5 @@
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.BlockState
import net.minecraft.block.Material
@ -12,15 +10,11 @@ import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties
import net.minecraft.util.ActionResult
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.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.DeviceBlock
import java.util.EnumSet
@ -33,8 +27,7 @@ abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBloc
.strength(1.5f)
.sounds(BlockSoundGroup.METAL)
),
NetworkComponentBlock,
AttributeProvider {
NetworkComponentBlock {
companion object {
val FACING = Properties.FACING
@ -55,7 +48,14 @@ abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBloc
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)
return ActionResult.SUCCESS
}
@ -68,8 +68,4 @@ abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBloc
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
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
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.entity.BlockEntityType
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.block.DeviceBlockEntity
import net.shadowfacts.phycon.component.*
import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.NetworkUtil
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import java.lang.ref.WeakReference
import java.util.*
import java.util.function.IntBinaryOperator
@ -30,7 +31,8 @@ import kotlin.properties.Delegates
/**
* @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,
ItemStackPacketHandler,
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
@ -43,14 +45,14 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
}
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
protected val inventoryCache = mutableMapOf<IPAddress, Storage<ItemVariant>>()
val internalBuffer = TerminalBufferInventory(18)
protected val pendingRequests = LinkedList<StackLocateRequest>()
override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = INSERTION_TIMEOUT
val cachedNetItems = ItemStackCollections.intMap()
val cachedNetItems = Object2IntOpenHashMap<ItemVariant>()
private var requestInventoryTimestamp: Long? = null
// 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) {
when (packet) {
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
is ReadItemStoragePacket -> handleReadItemStorage(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(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
updateAndSync()
}
@ -97,7 +99,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
private fun handleStackLocation(packet: StackLocationPacket) {
val request = pendingRequests.firstOrNull {
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
it.stack.equalsIgnoringAmount(packet.stack)
}
if (request != null) {
request.results.add(packet.amount to packet.stackProvider)
@ -136,10 +138,12 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockP
private fun updateNetItems() {
cachedNetItems.clear()
for (inventory in inventoryCache.values) {
for (stack in inventory.storedStacks) {
val amount = inventory.getAmount(stack)
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b })
val transaction = Transaction.openOuter()
for (view in inventory.iterator(transaction)) {
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()
}
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>()
}

View File

@ -80,7 +80,13 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
fun requestUpdatedItems() {
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) {
@ -115,10 +121,12 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
format.format(amount / 1_000.0) + "K"
}
amount < 1_000_000_000 -> {
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
format.format(amount / 1_000_000.0) + "M"
}
else -> {
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 textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
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()
immediate.draw()
}

View File

@ -1,24 +1,20 @@
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.SlotActionType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
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.util.SortMode
import net.shadowfacts.phycon.util.TerminalSettings
import net.shadowfacts.phycon.util.copyWithCount
import net.shadowfacts.phycon.util.name
import java.lang.ref.WeakReference
import kotlin.math.ceil
import kotlin.math.max
@ -49,7 +45,7 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
field = value
if (terminal.world!!.isClient) {
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
}
}
// 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)
}
@ -125,7 +123,16 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
// 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 {
@ -144,7 +151,12 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
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.settings = settings
this.scrollPosition = scrollPosition
@ -175,7 +187,8 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
// todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !cursorStack.isEmpty) {
// 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)
}
}
@ -203,8 +216,16 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
}
} else if (isPlayerSlot(slotId)) {
val slotsInsertedInto = tryInsertItem(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) }
val slotsInsertedInto = tryInsertItem(
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()) {
return ItemStack.EMPTY
}
@ -250,5 +271,5 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
fun isBufferSlot(id: Int) = id in bufferSlotsStart until 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

@ -1,8 +1,9 @@
package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil
import it.unimi.dsi.fastutil.ints.IntBinaryOperator
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
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.entity.player.PlayerEntity
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.packet.ItemStackPacket
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.toTag
import java.util.LinkedList
import kotlin.math.min
/**
* @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)
@ -52,20 +53,23 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
}
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()) {
val craftingInvStack = craftingInv.getStack(i)
if (craftingInvStack.isEmpty) continue
if (craftingInvStack.count >= craftingInvStack.maxCount) continue
if (craftingInvStack !in amounts) amounts[craftingInvStack] = IntArray(9) { 0 }
amounts[craftingInvStack]!![i] = min(maxAmount, craftingInvStack.maxCount - craftingInvStack.count)
stackToSlotsMap.mergeInt(ItemVariant.of(craftingInvStack), 1 shl i, or)
}
for ((stack, amountPerSlot) in amounts) {
val total = amountPerSlot.sum()
val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot)
for ((variant, slots) in stackToSlotsMap) {
val total = slots.countOneBits()
val stack = variant.toStack()
val request = CraftingStackLocateRequest(stack, total, counter, slots)
pendingRequests.add(request)
sendPacket(LocateStackPacket(stack, ipAddress))
}
@ -80,27 +84,29 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
}
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) {
var remaining = packet.stack.copy()
for (i in 0 until craftingInv.size()) {
val currentStack = craftingInv.getStack(i)
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 += toInsert
remaining.count -= toInsert
craftingReq.amountPerSlot[i] -= toInsert
craftingReq.received += toInsert
currentStack.count += 1
remaining.count -= 1
craftingReq.slots = craftingReq.slots and (1 shl i).inv()
craftingReq.received += 1
if (remaining.isEmpty) {
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)
}
@ -130,7 +136,10 @@ class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTer
stack: ItemStack,
amount: Int,
timestamp: Long,
val amountPerSlot: IntArray,
/**
* 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
}

View File

@ -23,7 +23,12 @@ class CraftingTerminalScreenHandler(
syncId: Int,
playerInv: PlayerInventory,
terminal: CraftingTerminalBlockEntity,
): AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) {
) : AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(
PhyScreens.CRAFTING_TERMINAL,
syncId,
playerInv,
terminal
) {
val craftingInv = CraftingInv(this)
val result = CraftingResultInventory()
@ -73,7 +78,14 @@ class CraftingTerminalScreenHandler(
ItemStack.EMPTY
}
result.setStack(0, resultStack)
player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), resultSlot.id, resultStack))
player.networkHandler.sendPacket(
ScreenHandlerSlotUpdateS2CPacket(
syncId,
nextRevision(),
resultSlot.id,
resultStack
)
)
}
}

View File

@ -84,7 +84,12 @@ class CraftingTerminalViewController(
}
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) {

View File

@ -10,12 +10,12 @@ import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.RequestInventoryPacket
/**
* @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) {
super.onActivate(player)

View File

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

View File

@ -24,7 +24,8 @@ class TerminalRequestAmountViewController(
) : ViewController() {
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

View File

@ -86,7 +86,12 @@ class ColoredCableModel(
rotationContainer: ModelBakeSettings,
modelId: Identifier
): BakedModel {
centerSprite = textureGetter.apply(SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/cable/${color.getName()}/straight")))
centerSprite = textureGetter.apply(
SpriteIdentifier(
PlayerScreenHandler.BLOCK_ATLAS_TEXTURE,
Identifier(PhysicalConnectivity.MODID, "block/cable/${color.getName()}/straight")
)
)
sideRotations.forEach { (side, rot) ->
this.side[side.ordinal] = loader.bakeRecoloredCable(SIDE, rot, textureGetter, color)

View File

@ -23,6 +23,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
private val interfaceCableCornerID = Identifier(PhysicalConnectivity.MODID, "block/interface_cable_corner")
private val interfaceCableCorner2ID = Identifier(PhysicalConnectivity.MODID, "block/interface_cable_corner_2")
private val interfaceCableCapID = Identifier(PhysicalConnectivity.MODID, "block/interface_cable_cap")
// private var interfaceCableStraight = Array<BakedModel?>(6) { null }
// private var interfaceCableCap = Array<BakedModel?>(6) { null }
// private var interfaceCableCorner = mutableMapOf<ModelRotation, BakedModel>()
@ -65,7 +66,8 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
for (tex in depTextures) {
if (tex.textureId.namespace == PhysicalConnectivity.MODID && tex.textureId.path.startsWith("block/cable/color/")) {
for (color in DyeColor.values()) {
val newPath = tex.textureId.path.replace("block/cable/color/", "block/cable/${color.getName()}/")
val newPath =
tex.textureId.path.replace("block/cable/color/", "block/cable/${color.getName()}/")
val substituted = SpriteIdentifier(tex.atlasId, Identifier(PhysicalConnectivity.MODID, newPath))
textures.add(substituted)
}
@ -77,7 +79,12 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
return textures
}
override fun bake(loader: ModelLoader, textureGetter: Function<SpriteIdentifier, Sprite>, rotationContainer: ModelBakeSettings, modelId: Identifier): BakedModel {
override fun bake(
loader: ModelLoader,
textureGetter: Function<SpriteIdentifier, Sprite>,
rotationContainer: ModelBakeSettings,
modelId: Identifier
): BakedModel {
bakeSideModels(loader, textureGetter)
DyeColor.values().forEach { color ->
@ -132,6 +139,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.WEST -> interfaceCableCorner[color]!![ModelRotation.X0_Y270]
else -> null
}
Direction.UP -> when (connection) {
FaceCableConnection.NORTH -> interfaceCableCorner[color]!![ModelRotation.X180_Y180]
FaceCableConnection.EAST -> interfaceCableCorner[color]!![ModelRotation.X180_Y270]
@ -139,6 +147,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.WEST -> interfaceCableCorner[color]!![ModelRotation.X180_Y90]
else -> null
}
Direction.NORTH -> when (connection) {
FaceCableConnection.UP -> interfaceCableCorner[color]!![ModelRotation.X270_Y0]
FaceCableConnection.EAST -> interfaceCableCorner2[color]!![ModelRotation.X180_Y180]
@ -146,6 +155,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.WEST -> interfaceCableCorner2[color]!![ModelRotation.X0_Y0]
else -> null
}
Direction.SOUTH -> when (connection) {
FaceCableConnection.UP -> interfaceCableCorner[color]!![ModelRotation.X270_Y180]
FaceCableConnection.WEST -> interfaceCableCorner2[color]!![ModelRotation.X180_Y0]
@ -153,6 +163,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.EAST -> interfaceCableCorner2[color]!![ModelRotation.X0_Y180]
else -> null
}
Direction.WEST -> when (connection) {
FaceCableConnection.UP -> interfaceCableCorner[color]!![ModelRotation.X270_Y270]
FaceCableConnection.NORTH -> interfaceCableCorner2[color]!![ModelRotation.X180_Y90]
@ -160,6 +171,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.SOUTH -> interfaceCableCorner2[color]!![ModelRotation.X0_Y270]
else -> null
}
Direction.EAST -> when (connection) {
FaceCableConnection.UP -> interfaceCableCorner[color]!![ModelRotation.X270_Y90]
FaceCableConnection.SOUTH -> interfaceCableCorner2[color]!![ModelRotation.X180_Y270]
@ -167,6 +179,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
FaceCableConnection.NORTH -> interfaceCableCorner2[color]!![ModelRotation.X0_Y90]
else -> null
}
else -> null
}
model?.getQuads(state, face, random) ?: listOf()

View File

@ -33,7 +33,10 @@ class ScreenDeviceModel(
) : UnbakedModel, BakedModel, FabricBakedModel {
companion object {
private val CASING = SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/casing"))
private val CASING = SpriteIdentifier(
PlayerScreenHandler.BLOCK_ATLAS_TEXTURE,
Identifier(PhysicalConnectivity.MODID, "block/casing")
)
}
private val screenTexture = SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, screenTexture)

View File

@ -40,65 +40,79 @@ class DeviceConsoleScreen(
)
)
if (device is ActivationController.ActivatableDevice) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/item/ender_pearl.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("gui.phycon.console.remote"),
ActivatableDeviceViewController(device),
device::canConfigureActivationController
))
)
)
}
if (device is RedstoneControllerBlockEntity) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/block/redstone_torch.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.redstone_controller"),
RedstoneControllerViewController(device)
))
)
)
}
if (device is MinerBlockEntity) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/item/diamond_pickaxe.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.miner"),
MinerViewController(device)
))
)
)
}
if (device is NetworkStackProvider) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
Label("P").apply { textColor = Color.TEXT },
TranslatableText("gui.phycon.console.provider"),
ProviderViewController(device),
device::canConfigureProviderPriority
))
)
)
}
if (device is NetworkStackReceiver) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
Label("R").apply { textColor = Color.TEXT },
TranslatableText("gui.phycon.console.receiver"),
ReceiverViewController(device),
))
)
)
}
if (device is RedstoneEmitterBlockEntity) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/item/redstone.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.redstone_emitter"),
RedstoneEmitterViewController(device)
))
)
)
}
if (device is P2PReceiverBlockEntity) {
tabs.add(TabViewController.SimpleTab(
tabs.add(
TabViewController.SimpleTab(
TextureView(Texture(Identifier("textures/item/ender_pearl.png"), 0, 0, 16, 16)).apply {
intrinsicContentSize = Size(16.0, 16.0)
},
TranslatableText("block.phycon.p2p_receiver"),
P2PReceiverViewController(device)
))
)
)
}
tabController = TabViewController(tabs)

View File

@ -1,6 +1,5 @@
package net.shadowfacts.phycon.component
import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.minecraft.block.entity.BlockEntity
import net.minecraft.item.ItemStack
import net.shadowfacts.phycon.api.util.IPAddress
@ -8,12 +7,15 @@ import net.shadowfacts.phycon.packet.CapacityPacket
import net.shadowfacts.phycon.packet.CheckCapacityPacket
import net.shadowfacts.phycon.packet.ItemStackPacket
import net.shadowfacts.phycon.util.copyWithCount
import net.shadowfacts.phycon.util.equalsIgnoringAmount
import kotlin.math.min
/**
* @author shadowfacts
*/
interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>>: ItemStackPacketHandler {
// TODO: use Transfer API transactions for this
interface NetworkStackDispatcher<Insertion : NetworkStackDispatcher.PendingInsertion<Insertion>> :
ItemStackPacketHandler {
val counter: Long
val dispatchStackTimeout: Long
@ -30,7 +32,7 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
fun handleCapacity(packet: CapacityPacket) {
pendingInsertions.firstOrNull { insertion ->
ItemStackUtil.areEqualIgnoreAmounts(packet.stack, insertion.stack) &&
packet.stack.equalsIgnoringAmount(insertion.stack) &&
insertion.results.none { it.second.ipAddress == packet.source }
}?.also { insertion ->
insertion.results.add(packet.capacity to packet.stackReceiver)

View File

@ -1,8 +1,14 @@
package net.shadowfacts.phycon.init
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType.ExtendedFactory
import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.ScreenHandlerType
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.block.inserter.InserterScreenHandler
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreenHandler
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
@ -19,12 +25,19 @@ object PhyScreens {
private set
lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler>
private set
lateinit var SWITCH_CONSOLE: ScreenHandlerType<SwitchConsoleScreenHandler>
private set
fun init() {
TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalBlock.ID, ::TerminalScreenHandler)
CRAFTING_TERMINAL = ScreenHandlerRegistry.registerExtended(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler)
INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler)
REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler)
TERMINAL = register(TerminalBlock.ID, ::TerminalScreenHandler)
CRAFTING_TERMINAL = register(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler)
INSERTER = register(InserterScreenHandler.ID, ::InserterScreenHandler)
REDSTONE_EMITTER = register(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler)
SWITCH_CONSOLE = register(SwitchConsoleScreenHandler.ID, ::SwitchConsoleScreenHandler)
}
fun <T : ScreenHandler> register(id: Identifier, factory: ExtendedFactory<T>): ScreenHandlerType<T> {
return Registry.register(Registry.SCREEN_HANDLER, id, ExtendedScreenHandlerType(factory))
}
}

View File

@ -1,14 +1,25 @@
package net.shadowfacts.phycon.item
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.client.MinecraftClient
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.Item
import net.minecraft.item.ItemUsageContext
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.DeviceBlock
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.netswitch.SwitchBlockEntity
import net.shadowfacts.phycon.client.screen.console.DeviceConsoleScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreen
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreenHandler
import net.shadowfacts.phycon.init.PhyBlocks
/**
* @author shadowfacts
@ -31,6 +42,14 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
}
return ActionResult.SUCCESS
}
} else if (block === PhyBlocks.SWITCH) {
val be = block.getBlockEntity(context.world, context.blockPos)
if (be != null) {
if (!context.world.isClient && context.player != null) {
openScreen(be, context.player!!)
}
return ActionResult.SUCCESS
}
}
return ActionResult.PASS
}
@ -41,4 +60,19 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
MinecraftClient.getInstance().setScreen(screen)
}
private fun openScreen(be: SwitchBlockEntity, player: PlayerEntity) {
val factory = object : ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return SwitchConsoleScreenHandler(syncId, be)
}
override fun getDisplayName() = PhyBlocks.SWITCH.name
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
buf.writeBlockPos(be.pos)
}
}
player.openHandledScreen(factory)
}
}

View File

@ -14,14 +14,18 @@ import kotlin.math.roundToInt
/**
* @author shadowfacts
*/
class FaceDeviceBlockItem(block: FaceDeviceBlock<*>, settings: Settings = Settings()): DeviceBlockItem(block, settings) {
class FaceDeviceBlockItem(block: FaceDeviceBlock<*>, settings: Settings = Settings()) :
DeviceBlockItem(block, settings) {
override fun getPlacementState(context: ItemPlacementContext): BlockState? {
val hitState = context.world.getBlockState(context.blockPos)
val hitBlock = hitState.block
if (hitBlock is CableBlock) {
val hitBlockEdge = context.hitPos.getComponentAlongAxis(context.side.axis) % 1 == 0.0
val hitBlockBeingPlacedIn = floor(context.hitPos.getComponentAlongAxis(context.side.axis)).toInt() == context.blockPos.getComponentAlongAxis(context.side.axis)
val hitBlockBeingPlacedIn =
floor(context.hitPos.getComponentAlongAxis(context.side.axis)).toInt() == context.blockPos.getComponentAlongAxis(
context.side.axis
)
val placementSide: Direction =
if (hitBlockEdge xor hitBlockBeingPlacedIn) {

View File

@ -42,14 +42,25 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
if (newState != null) {
context.world.setBlockState(context.blockPos, newState)
context.world.playSound(context.player, context.blockPos, SoundEvents.BLOCK_METAL_PLACE, SoundCategory.BLOCKS, 0.8f, 0.65f)
context.world.playSound(
context.player,
context.blockPos,
SoundEvents.BLOCK_METAL_PLACE,
SoundCategory.BLOCKS,
0.8f,
0.65f
)
return ActionResult.SUCCESS
} else {
return ActionResult.PASS
}
}
private fun screwdriverDeviceBlock(context: ItemUsageContext, state: BlockState, block: DeviceBlock<*>): BlockState? {
private fun screwdriverDeviceBlock(
context: ItemUsageContext,
state: BlockState,
block: DeviceBlock<*>
): BlockState? {
if (!context.world.isClient) {
val be = block.getBlockEntity(context.world, context.blockPos)!!
@ -66,7 +77,13 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
beTag.remove("InternalBuffer")
}
val entity = ItemEntity(context.world, context.blockPos.x.toDouble(), context.blockPos.y.toDouble(), context.blockPos.z.toDouble(), stack)
val entity = ItemEntity(
context.world,
context.blockPos.x.toDouble(),
context.blockPos.y.toDouble(),
context.blockPos.z.toDouble(),
stack
)
context.world.spawnEntity(entity)
}
@ -77,8 +94,16 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
}
}
private fun screwdriverFaceDeviceBlock(context: ItemUsageContext, state: BlockState, block: FaceDeviceBlock<*>): BlockState? {
val hitInsideBlock = Vec3d(context.hitPos.x - context.blockPos.x, context.hitPos.y - context.blockPos.y, context.hitPos.z - context.blockPos.z)
private fun screwdriverFaceDeviceBlock(
context: ItemUsageContext,
state: BlockState,
block: FaceDeviceBlock<*>
): BlockState? {
val hitInsideBlock = Vec3d(
context.hitPos.x - context.blockPos.x,
context.hitPos.y - context.blockPos.y,
context.hitPos.z - context.blockPos.z
)
val faceShape = block.faceShapes[state[FaceDeviceBlock.FACING]]!!
// if we hit the face part of block, leave the cable behind
if (faceShape.boundingBox.containsInclusive(hitInsideBlock)) {
@ -86,7 +111,13 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
return cableBlock.getInitialState(context.world, context.blockPos)
} else {
if (!context.world.isClient) {
val cable = ItemEntity(context.world, context.blockPos.x.toDouble(), context.blockPos.y.toDouble(), context.blockPos.z.toDouble(), ItemStack(state.block))
val cable = ItemEntity(
context.world,
context.blockPos.x.toDouble(),
context.blockPos.y.toDouble(),
context.blockPos.z.toDouble(),
ItemStack(state.block)
)
context.world.spawnEntity(cable)
}
return Blocks.AIR.defaultState
@ -95,7 +126,13 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
private fun screwdriverCableBlock(context: ItemUsageContext, state: BlockState): BlockState? {
if (!context.world.isClient) {
val entity = ItemEntity(context.world, context.blockPos.x.toDouble(), context.blockPos.y.toDouble(), context.blockPos.z.toDouble(), ItemStack(state.block))
val entity = ItemEntity(
context.world,
context.blockPos.x.toDouble(),
context.blockPos.y.toDouble(),
context.blockPos.z.toDouble(),
ItemStack(state.block)
)
context.world.spawnEntity(entity)
}
return Blocks.AIR.defaultState
@ -103,7 +140,13 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
private fun screwdriverSwitchBlock(context: ItemUsageContext): BlockState? {
if (!context.world.isClient) {
val entity = ItemEntity(context.world, context.blockPos.x.toDouble(), context.blockPos.y.toDouble(), context.blockPos.z.toDouble(), ItemStack(PhyBlocks.SWITCH))
val entity = ItemEntity(
context.world,
context.blockPos.x.toDouble(),
context.blockPos.y.toDouble(),
context.blockPos.z.toDouble(),
ItemStack(PhyBlocks.SWITCH)
)
context.world.spawnEntity(entity)
}
return Blocks.AIR.defaultState

View File

@ -31,7 +31,13 @@ object C2SConfigureDevice: ServerReceiver {
return createPacket(buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
override fun receive(
server: MinecraftServer,
player: ServerPlayerEntity,
handler: ServerPlayNetworkHandler,
buf: PacketByteBuf,
responseSender: PacketSender
) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val tag = buf.readNbt() ?: return

View File

@ -36,7 +36,13 @@ object C2STerminalCraftingButton: ServerReceiver {
return createPacket(buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
override fun receive(
server: MinecraftServer,
player: ServerPlayerEntity,
handler: ServerPlayNetworkHandler,
buf: PacketByteBuf,
responseSender: PacketSender
) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val action = Action.values()[buf.readByte().toInt()]

View File

@ -38,7 +38,13 @@ object C2STerminalRequestItem: ServerReceiver {
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
override fun receive(
server: MinecraftServer,
player: ServerPlayerEntity,
handler: ServerPlayNetworkHandler,
buf: PacketByteBuf,
responseSender: PacketSender
) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val stack = buf.readItemStackWithoutCount()

View File

@ -35,7 +35,13 @@ object C2STerminalUpdateDisplayedItems: ServerReceiver {
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
override fun receive(
server: MinecraftServer,
player: ServerPlayerEntity,
handler: ServerPlayNetworkHandler,
buf: PacketByteBuf,
responseSender: PacketSender
) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val query = buf.readString()

View File

@ -10,9 +10,7 @@ import net.minecraft.network.PacketByteBuf
import net.shadowfacts.phycon.PhysicalConnectivityClient
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler
import net.shadowfacts.phycon.util.TerminalSettings
import net.shadowfacts.phycon.util.readItemStackWithoutCount
import net.shadowfacts.phycon.util.writeItemStackWithoutCount
import net.shadowfacts.phycon.util.*
/**
* @author shadowfacts
@ -20,7 +18,14 @@ import net.shadowfacts.phycon.util.writeItemStackWithoutCount
object S2CTerminalUpdateDisplayedItems : ClientReceiver {
override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
operator fun invoke(terminal: AbstractTerminalBlockEntity, entries: List<AbstractTerminalScreenHandler.Entry>, query: String, settings: TerminalSettings, scrollPosition: Float, totalEntries: Int): Packet<*> {
operator fun invoke(
terminal: AbstractTerminalBlockEntity,
entries: List<AbstractTerminalScreenHandler.Entry>,
query: String,
settings: TerminalSettings,
scrollPosition: Float,
totalEntries: Int
): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
@ -28,7 +33,7 @@ object S2CTerminalUpdateDisplayedItems: ClientReceiver {
buf.writeVarInt(entries.size)
for (e in entries) {
buf.writeItemStackWithoutCount(e.stack)
buf.writeItemVariant(e.variant)
buf.writeVarInt(e.amount)
}
@ -40,13 +45,18 @@ object S2CTerminalUpdateDisplayedItems: ClientReceiver {
return ServerPlayNetworking.createS2CPacket(CHANNEL, buf)
}
override fun receive(client: MinecraftClient, handler: ClientPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
override fun receive(
client: MinecraftClient,
handler: ClientPlayNetworkHandler,
buf: PacketByteBuf,
responseSender: PacketSender
) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val entryCount = buf.readVarInt()
val entries = ArrayList<AbstractTerminalScreenHandler.Entry>(entryCount)
for (i in 0 until entryCount) {
entries.add(AbstractTerminalScreenHandler.Entry(buf.readItemStackWithoutCount(), buf.readVarInt()))
entries.add(AbstractTerminalScreenHandler.Entry(buf.readItemVariant(), buf.readVarInt()))
}
val query = buf.readString()
PhysicalConnectivityClient.terminalSettings.fromTag(buf.readNbt()!!)

View File

@ -6,5 +6,6 @@ import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class CheckCapacityPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
class CheckCapacityPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress) :
BasePacket(source, destination) {
}

View File

@ -6,6 +6,7 @@ import net.shadowfacts.phycon.block.DeviceBlockEntity
/**
* @author shadowfacts
*/
class DeviceRemovedPacket(source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination) {
class DeviceRemovedPacket(source: IPAddress, destination: IPAddress = IPAddress.BROADCAST) :
BasePacket(source, destination) {
constructor(device: DeviceBlockEntity) : this(device.ipAddress)
}

View File

@ -6,5 +6,6 @@ import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ExtractStackPacket(val stack: ItemStack, val amount: Int, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
class ExtractStackPacket(val stack: ItemStack, val amount: Int, source: IPAddress, destination: IPAddress) :
BasePacket(source, destination) {
}

View File

@ -6,6 +6,7 @@ import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
class ItemStackPacket(val stack: ItemStack, val bounceCount: Int, source: IPAddress, destination: IPAddress) :
BasePacket(source, destination) {
constructor(stack: ItemStack, source: IPAddress, destination: IPAddress) : this(stack, 0, source, destination)
}

View File

@ -6,5 +6,6 @@ import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class LocateStackPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress = IPAddress.BROADCAST): BasePacket(source, destination) {
class LocateStackPacket(val stack: ItemStack, source: IPAddress, destination: IPAddress = IPAddress.BROADCAST) :
BasePacket(source, destination) {
}

View File

@ -1,13 +0,0 @@
package net.shadowfacts.phycon.packet
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class ReadGroupedInventoryPacket(
val inventory: GroupedItemInvView,
source: IPAddress,
destination: IPAddress
): BasePacket(source, destination)

View File

@ -5,7 +5,8 @@ import net.shadowfacts.phycon.api.util.IPAddress
/**
* @author shadowfacts
*/
class RemoteActivationPacket(val mode: Mode, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
class RemoteActivationPacket(val mode: Mode, source: IPAddress, destination: IPAddress) :
BasePacket(source, destination) {
enum class Mode {
SINGLE,
ENABLE,

View File

@ -1,6 +1,5 @@
package net.shadowfacts.phycon.util
import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.minecraft.inventory.SimpleInventory
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
@ -45,7 +44,7 @@ fun SimpleInventory.insert(stack: ItemStack, slot: Int): ItemStack {
if (current.isEmpty) {
setStack(slot, stack)
return ItemStack.EMPTY
} else if (ItemStackUtil.areEqualIgnoreAmounts(stack, current)) {
} else if (stack.equalsIgnoringAmount(current)) {
val toTransfer = min(current.maxCount - current.count, stack.count)
current.count += toTransfer
stack.count -= toTransfer

View File

@ -0,0 +1,10 @@
package net.shadowfacts.phycon.util
/**
* @author shadowfacts
*/
sealed class Either<Left, Right> {
class Left<Left, Right>(val left: Left) : Either<Left, Right>()
class Right<Left, Right>(val right: Right) : Either<Left, Right>()
}

View File

@ -0,0 +1,69 @@
package net.shadowfacts.phycon.util
import kotlin.math.min
/**
* @author shadowfacts
*/
class IntRingBuffer(size: Int) : Iterable<Int> {
private val array = IntArray(size)
private var next = 0
private var wrapped = false
val size: Int
get() = if (wrapped) array.size else next
fun add(value: Int) {
array[next] = value
next = (next + 1) % array.size
wrapped = wrapped || next == 0
}
fun asContiguousArray(): IntArray {
if (wrapped) {
val array = IntArray(array.size)
this.array.copyInto(array, destinationOffset = array.size - next, startIndex = 0, endIndex = next)
this.array.copyInto(array, destinationOffset = 0, startIndex = next, endIndex = array.size)
return array
} else {
return this.array.copyOfRange(0, next)
}
}
fun replace(array: IntArray) {
val count = min(array.size, this.array.size)
array.copyInto(this.array, destinationOffset = 0, startIndex = array.size - count, endIndex = count)
next = (count + 1) % this.array.size
wrapped = array.size >= this.array.size
}
fun clear() {
next = 0
wrapped = false
}
override fun iterator() = Iterator(this)
class Iterator(private val ringBuffer: IntRingBuffer) : IntIterator() {
private var nextIndex = if (ringBuffer.wrapped) ringBuffer.next else 0
private var wrapped = false
override fun nextInt(): Int {
val res = ringBuffer.array[nextIndex]
nextIndex += 1
if (nextIndex >= ringBuffer.array.size) {
wrapped = true
nextIndex = 0
}
return res
}
override fun hasNext(): Boolean {
return if (ringBuffer.wrapped) {
!wrapped || nextIndex < ringBuffer.next
} else {
nextIndex < ringBuffer.next
}
}
}
}

View File

@ -1,10 +0,0 @@
package net.shadowfacts.phycon.util
import net.minecraft.item.ItemStack
/**
* @author shadowfacts
*/
fun ItemStack.copyWithCount(count: Int): ItemStack {
return copy().also { it.count = count }
}

View File

@ -0,0 +1,17 @@
package net.shadowfacts.phycon.util
import net.minecraft.item.ItemStack
/**
* @author shadowfacts
*/
fun ItemStack.copyWithCount(count: Int): ItemStack {
return copy().also { it.count = count }
}
fun ItemStack.equalsIgnoringAmount(other: ItemStack): Boolean {
if (this.isEmpty) return other.isEmpty
if (other.isEmpty) return false
return this.item === other.item && this.nbt == other.nbt;
}

View File

@ -0,0 +1,18 @@
package net.shadowfacts.phycon.util
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
val ItemVariant.name: Text
get() {
// don't use toStack because that copies the NBT
// we assume that items don't mutate NBT in getName
val stack = ItemStack(this.item, 1)
stack.nbt = this.nbt
return item.getName(stack)
}

View File

@ -37,6 +37,7 @@ object NetworkUtil {
return null
}
/*
fun findDestinations(world: World, startPos: BlockPos, direction: Direction? = null): List<PacketSink> {
val results = LinkedList<PacketSink>()
val visited = hashSetOf(startPos)
@ -62,8 +63,15 @@ object NetworkUtil {
return results
}
*/
private fun findEdges(queue: MutableList<BlockPos>, visited: Set<BlockPos>, world: World, pos: BlockPos, includeNonCables: Boolean = false) {
private fun findEdges(
queue: MutableList<BlockPos>,
visited: Set<BlockPos>,
world: World,
pos: BlockPos,
includeNonCables: Boolean = false
) {
val state = world.getBlockState(pos)
val block = state.block
if (block is NetworkComponentBlock && (includeNonCables || block is NetworkCableBlock)) {

View File

@ -1,5 +1,6 @@
package net.shadowfacts.phycon.util
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
@ -35,3 +36,11 @@ fun PacketByteBuf.readItemStackWithoutCount(): ItemStack {
stack
}
}
fun PacketByteBuf.writeItemVariant(variant: ItemVariant) {
variant.toPacket(this)
}
fun PacketByteBuf.readItemVariant(): ItemVariant {
return ItemVariant.fromPacket(this)
}

View File

@ -0,0 +1,48 @@
package net.shadowfacts.phycon.util
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertEquals
/**
* @author shadowfacts
*/
class IntRingBufferTests {
@Test
fun testEmpty() {
assertArrayEquals(intArrayOf(), IntRingBuffer(4).asContiguousArray())
}
@Test
fun testAsContiguousArray() {
val buffer = IntRingBuffer(4)
buffer.add(0)
buffer.add(1)
assertArrayEquals(intArrayOf(0, 1), buffer.asContiguousArray())
buffer.add(2)
buffer.add(3)
buffer.add(4)
assertArrayEquals(intArrayOf(1, 2, 3, 4), buffer.asContiguousArray())
}
@Test
fun testIterator() {
val buffer = IntRingBuffer(4)
val iterator = buffer.iterator()
assertFalse(iterator.hasNext())
buffer.add(0)
buffer.add(1)
assertEquals(listOf(0, 1), buffer.toList())
buffer.add(2)
buffer.add(3)
buffer.add(4)
assertEquals(listOf(1, 2, 3, 4), buffer.toList())
}
}