Compare commits

...

28 Commits
v0.2.0 ... main

Author SHA1 Message Date
Shadowfacts b64a05e0ad Fix P2P receiver constantly flooding network with request inventory packets 2023-03-13 11:24:55 -04:00
Shadowfacts 73de26387a Fix P2P receiver not invalidating target on netsplit 2023-03-13 11:21:53 -04:00
Shadowfacts 33614e0dc6 Tweak NetworkStackDispatcher behavior on incomplete insertions 2023-03-12 22:53:50 -04:00
Shadowfacts c18af9794b Store inventories as weak references in interface 2023-03-12 22:28:36 -04:00
Shadowfacts 6d97af8bdc Fix terminal not updating when all networked inventories are disconnected 2023-03-12 22:26:35 -04:00
Shadowfacts d527185888 Cache destination interfaces 2023-03-12 22:18:34 -04:00
Shadowfacts 82482ca9c6 Disable plugins 2023-03-12 22:15:32 -04:00
Shadowfacts 9200dea350 1.18.2 2022-06-16 10:45:32 -04:00
Shadowfacts e88ecd3215 Add P2P inventory system 2021-12-24 15:47:33 -05:00
Shadowfacts 9cbad193e2 fancy new java feature 2021-12-24 11:56:49 -05:00
Shadowfacts b416f98ef6 Fix not being able to place face devices by right-clicking non-full blocks 2021-12-23 14:41:55 -05:00
Shadowfacts b2e794e5a4 Add REI sync mode for terminal search field 2021-12-23 14:41:55 -05:00
Shadowfacts b21a45fbbb Fix twisted pair advancement not matching recipe 2021-12-23 12:53:22 -05:00
Shadowfacts ef9aa9e958 Fix terminal search field not unfocusing when REI focused 2021-12-23 11:36:46 -05:00
Shadowfacts c5ede3bd62 Add Cotton Resources tags 2021-12-22 22:35:28 -05:00
Shadowfacts 30300dbc40 Update REI plugin 2021-12-22 22:17:25 -05:00
Shadowfacts 5488cc295a Initial update to 1.18.1 2021-12-22 22:12:11 -05:00
Shadowfacts 172536cdb8 Fix magenta cable recipe 2021-11-08 18:19:44 -05:00
Shadowfacts 46e00cea97
Add Crafting Terminal loot table 2021-04-03 10:42:17 -04:00
Shadowfacts ce511e62e1
Fix dupe when shift-clicking crafting results out of terminal 2021-04-03 10:32:53 -04:00
Shadowfacts 5eb948802c
Fix crash when shift-clicking stack into terminal buffer but no slots available 2021-04-03 10:32:27 -04:00
Shadowfacts 7447c89394
TechReborn: Fix being client-only 2021-03-29 19:21:43 -04:00
Shadowfacts 87f0bdb85a
REI: Add support for filling recipes in the Crafting Terminal 2021-03-29 19:18:15 -04:00
Shadowfacts 236d6707f6
Don't show amount dialog if holding stack in cursor 2021-03-29 18:45:28 -04:00
Shadowfacts 961c74de34
Add Crafting Terminal recipe and advancement 2021-03-28 14:57:34 -04:00
Shadowfacts c278d137ef
Fix terminal model Z fighting 2021-03-28 14:57:34 -04:00
Shadowfacts 12e055d645
Add Crafting Terminal texture/model 2021-03-28 14:57:34 -04:00
Shadowfacts 9d98481ba5
Add Crafting Terminal 2021-03-28 14:57:32 -04:00
139 changed files with 2205 additions and 656 deletions

View File

@ -1,7 +1,7 @@
plugins {
id "fabric-loom" version "0.6.49"
id "fabric-loom" version "0.12.9"
id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id "org.jetbrains.kotlin.jvm" version "1.6.10"
}
archivesBaseName = project.archives_base_name
@ -10,8 +10,8 @@ group = project.maven_group
allprojects {
pluginManager.withPlugin("java") {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
@ -43,6 +43,8 @@ configure(allprojects.findAll { it.name != "kiwi-java" }) {
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
// Minecraft 1.18 (1.18-pre2) upwards uses Java 17.
options.release = 17
}
java {
@ -55,43 +57,54 @@ configure(allprojects.findAll { it.name != "kiwi-java" }) {
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
compileKotlin {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
kotlinOptions.jvmTarget = JavaVersion.VERSION_17
}
}
}
minecraft {
loom {
log4jConfigs.from "PhyConDebugLogging.xml"
}
repositories {
maven { url "https://mod-buildcraft.com/maven" }
maven { url "http://server.bbkr.space:8081/artifactory/libs-release/" }
// maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" }
maven { url "https://maven.terraformersmc.com/releases" }
jcenter()
maven { url "https://maven.shedaniel.me/" }
maven { url "https://maven.siphalor.de/" }
maven { url "https://jitpack.io" }
mavenCentral()
}
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.
modCompile "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}"
compile project(":kiwi-java")
implementation project(":kiwi-java")
include project(":kiwi-java")
runtimeOnly project(":plugin:mousewheelie")
include project(":plugin:mousewheelie")
runtimeOnly project(":plugin:rei")
include project(":plugin:rei")
runtimeOnly project(":plugin:techreborn")
include project(":plugin:techreborn")
// modRuntimeOnly "de.siphalor:mousewheelie-1.18:${project.mousewheelie_version}"
// runtimeOnly(project(":plugin:mousewheelie")) {
// transitive = false
// }
// include project(":plugin:mousewheelie")
//
// modRuntimeOnly "me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}"
// runtimeOnly(project(":plugin:rei")) {
// transitive = false
// }
// include project(":plugin:rei")
modRuntime("io.github.cottonmc:cotton-resources:${project.cotton_resources_version}") {
exclude group: "net.fabricmc.fabric-api"
}
modRuntime("com.terraformersmc:modmenu:${project.modmenu_version}") {
// runtimeOnly project(":plugin:techreborn")
// include project(":plugin:techreborn")
// modRuntime("io.github.cottonmc:cotton-resources:${project.cotton_resources_version}") {
// exclude group: "net.fabricmc.fabric-api"
// }
modRuntimeOnly("com.terraformersmc:modmenu:${project.modmenu_version}") {
exclude group: "net.fabricmc.fabric-api"
}
@ -105,22 +118,22 @@ jar {
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// publishing {
// publications {
// mavenJava(MavenPublication) {
// // add all the jars that should be included when publishing to maven
// artifact(remapJar) {
// builtBy remapJar
// }
// artifact(sourcesJar) {
// builtBy remapSourcesJar
// }
// }
// }
// select the repositories you want to publish to
repositories {
// uncomment to publish to the local maven
// mavenLocal()
}
}
// // select the repositories you want to publish to
// repositories {
// // uncomment to publish to the local maven
// // mavenLocal()
// }
// }

View File

@ -1,19 +1,21 @@
org.gradle.jvmargs=-Xmx1G
org.gradle.daemon=false
minecraft_version=1.16.5
yarn_mappings=1.16.5+build.4
loader_version=0.11.1
minecraft_version=1.18.2
yarn_mappings=1.18.2+build.3
loader_version=0.14.8
mod_version=0.2.0
maven_group=net.shadowfacts
archives_base_name=PhysicalConnectivity
fabric_version=0.30.0+1.16
fabric_kotlin_version=1.4.30+build.2
fabric_version=0.56.0+1.18.2
fabric_kotlin_version=1.8.0+kotlin.1.7.0
libblockattributes_version=0.8.5
cotton_resources_version=1.7.4
modmenu_version=1.16.8
libblockattributes_version=0.10.2
# cotton_resources_version=1.7.4
modmenu_version=3.2.2
rei_version=8.2.481
mousewheelie_version=1.9.0+mc1.18.2
junit_version = 5.4.0

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -3,7 +3,7 @@ plugins {
id "org.jetbrains.kotlin.jvm"
}
archivesBaseName = project.archives_base_name
archivesBaseName = "PhyCon-Plugin-MouseWheelie"
version = project.mod_version
group = project.maven_group
@ -11,13 +11,18 @@ repositories {
maven {
url = "https://maven.siphalor.de/"
}
jcenter()
maven {
url = "https://jitpack.io"
}
mavenCentral()
}
dependencies {
implementation project(":")
implementation(project(":")) {
transitive = false
}
modImplementation("de.siphalor:mousewheelie-1.16:${project.mousewheelie_version}") {
modImplementation("de.siphalor:mousewheelie-1.18:${project.mousewheelie_version}") {
exclude group: "net.fabricmc"
exclude group: "net.fabricmc.fabric-api"
exclude module: "modmenu"

View File

@ -1,4 +0,0 @@
archives_base_name=PhyCon-Plugin-MouseWheelie
mousewheelie_version=1.6.4+mc1.16.4

View File

@ -3,7 +3,7 @@ plugins {
id "org.jetbrains.kotlin.jvm"
}
archivesBaseName = project.archives_base_name
archivesBaseName = "PhyCon-Plugin-REI"
version = project.mod_version
group = project.maven_group
@ -11,16 +11,15 @@ repositories {
maven {
url = "https://maven.shedaniel.me/"
}
jcenter()
mavenCentral()
}
dependencies {
implementation project(":")
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:${project.rei_version}") {
exclude group: "net.fabricmc.fabric-api"
implementation(project(":")) {
transitive = false
}
modRuntime("me.shedaniel:RoughlyEnoughItems:${project.rei_version}") {
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:${project.rei_version}") {
exclude group: "net.fabricmc.fabric-api"
}
}

View File

@ -1,3 +0,0 @@
archives_base_name=PhyCon-Plugin-REI
rei_version=5.10.184

View File

@ -1,44 +0,0 @@
package net.shadowfacts.phycon.plugin.rei
import me.shedaniel.math.Rectangle
import me.shedaniel.rei.api.BaseBoundsHandler
import me.shedaniel.rei.api.DisplayHelper
import me.shedaniel.rei.api.REIHelper
import me.shedaniel.rei.api.plugins.REIPluginV0
import net.fabricmc.api.ClientModInitializer
import net.minecraft.client.MinecraftClient
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen
/**
* @author shadowfacts
*/
object PhyConPlugin: ClientModInitializer, REIPluginV0 {
const val MODID = "phycon_rei"
override fun onInitializeClient() {
AbstractTerminalScreen.registerClickHandler { mouseX, mouseY, button ->
REIHelper.getInstance().searchTextField?.also {
if (it.bounds.contains(mouseX, mouseY)) {
this.terminalVC.searchField.resignFirstResponder()
} else {
this.terminalVC.searchField.becomeFirstResponder()
}
}
null
}
}
override fun getPluginIdentifier() = Identifier(MODID, "rei_plugin")
override fun registerBounds(helper: DisplayHelper) {
BaseBoundsHandler.getInstance().registerExclusionZones(AbstractTerminalScreen::class.java) {
val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *>
val view = screen.terminalVC.settingsView
val rect = view.convert(view.bounds, to = null)
listOf(
Rectangle(rect.left.toInt(), rect.top.toInt(), 20, 20)
)
}
}
}

View File

@ -0,0 +1,87 @@
package net.shadowfacts.phycon.plugin.rei
import dev.architectury.event.EventResult
import dev.architectury.event.events.client.ClientScreenInputEvent
import me.shedaniel.math.Rectangle
import me.shedaniel.rei.api.client.REIRuntime
import me.shedaniel.rei.api.client.plugins.REIClientPlugin
import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry
import net.fabricmc.api.ClientModInitializer
import net.minecraft.client.MinecraftClient
import net.shadowfacts.phycon.PhysicalConnectivityClient
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen
import org.apache.logging.log4j.LogManager
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
/**
* @author shadowfacts
*/
object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener {
private val logger = LogManager.getLogger()
private var isHighlightingHandle: MethodHandle? = null
override fun onInitializeClient() {
ClientScreenInputEvent.MOUSE_RELEASED_PRE.register { client, screen, mouseX, mouseY, button ->
if (screen is AbstractTerminalScreen<*, *>) {
REIRuntime.getInstance().searchTextField?.also {
if (it.isFocused) {
screen.terminalVC.searchField.resignFirstResponder()
} else {
screen.terminalVC.searchField.becomeFirstResponder()
}
}
}
EventResult.pass()
}
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)
} catch (e: ReflectiveOperationException) {
logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e)
}
}
override fun registerScreens(registry: ScreenRegistry) {
registry.exclusionZones().register(AbstractTerminalScreen::class.java) {
val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *>
val view = screen.terminalVC.settingsView
val rect = view.convert(view.bounds, to = null)
listOf(
Rectangle(rect.left.toInt(), rect.top.toInt(), view.bounds.width.toInt(), view.bounds.height.toInt())
)
}
}
override fun terminalSearchQueryChanged(newValue: String) {
if (shouldSync()) {
REIRuntime.getInstance().searchTextField?.text = newValue
}
}
override fun requestTerminalSearchFieldUpdate(): String? {
return if (shouldSync()) {
REIRuntime.getInstance().searchTextField?.text
} else {
null
}
}
private fun shouldSync(): Boolean {
return when (PhysicalConnectivityClient.terminalSettings[PhyConPluginCommon.REI_SYNC_KEY]) {
REISyncMode.OFF -> false
REISyncMode.ON -> true
REISyncMode.HIGHLIGHT_ONLY -> {
if (isHighlightingHandle != null) {
isHighlightingHandle!!.invoke() as Boolean
} else {
false
}
}
}
}
}

View File

@ -0,0 +1,68 @@
package net.shadowfacts.phycon.plugin.rei
import me.shedaniel.rei.api.common.category.CategoryIdentifier
import me.shedaniel.rei.api.common.display.SimpleGridMenuDisplay
import me.shedaniel.rei.api.common.plugins.REIServerPlugin
import me.shedaniel.rei.api.common.transfer.info.MenuInfoContext
import me.shedaniel.rei.api.common.transfer.info.MenuInfoRegistry
import me.shedaniel.rei.api.common.transfer.info.simple.SimpleGridMenuInfo
import me.shedaniel.rei.api.common.transfer.info.simple.SimpleMenuInfoProvider
import me.shedaniel.rei.api.common.transfer.info.stack.SlotAccessor
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.api.PhyConAPI
import net.shadowfacts.phycon.api.PhyConPlugin
import net.shadowfacts.phycon.api.TerminalSettingKey
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
import java.util.stream.IntStream
/**
* @author shadowfacts
*/
object PhyConPluginCommon: REIServerPlugin, PhyConPlugin {
const val MODID = "phycon_rei"
lateinit var REI_SYNC_KEY: TerminalSettingKey<REISyncMode>
private set
override fun registerMenuInfo(registry: MenuInfoRegistry) {
registry.register(CategoryIdentifier.of("minecraft", "plugins/crafting"), CraftingTerminalScreenHandler::class.java, SimpleMenuInfoProvider.of(::TerminalInfo))
}
override fun initializePhyCon(api: PhyConAPI) {
REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF)
}
class TerminalInfo<D: SimpleGridMenuDisplay>(
private val display: D,
): SimpleGridMenuInfo<CraftingTerminalScreenHandler, D> {
override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int {
return menu.resultSlot.id
}
override fun getInputStackSlotIds(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): IntStream {
return IntStream.range(context.menu.craftingSlotsStart, context.menu.craftingSlotsEnd)
}
override fun getInventorySlots(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): Iterable<SlotAccessor> {
val slots = super.getInventorySlots(context).toMutableList()
for (i in (context.menu.bufferSlotsStart until context.menu.bufferSlotsEnd)) {
slots.add(SlotAccessor.fromSlot(context.menu.getSlot(i)))
}
return slots
}
override fun getCraftingWidth(menu: CraftingTerminalScreenHandler): Int {
return 3
}
override fun getCraftingHeight(menu: CraftingTerminalScreenHandler): Int {
return 3
}
override fun getDisplay(): D {
return display
}
}
}

View File

@ -0,0 +1,29 @@
package net.shadowfacts.phycon.plugin.rei
import net.minecraft.text.LiteralText
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.TerminalSetting
/**
* @author shadowfacts
*/
enum class REISyncMode: TerminalSetting {
OFF,
ON,
HIGHLIGHT_ONLY;
override fun getIconTexture() = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
override fun getUV() = when (this) {
OFF -> intArrayOf(0, 240)
ON -> intArrayOf(16, 240)
HIGHLIGHT_ONLY -> intArrayOf(32, 240)
}
override fun getTooltip() = when (this) {
OFF -> LiteralText("Don't sync with REI")
ON -> LiteralText("Sync with REI")
HIGHLIGHT_ONLY -> LiteralText("Sync in highlight mode")
}
}

View File

@ -12,18 +12,27 @@
"homepage": "https://git.shadowfacts.net/minecraft/PhysicalConnectivity"
},
"license": "LGPL-3.0",
"environment": "client",
"entrypoints": {
"client": [
{
"adapter": "kotlin",
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPlugin"
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPluginClient"
}
],
"rei_plugins": [
"rei": [
{
"adapter": "kotlin",
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPlugin"
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPluginClient"
},
{
"adapter": "kotlin",
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPluginCommon"
}
],
"phycon": [
{
"adapter": "kotlin",
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPluginCommon"
}
]
},
@ -34,7 +43,7 @@
"fabric": "*",
"fabric-language-kotlin": ">=1.3.50",
"phycon": "*",
"roughlyenoughitems": "*"
"roughlyenoughitems": ["^7.0.0", "^8.0.0"]
},
"custom": {

View File

@ -11,11 +11,13 @@ repositories {
maven {
url = "https://maven.modmuss50.me/"
}
jcenter()
mavenCentral()
}
dependencies {
implementation project(":")
implementation(project(":")) {
transitive = false
}
modImplementation("TechReborn:TechReborn-1.16:${project.techreborn_version}") {
exclude group: "net.fabricmc.fabric-api"

View File

@ -12,7 +12,6 @@
"homepage": "https://git.shadowfacts.net/minecraft/PhysicalConnectivity"
},
"license": "LGPL-3.0",
"environment": "client",
"entrypoints": {
"main": [
{

View File

@ -1,6 +1,6 @@
pluginManagement {
repositories {
jcenter()
mavenCentral()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
@ -12,4 +12,4 @@ pluginManagement {
include("kiwi-java")
include("plugin:mousewheelie")
include("plugin:rei")
include("plugin:techreborn")
// include("plugin:techreborn")

View File

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

View File

@ -1,10 +1,12 @@
package net.shadowfacts.phycon.api;
import org.jetbrains.annotations.NotNull;
/**
* @author shadowfacts
*/
public interface PhyConPlugin {
void initializePhyCon(PhyConAPI api);
void initializePhyCon(@NotNull PhyConAPI api);
}

View File

@ -11,4 +11,6 @@ public interface TerminalSettingKey<E extends Enum<E> & TerminalSetting> {
E getValue();
void setPriority(int priority);
}

View File

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

View File

@ -0,0 +1,28 @@
package net.shadowfacts.phycon.mixin.client;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.shadowfacts.cacao.AbstractCacaoScreen;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* @author shadowfacts
*/
@Mixin(MinecraftClient.class)
public class MixinMinecraftClient {
@Inject(
method = "setScreen(Lnet/minecraft/client/gui/screen/Screen;)V",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;", opcode = Opcodes.PUTFIELD, shift = At.Shift.AFTER)
)
private void setScreen(Screen screen, CallbackInfo ci) {
if (screen instanceof AbstractCacaoScreen cacaoScreen) {
cacaoScreen.screenWillAppear();
}
}
}

View File

@ -14,4 +14,6 @@ interface AbstractCacaoScreen {
fun removeWindow(window: Window)
}
fun screenWillAppear()
}

View File

@ -1,6 +1,6 @@
package net.shadowfacts.cacao
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
@ -14,6 +14,7 @@ import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.window.ScreenHandlerWindow
import net.shadowfacts.cacao.window.Window
import org.lwjgl.glfw.GLFW
import org.lwjgl.opengl.GL11
import java.util.*
/**
@ -29,10 +30,16 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
override val windows: List<Window> = _windows
private var hasAppeared = false
override fun <T: Window> addWindow(window: T, index: Int): T {
if (window is ScreenHandlerWindow && window.screenHandler != handler) {
throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported")
}
if (hasAppeared) {
window.viewController.viewWillAppear()
}
_windows.add(index, window)
@ -50,7 +57,13 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
override fun removeWindow(window: Window) {
_windows.remove(window)
if (windows.isEmpty()) {
onClose()
close()
}
}
override fun screenWillAppear() {
windows.forEach {
it.viewController.viewWillAppear()
}
}
@ -62,11 +75,12 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
}
}
override fun onClose() {
super.onClose()
override fun close() {
super.close()
windows.forEach {
// todo: VC callbacks
it.viewController.viewWillDisappear()
it.viewController.viewDidDisappear()
it.firstResponder = null
}
@ -82,8 +96,8 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val mouse = Point(mouseX, mouseY)
RenderSystem.pushMatrix()
RenderSystem.translatef(0f, 0f, -350f)
matrixStack.push()
matrixStack.translate(0.0, 0.0, -350.0)
for (i in windows.indices) {
val it = windows[i]
@ -100,7 +114,7 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
super.render(matrixStack, -1, -1, delta)
}
RenderSystem.popMatrix()
matrixStack.pop()
}
it.draw(matrixStack, mouse, delta)

View File

@ -1,5 +1,6 @@
package net.shadowfacts.cacao
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvents
@ -32,6 +33,8 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
*/
override val windows: List<Window> = _windows
private var hasAppeared = false
/**
* Adds the given window to this screen's window list at the given position.
*
@ -40,6 +43,10 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
* @return The window that was added, as a convenience.
*/
override fun <T: Window> addWindow(window: T, index: Int): T {
if (hasAppeared) {
window.viewController.viewWillAppear()
}
_windows.add(index, window)
window.screen = this
@ -62,7 +69,13 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
override fun removeWindow(window: Window) {
_windows.remove(window)
if (windows.isEmpty()) {
onClose()
close()
}
}
override fun screenWillAppear() {
windows.forEach {
it.viewController.viewWillAppear()
}
}
@ -74,11 +87,12 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
}
}
override fun onClose() {
super.onClose()
override fun close() {
super.close()
windows.forEach {
// todo: VC callbacks
it.viewController.viewWillDisappear()
it.viewController.viewDidDisappear()
// resign the current first responder (if any)
it.firstResponder = null

View File

@ -1,20 +1,21 @@
package net.shadowfacts.cacao.util
import com.mojang.blaze3d.platform.GlStateManager
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.render.*
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvent
import net.minecraft.text.LiteralText
import net.minecraft.text.OrderedText
import net.minecraft.text.Text
import net.minecraft.util.math.Matrix4f
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.util.texture.Texture
import org.lwjgl.opengl.GL11
import kotlin.math.roundToInt
/**
* Helper methods for rendering using Minecraft's utilities from Cacao views.
@ -45,18 +46,18 @@ object RenderHelper: DrawableHelper() {
*/
fun draw(matrixStack: MatrixStack, rect: Rect, texture: Texture) {
if (disabled) return
color(1f, 1f, 1f, 1f)
MinecraftClient.getInstance().textureManager.bindTexture(texture.location)
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)
}
fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) {
if (disabled) return
GlStateManager.lineWidth(width)
RenderSystem.lineWidth(width)
val tessellator = Tessellator.getInstance()
val buffer = tessellator.buffer
buffer.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR)
buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR)
buffer.vertex(start.x, start.y, z).color(color).next()
buffer.vertex(end.x, end.y, z).color(color).next()
tessellator.draw()
@ -71,19 +72,19 @@ object RenderHelper: DrawableHelper() {
val uEnd = (u + width).toFloat() / textureWidth
val vStart = v.toFloat() / textureHeight
val vEnd = (v + height).toFloat() / textureHeight
drawTexturedQuad(matrixStack.peek().model, 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) {
val bufferBuilder = Tessellator.getInstance().buffer
bufferBuilder.begin(GL11.GL_QUADS, VertexFormats.POSITION_TEXTURE)
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE)
bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next()
bufferBuilder.vertex(matrix, x1.toFloat(), y1.toFloat(), z.toFloat()).texture(u1, v1).next()
bufferBuilder.vertex(matrix, x1.toFloat(), y0.toFloat(), z.toFloat()).texture(u1, v0).next()
bufferBuilder.vertex(matrix, x0.toFloat(), y0.toFloat(), z.toFloat()).texture(u0, v0).next()
bufferBuilder.end()
RenderSystem.enableAlphaTest()
BufferRenderer.draw(bufferBuilder)
}
@ -95,110 +96,22 @@ object RenderHelper: DrawableHelper() {
drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse)
}
// Based on Screen.renderOrderedTooltip
private val dummyScreen = object: Screen(LiteralText("")) {
init {
textRenderer = MinecraftClient.getInstance().textRenderer
itemRenderer = MinecraftClient.getInstance().itemRenderer
}
}
@JvmName("drawOrderedTooltip")
fun drawTooltip(matrixStack: MatrixStack, texts: List<OrderedText>, mouse: Point) {
if (disabled) return
if (texts.isEmpty()) return
val client = MinecraftClient.getInstance()
val textRenderer = client.textRenderer
val maxWidth = texts.maxOf(textRenderer::getWidth)
var x = mouse.x.toInt() + 12
var y = mouse.y.toInt() - 12
var p = 8
if (texts.size > 1) {
p += 2 + (texts.size - 1) * 8
}
if (x + maxWidth > client.window.scaledWidth) {
x -= 28 + maxWidth
}
if (y + p + 6 > client.window.scaledHeight) {
y = client.window.scaledHeight - p - 6
}
matrixStack.push()
val q = -267386864
val r = 1347420415
val s = 1344798847
val t = 1
val tessellator = Tessellator.getInstance()
val buffer = tessellator.buffer
buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_COLOR)
val matrix = matrixStack.peek().model
val z = 400
fillGradient(matrix, buffer, x - 3, y - 4, x + maxWidth + 3, y - 3, z, q, q)
fillGradient(matrix, buffer, x - 3, y + p + 3, x + maxWidth + 3, y + p + 4, z, q, q)
fillGradient(matrix, buffer, x - 3, y - 3, x + maxWidth + 3, y + p + 3, z, q, q)
fillGradient(matrix, buffer, x - 4, y - 3, x - 3, y + p + 3, z, q, q)
fillGradient(matrix, buffer, x + maxWidth + 3, y - 3, x + maxWidth + 4, y + p + 3, z, q, q)
fillGradient(matrix, buffer, x - 3, y - 3 + 1, x - 3 + 1, y + p + 3 - 1, z, r, s)
fillGradient(matrix, buffer, x + maxWidth + 2, y - 3 + 1, x + maxWidth + 3, y + p + 3 - 1, z, r, s)
fillGradient(matrix, buffer, x - 3, y - 3, x + maxWidth + 3, y - 3 + 1, z, r, r)
fillGradient(matrix, buffer, x - 3, y + p + 2, x + maxWidth + 3, y + p + 3, z, s, s)
RenderSystem.enableDepthTest()
RenderSystem.disableTexture()
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.shadeModel(7425)
buffer.end()
BufferRenderer.draw(buffer)
RenderSystem.shadeModel(7424)
RenderSystem.disableBlend()
RenderSystem.enableTexture()
val immediate = VertexConsumerProvider.immediate(buffer)
matrixStack.translate(0.0, 0.0, 400.0)
for (i in texts.indices) {
val text = texts[i]
textRenderer.draw(text, x.toFloat(), y.toFloat(), -1, true, matrix, immediate, false, 0, 15728880)
if (i == 0) {
y += 2
}
y += 10
}
immediate.draw()
matrixStack.pop()
}
/**
* @see org.lwjgl.opengl.GL11.glPushMatrix
*/
fun pushMatrix() {
if (disabled) return
RenderSystem.pushMatrix()
}
/**
* @see org.lwjgl.opengl.GL11.glPopMatrix
*/
fun popMatrix() {
if (disabled) return
RenderSystem.popMatrix()
}
/**
* @see org.lwjgl.opengl.GL11.glTranslated
*/
fun translate(x: Double, y: Double, z: Double = 0.0) {
if (disabled) return
RenderSystem.translated(x, y, z)
}
/**
* @see org.lwjgl.opengl.GL11.glScaled
*/
fun scale(x: Double, y: Double, z: Double = 1.0) {
if (disabled) return
RenderSystem.scaled(x, y, z)
dummyScreen.width = client.window.scaledWidth
dummyScreen.height = client.window.scaledHeight
dummyScreen.renderOrderedTooltip(matrixStack, texts, mouse.x.roundToInt(), mouse.y.roundToInt())
}
/**
@ -206,7 +119,7 @@ object RenderHelper: DrawableHelper() {
*/
fun color(r: Float, g: Float, b: Float, alpha: Float) {
if (disabled) return
RenderSystem.color4f(r, g, b, alpha)
RenderSystem.setShaderColor(r, g, b, alpha)
}
private fun VertexConsumer.color(color: Color): VertexConsumer {

View File

@ -26,11 +26,13 @@ class BezierCurveView(val curve: BezierCurve): View() {
var lineColor = Color.BLACK
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.scale(bounds.width, bounds.height)
matrixStack.push()
matrixStack.scale(bounds.width.toFloat(), bounds.height.toFloat(), 1f)
for ((index, point) in points.withIndex()) {
val next = points.getOrNull(index + 1) ?: break
RenderHelper.drawLine(point, next, zIndex, lineWidth, lineColor)
}
matrixStack.pop()
}
}
}

View File

@ -29,7 +29,7 @@ class DialogView(
CANCEL, CONFIRM, OK, CLOSE;
override val localizedName: Text
get() = LiteralText(name.toLowerCase().capitalize()) // todo: actually localize me
get() = LiteralText(name.lowercase().replaceFirstChar(Char::titlecase)) // todo: actually localize me
}
private lateinit var background: NinePatchView

View File

@ -12,7 +12,6 @@ import no.birkett.kiwi.Constraint
import no.birkett.kiwi.Solver
import java.lang.RuntimeException
import java.util.*
import kotlin.collections.HashSet
import kotlin.math.floor
/**
@ -368,8 +367,8 @@ open class View(): Responder {
* @param delta The time since the last frame.
*/
open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.pushMatrix()
RenderHelper.translate(floor(frame.left), floor(frame.top))
matrixStack.push()
matrixStack.translate(frame.left, frame.top, 0.0)
RenderHelper.fill(matrixStack, bounds, backgroundColor)
@ -380,7 +379,7 @@ open class View(): Responder {
it.draw(matrixStack, mouseInView, delta)
}
RenderHelper.popMatrix()
matrixStack.pop()
}
/**

View File

@ -109,8 +109,8 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
}
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.pushMatrix()
RenderHelper.translate(floor(frame.left), floor(frame.top))
matrixStack.push()
matrixStack.translate(frame.left, frame.top, 0.0)
RenderHelper.fill(matrixStack, bounds, backgroundColor)
@ -123,7 +123,7 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
// don't draw subviews, otherwise all background views + content will get drawn
RenderHelper.popMatrix()
matrixStack.pop()
if (tooltip != null && mouse in bounds) {
window!!.drawTooltip(listOf(tooltip!!))

View File

@ -1,15 +1,12 @@
package net.shadowfacts.cacao.view.textfield
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.TickableElement
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.LiteralText
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.KeyModifiers
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.view.View
import net.shadowfacts.phycon.mixin.client.TextFieldWidgetAccessor
import org.lwjgl.glfw.GLFW
@ -25,7 +22,7 @@ import org.lwjgl.glfw.GLFW
*/
abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
initialText: String
): View(), TickableElement {
): View() {
/**
* A function that is invoked when the text in this text field changes.
@ -70,7 +67,7 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
var drawBackground = true
set(value) {
field = value
minecraftWidget.setHasBorder(value)
minecraftWidget.setDrawsBackground(value)
}
private lateinit var originInWindow: Point
@ -79,7 +76,7 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
init {
minecraftWidget.text = initialText
minecraftWidget.setTextPredicate { this.validate(it) }
minecraftWidget.setHasBorder(drawBackground)
minecraftWidget.setDrawsBackground(drawBackground)
}
/**
@ -103,14 +100,14 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
}
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
RenderHelper.pushMatrix()
RenderHelper.translate(-originInWindow.x, -originInWindow.y)
matrixStack.push()
matrixStack.translate(-originInWindow.x, -originInWindow.y, 0.0)
val mouseXInWindow = (mouse.x + originInWindow.x).toInt()
val mouseYInWindow = (mouse.y + originInWindow.y).toInt()
minecraftWidget.render(matrixStack, mouseXInWindow, mouseYInWindow, delta)
RenderHelper.popMatrix()
matrixStack.pop()
}
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
@ -135,12 +132,12 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
override fun didBecomeFirstResponder() {
super.didBecomeFirstResponder()
minecraftWidget.setSelected(true)
minecraftWidget.setTextFieldFocused(true)
}
override fun didResignFirstResponder() {
super.didResignFirstResponder()
minecraftWidget.setSelected(false)
minecraftWidget.setTextFieldFocused(false)
}
override fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
@ -164,7 +161,7 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
return result || (isFirstResponder && keyCode != GLFW.GLFW_KEY_ESCAPE)
}
override fun tick() {
fun tick() {
minecraftWidget.tick()
}

View File

@ -147,11 +147,6 @@ class TabViewController<T: TabViewController.Tab>(
currentTabController.viewWillAppear()
}
override fun viewDidAppear() {
super.viewDidAppear()
currentTabController.viewDidAppear()
}
override fun viewWillDisappear() {
super.viewWillDisappear()
currentTabController.viewWillDisappear()
@ -225,7 +220,6 @@ class TabViewController<T: TabViewController.Tab>(
embedChild(currentTabController, tabVCContainer)
currentTabController.didMoveTo(this)
currentTabController.viewWillAppear()
currentTabController.viewDidAppear()
onTabChange?.invoke(currentTab)

View File

@ -122,12 +122,12 @@ abstract class ViewController {
children.forEach(ViewController::viewWillAppear)
}
/**
* Called immediately after the VC's view has first been displayed on screen.
*/
open fun viewDidAppear() {
children.forEach(ViewController::viewDidAppear)
}
// /**
// * Called immediately after the VC's view has first been displayed on screen.
// */
// open fun viewDidAppear() {
// children.forEach(ViewController::viewDidAppear)
// }
/**
* Called before the view will disappear from the screen, either because the VC has been removed from it's parent/screen

View File

@ -100,4 +100,4 @@ class KiwiContext(val solver: Solver) {
fun Solver.dsl(init: KiwiContext.() -> Unit): Solver {
KiwiContext(this).init()
return this
}
}

View File

@ -16,6 +16,7 @@ object DefaultPlugin: PhyConPlugin {
override fun initializePhyCon(api: PhyConAPI) {
SORT_MODE = api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST)
SORT_MODE.setPriority(Int.MAX_VALUE)
}
}

View File

@ -2,8 +2,10 @@ package net.shadowfacts.phycon
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
import net.fabricmc.loader.api.FabricLoader
import net.shadowfacts.phycon.api.PhyConPlugin
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlockEntity
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
@ -26,9 +28,12 @@ object PhysicalConnectivity: ModInitializer {
PhyItems.init()
PhyScreens.init()
registerGlobalReceiver(C2SConfigureDevice)
registerGlobalReceiver(C2STerminalCraftingButton)
registerGlobalReceiver(C2STerminalRequestItem)
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
registerGlobalReceiver(C2SConfigureDevice)
ItemStorage.SIDED.registerForBlockEntity(P2PReceiverBlockEntity::provideItemStorage, PhyBlockEntities.P2P_RECEIVER)
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
it.initializePhyCon(PhyConAPIImpl)

View File

@ -8,6 +8,7 @@ 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.redstone_emitter.RedstoneEmitterScreen
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.block.terminal.TerminalScreen
import net.shadowfacts.phycon.client.PhyExtendedModelProvider
@ -40,6 +41,7 @@ object PhysicalConnectivityClient: ClientModInitializer {
}
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)

View File

@ -2,15 +2,20 @@ package net.shadowfacts.phycon.block
import net.minecraft.block.Block
import net.minecraft.block.BlockEntityProvider
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.minecraft.world.World
/**
* @author shadowfacts
*/
abstract class BlockWithEntity<T: BlockEntity>(settings: Settings): Block(settings), BlockEntityProvider {
abstract override fun createBlockEntity(world: BlockView): T?
abstract override fun createBlockEntity(pos: BlockPos, state: BlockState): T?
fun getBlockEntity(world: BlockView, pos: BlockPos): T? {
val entity = world.getBlockEntity(pos)
return if (entity != null) {
@ -19,4 +24,4 @@ abstract class BlockWithEntity<T: BlockEntity>(settings: Settings): Block(settin
null
}
}
}
}

View File

@ -1,6 +1,9 @@
package net.shadowfacts.phycon.block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
@ -25,4 +28,14 @@ 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>? {
return if (world.isClient) {
null
} else {
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
(blockEntity as DeviceBlockEntity).tick()
}
}
}
}

View File

@ -1,34 +1,34 @@
package net.shadowfacts.phycon.block
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.util.NetworkUtil
import net.shadowfacts.phycon.frame.ARPQueryFrame
import net.shadowfacts.phycon.frame.ARPResponseFrame
import net.shadowfacts.phycon.frame.BasePacketFrame
import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.NetworkUtil
import java.lang.ref.WeakReference
import java.util.*
/**
* @author shadowfacts
*/
abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
BlockEntityClientSerializable,
Tickable,
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): BlockEntity(type, pos, state),
PacketSink,
PacketSource,
Interface {
@ -45,6 +45,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
private val packetQueue = LinkedList<PendingPacket>()
private var cachedDestination: WeakReference<Interface>? = null
var counter: Long = 0
@ -59,6 +60,9 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
is DeviceRemovedPacket -> {
arpTable.remove(packet.source)
}
is PingPacket -> {
sendPacket(PongPacket(ipAddress, packet.source))
}
}
handle(packet)
}
@ -72,6 +76,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
when (frame) {
is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame)
is NetworkSplitFrame -> handleNetworkSplit()
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
doHandlePacket(frame.packet)
@ -104,6 +109,11 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
packetQueue.removeAll(toRemove)
}
protected open fun handleNetworkSplit() {
arpTable.clear()
cachedDestination = null
}
override fun sendPacket(packet: Packet) {
if (packet.destination.isBroadcast) {
send(BasePacketFrame(packet, macAddress, MACAddress.BROADCAST))
@ -118,15 +128,29 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
}
open fun findDestination(): Interface? {
val cachedDestination = this.cachedDestination?.get()
if (cachedDestination != null) {
return cachedDestination
}
val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
return when (sides.size) {
0 -> null
1 -> NetworkUtil.findConnectedInterface(world!!, pos, sides.first())
1 -> {
NetworkUtil.findConnectedInterface(world!!, pos, sides.first())?.also {
this.cachedDestination = WeakReference(it)
}
}
else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
}
}
override fun tick() {
override fun cableDisconnected() {
cachedDestination = null
handleNetworkSplit()
}
open fun tick() {
counter++
if (!world!!.isClient) {
@ -148,33 +172,50 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
}
}
protected open fun toCommonTag(tag: CompoundTag) {
protected open fun toCommonTag(tag: NbtCompound) {
tag.putInt("IPAddress", ipAddress.address)
tag.putLong("MACAddress", macAddress.address)
}
protected open fun fromCommonTag(tag: CompoundTag) {
protected open fun fromCommonTag(tag: NbtCompound) {
ipAddress = IPAddress(tag.getInt("IPAddress"))
macAddress = MACAddress(tag.getLong("MACAddress"))
}
override fun toTag(tag: CompoundTag): CompoundTag {
override fun writeNbt(tag: NbtCompound) {
super.writeNbt(tag)
toCommonTag(tag)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
override fun readNbt(tag: NbtCompound) {
super.readNbt(tag)
fromCommonTag(tag)
if (tag.getBoolean("_SyncPacket")) {
fromClientTag(tag)
}
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
override fun toUpdatePacket(): BlockEntityUpdateS2CPacket {
return BlockEntityUpdateS2CPacket.create(this)
}
override fun toInitialChunkDataNbt(): NbtCompound {
val tag = NbtCompound()
tag.putBoolean("_SyncPacket", true)
return toClientTag(tag)
}
open fun toClientTag(tag: NbtCompound): NbtCompound {
toCommonTag(tag)
return tag
}
override fun fromClientTag(tag: CompoundTag) {
fromCommonTag(tag)
open fun fromClientTag(tag: NbtCompound) {
}
fun markUpdate() {
markDirty()
world!!.updateListeners(pos, cachedState, cachedState, 3)
}
fun onBreak() {

View File

@ -2,8 +2,10 @@ package net.shadowfacts.phycon.block.cable
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
import net.minecraft.block.*
import net.minecraft.block.entity.BlockEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
import net.minecraft.util.ActionResult
@ -23,9 +25,11 @@ import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkCableBlock
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.item.FaceDeviceBlockItem
import net.shadowfacts.phycon.util.CableConnection
import net.shadowfacts.phycon.util.NetworkUtil
import net.shadowfacts.phycon.util.containsInclusive
import java.util.*
@ -38,11 +42,10 @@ class CableBlock(
FabricBlockSettings.of(CABLE_MATERIAL)
.strength(0.3f)
.nonOpaque()
.breakByHand(true)
), NetworkCableBlock {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "cable")
val CABLE_MATERIAL = Material.Builder(MaterialColor.BLUE).build()
val CABLE_MATERIAL = Material.Builder(MapColor.BLUE).build()
val CENTER_SHAPE = createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 10.0)
val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
Direction.DOWN to createCuboidShape(6.0, 0.0, 6.0, 10.0, 6.0, 10.0),
@ -196,4 +199,18 @@ class CableBlock(
return getShape(state)
}
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
super.onBreak(world, pos, state, player)
if (!world.isClient) {
world.server?.execute {
// notify devices on either end that the connection was broken (i.e., unset the cached receivers)
val connectedSides = getNetworkConnectedSides(state, world, pos)
for (side in connectedSides) {
val dest = NetworkUtil.findConnectedInterface(world, pos, side)
dest?.cableDisconnected()
}
}
}
}
}

View File

@ -15,6 +15,8 @@ import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import kotlin.math.max
import kotlin.math.min
/**
* @author shadowfacts
@ -57,7 +59,7 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
arr[i] = 16.0 - arr[i]
}
}
createCuboidShape(arr[0], arr[1], arr[2], arr[3], arr[4], 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) }
}
@ -67,7 +69,7 @@ class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
override val faceThickness = 6.0
override val faceShapes: Map<Direction, VoxelShape> = EXTRACTOR_SHAPES
override fun createBlockEntity(world: BlockView) = 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) {

View File

@ -5,8 +5,10 @@ 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.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
@ -26,7 +28,7 @@ import kotlin.properties.Delegates
/**
* @author shadowfacts
*/
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.EXTRACTOR, pos, state),
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice,
ClientConfigurableDevice {
@ -106,21 +108,21 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
return false
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putString("ActivationMode", controller.activationMode.name)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}

View File

@ -25,6 +25,8 @@ import net.minecraft.world.World
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import java.util.*
import kotlin.math.max
import kotlin.math.min
/**
* @author shadowfacts
@ -66,7 +68,7 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
arr[i] = 16.0 - arr[i]
}
}
createCuboidShape(arr[0], arr[1], arr[2], arr[3], arr[4], 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) }
}
@ -76,7 +78,7 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
override val faceThickness = 6.0
override val faceShapes: Map<Direction, VoxelShape> = INSERTER_SHAPES
override fun createBlockEntity(world: BlockView) = 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) {
@ -94,7 +96,7 @@ class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
if (!world.isClient) {
val be = getBlockEntity(world, pos)!!
be.sync()
be.markUpdate()
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {

View File

@ -5,8 +5,10 @@ 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.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.block.DeviceBlockEntity
@ -25,7 +27,7 @@ import kotlin.math.min
/**
* @author shadowfacts
*/
class InserterBlockEntity: DeviceBlockEntity(PhyBlockEntities.INSERTER),
class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state),
ItemStackPacketHandler,
ActivationController.ActivatableDevice,
ClientConfigurableDevice,
@ -132,24 +134,24 @@ class InserterBlockEntity: DeviceBlockEntity(PhyBlockEntities.INSERTER),
currentRequest = null
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
tag.put("StackToExtract", stackToExtract.toTag(CompoundTag()))
tag.put("StackToExtract", stackToExtract.writeNbt(NbtCompound()))
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
stackToExtract = ItemStack.fromTag(tag.getCompound("StackToExtract"))
stackToExtract = ItemStack.fromNbt(tag.getCompound("StackToExtract"))
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putString("ActivationMode", controller.activationMode.name)
tag.putInt("AmountToExtract", amountToExtract)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
amountToExtract = tag.getInt("AmountToExtract")
}

View File

@ -3,6 +3,7 @@ package net.shadowfacts.phycon.block.inserter
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.screen.slot.Slot
@ -44,9 +45,9 @@ class InserterScreen(
amountField = TextFieldWidget(textRenderer, x + 57, y + 24, 80, 9, LiteralText("Amount"))
amountField.text = handler.inserter.amountToExtract.toString()
amountField.setHasBorder(false)
amountField.setDrawsBackground(false)
amountField.isVisible = true
amountField.setSelected(true)
amountField.setTextFieldFocused(true)
amountField.setEditableColor(0xffffff)
amountField.setTextPredicate {
if (it.isEmpty()) {
@ -60,7 +61,7 @@ class InserterScreen(
}
}
}
addChild(amountField)
addDrawableChild(amountField)
}
fun amountUpdated() {
@ -70,16 +71,16 @@ class InserterScreen(
}
}
override fun tick() {
super.tick()
override fun handledScreenTick() {
super.handledScreenTick()
amountField.tick()
}
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
renderBackground(matrixStack)
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND)
RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.setShaderTexture(0, BACKGROUND)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
@ -96,7 +97,7 @@ class InserterScreen(
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, slotActionType: SlotActionType?) {
super.onMouseClick(slot, invSlot, clickData, slotActionType)
amountField.setSelected(true)
amountField.setTextFieldFocused(true)
}
override fun charTyped(c: Char, i: Int): Boolean {

View File

@ -60,17 +60,17 @@ class InserterScreenHandler(
return true
}
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
// fake slot
if (slotId == 0) {
if (player.inventory.cursorStack.isEmpty) {
if (cursorStack.isEmpty) {
inserter.stackToExtract = ItemStack.EMPTY
} else {
inserter.stackToExtract = player.inventory.cursorStack.copyWithCount(1)
inserter.stackToExtract = cursorStack.copyWithCount(1)
}
stackToExtractChanged()
}
return super.onSlotClick(slotId, clickData, actionType, player)
super.onSlotClick(slotId, clickData, actionType, player)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {

View File

@ -51,7 +51,7 @@ class MinerBlock: DeviceBlock<MinerBlockEntity>(
builder.add(FACING)
}
override fun createBlockEntity(world: BlockView) = MinerBlockEntity()
override fun createBlockEntity(pos: BlockPos, state: BlockState) = MinerBlockEntity(pos, state)
override fun getPlacementState(context: ItemPlacementContext): BlockState? {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite

View File

@ -7,7 +7,7 @@ import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.server.world.ServerWorld
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos
@ -27,7 +27,7 @@ import kotlin.math.min
/**
* @author shadowfacts
*/
class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.MINER, pos, state),
NetworkStackProvider,
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>,
ActivationController.ActivatableDevice,
@ -57,10 +57,10 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
}
private fun handleRequestInventory(packet: RequestInventoryPacket) {
if (minerMode != MinerMode.ON_DEMAND) {
if (minerMode != MinerMode.ON_DEMAND || packet.kind != RequestInventoryPacket.Kind.GROUPED) {
return
}
sendPacket(ReadInventoryPacket(invProxy, ipAddress, packet.source))
sendPacket(ReadGroupedInventoryPacket(invProxy, ipAddress, packet.source))
}
private fun handleLocateStack(packet: LocateStackPacket) {
@ -156,23 +156,23 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
return minerMode == MinerMode.ON_DEMAND
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putString("MinerMode", minerMode.name)
tag.putString("ActivationMode", controller.activationMode.name)
tag.putInt("ProviderPriority", providerPriority)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
minerMode = MinerMode.valueOf(tag.getString("MinerMode"))
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
providerPriority = tag.getInt("ProviderPriority")
@ -181,7 +181,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
enum class MinerMode {
ON_DEMAND, AUTOMATIC;
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.toLowerCase()}")
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}")
}
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView {
@ -205,7 +205,8 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
if (cachedDrops == null || realState != cachedState || recalculate) {
cachedState = realState
val be = if (realState.block.hasBlockEntity()) world.getBlockEntity(targetPos) else null
val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null
cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL)
}
return cachedDrops!!

View File

@ -40,7 +40,7 @@ class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>(
Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
)
override fun createBlockEntity(world: BlockView) = 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) {

View File

@ -6,7 +6,8 @@ import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
@ -18,12 +19,13 @@ 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 kotlin.math.min
/**
* @author shadowfacts
*/
class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INTERFACE, pos, state),
ItemStackPacketHandler,
NetworkStackProvider,
NetworkStackReceiver,
@ -36,20 +38,21 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
override var receiverPriority = 0
var syncPriorities = true
// todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null
private var inventory: WeakReference<GroupedItemInv>? = null
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option).let {
WeakReference(it)
}
}
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 == null) updateInventory()
return inventory
if (inventory?.get() == null) updateInventory()
return inventory?.get()
}
override fun handle(packet: Packet) {
@ -63,8 +66,11 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
}
private fun handleRequestInventory(packet: RequestInventoryPacket) {
if (packet.kind != RequestInventoryPacket.Kind.GROUPED) {
return
}
getInventory()?.also { inv ->
sendPacket(ReadInventoryPacket(inv, ipAddress, packet.source))
sendPacket(ReadGroupedInventoryPacket(inv, ipAddress, packet.source))
}
}
@ -109,23 +115,23 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
}
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putInt("ProviderPriority", providerPriority)
tag.putInt("ReceiverPriority", receiverPriority)
tag.putBoolean("SyncPriorities", syncPriorities)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
providerPriority = tag.getInt("ProviderPriority")
receiverPriority = tag.getInt("ReceiverPriority")
syncPriorities = tag.getBoolean("SyncPriorities")

View File

@ -4,6 +4,9 @@ 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
import net.minecraft.block.entity.BlockEntityTicker
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.sound.BlockSoundGroup
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
@ -40,7 +43,17 @@ class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
}
override fun createBlockEntity(world: BlockView) = SwitchBlockEntity()
override fun createBlockEntity(pos: BlockPos, state: BlockState) = SwitchBlockEntity(pos, state)
override fun <T: BlockEntity> getTicker(world: World, state: BlockState, type: BlockEntityType<T>): BlockEntityTicker<T>? {
return if (world.isClient) {
null
} else {
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
(blockEntity as SwitchBlockEntity).tick()
}
}
}
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.netswitch
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.util.Tickable
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtList
import net.minecraft.network.Packet
import net.minecraft.network.listener.ClientPlayPacketListener
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
@ -15,6 +17,7 @@ import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress
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.NetworkUtil
@ -25,9 +28,7 @@ import java.util.LinkedList
/**
* @author shadowfacts
*/
class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
BlockEntityClientSerializable,
Tickable {
class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockEntities.SWITCH, pos, state) {
companion object {
var SWITCHING_CAPACITY = 256 // 256 packets/tick
@ -36,6 +37,7 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
val interfaces = Direction.values().map { SwitchInterface(it, WeakReference(this), MACAddress.random()) }
private val macTable = mutableMapOf<MACAddress, Direction>()
private val destinationCache = Array<WeakReference<Interface>?>(6) { null }
private var packetsHandledThisTick = 0
private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
@ -65,12 +67,12 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
interfaceForSide(dir).send(frame)
} else {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame)
flood(frame, fromItf)
}
}
private fun flood(frame: EthernetFrame, source: Interface) {
private fun flood(frame: EthernetFrame, source: SwitchInterface) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, source.side, source.macAddress, frame)
for (itf in interfaces) {
if (source == itf) continue
itf.send(frame)
@ -79,10 +81,23 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
private fun findDestination(fromItf: Interface): Interface? {
val side = (fromItf as SwitchInterface).side
return NetworkUtil.findConnectedInterface(world!!, pos, side)
return destinationCache[side.ordinal]?.get()
?: NetworkUtil.findConnectedInterface(world!!, pos, side)?.also {
destinationCache[side.ordinal] = WeakReference(it)
}
}
override fun tick() {
private fun cableDisconnected(itf: SwitchInterface) {
macTable.entries.filter {
it.value == itf.side
}.forEach {
macTable.remove(it.key)
}
destinationCache[itf.side.ordinal] = null
flood(NetworkSplitFrame(itf.macAddress), itf)
}
fun tick() {
packetsHandledThisTick = 0
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
@ -91,39 +106,41 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
override fun writeNbt(tag: NbtCompound) {
super.writeNbt(tag)
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
val list = ListTag()
val list = NbtList()
for ((frame, fromItf) in delayedPackets) {
val packet = frame.packet
if (packet !is ItemStackPacket) continue
val compound = CompoundTag()
val compound = NbtCompound()
compound.putInt("FromItfSide", fromItf.side.ordinal)
compound.putInt("SourceIP", packet.source.address)
compound.putInt("DestinationIP", packet.destination.address)
compound.putLong("SourceMAC", frame.source.address)
compound.putLong("DestinationMAC", frame.destination.address)
compound.put("Stack", packet.stack.toTag(CompoundTag()))
compound.put("Stack", packet.stack.writeNbt(NbtCompound()))
list.add(compound)
}
tag.put("DelayedStackPackets", list)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
override fun readNbt(tag: NbtCompound) {
super.readNbt(tag)
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
tag.getList("DelayedStackPackets", 10).forEach { it ->
val compound = it as CompoundTag
val compound = it as NbtCompound
val fromItfSide = Direction.values()[compound.getInt("FromItfSide")]
val fromItf = interfaces.find { it.side == fromItfSide }!!
val sourceIP = IPAddress(compound.getInt("SourceIP"))
val destinationIP = IPAddress(compound.getInt("DestinationIP"))
val sourceMAC = MACAddress(compound.getLong("SourceMAC"))
val destinationMAC = MACAddress(compound.getLong("DestinationMAC"))
val stack = ItemStack.fromTag(compound.getCompound("Stack"))
val stack = ItemStack.fromNbt(compound.getCompound("Stack"))
if (!stack.isEmpty) {
val packet = ItemStackPacket(stack, sourceIP, destinationIP)
val frame = BasePacketFrame(packet, sourceMAC, destinationMAC)
@ -132,15 +149,14 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
}
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return tag
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
return BlockEntityUpdateS2CPacket.create(this)
}
override fun fromClientTag(tag: CompoundTag) {
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
override fun toInitialChunkDataNbt(): NbtCompound {
val tag = NbtCompound()
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return tag
}
class SwitchInterface(
@ -157,6 +173,10 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
override fun send(frame: EthernetFrame) {
switch.get()?.findDestination(this)?.receive(frame)
}
override fun cableDisconnected() {
switch.get()?.cableDisconnected(this)
}
}
}

View File

@ -0,0 +1,37 @@
package net.shadowfacts.phycon.block.p2p
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.sound.BlockSoundGroup
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.FaceDeviceBlock
/**
* @author shadowfacts
*/
class P2PInterfaceBlock: FaceDeviceBlock<P2PInterfaceBlockEntity>(
Settings.of(Material.METAL)
.strength(1.5f)
.sounds(BlockSoundGroup.METAL)
) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "p2p_interface")
}
override val faceThickness = 4.0
override val faceShapes = mapOf(
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
)
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PInterfaceBlockEntity(pos, state)
}

View File

@ -0,0 +1,47 @@
package net.shadowfacts.phycon.block.p2p
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.minecraft.block.BlockState
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.ReadItemStoragePacket
import net.shadowfacts.phycon.packet.RequestInventoryPacket
/**
* @author shadowfacts
*/
class P2PInterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_INTERFACE, pos, state) {
private var inventory: Storage<ItemVariant>? = null
private fun updateInventory() {
val facing = cachedState[FaceDeviceBlock.FACING]
inventory = ItemStorage.SIDED.find(world!!, pos.offset(facing), facing.opposite)
}
private fun getInventory(): Storage<ItemVariant>? {
if (inventory == null) updateInventory()
return inventory
}
override fun handle(packet: Packet) {
when (packet) {
is RequestInventoryPacket -> handleRequestInventory(packet)
}
}
private fun handleRequestInventory(packet: RequestInventoryPacket) {
if (packet.kind != RequestInventoryPacket.Kind.SIDED) {
return
}
getInventory()?.also {
sendPacket(ReadItemStoragePacket(it, ipAddress, packet.source))
}
}
}

View File

@ -0,0 +1,40 @@
package net.shadowfacts.phycon.block.p2p
import net.minecraft.block.BlockState
import net.minecraft.block.InventoryProvider
import net.minecraft.block.Material
import net.minecraft.inventory.SidedInventory
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.util.shape.VoxelShape
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.FaceDeviceBlock
/**
* @author shadowfacts
*/
class P2PReceiverBlock: FaceDeviceBlock<P2PReceiverBlockEntity>(
Settings.of(Material.METAL)
.strength(1.5f)
.sounds(BlockSoundGroup.METAL)
) {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "p2p_receiver")
}
override val faceThickness = 4.0
override val faceShapes = mapOf(
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
)
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PReceiverBlockEntity(pos, state)
}

View File

@ -0,0 +1,144 @@
package net.shadowfacts.phycon.block.p2p
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.nbt.NbtCompound
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import java.lang.ref.WeakReference
/**
* @author shadowfacts
*/
class P2PReceiverBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_RECEIVER, pos, state),
ClientConfigurableDevice {
enum class Status {
OK,
NO_TARGET,
WAITING_FOR_RESPONSE;
val displayName: Text
get() = when (this) {
OK -> TranslatableText("gui.phycon.p2p_receiver.status.ok")
NO_TARGET -> TranslatableText("gui.phycon.p2p_receiver.status.no_target")
WAITING_FOR_RESPONSE -> TranslatableText("gui.phycon.p2p_receiver.status.waiting_for_response")
}
}
companion object {
const val REQUEST_INVENTORY_TIMEOUT: Long = 100 // ticks
fun provideItemStorage(be: P2PReceiverBlockEntity, side: Direction): Storage<ItemVariant>? {
if (side == be.cachedState[FaceDeviceBlock.FACING]) {
return be.getTargetInventory()
}
return null
}
}
var target: IPAddress? = null
var status = Status.NO_TARGET
private var requestTimestamp: Long = 0
var clientObserver: (() -> Unit)? = null
private var isFirstTick = true
private var targetInventory: WeakReference<Storage<ItemVariant>>? = null
override fun handle(packet: Packet) {
when (packet) {
is PongPacket -> if (packet.source == target) status = Status.OK
is ReadItemStoragePacket -> targetInventory = WeakReference(packet.inventory)
is DeviceRemovedPacket -> if (packet.source == target) targetInventory = null
}
}
override fun handleNetworkSplit() {
super.handleNetworkSplit()
targetInventory = null
}
override fun tick() {
super.tick()
if (isFirstTick) {
isFirstTick = false
updateStatus()
}
}
fun getTargetInventory(): Storage<ItemVariant>? {
if (target == null) {
return null
}
if (targetInventory?.get() == null && (counter - requestTimestamp) >= REQUEST_INVENTORY_TIMEOUT) {
status = Status.WAITING_FOR_RESPONSE
requestTimestamp = counter
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.SIDED, ipAddress, target!!))
}
return targetInventory?.get()
}
private fun updateStatus() {
if (world?.isClient != false) {
return
}
assert(!world!!.isClient)
if (target == null) {
status = Status.NO_TARGET
} else {
status = Status.WAITING_FOR_RESPONSE
sendPacket(PingPacket(ipAddress, target!!))
}
markUpdate()
}
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun toClientTag(tag: NbtCompound): NbtCompound {
tag.putInt("Status", status.ordinal)
return super.toClientTag(tag)
}
override fun fromClientTag(tag: NbtCompound) {
super.fromClientTag(tag)
status = Status.values()[tag.getInt("Status")]
clientObserver?.invoke()
}
override fun writeDeviceConfiguration(tag: NbtCompound) {
target?.address?.let { tag.putInt("Target", it) }
}
override fun loadDeviceConfiguration(tag: NbtCompound) {
target = if (tag.contains("Target")) {
IPAddress(tag.getInt("Target"))
} else {
null
}
updateStatus()
}
}

View File

@ -44,7 +44,7 @@ class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>(
builder.add(POWERED)
}
override fun createBlockEntity(world: BlockView) = RedstoneControllerBlockEntity()
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneControllerBlockEntity(pos, state)
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val state = super.getPlacementState(context)

View File

@ -1,6 +1,8 @@
package net.shadowfacts.phycon.block.redstone_controller
import net.minecraft.nbt.CompoundTag
import net.minecraft.block.BlockState
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
@ -12,7 +14,7 @@ import net.shadowfacts.phycon.util.RedstoneMode
/**
* @author shadowfacts
*/
class RedstoneControllerBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER),
class RedstoneControllerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER, pos, state),
ClientConfigurableDevice {
var managedDevices = Array<IPAddress?>(5) { null }
@ -53,22 +55,22 @@ class RedstoneControllerBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE
}
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
tag.putString("RedstoneMode", redstoneMode.name)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
val addresses = tag.getIntArray("ManagedDevices")
managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))

View File

@ -45,7 +45,7 @@ class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
)
override fun createBlockEntity(world: BlockView) = RedstoneEmitterBlockEntity()
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneEmitterBlockEntity(pos, state)
override fun emitsRedstonePower(state: BlockState): Boolean {
return true
@ -67,7 +67,7 @@ class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
if (!world.isClient) {
val be = getBlockEntity(world, pos)!!
be.sync()
be.markUpdate()
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {

View File

@ -1,16 +1,18 @@
package net.shadowfacts.phycon.block.redstone_emitter
import alexiil.mc.lib.attributes.item.GroupedItemInvView
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.DeviceRemovedPacket
import net.shadowfacts.phycon.packet.ReadInventoryPacket
import net.shadowfacts.phycon.packet.ReadGroupedInventoryPacket
import net.shadowfacts.phycon.packet.RequestInventoryPacket
import net.shadowfacts.phycon.util.ClientConfigurableDevice
import net.shadowfacts.phycon.util.GhostInv
@ -19,7 +21,7 @@ import kotlin.math.round
/**
* @author shadowfacts
*/
class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER),
class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state),
ClientConfigurableDevice,
GhostInv {
@ -40,12 +42,12 @@ class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EM
override fun handle(packet: Packet) {
when (packet) {
is ReadInventoryPacket -> handleReadInventory(packet)
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
}
}
private fun handleReadInventory(packet: ReadInventoryPacket) {
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
inventoryCache[packet.source] = packet.inventory
recalculateRedstone()
}
@ -68,7 +70,7 @@ class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EM
}
private fun updateInventories() {
sendPacket(RequestInventoryPacket(ipAddress))
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
}
private fun recalculateRedstone() {
@ -100,26 +102,26 @@ class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EM
world!!.updateNeighborsAlways(pos.offset(cachedState[FaceDeviceBlock.FACING]), cachedState.block)
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
tag.putInt("CachedEmittedPower", cachedEmittedPower)
tag.put("StackToMonitor", stackToMonitor.toTag(CompoundTag()))
tag.put("StackToMonitor", stackToMonitor.writeNbt(NbtCompound()))
writeDeviceConfiguration(tag)
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
cachedEmittedPower = tag.getInt("CachedEmittedPower")
stackToMonitor = ItemStack.fromTag(tag.getCompound("StackToMonitor"))
stackToMonitor = ItemStack.fromNbt(tag.getCompound("StackToMonitor"))
loadDeviceConfiguration(tag)
}
override fun writeDeviceConfiguration(tag: CompoundTag) {
override fun writeDeviceConfiguration(tag: NbtCompound) {
tag.putInt("MaxAmount", maxAmount)
tag.putString("Mode", mode.name)
}
override fun loadDeviceConfiguration(tag: CompoundTag) {
override fun loadDeviceConfiguration(tag: NbtCompound) {
maxAmount = tag.getInt("MaxAmount")
mode = Mode.valueOf(tag.getString("Mode"))
}
@ -127,7 +129,7 @@ class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EM
enum class Mode {
ANALOG, DIGITAL;
val friendlyName = TranslatableText("gui.phycon.redstone_emitter_mode.${name.toLowerCase()}")
val friendlyName = TranslatableText("gui.phycon.redstone_emitter_mode.${name.lowercase()}")
}
}

View File

@ -2,6 +2,7 @@ package net.shadowfacts.phycon.block.redstone_emitter
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.text.Text
@ -49,8 +50,8 @@ class RedstoneEmitterScreen(
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
super.drawBackground(matrixStack, delta, mouseX, mouseY)
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND)
RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.setShaderTexture(0, BACKGROUND)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)

View File

@ -55,16 +55,16 @@ class RedstoneEmitterScreenHandler(
return true
}
override fun onSlotClick(slotId: Int, clickData: Int, slotActionType: SlotActionType, player: PlayerEntity): ItemStack {
override fun onSlotClick(slotId: Int, clickData: Int, slotActionType: SlotActionType, player: PlayerEntity) {
// fake slot
if (slotId == 0) {
if (player.inventory.cursorStack.isEmpty) {
if (cursorStack.isEmpty) {
emitter.stackToMonitor = ItemStack.EMPTY
} else {
emitter.stackToMonitor = player.inventory.cursorStack.copyWithCount(1)
emitter.stackToMonitor = cursorStack.copyWithCount(1)
}
}
return super.onSlotClick(slotId, clickData, slotActionType, player)
super.onSlotClick(slotId, clickData, slotActionType, player)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {

View File

@ -3,42 +3,35 @@ 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 net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.Inventory
import net.minecraft.inventory.InventoryChangedListener
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.TranslatableText
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.ItemScatterer
import net.minecraft.util.Tickable
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.util.NetworkUtil
import net.shadowfacts.phycon.component.*
import net.shadowfacts.phycon.frame.NetworkSplitFrame
import net.shadowfacts.phycon.packet.*
import net.shadowfacts.phycon.util.NetworkUtil
import java.lang.ref.WeakReference
import java.util.*
import java.util.function.IntBinaryOperator
import kotlin.math.min
import kotlin.properties.Delegates
/**
* @author shadowfacts
*/
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBlockEntity(type),
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state),
InventoryChangedListener,
BlockEntityClientSerializable,
Tickable,
ItemStackPacketHandler,
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
@ -46,18 +39,19 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
val INSERTION_TIMEOUT: Long = 1
val INSERTION_TIMEOUT: Long = 1 // ticks
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
}
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
val internalBuffer = TerminalBufferInventory(18)
private val pendingRequests = LinkedList<StackLocateRequest>()
protected val pendingRequests = LinkedList<StackLocateRequest>()
override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = INSERTION_TIMEOUT
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
private var requestInventoryTimestamp: Long? = null
// todo: multiple players could have the terminal open simultaneously
var netItemObserver: WeakReference<NetItemObserver>? = null
@ -76,9 +70,14 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
return null
}
override fun handleNetworkSplit() {
super.handleNetworkSplit()
inventoryCache.clear()
}
override fun handle(packet: Packet) {
when (packet) {
is ReadInventoryPacket -> handleReadInventory(packet)
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
is StackLocationPacket -> handleStackLocation(packet)
is ItemStackPacket -> handleItemStack(packet)
@ -86,7 +85,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
}
}
private fun handleReadInventory(packet: ReadInventoryPacket) {
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
inventoryCache[packet.source] = packet.inventory
updateAndSync()
}
@ -129,7 +128,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
protected fun updateAndSync() {
updateNetItems()
// syncs the internal buffer to the client
sync()
markUpdate()
// syncs the open container (if any) to the client
netItemObserver?.get()?.netItemsChanged()
}
@ -139,7 +138,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
for (inventory in inventoryCache.values) {
for (stack in inventory.storedStacks) {
val amount = inventory.getAmount(stack)
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b })
}
}
}
@ -172,14 +171,26 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
if (!world!!.isClient) {
finishPendingRequests()
finishTimedOutPendingInsertions()
}
if (counter % 20 == 0L && !world!!.isClient) {
beginInsertions()
if (counter % 20 == 0L) {
beginInsertions()
}
if (requestInventoryTimestamp != null && (counter - requestInventoryTimestamp!!) >= REQUEST_INVENTORY_TIMEOUT) {
updateAndSync()
requestInventoryTimestamp = null
}
}
}
open fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
updateAndSync()
inventoryCache.clear()
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
requestInventoryTimestamp = counter
}
}
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
@ -190,7 +201,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
sendPacket(LocateStackPacket(stack, ipAddress))
}
private fun stackLocateRequestCompleted(request: StackLocateRequest) {
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
pendingRequests.remove(request)
val sortedResults = request.results.toMutableList()
@ -220,13 +231,13 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
updateAndSync()
return remaining
// don't start a second insertion, since remaining will be dispatched at the next beginInsertions
return ItemStack.EMPTY
}
override fun onInventoryChanged(inv: Inventory) {
if (inv == internalBuffer && world != null && !world!!.isClient) {
markDirty()
sync()
markUpdate()
}
}
@ -234,12 +245,12 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
ItemScatterer.spawn(world, pos, internalBuffer)
}
override fun toCommonTag(tag: CompoundTag) {
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
tag.put("InternalBuffer", internalBuffer.toTag())
}
override fun fromCommonTag(tag: CompoundTag) {
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
}
@ -252,19 +263,20 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc
var bufferSlot by Delegates.notNull<Int>()
}
data class StackLocateRequest(
open class StackLocateRequest(
val stack: ItemStack,
val amount: Int,
val timestamp: Long,
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
) {
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
val totalResultAmount: Int
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
fun isFinishable(currentTimestamp: Long): Boolean {
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
// correctly sort by priority
return currentTimestamp - timestamp >= AbstractTerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT
}
}

View File

@ -4,7 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.Element
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.util.math.MatrixStack
@ -12,7 +12,6 @@ import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.shadowfacts.cacao.CacaoHandledScreen
@ -22,7 +21,6 @@ import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
import java.math.RoundingMode
import java.text.DecimalFormat
import java.util.LinkedList
import kotlin.math.ceil
import kotlin.math.min
@ -37,12 +35,13 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
val terminalBackgroundHeight: Int,
): CacaoHandledScreen<T>(handler, playerInv, title) {
companion object {
private val clickHandlers = LinkedList<AbstractTerminalScreen<*, *>.(Double, Double, Int) -> Boolean?>()
interface SearchQueryListener {
fun terminalSearchQueryChanged(newValue: String)
fun requestTerminalSearchFieldUpdate(): String?
}
fun registerClickHandler(handler: AbstractTerminalScreen<*, *>.(Double, Double, Int) -> Boolean?) {
clickHandlers.add(handler)
}
companion object {
var searchQueryListener: SearchQueryListener? = null
}
abstract val backgroundTexture: Identifier
@ -50,7 +49,15 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
val terminalVC: AbstractTerminalViewController<*, *, *>
var amountVC: TerminalRequestAmountViewController? = null
private var prevSearchQuery = ""
var searchQuery = ""
set(value) {
field = value
if (prevSearchQuery != value) {
searchQueryListener?.terminalSearchQueryChanged(value)
}
prevSearchQuery = value
}
var scrollPosition = 0.0
init {
@ -100,7 +107,7 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
private val DECIMAL_FORMAT = DecimalFormat("#.#").apply { roundingMode = RoundingMode.HALF_UP }
private val FORMAT = DecimalFormat("##").apply { roundingMode = RoundingMode.HALF_UP }
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int, slot: Slot, matrixStack: MatrixStack) {
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int) {
val amount = stack.count
val s = when {
amount < 1_000 -> amount.toString()
@ -121,16 +128,17 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
// empty string for label because vanilla renders the count behind the damage bar
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
matrixStack.push()
// ItemRenderer.renderGuiItemOverlay creates a new MatrixStack specifically for drawing the overlay
val matrixStack = MatrixStack()
matrixStack.translate(x.toDouble(), y.toDouble(), itemRenderer.zOffset + 200.0)
val scale = 2 / 3f
matrixStack.scale(scale, scale, 1.0f)
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().model, immediate, false, 0, 0xF000F0)
textRenderer.draw(s, textX, textY, 0xffffff, true, matrixStack.peek().positionMatrix, immediate, false, 0, 0xF000F0)
RenderSystem.enableDepthTest()
immediate.draw()
matrixStack.pop()
}
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
@ -140,38 +148,35 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
}
open fun drawBackgroundTexture(matrixStack: MatrixStack) {
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(backgroundTexture)
RenderSystem.setShader(GameRenderer::getPositionTexColorShader)
RenderSystem.setShaderTexture(0, backgroundTexture)
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
}
override fun tick() {
super.tick()
override fun handledScreenTick() {
super.handledScreenTick()
if (amountVC != null) {
amountVC!!.field.tick()
} else {
terminalVC.searchField.tick()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
for (handler in clickHandlers) {
val res = handler(mouseX, mouseY, button)
if (res != null) {
return res
}
val newSearchQuery = searchQueryListener?.requestTerminalSearchFieldUpdate()
if (newSearchQuery != null && searchQuery != newSearchQuery) {
searchQuery = newSearchQuery
terminalVC.searchField.text = newSearchQuery
requestUpdatedItems()
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
super.onMouseClick(slot, invSlot, clickData, type)
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id) && handler.cursorStack.isEmpty) {
val stack = slot.stack
if (type == SlotActionType.QUICK_MOVE) {
@ -188,12 +193,11 @@ abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: Abstra
}
}
private val fakeFocusedElement = TextFieldWidget(textRenderer, 0, 0, 0, 0, LiteralText(""))
override fun getFocused(): Element? {
return if (windows.last().firstResponder != null) {
fakeFocusedElement
} else {
null
override fun setFocused(element: Element?) {
super.setFocused(element)
// so that when something else (e.g., REI) steals focus and calls setFocused(null) on us, any first responder resigns
if (element == null) {
windows.last().firstResponder?.resignFirstResponder()
}
}

View File

@ -56,6 +56,8 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
var itemsForDisplay = listOf<ItemStack>()
private set
open val xOffset: Int = 0
init {
if (!terminal.world!!.isClient) {
assert(terminal.netItemObserver?.get() === null)
@ -63,29 +65,31 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
}
val xOffset = xOffset
// network
for (y in 0 until 6) {
for (x in 0 until 9) {
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, 66 + x * 18, 18 + y * 18))
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, xOffset + 66 + x * 18, 18 + y * 18))
}
}
// internal buffer
for (y in 0 until 6) {
for (x in 0 until 3) {
addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18))
addSlot(Slot(terminal.internalBuffer, y * 3 + x, xOffset + 8 + x * 18, 18 + y * 18))
}
}
// player inv
for (y in 0 until 3) {
for (x in 0 until 9) {
addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18))
addSlot(Slot(playerInv, x + y * 9 + 9, xOffset + 66 + x * 18, 140 + y * 18))
}
}
// hotbar
for (x in 0 until 9) {
addSlot(Slot(playerInv, x, 66 + x * 18, 198))
addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198))
}
}
@ -166,16 +170,16 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
terminal.netItemObserver = null
}
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
if (isBufferSlot(slotId)) {
// todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
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
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
}
}
return super.onSlotClick(slotId, clickData, actionType, player)
super.onSlotClick(slotId, clickData, actionType, player)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
@ -201,7 +205,7 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
} 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) }
if (slot.stack.isEmpty) {
if (slotsInsertedInto.isEmpty()) {
return ItemStack.EMPTY
}
}
@ -223,7 +227,7 @@ abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
slot.markDirty()
slotsInsertedInto.add(index)
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
} else if (ItemStack.canCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
val maxToMove = slotStack.maxCount - slotStack.count
val toMove = min(maxToMove, stack.count)
slotStack.increment(toMove)

View File

@ -64,7 +64,7 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
buffer = view.addLayoutGuide()
view.solver.dsl {
buffer.leftAnchor equalTo (pane.leftAnchor + 7)
buffer.leftAnchor equalTo (pane.leftAnchor + 7 + handler.xOffset)
buffer.topAnchor equalTo (pane.topAnchor + 17)
buffer.widthAnchor equalTo (18 * 3)
buffer.heightAnchor equalTo (18 * 6)
@ -72,7 +72,7 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
network = view.addLayoutGuide()
view.solver.dsl {
network.leftAnchor equalTo (pane.leftAnchor + 65)
network.leftAnchor equalTo (pane.leftAnchor + 65 + handler.xOffset)
network.topAnchor equalTo buffer.topAnchor
network.widthAnchor equalTo (18 * 9)
network.heightAnchor equalTo (18 * 6)
@ -100,13 +100,12 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
handler = ::searchFieldChanged
drawBackground = false
}
searchField.becomeFirstResponder()
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
settingsView = settingsStack
TerminalSettings.allKeys.forEach { key ->
TerminalSettings.allKeys.sortedByDescending { it.priority }.forEach { key ->
val button = SettingButton(key)
button.handler = { settingsChanged() }
settingsStack.addArrangedSubview(button)
@ -122,12 +121,12 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
playerInvLabel.topAnchor equalTo (pane.topAnchor + 128)
searchField.leftAnchor equalTo (pane.leftAnchor + 138)
searchField.leftAnchor equalTo (pane.leftAnchor + 138 + handler.xOffset)
searchField.topAnchor equalTo (pane.topAnchor + 5)
searchField.widthAnchor equalTo 80
searchField.heightAnchor equalTo 9
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232)
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232 + handler.xOffset)
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
scrollTrack.widthAnchor equalTo 12
@ -137,6 +136,12 @@ abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S
}
}
override fun viewWillAppear() {
super.viewWillAppear()
searchField.becomeFirstResponder()
}
private fun searchFieldChanged(field: TextField) {
screen.searchQuery = field.text
screen.requestUpdatedItems()

View File

@ -0,0 +1,20 @@
package net.shadowfacts.phycon.block.terminal
import net.minecraft.block.BlockState
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.world.BlockView
import net.shadowfacts.phycon.PhysicalConnectivity
/**
* @author shadowfacts
*/
class CraftingTerminalBlock: AbstractTerminalBlock<CraftingTerminalBlockEntity>() {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal")
}
override fun createBlockEntity(pos: BlockPos, state: BlockState) = CraftingTerminalBlockEntity(pos, state)
}

View File

@ -0,0 +1,138 @@
package net.shadowfacts.phycon.block.terminal
import alexiil.mc.lib.attributes.item.ItemStackCollections
import alexiil.mc.lib.attributes.item.ItemStackUtil
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.SimpleInventory
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
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.ItemStackPacket
import net.shadowfacts.phycon.packet.LocateStackPacket
import net.shadowfacts.phycon.packet.RequestInventoryPacket
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) {
val craftingInv = SimpleInventory(9)
private val completedCraftingStackRequests = LinkedList<CraftingStackLocateRequest>()
override fun onActivate(player: PlayerEntity) {
super.onActivate(player)
if (!world!!.isClient) {
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity)
}
override fun getDisplayName() = TranslatableText("block.phycon.crafting_terminal")
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
buf.writeBlockPos(this@CraftingTerminalBlockEntity.pos)
}
}
player.openHandledScreen(factory)
}
}
fun requestItemsForCrafting(maxAmount: Int) {
val amounts = ItemStackCollections.map<IntArray>()
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)
}
for ((stack, amountPerSlot) in amounts) {
val total = amountPerSlot.sum()
val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot)
pendingRequests.add(request)
sendPacket(LocateStackPacket(stack, ipAddress))
}
}
override fun stackLocateRequestCompleted(request: StackLocateRequest) {
if (request is CraftingStackLocateRequest) {
completedCraftingStackRequests.add(request)
}
super.stackLocateRequestCompleted(request)
}
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
val craftingReq = completedCraftingStackRequests.find { ItemStackUtil.areEqualIgnoreAmounts(it.stack, 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
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
if (remaining.isEmpty) {
break
}
}
if (craftingReq.amountPerSlot.sum() == 0 || craftingReq.received >= craftingReq.totalResultAmount) {
completedCraftingStackRequests.remove(craftingReq)
}
if (!remaining.isEmpty) {
remaining = internalBuffer.insert(remaining, TerminalBufferInventory.Mode.FROM_NETWORK)
}
updateAndSync()
return remaining
} else {
return super.doHandleItemStack(packet)
}
}
override fun toCommonTag(tag: NbtCompound) {
super.toCommonTag(tag)
tag.put("CraftingInv", craftingInv.toTag())
}
override fun fromCommonTag(tag: NbtCompound) {
super.fromCommonTag(tag)
craftingInv.fromTag(tag.getList("CraftingInv", 10))
}
class CraftingStackLocateRequest(
stack: ItemStack,
amount: Int,
timestamp: Long,
val amountPerSlot: IntArray,
): StackLocateRequest(stack, amount, timestamp) {
var received: Int = 0
}
}

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.block.terminal
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
/**
* @author shadowfacts
*/
class CraftingTerminalScreen(
handler: CraftingTerminalScreenHandler,
playerInv: PlayerInventory,
title: Text,
): AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>(
handler,
playerInv,
title,
259,
252,
) {
companion object {
private val BACKGROUND_1 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_1.png")
private val BACKGROUND_2 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_2.png")
}
override val backgroundTexture = BACKGROUND_1
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
return CraftingTerminalViewController(this, handler)
}
override fun drawBackgroundTexture(matrixStack: MatrixStack) {
RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.setShaderTexture(0, BACKGROUND_1)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, 256, 252)
RenderSystem.setShaderTexture(0, BACKGROUND_2)
drawTexture(matrixStack, x + 256, y, 0, 0, 3, 252)
}
}

View File

@ -0,0 +1,165 @@
package net.shadowfacts.phycon.block.terminal
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.inventory.CraftingInventory
import net.minecraft.inventory.CraftingResultInventory
import net.minecraft.inventory.Inventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
import net.minecraft.recipe.RecipeMatcher
import net.minecraft.recipe.RecipeType
import net.minecraft.screen.slot.CraftingResultSlot
import net.minecraft.screen.slot.Slot
import net.minecraft.server.network.ServerPlayerEntity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
/**
* @author shadowfacts
*/
class CraftingTerminalScreenHandler(
syncId: Int,
playerInv: PlayerInventory,
terminal: CraftingTerminalBlockEntity,
): AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) {
val craftingInv = CraftingInv(this)
val result = CraftingResultInventory()
val resultSlot: CraftingResultSlot
val craftingSlotsStart: Int
val craftingSlotsEnd: Int
get() = craftingSlotsStart + 9
override val xOffset: Int
get() = 5
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
this(
syncId,
playerInv,
PhyBlocks.CRAFTING_TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
)
init {
craftingSlotsStart = slots.size
for (y in 0 until 3) {
for (x in 0 until 3) {
this.addSlot(Slot(craftingInv, x + y * 3, 13 + x * 18, 140 + y * 18))
}
}
resultSlot = CraftingResultSlot(playerInv.player, craftingInv, result, 0, 31, 224)
addSlot(resultSlot)
updateCraftingResult()
}
override fun onContentChanged(inventory: Inventory?) {
updateCraftingResult()
}
private fun updateCraftingResult() {
val world = playerInv.player.world
if (!world.isClient) {
val player = playerInv.player as ServerPlayerEntity
val recipe = world.server!!.recipeManager.getFirstMatch(RecipeType.CRAFTING, craftingInv, world)
val resultStack =
if (recipe.isPresent && result.shouldCraftRecipe(world, player, recipe.get())) {
recipe.get().craft(craftingInv)
} else {
ItemStack.EMPTY
}
result.setStack(0, resultStack)
player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), resultSlot.id, resultStack))
}
}
fun clearCraftingGrid() {
assert(!playerInv.player.world.isClient)
for (i in 0 until terminal.craftingInv.size()) {
val craftingInvStack = terminal.craftingInv.getStack(i)
if (craftingInvStack.isEmpty) continue
val remainder = terminal.internalBuffer.insert(craftingInvStack, TerminalBufferInventory.Mode.TO_NETWORK)
terminal.craftingInv.setStack(i, remainder)
}
updateCraftingResult()
sendContentUpdates()
}
fun requestMoreCraftingIngredients(maxAmount: Int) {
assert(!playerInv.player.world.isClient)
terminal.requestItemsForCrafting(maxAmount)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (slotId == resultSlot.id && resultSlot.hasStack()) {
val craftingResult = resultSlot.stack
val originalResult = craftingResult.copy()
// todo: CraftingScreenHandler calls onCraft, but I don't think that's necessary because onStackChanged should handle it
craftingResult.item.onCraft(craftingResult, player.world, player)
if (!insertItem(craftingResult, playerSlotsStart, playerSlotsEnd, true)) {
return ItemStack.EMPTY
}
resultSlot.onQuickTransfer(craftingResult, originalResult)
if (craftingResult.isEmpty) {
resultSlot.stack = ItemStack.EMPTY
}
if (craftingResult.count == originalResult.count) {
return ItemStack.EMPTY
}
resultSlot.onTakeItem(player, craftingResult)
player.dropItem(craftingResult, false)
return originalResult
} else {
return super.transferSlot(player, slotId)
}
}
// RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so...
class CraftingInv(val handler: CraftingTerminalScreenHandler): CraftingInventory(handler, 3, 3) {
private val backing = handler.terminal.craftingInv
override fun isEmpty(): Boolean {
return backing.isEmpty
}
override fun getStack(i: Int): ItemStack {
return backing.getStack(i)
}
override fun removeStack(i: Int): ItemStack {
return backing.removeStack(i)
}
override fun removeStack(i: Int, j: Int): ItemStack {
val res = backing.removeStack(i, j)
if (!res.isEmpty) {
handler.onContentChanged(this)
}
return res
}
override fun setStack(i: Int, itemStack: ItemStack?) {
backing.setStack(i, itemStack)
handler.onContentChanged(this)
}
override fun clear() {
backing.clear()
}
override fun provideRecipeInputs(finder: RecipeMatcher) {
TODO()
}
}
}

View File

@ -0,0 +1,101 @@
package net.shadowfacts.phycon.block.terminal
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.InputUtil
import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.LayoutGuide
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.TextureView
import net.shadowfacts.cacao.view.button.Button
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.networking.C2STerminalCraftingButton
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class CraftingTerminalViewController(
screen: CraftingTerminalScreen,
handler: CraftingTerminalScreenHandler,
): AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>(
screen,
handler,
) {
companion object {
val SMALL_BUTTON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 0, 48)
val SMALL_BUTTON_HOVERED = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 16, 48)
val CLEAR_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 32, 48)
val PLUS_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 48, 48)
}
lateinit var craftingInv: LayoutGuide
override fun viewDidLoad() {
super.viewDidLoad()
craftingInv = view.addLayoutGuide()
view.solver.dsl {
craftingInv.leftAnchor equalTo buffer.leftAnchor
craftingInv.topAnchor equalTo playerInv.topAnchor
craftingInv.widthAnchor equalTo buffer.widthAnchor
craftingInv.heightAnchor equalTo 54
}
val craftingLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_crafting"))).apply {
textColor = Color.TEXT
}
view.solver.dsl {
craftingLabel.leftAnchor equalTo craftingInv.leftAnchor
craftingLabel.topAnchor equalTo playerInvLabel.topAnchor
}
val clearIcon = TextureView(CLEAR_ICON).apply {
intrinsicContentSize = Size(3.0,3.0)
}
val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply {
background = TextureView(SMALL_BUTTON)
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
tooltip = TranslatableText("gui.phycon.terminal.clear_crafting")
}
view.solver.dsl {
clearButton.topAnchor equalTo craftingInv.topAnchor
clearButton.leftAnchor equalTo (pane.leftAnchor + 4)
}
val plusIcon = TextureView(PLUS_ICON).apply {
intrinsicContentSize = Size(3.0, 3.0)
}
val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply {
background= TextureView(SMALL_BUTTON)
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
tooltip = TranslatableText("gui.phycon.terminal.more_crafting")
}
view.solver.dsl {
plusButton.topAnchor equalTo (clearButton.bottomAnchor + 2)
plusButton.leftAnchor equalTo clearButton.leftAnchor
}
}
private fun clearPressed(button: Button) {
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, C2STerminalCraftingButton.Action.CLEAR_GRID))
}
private fun plusPressed(button: Button) {
val client = MinecraftClient.getInstance()
val action =
if (Screen.hasShiftDown()) {
C2STerminalCraftingButton.Action.REQUEST_MAX_MORE
} else {
C2STerminalCraftingButton.Action.REQUEST_ONE_MORE
}
client.player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, action))
}
}

View File

@ -1,7 +1,8 @@
package net.shadowfacts.phycon.block.terminal
import net.minecraft.block.BlockState
import net.minecraft.util.Identifier
import net.minecraft.world.BlockView
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.PhysicalConnectivity
/**
@ -13,6 +14,6 @@ class TerminalBlock: AbstractTerminalBlock<TerminalBlockEntity>() {
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
}
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
override fun createBlockEntity(pos: BlockPos, state: BlockState) = TerminalBlockEntity(pos, state)
}

View File

@ -1,26 +1,26 @@
package net.shadowfacts.phycon.block.terminal
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.TranslatableText
import net.minecraft.util.math.BlockPos
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.packet.RequestInventoryPacket
/**
* @author shadowfacts
*/
class TerminalBlockEntity: AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL) {
class TerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL, pos, state) {
override fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
updateAndSync()
super.onActivate(player)
inventoryCache.clear()
sendPacket(RequestInventoryPacket(ipAddress))
if (!world!!.isClient) {
val factory = object: ExtendedScreenHandlerFactory {
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity)

View File

@ -3,8 +3,8 @@ 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.CompoundTag
import net.minecraft.nbt.IntArrayTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtIntArray
import net.shadowfacts.phycon.util.fromTag
import net.shadowfacts.phycon.util.toTag
import kotlin.math.min
@ -21,14 +21,14 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
var modes = Array(size) { Mode.UNASSIGNED }
private set
fun toTag(): CompoundTag {
val compound = CompoundTag()
fun toTag(): NbtCompound {
val compound = NbtCompound()
compound.put("Inventory", (this as SimpleInventory).toTag())
compound.put("Modes", IntArrayTag(modes.map(Mode::ordinal)))
compound.put("Modes", NbtIntArray(modes.map(Mode::ordinal)))
return compound
}
fun fromTag(tag: CompoundTag) {
fun fromTag(tag: NbtCompound) {
val inventory = tag.getList("Inventory", 10)
(this as SimpleInventory).fromTag(inventory)
tag.getIntArray("Modes").forEachIndexed { i, it -> modes[i] = Mode.values()[it] }

View File

@ -6,7 +6,7 @@ import net.minecraft.client.render.model.UnbakedModel
import net.minecraft.resource.ResourceManager
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.client.model.TerminalModel
import net.shadowfacts.phycon.client.model.ScreenDeviceModel
/**
* @author shadowfacts
@ -15,11 +15,13 @@ class PhyExtendedModelProvider(resourceManager: ResourceManager): ModelResourceP
companion object {
val TERMINAL = Identifier(PhysicalConnectivity.MODID, "block/terminal")
val CRAFTING_TERMINAL = Identifier(PhysicalConnectivity.MODID, "block/crafting_terminal")
}
override fun loadModelResource(resourceId: Identifier, context: ModelProviderContext): UnbakedModel? {
return when (resourceId) {
TERMINAL -> TerminalModel
TERMINAL -> ScreenDeviceModel(TERMINAL)
CRAFTING_TERMINAL -> ScreenDeviceModel(CRAFTING_TERMINAL)
else -> null
}
}

View File

@ -25,6 +25,10 @@ class PhyModelProvider(resourceManager: ResourceManager) : ModelResourceProvider
val EXTRACTOR_SIDE = Identifier(PhysicalConnectivity.MODID, "block/extractor_side")
val INSERTER = Identifier(PhysicalConnectivity.MODID, "block/inserter")
val INSERTER_SIDE = Identifier(PhysicalConnectivity.MODID, "block/inserter_side")
val P2P_INTERFACE = Identifier(PhysicalConnectivity.MODID, "block/p2p_interface")
val P2P_INTERFACE_SIDE = Identifier(PhysicalConnectivity.MODID, "block/p2p_interface_side")
val P2P_RECEIVER = Identifier(PhysicalConnectivity.MODID, "block/p2p_receiver")
val P2P_RECEIVER_SIDE = Identifier(PhysicalConnectivity.MODID, "block/p2p_receiver_side")
val CABLES = DyeColor.values().map {
Identifier(PhysicalConnectivity.MODID, "block/cable/${it.getName()}") to it
@ -38,6 +42,8 @@ class PhyModelProvider(resourceManager: ResourceManager) : ModelResourceProvider
REDSTONE_EMITTER -> SimpleFaceDeviceModel(REDSTONE_EMITTER_SIDE)
EXTRACTOR -> SimpleFaceDeviceModel(EXTRACTOR_SIDE)
INSERTER -> SimpleFaceDeviceModel(INSERTER_SIDE)
P2P_INTERFACE -> SimpleFaceDeviceModel(P2P_INTERFACE_SIDE)
P2P_RECEIVER -> SimpleFaceDeviceModel(P2P_RECEIVER_SIDE)
in CABLES -> ColoredCableModel(CABLES[resourceId]!!)
else -> null
}

View File

@ -6,6 +6,7 @@ import net.minecraft.client.render.model.*
import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.util.SpriteIdentifier
import net.minecraft.screen.PlayerScreenHandler
import net.minecraft.util.DyeColor
import net.minecraft.util.Identifier
import net.minecraft.util.math.Direction
@ -85,7 +86,7 @@ class ColoredCableModel(
rotationContainer: ModelBakeSettings,
modelId: Identifier
): BakedModel {
centerSprite = textureGetter.apply(SpriteIdentifier(SpriteAtlasTexture.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)
@ -260,7 +261,7 @@ class ColoredCableModel(
override fun isBuiltin() = false
override fun getSprite() = centerSprite
override fun getParticleSprite() = centerSprite
override fun getTransformation() = null

View File

@ -120,9 +120,9 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
val sideQuads = getSideModel(state)?.getQuads(state, face, random) ?: listOf()
val cableQuads = if (connection.direction == facing.opposite) {
interfaceCableStraight[color]!![facing.ordinal]?.getQuads(state, face, random) ?: listOf()
interfaceCableStraight[color]!![facing.ordinal].getQuads(state, face, random) ?: listOf()
} else if (connection == FaceCableConnection.NONE) {
interfaceCableCap[color]!![facing.ordinal]?.getQuads(state, face, random) ?: listOf()
interfaceCableCap[color]!![facing.ordinal].getQuads(state, face, random) ?: listOf()
} else {
val model = when (facing) {
Direction.DOWN -> when (connection) {
@ -183,7 +183,7 @@ abstract class FaceDeviceModel: UnbakedModel, BakedModel {
override fun isBuiltin() = false
abstract override fun getSprite(): Sprite
abstract override fun getParticleSprite(): Sprite
override fun getTransformation() = null

View File

@ -40,8 +40,8 @@ object RedstoneControllerModel: FaceDeviceModel() {
}
}
override fun getSprite(): Sprite {
return offModels.first()!!.sprite
override fun getParticleSprite(): Sprite {
return offModels.first()!!.particleSprite
}
}

View File

@ -13,6 +13,7 @@ import net.minecraft.client.texture.Sprite
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.client.util.SpriteIdentifier
import net.minecraft.item.ItemStack
import net.minecraft.screen.PlayerScreenHandler
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
@ -27,13 +28,18 @@ import java.util.function.Supplier
/**
* @author shadowfacts
*/
object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
class ScreenDeviceModel(
screenTexture: Identifier,
): UnbakedModel, BakedModel, FabricBakedModel {
private val TERMINAL = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/terminal"))
private val CASING = SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/casing"))
companion object {
private val CASING = SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, Identifier(PhysicalConnectivity.MODID, "block/casing"))
}
private val screenTexture = SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, screenTexture)
private lateinit var meshes: Array<Mesh>
private lateinit var terminalSprite: Sprite
private lateinit var screenSprite: Sprite
override fun getModelDependencies(): Collection<Identifier> {
return listOf()
@ -43,7 +49,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
unbakedModelGetter: Function<Identifier, UnbakedModel>,
unresolvedTextureDependencies: MutableSet<Pair<String, String>>
): Collection<SpriteIdentifier> {
return listOf(TERMINAL, CASING)
return listOf(screenTexture, CASING)
}
override fun bake(
@ -53,7 +59,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
modelId: Identifier
): BakedModel {
terminalSprite = textureGetter.apply(TERMINAL)
screenSprite = textureGetter.apply(screenTexture)
val casingSprite = textureGetter.apply(CASING)
val renderer = RendererAccess.INSTANCE.renderer!!
@ -66,26 +72,63 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
for (dir in Direction.values()) {
if (dir == facing) {
emitter.square(facing, 0f, 0f, 1f, 1f, QuadEmitter.CULL_FACE_EPSILON * 10)
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
// screen border
emitter.square(facing, 0f, 0f, 3/16f, 3/16f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 13/16f, 0f, 1f, 3/16f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 13/16f, 13/16f, 1f, 1f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 0f, 13/16f, 3/16f, 1f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 3/16f, 0f, 13/16f, 2/16f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 3/16f, 14/16f, 13/16f, 1f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 0f, 3/16f, 2/16f, 13/16f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.square(facing, 14/16f, 3/16f, 1f, 13/16f, 0f)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
// screen
emitter.material(PhysicalConnectivityClient.screenMaterial)
emitter.square(facing, 3/16f, 2/16f, 13/16f, 3/16f, 0f)
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.material(PhysicalConnectivityClient.screenMaterial)
emitter.square(facing, 2/16f, 3/16f, 14/16f, 13/16f, 0f)
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
emitter.material(PhysicalConnectivityClient.screenMaterial)
emitter.square(facing, 3/16f, 13/16f, 13/16f, 14/16f, 0f)
emitter.spriteBake(0, terminalSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteBake(0, screenSprite, MutableQuadView.BAKE_LOCK_UV)
emitter.spriteColor(0, -1, -1, -1, -1)
emitter.emit()
} else {
@ -129,7 +172,7 @@ object TerminalModel: UnbakedModel, BakedModel, FabricBakedModel {
override fun isBuiltin() = false
override fun getSprite() = terminalSprite
override fun getParticleSprite() = screenSprite
override fun getTransformation() = null

View File

@ -35,7 +35,7 @@ class SimpleFaceDeviceModel(
return sideModels[state[FaceDeviceBlock.COLOR]]!![state[FaceDeviceBlock.FACING].ordinal]
}
override fun getSprite(): Sprite {
return sideModels[DyeColor.BLACK]!!.first().sprite
override fun getParticleSprite(): Sprite {
return sideModels[DyeColor.BLACK]!!.first().particleSprite
}
}

View File

@ -1,48 +0,0 @@
package net.shadowfacts.phycon.client.screen
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.shadowfacts.phycon.util.FriendlyNameable
import net.shadowfacts.phycon.util.RotatableEnum
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import kotlin.reflect.KMutableProperty
/**
* @author shadowfacts
*/
class EnumButton<E>(
val prop: KMutableProperty<E>,
x: Int,
y: Int,
width: Int,
height: Int,
val onChange: () -> Unit
): AbstractPressableButtonWidget(
x,
y,
width,
height,
prop.getter.call().friendlyName
) where E: Enum<E>, E: RotatableEnum, E: FriendlyNameable {
private var currentButton: Int? = null
override fun mouseClicked(d: Double, e: Double, button: Int): Boolean {
currentButton = button
val res = super.mouseClicked(d, e, button)
currentButton = null
return res
}
override fun isValidClickButton(i: Int): Boolean {
return i == 0 || i == 1
}
override fun onPress() {
val newVal = if ((currentButton ?: 0) == 0) prop.getter.call().next else prop.getter.call().prev
prop.setter.call(newVal)
message = newVal.friendlyName
onChange()
}
}

View File

@ -18,7 +18,7 @@ class ScrollTrackView(
): View() {
companion object {
private val THUMB = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png"), 52, 230)
private val THUMB = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png"), 52, 224)
private const val THUMB_WIDTH = 12.0
private const val THUMB_HEIGHT = 15.0
}
@ -50,16 +50,16 @@ class ScrollTrackView(
})
}
override fun mouseDragged(point: Point, delta: Point, mouseButton: MouseButton): Boolean {
if (grabbedY == null && point !in thumbView.frame) {
val newCenter = MathHelper.clamp(point.y, THUMB_HEIGHT / 2, frame.height - THUMB_HEIGHT / 2)
override fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
if (grabbedY == null && startPoint !in thumbView.frame) {
val newCenter = MathHelper.clamp(startPoint.y, THUMB_HEIGHT / 2, frame.height - THUMB_HEIGHT / 2)
thumbView.frame = Rect(0.0, newCenter - THUMB_HEIGHT / 2, THUMB_WIDTH, THUMB_HEIGHT)
}
if (grabbedY == null) {
grabbedY = point.y - thumbView.frame.top
grabbedY = startPoint.y - thumbView.frame.top
}
val newTop = MathHelper.clamp(point.y - grabbedY!!, 0.0, frame.height - THUMB_HEIGHT)
val newTop = MathHelper.clamp(startPoint.y - grabbedY!!, 0.0, frame.height - THUMB_HEIGHT)
thumbView.frame = Rect(0.0, newTop, THUMB_WIDTH, THUMB_HEIGHT)
scrollPositionChanged(this)

View File

@ -14,6 +14,7 @@ import net.shadowfacts.cacao.window.Window
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.block.DeviceBlockEntity
import net.shadowfacts.phycon.block.miner.MinerBlockEntity
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlockEntity
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity
import net.shadowfacts.phycon.component.ActivationController
@ -90,6 +91,15 @@ class DeviceConsoleScreen(
RedstoneEmitterViewController(device)
))
}
if (device is P2PReceiverBlockEntity) {
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)
@ -108,11 +118,11 @@ class DeviceConsoleScreen(
addWindow(Window(root))
}
override fun isPauseScreen() = false
override fun shouldPause() = false
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
if (keyCode == GLFW.GLFW_KEY_E) {
onClose()
close()
return true
}
return super.keyPressed(keyCode, scanCode, modifiers)

View File

@ -0,0 +1,58 @@
package net.shadowfacts.phycon.client.screen.console
import net.minecraft.client.MinecraftClient
import net.minecraft.text.TranslatableText
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.textfield.TextField
import net.shadowfacts.cacao.viewcontroller.ViewController
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlockEntity
import net.shadowfacts.phycon.networking.C2SConfigureDevice
/**
* @author shadowfacts
*/
class P2PReceiverViewController(
val device: P2PReceiverBlockEntity,
): ViewController() {
override fun viewDidLoad() {
super.viewDidLoad()
val label = Label(TranslatableText("gui.phycon.console.p2p_receiver.target")).apply {
textColor = Color.TEXT
}
view.addSubview(label)
val textField =
TextField(device.target?.toString() ?: "") {
device.target = IPAddress.parse(it.text)
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(device))
}
view.addSubview(textField)
val status = Label(device.status.displayName).apply {
textColor = Color.TEXT
}
view.addSubview(status)
device.clientObserver = {
status.text = device.status.displayName
}
view.solver.dsl {
textField.widthAnchor equalTo 100
textField.heightAnchor equalTo 20
textField.topAnchor equalTo view.topAnchor
textField.rightAnchor equalTo view.rightAnchor
label.centerYAnchor equalTo textField.centerYAnchor
label.rightAnchor equalTo (textField.leftAnchor - 4)
status.centerXAnchor equalTo view.centerXAnchor
status.centerYAnchor equalTo (view.centerYAnchor - 10)
}
}
}

View File

@ -36,7 +36,9 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(this)) {
val remaining = finishInsertion(insertion)
// todo: do something with remaining
if (!remaining.isEmpty) {
pendingInsertions.add(createPendingInsertion(remaining))
}
}
}
}
@ -56,12 +58,13 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
// copy the insertion stack so subclasses that override this method can still see the originally dispatched stack after the super call
val remaining = insertion.stack.copy()
while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeFirst()
val (capacity, receiver) = sortedResults.removeFirst()
if (capacity <= 0) continue
val copy = remaining.copyWithCount(min(capacity, remaining.count))
sendPacket(ItemStackPacket(copy, ipAddress, receivingInterface.ipAddress))
val sentCount = min(capacity, remaining.count)
val copy = remaining.copyWithCount(sentCount)
sendPacket(ItemStackPacket(copy, ipAddress, receiver.ipAddress))
// todo: the destination should confirm how much was actually inserted, in case of race condition
remaining.count -= capacity
remaining.count -= sentCount
}
return remaining
@ -89,7 +92,11 @@ fun <Self, Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>> Self.f
val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion)
// todo: do something with remaining?
for (insertion in finishable) {
val remaining = finishInsertion(insertion)
if (!remaining.isEmpty) {
pendingInsertions.add(createPendingInsertion(remaining))
}
}
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
}

View File

@ -1,5 +1,6 @@
package net.shadowfacts.phycon.frame
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress

View File

@ -7,8 +7,8 @@ import net.shadowfacts.phycon.api.util.MACAddress
* @author shadowfacts
*/
open class BaseFrame(
@JvmField private val source: MACAddress,
@JvmField private val destination: MACAddress
private val source: MACAddress,
private val destination: MACAddress
): EthernetFrame {
override fun getSource() = source
override fun getDestination() = destination

View File

@ -8,7 +8,7 @@ import net.shadowfacts.phycon.api.util.MACAddress
* @author shadowfacts
*/
class BasePacketFrame(
@JvmField private val packet: Packet,
private val packet: Packet,
source: MACAddress,
destination: MACAddress,
): BaseFrame(source, destination), PacketFrame {

View File

@ -0,0 +1,8 @@
package net.shadowfacts.phycon.frame
import net.shadowfacts.phycon.api.util.MACAddress
/**
* @author shadowfacts
*/
class NetworkSplitFrame(source: MACAddress): BaseFrame(source, MACAddress.BROADCAST)

View File

@ -1,9 +1,11 @@
package net.shadowfacts.phycon.init
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.block.extractor.ExtractorBlockEntity
@ -15,10 +17,16 @@ import net.shadowfacts.phycon.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.block.netswitch.SwitchBlockEntity
import net.shadowfacts.phycon.block.p2p.P2PInterfaceBlock
import net.shadowfacts.phycon.block.p2p.P2PInterfaceBlockEntity
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlock
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlockEntity
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlockEntity
import net.shadowfacts.phycon.block.terminal.TerminalBlock
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
@ -29,26 +37,32 @@ object PhyBlockEntities {
val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE)
val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL)
val CRAFTING_TERMINAL = create(::CraftingTerminalBlockEntity, PhyBlocks.CRAFTING_TERMINAL)
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
val INSERTER = create(::InserterBlockEntity, PhyBlocks.INSERTER)
val MINER = create(::MinerBlockEntity, PhyBlocks.MINER)
val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER)
val REDSTONE_EMITTER = create(::RedstoneEmitterBlockEntity, PhyBlocks.REDSTONE_EMITTER)
val P2P_INTERFACE = create(::P2PInterfaceBlockEntity, PhyBlocks.P2P_INTERFACE)
val P2P_RECEIVER = create(::P2PReceiverBlockEntity, PhyBlocks.P2P_RECEIVER)
private fun <T: BlockEntity> create(builder: () -> T, block: Block): BlockEntityType<T> {
private fun <T: BlockEntity> create(builder: (BlockPos, BlockState) -> T, block: Block): BlockEntityType<T> {
return BlockEntityType.Builder.create(builder, block).build(null)
}
fun init() {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(InserterBlock.ID, INSERTER)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER)
register(P2PInterfaceBlock.ID, P2P_INTERFACE)
register(P2PReceiverBlock.ID, P2P_RECEIVER)
}
private fun register(id: Identifier, type: BlockEntityType<*>) {

View File

@ -11,8 +11,11 @@ import net.shadowfacts.phycon.block.inserter.InserterBlock
import net.shadowfacts.phycon.block.miner.MinerBlock
import net.shadowfacts.phycon.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.block.p2p.P2PInterfaceBlock
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlock
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.TerminalBlock
/**
@ -24,12 +27,15 @@ object PhyBlocks {
val INTERFACE = InterfaceBlock()
val TERMINAL = TerminalBlock()
val CRAFTING_TERMINAL = CraftingTerminalBlock()
val SWITCH = SwitchBlock()
val EXTRACTOR = ExtractorBlock()
val INSERTER = InserterBlock()
val MINER = MinerBlock()
val REDSTONE_CONTROLLER = RedstoneControllerBlock()
val REDSTONE_EMITTER = RedstoneEmitterBlock()
val P2P_INTERFACE = P2PInterfaceBlock()
val P2P_RECEIVER = P2PReceiverBlock()
fun init() {
for ((color, block) in CABLES) {
@ -38,12 +44,15 @@ object PhyBlocks {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(InserterBlock.ID, INSERTER)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER)
register(P2PInterfaceBlock.ID, P2P_INTERFACE)
register(P2PReceiverBlock.ID, P2P_RECEIVER)
}
private fun register(id: Identifier, block: Block) {

View File

@ -12,11 +12,15 @@ import net.shadowfacts.phycon.block.inserter.InserterBlock
import net.shadowfacts.phycon.block.miner.MinerBlock
import net.shadowfacts.phycon.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.block.p2p.P2PInterfaceBlock
import net.shadowfacts.phycon.block.p2p.P2PReceiverBlock
import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.TerminalBlock
import net.shadowfacts.phycon.item.DeviceBlockItem
import net.shadowfacts.phycon.item.FaceDeviceBlockItem
import net.shadowfacts.phycon.util.text
/**
* @author shadowfacts
@ -29,12 +33,15 @@ object PhyItems {
val INTERFACE = FaceDeviceBlockItem(PhyBlocks.INTERFACE, Item.Settings())
val TERMINAL = DeviceBlockItem(PhyBlocks.TERMINAL, Item.Settings())
val CRAFTING_TERMINAL = DeviceBlockItem(PhyBlocks.CRAFTING_TERMINAL, Item.Settings())
val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings())
val EXTRACTOR = FaceDeviceBlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
val INSERTER = FaceDeviceBlockItem(PhyBlocks.INSERTER, Item.Settings())
val MINER = DeviceBlockItem(PhyBlocks.MINER, Item.Settings())
val REDSTONE_CONTROLLER = FaceDeviceBlockItem(PhyBlocks.REDSTONE_CONTROLLER, Item.Settings())
val REDSTONE_EMITTER = FaceDeviceBlockItem(PhyBlocks.REDSTONE_EMITTER, Item.Settings())
val P2P_INTERFACE = FaceDeviceBlockItem(PhyBlocks.P2P_INTERFACE, Item.Settings())
val P2P_RECEIVER = FaceDeviceBlockItem(PhyBlocks.P2P_RECEIVER, Item.Settings())
val SCREWDRIVER = ScrewdriverItem()
val CONSOLE = ConsoleItem()
@ -52,12 +59,25 @@ object PhyItems {
register(InterfaceBlock.ID, INTERFACE)
register(TerminalBlock.ID, TERMINAL)
register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL)
register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(InserterBlock.ID, INSERTER)
register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(RedstoneEmitterBlock.ID, REDSTONE_EMITTER)
register(P2PInterfaceBlock.ID, P2P_INTERFACE)
P2P_INTERFACE.addTooltip(text {
withStyle(darkGray) {
+translate("tooltip.phycon.p2p_interface")
}
})
register(P2PReceiverBlock.ID, P2P_RECEIVER)
P2P_RECEIVER.addTooltip(text {
withStyle(darkGray) {
+translate("tooltip.phycon.p2p_receiver")
}
})
register(ScrewdriverItem.ID, SCREWDRIVER)
register(ConsoleItem.ID, CONSOLE)

View File

@ -4,6 +4,8 @@ import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry
import net.minecraft.screen.ScreenHandlerType
import net.shadowfacts.phycon.block.inserter.InserterScreenHandler
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
import net.shadowfacts.phycon.block.terminal.TerminalBlock
import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler
@ -11,6 +13,8 @@ object PhyScreens {
lateinit var TERMINAL: ScreenHandlerType<TerminalScreenHandler>
private set
lateinit var CRAFTING_TERMINAL: ScreenHandlerType<CraftingTerminalScreenHandler>
private set
lateinit var INSERTER: ScreenHandlerType<InserterScreenHandler>
private set
lateinit var REDSTONE_EMITTER: ScreenHandlerType<RedstoneEmitterScreenHandler>
@ -18,6 +22,7 @@ object PhyScreens {
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)
}

View File

@ -27,7 +27,7 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
if (context.world.isClient) {
this.openScreen(be)
} else {
be.sync()
be.markUpdate()
}
return ActionResult.SUCCESS
}
@ -38,7 +38,7 @@ class ConsoleItem: Item(Settings().maxCount(1)) {
private fun openScreen(be: DeviceBlockEntity) {
// val screen = TestCacaoScreen()
val screen = DeviceConsoleScreen(be)
MinecraftClient.getInstance().openScreen(screen)
MinecraftClient.getInstance().setScreen(screen)
}
}

View File

@ -14,8 +14,16 @@ import net.shadowfacts.phycon.util.text
*/
open class DeviceBlockItem(block: DeviceBlock<*>, settings: Settings = Settings()): BlockItem(block, settings) {
private var tooltip = mutableListOf<Text>()
fun addTooltip(tooltip: Text) {
this.tooltip.add(tooltip)
}
override fun appendTooltip(stack: ItemStack, world: World?, list: MutableList<Text>, context: TooltipContext) {
val beTag = stack.getSubTag("BlockEntityTag")
list.addAll(tooltip)
val beTag = stack.getSubNbt("BlockEntityTag")
if (beTag != null) {
val ip = IPAddress(beTag.getInt("IPAddress"))
list.add(text {

View File

@ -2,11 +2,14 @@ package net.shadowfacts.phycon.item
import net.minecraft.block.BlockState
import net.minecraft.item.ItemPlacementContext
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.block.FaceDeviceBlock
import net.shadowfacts.phycon.block.cable.CableBlock
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.util.CableConnection
import kotlin.math.floor
import kotlin.math.roundToInt
/**
* @author shadowfacts
@ -18,8 +21,14 @@ class FaceDeviceBlockItem(block: FaceDeviceBlock<*>, settings: Settings = Settin
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 placementSide = if (hitBlockEdge) context.side.opposite else context.side
val placementSide: Direction =
if (hitBlockEdge xor hitBlockBeingPlacedIn) {
context.side
} else {
context.side.opposite
}
if (hitState[CableBlock.CONNECTIONS[placementSide]] != CableConnection.ON) {
var connection = FaceDeviceBlock.FaceCableConnection.NONE

View File

@ -54,12 +54,12 @@ class ScrewdriverItem: Item(Settings().maxCount(1)) {
val be = block.getBlockEntity(context.world, context.blockPos)!!
val stack = ItemStack(block)
val beTag = stack.getOrCreateSubTag("BlockEntityTag")
be.toTag(beTag)
val beTag = be.createNbt()
// remove x, y, z entries for stacking purposes
beTag.remove("x")
beTag.remove("y")
beTag.remove("z")
stack.setSubNbt("BlockEntityTag", beTag)
if (block === PhyBlocks.TERMINAL) {
// remove the terminal's internal buffer since it drops its items

View File

@ -3,7 +3,7 @@ package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.block.entity.BlockEntity
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
@ -24,9 +24,9 @@ object C2SConfigureDevice: ServerReceiver {
buf.writeIdentifier(be.world!!.registryKey.value)
buf.writeBlockPos(be.pos)
val tag = CompoundTag()
val tag = NbtCompound()
be.writeDeviceConfiguration(tag)
buf.writeCompoundTag(tag)
buf.writeNbt(tag)
return createPacket(buf)
}
@ -34,7 +34,7 @@ object C2SConfigureDevice: ServerReceiver {
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val tag = buf.readCompoundTag() ?: return
val tag = buf.readNbt() ?: return
server.execute {
// todo: check if the player is close enough

View File

@ -0,0 +1,56 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.minecraft.util.registry.RegistryKey
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlockEntity
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler
/**
* @author shadowfacts
*/
object C2STerminalCraftingButton: ServerReceiver {
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_crafting_button")
enum class Action {
CLEAR_GRID,
REQUEST_ONE_MORE,
REQUEST_MAX_MORE,
}
operator fun invoke(terminal: CraftingTerminalBlockEntity, action: Action): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeByte(action.ordinal)
return createPacket(buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val action = Action.values()[buf.readByte().toInt()]
server.execute {
val key = RegistryKey.of(Registry.WORLD_KEY, dimID)
val screenHandler = player.currentScreenHandler as? CraftingTerminalScreenHandler ?: return@execute
if (screenHandler.terminal.pos != pos || screenHandler.terminal.world!!.registryKey != key) return@execute
when (action) {
Action.CLEAR_GRID -> screenHandler.clearCraftingGrid()
Action.REQUEST_ONE_MORE -> screenHandler.requestMoreCraftingIngredients(1)
Action.REQUEST_MAX_MORE -> screenHandler.requestMoreCraftingIngredients(64)
}
}
}
}

View File

@ -45,7 +45,7 @@ object C2STerminalRequestItem: ServerReceiver {
val amount = buf.readVarInt()
server.execute {
val key = RegistryKey.of(Registry.DIMENSION, dimID)
val key = RegistryKey.of(Registry.WORLD_KEY, dimID)
val world = server.getWorld(key) ?: return@execute
val terminal = world.getBlockEntity(pos) as? AbstractTerminalBlockEntity ?: return@execute
terminal.requestItem(stack, amount)

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