Compare commits
57 Commits
Author | SHA1 | Date |
---|---|---|
Shadowfacts | b64a05e0ad | |
Shadowfacts | 73de26387a | |
Shadowfacts | 33614e0dc6 | |
Shadowfacts | c18af9794b | |
Shadowfacts | 6d97af8bdc | |
Shadowfacts | d527185888 | |
Shadowfacts | 82482ca9c6 | |
Shadowfacts | 9200dea350 | |
Shadowfacts | e88ecd3215 | |
Shadowfacts | 9cbad193e2 | |
Shadowfacts | b416f98ef6 | |
Shadowfacts | b2e794e5a4 | |
Shadowfacts | b21a45fbbb | |
Shadowfacts | ef9aa9e958 | |
Shadowfacts | c5ede3bd62 | |
Shadowfacts | 30300dbc40 | |
Shadowfacts | 5488cc295a | |
Shadowfacts | 172536cdb8 | |
Shadowfacts | 46e00cea97 | |
Shadowfacts | ce511e62e1 | |
Shadowfacts | 5eb948802c | |
Shadowfacts | 7447c89394 | |
Shadowfacts | 87f0bdb85a | |
Shadowfacts | 236d6707f6 | |
Shadowfacts | 961c74de34 | |
Shadowfacts | c278d137ef | |
Shadowfacts | 12e055d645 | |
Shadowfacts | 9d98481ba5 | |
Shadowfacts | e4662b0f6f | |
Shadowfacts | 4fa5a12746 | |
Shadowfacts | 7286efcfc2 | |
Shadowfacts | 7cb0168c2f | |
Shadowfacts | b435948ee3 | |
Shadowfacts | c15700bf5d | |
Shadowfacts | 9418ff8917 | |
Shadowfacts | 6f1f43f36a | |
Shadowfacts | f12965fc6c | |
Shadowfacts | 28e14ae8bf | |
Shadowfacts | e41c9e3ccb | |
Shadowfacts | b87a36caa4 | |
Shadowfacts | 93b082ee55 | |
Shadowfacts | 2466923d96 | |
Shadowfacts | 84e2c6d6e9 | |
Shadowfacts | 2774cabfcc | |
Shadowfacts | 81ce590231 | |
Shadowfacts | f0fe1e4a3d | |
Shadowfacts | f375d157b0 | |
Shadowfacts | cbea57006a | |
Shadowfacts | a1df6cda25 | |
Shadowfacts | 47ff975449 | |
Shadowfacts | 369dcebe1b | |
Shadowfacts | ee6fb1e725 | |
Shadowfacts | bc50017b4a | |
Shadowfacts | 611c4bb0ae | |
Shadowfacts | 4ab6dbbf38 | |
Shadowfacts | f536bf72a9 | |
Shadowfacts | a86058f8bd |
86
build.gradle
86
build.gradle
|
@ -1,8 +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 "com.github.johnrengelman.shadow" version "4.0.4"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.6.10"
|
||||
}
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
|
@ -11,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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,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 {
|
||||
|
@ -56,20 +57,23 @@ 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 {
|
||||
|
@ -79,16 +83,28 @@ dependencies {
|
|||
include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
|
||||
include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}"
|
||||
|
||||
compile project(":kiwi-java")
|
||||
shadow project(":kiwi-java")
|
||||
implementation project(":kiwi-java")
|
||||
include project(":kiwi-java")
|
||||
|
||||
runtimeOnly project(":plugin:rei")
|
||||
include project(":plugin:rei")
|
||||
// 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"
|
||||
}
|
||||
|
||||
|
@ -102,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()
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
base_color=blue
|
||||
|
||||
modulate() {
|
||||
echo $1, $2
|
||||
mkdir -p src/main/resources/assets/phycon/textures/block/cable/$1
|
||||
for f in src/main/resources/assets/phycon/textures/block/cable/$base_color/*; do
|
||||
echo $f
|
||||
convert $f -modulate $2 src/main/resources/assets/phycon/textures/block/cable/$1/$(basename $f)
|
||||
done
|
||||
echo "---"
|
||||
}
|
||||
|
||||
# modulate <color> <brightness>,<saturation>,<hue>
|
||||
# brightness: 100 is initial, +/- 100
|
||||
# saturnation: 100 is initial, +/- 100
|
||||
# hue: (<gimp hue> / 180) * 100 + 100
|
||||
|
||||
modulate white 200,0,100
|
||||
modulate orange 100,120,200
|
||||
modulate magenta 100,100,146
|
||||
modulate light_blue 140,65,100
|
||||
modulate yellow 100,109,8
|
||||
modulate lime 100,100,53
|
||||
modulate pink 172,50,172
|
||||
modulate gray 60,10,100
|
||||
modulate light_gray 105,3,39
|
||||
modulate cyan 112,65,84
|
||||
modulate purple 109,76,133
|
||||
modulate brown 60,45,190
|
||||
modulate green 60,50,30
|
||||
modulate red 100,100,176
|
||||
modulate black 30,0,100
|
||||
|
|
@ -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.1.0
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
plugins {
|
||||
id "fabric-loom"
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
archivesBaseName = "PhyCon-Plugin-MouseWheelie"
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://maven.siphalor.de/"
|
||||
}
|
||||
maven {
|
||||
url = "https://jitpack.io"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":")) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
modImplementation("de.siphalor:mousewheelie-1.18:${project.mousewheelie_version}") {
|
||||
exclude group: "net.fabricmc"
|
||||
exclude group: "net.fabricmc.fabric-api"
|
||||
exclude module: "modmenu"
|
||||
exclude group: "me.shedaniel.cloth"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package net.shadowfacts.phycon.plugin.mousewheelie.mixin;
|
||||
|
||||
import de.siphalor.mousewheelie.client.util.ScrollAction;
|
||||
import de.siphalor.mousewheelie.client.util.accessors.IContainerScreen;
|
||||
import de.siphalor.mousewheelie.client.util.accessors.ISpecialScrollableScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import net.minecraft.text.Text;
|
||||
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen;
|
||||
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
@Mixin(AbstractTerminalScreen.class)
|
||||
public abstract class MixinTerminalScreen extends HandledScreen<AbstractTerminalScreenHandler> implements ISpecialScrollableScreen, IContainerScreen {
|
||||
|
||||
private MixinTerminalScreen(AbstractTerminalScreenHandler screenHandler, PlayerInventory playerInventory, Text text) {
|
||||
super(screenHandler, playerInventory, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScrollAction mouseWheelie_onMouseScrolledSpecial(double mouseX, double mouseY, double scrollAmount) {
|
||||
Slot slot = mouseWheelie_getSlotAt(mouseX, mouseY);
|
||||
if (slot == null) {
|
||||
return ScrollAction.PASS;
|
||||
}
|
||||
|
||||
if (slot.id < handler.getBufferSlotsEnd()) {
|
||||
// scrolling in the network inventory is never allowed
|
||||
// scrolling out of the buffer is theoretically possible, but there isn't a straightforward way
|
||||
// of telling the server not to mark the buffer slot as TO_NETWORK after mouse wheelie's song and dance
|
||||
return ScrollAction.ABORT;
|
||||
} else {
|
||||
return ScrollAction.PASS;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "phycon_mousewheelie",
|
||||
"version": "${version}",
|
||||
|
||||
"name": "PhyCon Mouse Wheelie Integration",
|
||||
"description": "",
|
||||
"authors": [
|
||||
"Shadowfacts"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://git.shadowfacts.net/minecraft/PhysicalConnectivity"
|
||||
},
|
||||
"license": "LGPL-3.0",
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "phycon-mousewheelie-client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.4.0",
|
||||
"fabric": "*",
|
||||
"fabric-language-kotlin": ">=1.3.50",
|
||||
"phycon": "*",
|
||||
"mousewheelie": "*"
|
||||
},
|
||||
|
||||
"custom": {
|
||||
"modmenu:parent": "phycon"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.shadowfacts.phycon.plugin.mousewheelie.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"MixinTerminalScreen"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
archives_base_name=PhyCon-Plugin-REI
|
||||
|
||||
rei_version=5.10.184
|
|
@ -1,27 +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.plugins.REIPluginV0
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.util.Identifier
|
||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
object PhyConPlugin: REIPluginV0 {
|
||||
const val MODID = "phycon_rei"
|
||||
|
||||
override fun getPluginIdentifier() = Identifier(MODID, "rei_plugin")
|
||||
|
||||
override fun registerBounds(helper: DisplayHelper) {
|
||||
BaseBoundsHandler.getInstance().registerExclusionZones(TerminalScreen::class.java) {
|
||||
val screen = MinecraftClient.getInstance().currentScreen as TerminalScreen
|
||||
listOf(
|
||||
Rectangle(screen.sortButtonX, screen.sortButtonY, 20, 20)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -12,12 +12,27 @@
|
|||
"homepage": "https://git.shadowfacts.net/minecraft/PhysicalConnectivity"
|
||||
},
|
||||
"license": "LGPL-3.0",
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"rei_plugins": [
|
||||
"client": [
|
||||
{
|
||||
"adapter": "kotlin",
|
||||
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPlugin"
|
||||
"value": "net.shadowfacts.phycon.plugin.rei.PhyConPluginClient"
|
||||
}
|
||||
],
|
||||
"rei": [
|
||||
{
|
||||
"adapter": "kotlin",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -28,7 +43,7 @@
|
|||
"fabric": "*",
|
||||
"fabric-language-kotlin": ">=1.3.50",
|
||||
"phycon": "*",
|
||||
"roughlyenoughitems": "*"
|
||||
"roughlyenoughitems": ["^7.0.0", "^8.0.0"]
|
||||
},
|
||||
|
||||
"custom": {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
plugins {
|
||||
id "fabric-loom"
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "https://maven.modmuss50.me/"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":")) {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
modImplementation("TechReborn:TechReborn-1.16:${project.techreborn_version}") {
|
||||
exclude group: "net.fabricmc.fabric-api"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
archives_base_name=PhyCon-Plugin-TechReborn
|
||||
|
||||
techreborn_version=3.8.2+build.222
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package net.shadowfacts.phycon.plugin.techreborn
|
||||
|
||||
import alexiil.mc.lib.attributes.AttributeList
|
||||
import alexiil.mc.lib.attributes.AttributeSourceType
|
||||
import alexiil.mc.lib.attributes.item.GroupedItemInv
|
||||
import alexiil.mc.lib.attributes.item.ItemAttributes
|
||||
import net.fabricmc.api.ModInitializer
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
import techreborn.blockentity.storage.item.StorageUnitBaseBlockEntity
|
||||
import techreborn.init.TRContent
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
object PhyConTR: ModInitializer {
|
||||
|
||||
override fun onInitialize() {
|
||||
TRContent.StorageUnit.values().forEach {
|
||||
ItemAttributes.GROUPED_INV.setBlockAdder(AttributeSourceType.COMPAT_WRAPPER, it.block, ::addStorageUnitGroupedInv)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addStorageUnitGroupedInv(world: World, pos: BlockPos, state: BlockState, to: AttributeList<GroupedItemInv>) {
|
||||
(world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su ->
|
||||
to.offer(StorageUnitWrapper(su))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package net.shadowfacts.phycon.plugin.techreborn
|
||||
|
||||
import alexiil.mc.lib.attributes.Simulation
|
||||
import alexiil.mc.lib.attributes.item.GroupedItemInv
|
||||
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
||||
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
||||
import alexiil.mc.lib.attributes.item.filter.ItemFilter
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.shadowfacts.phycon.util.copyWithCount
|
||||
import techreborn.blockentity.storage.item.StorageUnitBaseBlockEntity
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class StorageUnitWrapper(
|
||||
val be: StorageUnitBaseBlockEntity,
|
||||
): GroupedItemInv {
|
||||
|
||||
override fun getStoredStacks(): Set<ItemStack> {
|
||||
val set = ItemStackCollections.set()
|
||||
if (!be.storedStack.isEmpty) {
|
||||
set.add(be.storedStack)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
override fun getTotalCapacity(): Int {
|
||||
return be.maxCapacity
|
||||
}
|
||||
|
||||
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
|
||||
// todo: should spaceAddable really be zero? that's what SimpleGroupedItemInv does
|
||||
return if (be.storedStack.isEmpty) {
|
||||
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, totalCapacity)
|
||||
} else if (filter.matches(be.storedStack)) {
|
||||
// don't use the storedAmount field, it's only used on the client for rendering
|
||||
val amount = be.getStoredAmount()
|
||||
GroupedItemInvView.ItemInvStatistic(filter, amount, 0, totalCapacity - amount)
|
||||
} else {
|
||||
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun attemptInsertion(filter: ItemStack, simulation: Simulation): ItemStack {
|
||||
if (simulation.isAction) {
|
||||
return be.processInput(filter)
|
||||
}
|
||||
|
||||
if (be.storedStack.isEmpty) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
if (!ItemStackUtil.areEqualIgnoreAmounts(be.storedStack, filter)) {
|
||||
return filter
|
||||
}
|
||||
|
||||
val availableCapacity = totalCapacity - be.getStoredAmount()
|
||||
return if (availableCapacity >= filter.count) {
|
||||
ItemStack.EMPTY
|
||||
} else {
|
||||
filter.copyWithCount(filter.count - availableCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun attemptExtraction(filter: ItemFilter, maxAmount: Int, simulation: Simulation): ItemStack {
|
||||
if (be.storedStack.isEmpty || !filter.matches(be.storedStack)) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
val extracted = min(maxAmount, be.getStoredAmount())
|
||||
|
||||
if (simulation.isAction) {
|
||||
be.storedStack.decrement(extracted)
|
||||
}
|
||||
|
||||
return be.storedStack.copyWithCount(extracted)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "phycon_techreborn",
|
||||
"version": "${version}",
|
||||
|
||||
"name": "PhyCon TechReborn Integration",
|
||||
"description": "",
|
||||
"authors": [
|
||||
"Shadowfacts"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://git.shadowfacts.net/minecraft/PhysicalConnectivity"
|
||||
},
|
||||
"license": "LGPL-3.0",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
{
|
||||
"adapter": "kotlin",
|
||||
"value": "net.shadowfacts.phycon.plugin.techreborn.PhyConTR"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.4.0",
|
||||
"fabric": "*",
|
||||
"fabric-language-kotlin": ">=1.3.50",
|
||||
"phycon": "*",
|
||||
"techreborn": "*"
|
||||
},
|
||||
|
||||
"custom": {
|
||||
"modmenu:parent": "phycon"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
|
@ -10,4 +10,6 @@ pluginManagement {
|
|||
}
|
||||
|
||||
include("kiwi-java")
|
||||
include("plugin:mousewheelie")
|
||||
include("plugin:rei")
|
||||
// include("plugin:techreborn")
|
||||
|
|
|
@ -16,4 +16,6 @@ public interface Interface {
|
|||
|
||||
void send(@NotNull EthernetFrame frame);
|
||||
|
||||
default void cableDisconnected() {}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package net.shadowfacts.phycon.api;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
public interface PhyConAPI {
|
||||
|
||||
@NotNull
|
||||
<E extends Enum<E> & TerminalSetting> TerminalSettingKey<E> registerTerminalSetting(Identifier id, E defaultValue);
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.shadowfacts.phycon.api;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
public interface PhyConPlugin {
|
||||
|
||||
void initializePhyCon(@NotNull PhyConAPI api);
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package net.shadowfacts.phycon.api;
|
||||
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
public interface TerminalSetting {
|
||||
|
||||
Identifier getIconTexture();
|
||||
|
||||
int[] getUV();
|
||||
|
||||
@Nullable Text getTooltip();
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package net.shadowfacts.phycon.api;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
public interface TerminalSettingKey<E extends Enum<E> & TerminalSetting> {
|
||||
|
||||
Identifier getID();
|
||||
|
||||
E getValue();
|
||||
|
||||
void setPriority(int priority);
|
||||
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
package net.shadowfacts.phycon.mixin.client;
|
||||
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.client.render.item.ItemRenderer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.screen.slot.Slot;
|
||||
import net.shadowfacts.phycon.block.terminal.TerminalScreen;
|
||||
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreen;
|
||||
import net.shadowfacts.phycon.block.terminal.AbstractTerminalScreenHandler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
|
@ -15,12 +21,29 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
@Mixin(HandledScreen.class)
|
||||
public class MixinHandledScreen {
|
||||
|
||||
@Inject(method = "drawSlot(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V"))
|
||||
private void drawSlot(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
|
||||
if ((Object)this instanceof TerminalScreen) {
|
||||
TerminalScreen self = (TerminalScreen)(Object)this;
|
||||
@Inject(
|
||||
method = "drawSlot(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V",
|
||||
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<?, ?> self) {
|
||||
self.drawSlotUnderlay(matrixStack, slot);
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "drawSlot(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/screen/slot/Slot;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
|
||||
)
|
||||
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
|
||||
if ((Object)this instanceof AbstractTerminalScreen<?, ?> self) {
|
||||
AbstractTerminalScreenHandler<?> handler = self.getScreenHandler();
|
||||
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
|
||||
self.drawNetworkSlotAmount(stack, x, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, countLabel);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,4 +14,6 @@ interface AbstractCacaoScreen {
|
|||
|
||||
fun removeWindow(window: Window)
|
||||
|
||||
}
|
||||
fun screenWillAppear()
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.shadowfacts.cacao
|
||||
|
||||
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
|
||||
|
@ -12,6 +13,8 @@ import net.shadowfacts.cacao.util.MouseButton
|
|||
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.*
|
||||
|
||||
/**
|
||||
|
@ -27,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)
|
||||
|
||||
|
@ -47,6 +56,15 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
|
|||
|
||||
override fun removeWindow(window: Window) {
|
||||
_windows.remove(window)
|
||||
if (windows.isEmpty()) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun screenWillAppear() {
|
||||
windows.forEach {
|
||||
it.viewController.viewWillAppear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
|
@ -57,8 +75,18 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
|
|||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
|
||||
windows.forEach {
|
||||
it.viewController.viewWillDisappear()
|
||||
it.viewController.viewDidDisappear()
|
||||
|
||||
it.firstResponder = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||
renderBackground(matrixStack)
|
||||
}
|
||||
|
||||
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
|
||||
|
@ -67,15 +95,28 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
|
|||
|
||||
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val mouse = Point(mouseX, mouseY)
|
||||
windows.forEachIndexed { index, it ->
|
||||
|
||||
matrixStack.push()
|
||||
matrixStack.translate(0.0, 0.0, -350.0)
|
||||
|
||||
for (i in windows.indices) {
|
||||
val it = windows[i]
|
||||
|
||||
if (i == windows.size - 1) {
|
||||
renderBackground(matrixStack)
|
||||
}
|
||||
|
||||
if (it is ScreenHandlerWindow) {
|
||||
if (index == windows.size - 1) {
|
||||
if (i == windows.size - 1) {
|
||||
super.render(matrixStack, mouseX, mouseY, delta)
|
||||
} else {
|
||||
// if the screen handler window is not the frontmost, we fake the mouse x/y to disable the slot mouseover effect
|
||||
super.render(matrixStack, -1, -1, delta)
|
||||
}
|
||||
|
||||
matrixStack.pop()
|
||||
}
|
||||
|
||||
it.draw(matrixStack, mouse, delta)
|
||||
}
|
||||
|
||||
|
@ -95,12 +136,49 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
|
|||
}
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||
return true
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
val window = windows.lastOrNull()
|
||||
val startPoint = Point(mouseX, mouseY)
|
||||
val delta = Point(deltaX, deltaY)
|
||||
val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button))
|
||||
return if (result == true) {
|
||||
true
|
||||
} else if (window is ScreenHandlerWindow) {
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val window = windows.lastOrNull()
|
||||
val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
||||
return if (result == true) {
|
||||
true
|
||||
} else if (window is ScreenHandlerWindow) {
|
||||
super.mouseReleased(mouseX, mouseY, button)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
||||
val window = windows.lastOrNull()
|
||||
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
||||
return result == true
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
||||
windows.lastOrNull()?.removeFromScreen()
|
||||
return true
|
||||
} else {
|
||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||
return true
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
|
||||
override fun charTyped(char: Char, modifiers: Int): Boolean {
|
||||
|
@ -111,4 +189,8 @@ open class CacaoHandledScreen<Handler: ScreenHandler>(
|
|||
return super.charTyped(char, modifiers)
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -10,6 +11,7 @@ import net.shadowfacts.cacao.util.KeyModifiers
|
|||
import net.shadowfacts.cacao.util.MouseButton
|
||||
import net.shadowfacts.cacao.util.RenderHelper
|
||||
import net.shadowfacts.cacao.window.Window
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
@ -31,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.
|
||||
*
|
||||
|
@ -39,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
|
||||
|
@ -60,7 +68,15 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
|
|||
*/
|
||||
override fun removeWindow(window: Window) {
|
||||
_windows.remove(window)
|
||||
// todo: VC callbacks
|
||||
if (windows.isEmpty()) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun screenWillAppear() {
|
||||
windows.forEach {
|
||||
it.viewController.viewWillAppear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
|
@ -71,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
|
||||
|
@ -119,12 +136,23 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
|
|||
return result == true
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
||||
val window = windows.lastOrNull()
|
||||
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
||||
return result == true
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
||||
windows.lastOrNull()?.removeFromScreen()
|
||||
return true
|
||||
} else {
|
||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||
return true
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||
}
|
||||
|
||||
override fun keyReleased(i: Int, j: Int, k: Int): Boolean {
|
||||
|
@ -139,6 +167,10 @@ open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title),
|
|||
return super.charTyped(char, modifiers)
|
||||
}
|
||||
|
||||
override fun shouldCloseOnEsc(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun AbstractCacaoScreen.findResponder(fn: (Responder) -> Boolean): Boolean {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.shadowfacts.cacao
|
||||
|
||||
import net.shadowfacts.cacao.util.LayoutGuide
|
||||
import net.shadowfacts.cacao.view.View
|
||||
import no.birkett.kiwi.Variable
|
||||
|
||||
|
@ -9,10 +10,23 @@ import no.birkett.kiwi.Variable
|
|||
*
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class LayoutVariable(val owner: View, val property: String): Variable("LayoutVariable") {
|
||||
class LayoutVariable(
|
||||
val view: View?,
|
||||
val layoutGuide: LayoutGuide?,
|
||||
val property: String,
|
||||
): Variable("LayoutVariable") {
|
||||
|
||||
override fun getName() = "$owner.$property"
|
||||
constructor(view: View, property: String): this(view, null, property)
|
||||
constructor(layoutGuide: LayoutGuide, property: String): this(null, layoutGuide, property)
|
||||
|
||||
init {
|
||||
if ((view == null) == (layoutGuide == null)) {
|
||||
throw RuntimeException("LayoutVariable must be constructed with either a view or layout guide")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName() = "${view ?: layoutGuide}.$property"
|
||||
|
||||
override fun toString() = "LayoutVariable(name=$name, value=$value)"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package net.shadowfacts.cacao.util
|
||||
|
||||
import net.shadowfacts.cacao.LayoutVariable
|
||||
import net.shadowfacts.cacao.geometry.Rect
|
||||
import net.shadowfacts.cacao.view.View
|
||||
|
||||
/**
|
||||
* A layout guide is a non-view object that represents a rectangular area for the purposes of constraint-based layout.
|
||||
* It allows you to define complex layouts without needing empty container views.
|
||||
*
|
||||
* A layout guide is always owned by a view. The owning view's dimensions are not necessarily tied to the layout guide's.
|
||||
*
|
||||
* To create a layout guide, call [View.addLayoutGuide] on the owning view.
|
||||
*
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class LayoutGuide(
|
||||
val owningView: View,
|
||||
) {
|
||||
|
||||
val leftAnchor: LayoutVariable = LayoutVariable(this, "left")
|
||||
val rightAnchor: LayoutVariable = LayoutVariable(this, "right")
|
||||
val topAnchor: LayoutVariable = LayoutVariable(this, "top")
|
||||
val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom")
|
||||
val widthAnchor: LayoutVariable = LayoutVariable(this, "width")
|
||||
val heightAnchor: LayoutVariable = LayoutVariable(this, "height")
|
||||
val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX")
|
||||
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
|
||||
|
||||
val frame: Rect
|
||||
get() = Rect(leftAnchor.value - owningView.leftAnchor.value, topAnchor.value - owningView.topAnchor.value, widthAnchor.value, heightAnchor.value)
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,6 +6,7 @@ import net.shadowfacts.cacao.geometry.Rect
|
|||
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
||||
import net.shadowfacts.cacao.util.RenderHelper
|
||||
import net.shadowfacts.cacao.util.properties.ResettableLazyProperty
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A helper class for drawing a [NinePatchTexture] in a view.
|
||||
|
@ -91,22 +92,22 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
|
|||
|
||||
private fun drawEdges(matrixStack: MatrixStack) {
|
||||
// Horizontal
|
||||
for (i in 0 until (topMiddle.width.toInt() / ninePatch.centerWidth)) {
|
||||
for (i in 0 until (topMiddle.width.roundToInt() / ninePatch.centerWidth)) {
|
||||
RenderHelper.draw(matrixStack, topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), topMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
|
||||
RenderHelper.draw(matrixStack, bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), bottomMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
|
||||
}
|
||||
val remWidth = topMiddle.width.toInt() % ninePatch.centerWidth
|
||||
val remWidth = topMiddle.width.roundToInt() % ninePatch.centerWidth
|
||||
if (remWidth > 0) {
|
||||
RenderHelper.draw(matrixStack, topMiddle.right - remWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
RenderHelper.draw(matrixStack, bottomMiddle.right - remWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
}
|
||||
|
||||
// Vertical
|
||||
for (i in 0 until (leftMiddle.height.toInt() / ninePatch.centerHeight)) {
|
||||
for (i in 0 until (leftMiddle.height.roundToInt() / ninePatch.centerHeight)) {
|
||||
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.top + i * ninePatch.centerHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.top + i * ninePatch.centerHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
}
|
||||
val remHeight = leftMiddle.height.toInt() % ninePatch.centerHeight
|
||||
val remHeight = leftMiddle.height.roundToInt() % ninePatch.centerHeight
|
||||
if (remHeight > 0) {
|
||||
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.bottom - remHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.bottom - remHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
||||
|
@ -114,20 +115,20 @@ open class NinePatchView(val ninePatch: NinePatchTexture): View() {
|
|||
}
|
||||
|
||||
private fun drawCenter(matrixStack: MatrixStack) {
|
||||
for (i in 0 until (center.height.toInt() / ninePatch.centerHeight)) {
|
||||
for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) {
|
||||
drawCenterRow(matrixStack, center.top + i * ninePatch.centerHeight.toDouble(), ninePatch.centerHeight.toDouble())
|
||||
}
|
||||
val remHeight = center.height.toInt() % ninePatch.centerHeight
|
||||
val remHeight = center.height.roundToInt() % ninePatch.centerHeight
|
||||
if (remHeight > 0) {
|
||||
drawCenterRow(matrixStack, center.bottom - remHeight, remHeight.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) {
|
||||
for (i in 0 until (center.width.toInt() / ninePatch.centerWidth)) {
|
||||
for (i in 0 until (center.width.roundToInt() / ninePatch.centerWidth)) {
|
||||
RenderHelper.draw(matrixStack, center.left + i * ninePatch.centerWidth, y, ninePatch.center.u, ninePatch.center.v, ninePatch.centerWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height)
|
||||
}
|
||||
val remWidth = center.width.toInt() % ninePatch.centerWidth
|
||||
val remWidth = center.width.roundToInt() % ninePatch.centerWidth
|
||||
if (remWidth > 0) {
|
||||
RenderHelper.draw(matrixStack, center.right - remWidth, y, ninePatch.center.u, ninePatch.center.v, remWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height)
|
||||
}
|
||||
|
|
|
@ -11,10 +11,12 @@ import net.shadowfacts.cacao.util.texture.Texture
|
|||
*
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TextureView(var texture: Texture): View() {
|
||||
class TextureView(var texture: Texture?): View() {
|
||||
|
||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||
RenderHelper.draw(matrixStack, bounds, texture)
|
||||
texture?.also {
|
||||
RenderHelper.draw(matrixStack, bounds, it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ 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
|
||||
|
||||
/**
|
||||
* The base Cacao View class. Provides layout anchors, properties, and helper methods.
|
||||
|
@ -84,6 +84,17 @@ open class View(): Responder {
|
|||
*/
|
||||
val centerYAnchor = LayoutVariable(this, "centerY")
|
||||
|
||||
private val _layoutGuides = LinkedList<LayoutGuide>()
|
||||
|
||||
/**
|
||||
* All the layout guides attached to this view.
|
||||
*
|
||||
* To add a layout guide, call [addLayoutGuide].
|
||||
*
|
||||
* @see LayoutGuide
|
||||
*/
|
||||
val layoutGuides: List<LayoutGuide> = _layoutGuides
|
||||
|
||||
/**
|
||||
* Whether this view uses constraint-based layout.
|
||||
* If `false`, the view's `frame` must be set manually and the layout anchors may not be used.
|
||||
|
@ -229,7 +240,7 @@ open class View(): Responder {
|
|||
for (b in a + 1 until variables.size) {
|
||||
// if the variable views have no common ancestor after the removed view's superview is unset,
|
||||
// the constraint crossed the this<->view boundary and should be removed
|
||||
val ancestor = LowestCommonAncestor.find(variables[a].owner, variables[b].owner, View::superview)
|
||||
val ancestor = LowestCommonAncestor.find(variables[a].viewOrLayoutGuideView, variables[b].viewOrLayoutGuideView, View::superview)
|
||||
if (ancestor == null) {
|
||||
return@filter true
|
||||
}
|
||||
|
@ -247,6 +258,18 @@ open class View(): Responder {
|
|||
// view.wasRemoved()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new layout guide with this view as its owner.
|
||||
*/
|
||||
fun addLayoutGuide(): LayoutGuide {
|
||||
val guide = LayoutGuide(this)
|
||||
_layoutGuides.add(guide)
|
||||
if (hasSolver) {
|
||||
guide.attachTo(solver)
|
||||
}
|
||||
return guide
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this view from its superview, if it has one.
|
||||
*/
|
||||
|
@ -283,6 +306,10 @@ open class View(): Responder {
|
|||
open fun wasAdded() {
|
||||
createInternalConstraints()
|
||||
updateIntrinsicContentSizeConstraints(null, intrinsicContentSize)
|
||||
|
||||
layoutGuides.forEach {
|
||||
it.attachTo(solver)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -340,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(frame.left, frame.top)
|
||||
matrixStack.push()
|
||||
matrixStack.translate(frame.left, frame.top, 0.0)
|
||||
|
||||
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
||||
|
||||
|
@ -352,7 +379,7 @@ open class View(): Responder {
|
|||
it.draw(matrixStack, mouseInView, delta)
|
||||
}
|
||||
|
||||
RenderHelper.popMatrix()
|
||||
matrixStack.pop()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -419,6 +446,23 @@ open class View(): Responder {
|
|||
return false
|
||||
}
|
||||
|
||||
open fun mouseDragEnded(point: Point, mouseButton: MouseButton) {
|
||||
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
||||
if (view != null) {
|
||||
val pointInView = convert(point, to = view)
|
||||
return view.mouseDragEnded(pointInView, mouseButton)
|
||||
}
|
||||
}
|
||||
|
||||
open fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
||||
if (view != null) {
|
||||
val pointInView = convert(point, to = view)
|
||||
return view.mouseScrolled(pointInView, amount)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given point in this view's coordinate system to the coordinate system of another view or the window.
|
||||
*
|
||||
|
@ -463,3 +507,15 @@ open class View(): Responder {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private fun LayoutGuide.attachTo(solver: Solver) {
|
||||
solver.dsl {
|
||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
||||
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
||||
}
|
||||
}
|
||||
|
||||
private val LayoutVariable.viewOrLayoutGuideView: View
|
||||
get() = view ?: layoutGuide!!.owningView
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.shadowfacts.cacao.view.button
|
||||
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
import net.shadowfacts.cacao.geometry.Point
|
||||
import net.shadowfacts.cacao.util.MouseButton
|
||||
import net.shadowfacts.cacao.util.texture.NinePatchTexture
|
||||
|
@ -8,6 +9,7 @@ import net.shadowfacts.cacao.util.RenderHelper
|
|||
import net.shadowfacts.cacao.view.NinePatchView
|
||||
import net.shadowfacts.cacao.view.View
|
||||
import net.shadowfacts.kiwidsl.dsl
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* An abstract button class. Cannot be constructed directly, used for creating button implementations with their own
|
||||
|
@ -44,18 +46,38 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
|
|||
* unless the background view is not fully opaque.
|
||||
*/
|
||||
var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG)
|
||||
set(value) {
|
||||
field?.removeFromSuperview()
|
||||
field = value
|
||||
value?.also(::addBackground)
|
||||
}
|
||||
/**
|
||||
* The background to draw when the button is hovered over by the mouse.
|
||||
* If `null`, the normal [background] will be used.
|
||||
* @see background
|
||||
*/
|
||||
var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG)
|
||||
set(value) {
|
||||
field?.removeFromSuperview()
|
||||
field = value
|
||||
value?.also(::addBackground)
|
||||
}
|
||||
/**
|
||||
* The background to draw when the button is [disabled].
|
||||
* If `null`, the normal [background] will be used.
|
||||
* @see background
|
||||
*/
|
||||
var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG)
|
||||
set(value) {
|
||||
field?.removeFromSuperview()
|
||||
field = value
|
||||
value?.also(::addBackground)
|
||||
}
|
||||
|
||||
/**
|
||||
* The tooltip text shown when this button is hovered.
|
||||
*/
|
||||
var tooltip: Text? = null
|
||||
|
||||
override fun wasAdded() {
|
||||
solver.dsl {
|
||||
|
@ -67,22 +89,28 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
|
|||
content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK)
|
||||
content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK)
|
||||
content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK)
|
||||
|
||||
listOfNotNull(background, hoveredBackground, disabledBackground).forEach {
|
||||
addSubview(it)
|
||||
it.leftAnchor equalTo leftAnchor
|
||||
it.rightAnchor equalTo rightAnchor
|
||||
it.topAnchor equalTo topAnchor
|
||||
it.bottomAnchor equalTo bottomAnchor
|
||||
}
|
||||
}
|
||||
|
||||
listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground)
|
||||
|
||||
super.wasAdded()
|
||||
}
|
||||
|
||||
private fun addBackground(view: View) {
|
||||
if (superview != null && hasSolver) {
|
||||
addSubview(view)
|
||||
solver.dsl {
|
||||
view.leftAnchor equalTo leftAnchor
|
||||
view.rightAnchor equalTo rightAnchor
|
||||
view.topAnchor equalTo topAnchor
|
||||
view.bottomAnchor equalTo bottomAnchor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||
RenderHelper.pushMatrix()
|
||||
RenderHelper.translate(frame.left, frame.top)
|
||||
matrixStack.push()
|
||||
matrixStack.translate(frame.left, frame.top, 0.0)
|
||||
|
||||
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
||||
|
||||
|
@ -95,7 +123,11 @@ 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!!))
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||
|
|
|
@ -41,7 +41,9 @@ class EnumButton<E: Enum<E>>(
|
|||
value = when (mouseButton) {
|
||||
MouseButton.LEFT -> EnumHelper.next(value)
|
||||
MouseButton.RIGHT -> EnumHelper.previous(value)
|
||||
else -> value
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,11 @@ 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
|
||||
|
||||
/**
|
||||
* An abstract text field class. Cannot be constructed directly, use for creating other text fields with more specific
|
||||
|
@ -68,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
|
||||
|
@ -77,7 +76,7 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
|||
init {
|
||||
minecraftWidget.text = initialText
|
||||
minecraftWidget.setTextPredicate { this.validate(it) }
|
||||
minecraftWidget.setHasBorder(drawBackground)
|
||||
minecraftWidget.setDrawsBackground(drawBackground)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,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 {
|
||||
|
@ -133,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 {
|
||||
|
@ -159,7 +158,11 @@ abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
|||
@Suppress("UNCHECKED_CAST")
|
||||
handler?.invoke(this as Impl)
|
||||
}
|
||||
return result
|
||||
return result || (isFirstResponder && keyCode != GLFW.GLFW_KEY_ESCAPE)
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
minecraftWidget.tick()
|
||||
}
|
||||
|
||||
// todo: label for the TextFieldWidget?
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.shadowfacts.cacao.view.textfield
|
|||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class NumberField(
|
||||
open class NumberField(
|
||||
initialValue: Int,
|
||||
handler: ((NumberField) -> Unit)? = null,
|
||||
): AbstractTextField<NumberField>(initialValue.toString()) {
|
||||
|
|
|
@ -7,7 +7,7 @@ package net.shadowfacts.cacao.view.textfield
|
|||
* @param initialText The initial value of this text field.
|
||||
* @param handler A function that is invoked when the value of the text field changes.
|
||||
*/
|
||||
class TextField(
|
||||
open class TextField(
|
||||
initialText: String,
|
||||
handler: ((TextField) -> Unit)? = null
|
||||
): AbstractTextField<TextField>(initialText) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -234,7 +234,8 @@ open class Window(
|
|||
fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
|
||||
val currentlyDraggedView = this.currentDragReceiver
|
||||
if (currentlyDraggedView != null) {
|
||||
return currentlyDraggedView.mouseDragged(startPoint, delta, mouseButton)
|
||||
val pointInView = viewController.view.convert(startPoint, to = currentlyDraggedView)
|
||||
return currentlyDraggedView.mouseDragged(pointInView, delta, mouseButton)
|
||||
} else if (startPoint in viewController.view.frame) {
|
||||
val startInView =
|
||||
Point(startPoint.x - viewController.view.frame.left, startPoint.y - viewController.view.frame.top)
|
||||
|
@ -246,7 +247,12 @@ open class Window(
|
|||
view = view.subviewsAtPoint(pointInView).maxByOrNull(View::zIndex)
|
||||
}
|
||||
this.currentDragReceiver = view ?: prevView
|
||||
return this.currentDragReceiver?.mouseDragged(startPoint, delta, mouseButton) ?: false
|
||||
return if (this.currentDragReceiver != null) {
|
||||
val pointInView = viewController.view.convert(startPoint, to = this.currentDragReceiver!!)
|
||||
this.currentDragReceiver!!.mouseDragged(pointInView, delta, mouseButton)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -254,10 +260,16 @@ open class Window(
|
|||
fun mouseReleased(point: Point, mouseButton: MouseButton): Boolean {
|
||||
val currentlyDraggedView = this.currentDragReceiver
|
||||
if (currentlyDraggedView != null) {
|
||||
val pointInView = viewController.view.convert(point, to = currentlyDraggedView)
|
||||
currentlyDraggedView.mouseDragEnded(pointInView, mouseButton)
|
||||
this.currentDragReceiver = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||
return viewController.view.mouseScrolled(point, amount)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -100,4 +100,4 @@ class KiwiContext(val solver: Solver) {
|
|||
fun Solver.dsl(init: KiwiContext.() -> Unit): Solver {
|
||||
KiwiContext(this).init()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package net.shadowfacts.phycon
|
||||
|
||||
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.util.SortMode
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
object DefaultPlugin: PhyConPlugin {
|
||||
|
||||
lateinit var SORT_MODE: TerminalSettingKey<SortMode>
|
||||
private set
|
||||
|
||||
override fun initializePhyCon(api: PhyConAPI) {
|
||||
SORT_MODE = api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST)
|
||||
SORT_MODE.setPriority(Int.MAX_VALUE)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package net.shadowfacts.phycon
|
||||
|
||||
import net.minecraft.util.Identifier
|
||||
import net.shadowfacts.phycon.api.PhyConAPI
|
||||
import net.shadowfacts.phycon.api.TerminalSetting
|
||||
import net.shadowfacts.phycon.api.TerminalSettingKey
|
||||
import net.shadowfacts.phycon.util.TerminalSettings
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
object PhyConAPIImpl: PhyConAPI {
|
||||
|
||||
override fun <E> registerTerminalSetting(id: Identifier, defaultValue: E): TerminalSettingKey<E> where E: Enum<E>, E: TerminalSetting? {
|
||||
return TerminalSettings.register(id, defaultValue)
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +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
|
||||
|
@ -24,9 +28,16 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerGlobalReceiver(receiver: ServerReceiver) {
|
||||
|
|
|
@ -4,23 +4,44 @@ import net.fabricmc.api.ClientModInitializer
|
|||
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
|
||||
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
|
||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
|
||||
import net.shadowfacts.phycon.block.inserter.InserterScreen
|
||||
import net.shadowfacts.phycon.block.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
|
||||
import net.shadowfacts.phycon.client.PhyModelProvider
|
||||
import net.shadowfacts.phycon.networking.ClientReceiver
|
||||
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
||||
import net.shadowfacts.phycon.util.TerminalSettings
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
object PhysicalConnectivityClient: ClientModInitializer {
|
||||
|
||||
val terminalSettings = TerminalSettings()
|
||||
|
||||
var screenMaterial: RenderMaterial? = null
|
||||
private set
|
||||
|
||||
override fun onInitializeClient() {
|
||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyModelProvider)
|
||||
|
||||
RendererAccess.INSTANCE.renderer?.also { renderer ->
|
||||
screenMaterial = renderer.materialFinder()
|
||||
.emissive(0, true)
|
||||
.disableAo(0, true)
|
||||
.disableDiffuse(0, true)
|
||||
.find()
|
||||
|
||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyExtendedModelProvider)
|
||||
}
|
||||
|
||||
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
|
||||
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
|
||||
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
|
||||
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -4,20 +4,23 @@ import net.minecraft.block.Block
|
|||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.ShapeContext
|
||||
import net.minecraft.item.ItemPlacementContext
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.EnumProperty
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.DyeColor
|
||||
import net.minecraft.util.StringIdentifiable
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.util.shape.VoxelShape
|
||||
import net.minecraft.util.shape.VoxelShapes
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.shadowfacts.phycon.api.Interface
|
||||
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||
import net.shadowfacts.phycon.block.cable.CableBlock
|
||||
import net.shadowfacts.phycon.init.PhyItems
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
@ -28,6 +31,7 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
|
|||
companion object {
|
||||
val FACING = Properties.FACING
|
||||
val CABLE_CONNECTION = EnumProperty.of("cable_connection", FaceCableConnection::class.java)
|
||||
val COLOR = EnumProperty.of("color", DyeColor::class.java)
|
||||
}
|
||||
|
||||
enum class FaceCableConnection : StringIdentifiable {
|
||||
|
@ -100,44 +104,56 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
|
|||
super.appendProperties(builder)
|
||||
builder.add(FACING)
|
||||
builder.add(CABLE_CONNECTION)
|
||||
builder.add(COLOR)
|
||||
}
|
||||
|
||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
|
||||
val cableConnection = FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing))
|
||||
// todo: this should never be called
|
||||
val cableConnection = FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing, DyeColor.BLUE))
|
||||
return defaultState
|
||||
.with(FACING, facing)
|
||||
.with(CABLE_CONNECTION, cableConnection)
|
||||
}
|
||||
|
||||
private fun getCableConnectedSide(world: WorldAccess, pos: BlockPos, facing: Direction): Direction? {
|
||||
private fun getCableConnectedSide(world: WorldAccess, pos: BlockPos, facing: Direction, color: DyeColor): Direction? {
|
||||
for (side in Direction.values()) {
|
||||
if (side == facing) {
|
||||
continue
|
||||
}
|
||||
val offsetPos = pos.offset(side)
|
||||
val state = world.getBlockState(offsetPos)
|
||||
val block = state.block
|
||||
if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) {
|
||||
if (canConnectTo(world, side, state, offsetPos, color)) {
|
||||
return side
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun canConnectTo(world: WorldAccess, side: Direction, candidateState: BlockState, candidatePos: BlockPos, myColor: DyeColor): Boolean {
|
||||
val block = candidateState.block
|
||||
return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) {
|
||||
true
|
||||
} else if (block is CableBlock && block.color == myColor) {
|
||||
true
|
||||
} else {
|
||||
block is NetworkComponentBlock && block.getNetworkConnectedSides(candidateState, world, candidatePos).contains(side.opposite)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
||||
val current = state[CABLE_CONNECTION]
|
||||
var newConnection = current
|
||||
|
||||
if (current == FaceCableConnection.NONE) {
|
||||
if (neighborState.block is NetworkComponentBlock) {
|
||||
if (canConnectTo(world, side, neighborState, neighborPos, state[COLOR])) {
|
||||
newConnection = FaceCableConnection.from(side)
|
||||
}
|
||||
} else {
|
||||
val currentConnectedPos = pos.offset(current.direction)
|
||||
if (neighborPos == currentConnectedPos && neighborState.block !is NetworkComponentBlock) {
|
||||
// the old cable connection is no longer correct, try to find another
|
||||
newConnection = FaceCableConnection.from(getCableConnectedSide(world, pos, state[FACING]))
|
||||
newConnection = FaceCableConnection.from(getCableConnectedSide(world, pos, state[FACING], state[COLOR]))
|
||||
}
|
||||
}
|
||||
return state.with(CABLE_CONNECTION, newConnection)
|
||||
|
@ -146,4 +162,11 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
|
|||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
|
||||
return getShape(state[FACING], state[CABLE_CONNECTION])
|
||||
}
|
||||
|
||||
override fun onStacksDropped(state: BlockState, world: ServerWorld, pos: BlockPos, stack: ItemStack) {
|
||||
super.onStacksDropped(state, world, pos, stack)
|
||||
|
||||
val cableStack = ItemStack(PhyItems.CABLES[state[COLOR]])
|
||||
dropStack(world, pos, cableStack)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package net.shadowfacts.phycon.block.cable
|
|||
|
||||
import net.fabricmc.fabric.api.`object`.builder.v1.block.FabricBlockSettings
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.block.piston.PistonBehavior
|
||||
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
|
||||
import net.minecraft.util.DyeColor
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
|
@ -23,24 +25,27 @@ 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.*
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class CableBlock: Block(
|
||||
class CableBlock(
|
||||
val color: DyeColor,
|
||||
): Block(
|
||||
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),
|
||||
|
@ -50,19 +55,25 @@ class CableBlock: Block(
|
|||
Direction.WEST to createCuboidShape(0.0, 6.0, 6.0, 6.0, 10.0, 10.0),
|
||||
Direction.EAST to createCuboidShape(10.0, 6.0, 6.0, 16.0, 10.0, 10.0)
|
||||
)
|
||||
private val SHAPE_CACHE = mutableMapOf<BlockState, VoxelShape>()
|
||||
private val SHAPE_CACHE = Array<VoxelShape>(64) { key ->
|
||||
val connectedSides = Direction.values().filterIndexed { index, _ ->
|
||||
((key shr index) and 1) == 1
|
||||
}
|
||||
connectedSides.fold(CENTER_SHAPE) { acc, side ->
|
||||
VoxelShapes.union(acc, SIDE_SHAPES[side])
|
||||
}
|
||||
}
|
||||
val CONNECTIONS: Map<Direction, EnumProperty<CableConnection>> = Direction.values().associate { it to EnumProperty.of(it.name.toLowerCase(), CableConnection::class.java) }
|
||||
|
||||
fun getShape(state: BlockState): VoxelShape {
|
||||
return SHAPE_CACHE.getOrPut(state) {
|
||||
var shape = CENTER_SHAPE
|
||||
for ((side, prop) in CONNECTIONS) {
|
||||
if (state[prop] == CableConnection.ON) {
|
||||
shape = VoxelShapes.union(shape, SIDE_SHAPES[side])
|
||||
}
|
||||
val key = Direction.values().foldIndexed(0) { i, acc, dir ->
|
||||
if (state[CONNECTIONS[dir]] == CableConnection.ON) {
|
||||
acc or (1 shl i)
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
return shape
|
||||
}
|
||||
return SHAPE_CACHE[key]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,22 +123,20 @@ class CableBlock: Block(
|
|||
val offsetPos = pos.offset(direction)
|
||||
val state = world.getBlockState(offsetPos)
|
||||
val block = state.block
|
||||
return when (block) {
|
||||
this -> {
|
||||
val prop = CONNECTIONS[direction.opposite]
|
||||
when (state[prop]) {
|
||||
CableConnection.DISABLED -> CableConnection.DISABLED
|
||||
else -> CableConnection.ON
|
||||
}
|
||||
return if (block == this) {
|
||||
val prop = CONNECTIONS[direction.opposite]
|
||||
when (state[prop]) {
|
||||
CableConnection.DISABLED -> CableConnection.DISABLED
|
||||
else -> CableConnection.ON
|
||||
}
|
||||
is NetworkComponentBlock -> {
|
||||
if (block.getNetworkConnectedSides(state, world, offsetPos).contains(direction.opposite)) {
|
||||
CableConnection.ON
|
||||
} else {
|
||||
CableConnection.OFF
|
||||
}
|
||||
} else if (block is NetworkComponentBlock && block !is CableBlock) {
|
||||
if (block.getNetworkConnectedSides(state, world, offsetPos).contains(direction.opposite)) {
|
||||
CableConnection.ON
|
||||
} else {
|
||||
CableConnection.OFF
|
||||
}
|
||||
else -> CableConnection.OFF
|
||||
} else {
|
||||
CableConnection.OFF
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,4 +199,18 @@ class CableBlock: Block(
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
@ -19,15 +21,17 @@ import net.shadowfacts.phycon.init.PhyBlockEntities
|
|||
import net.shadowfacts.phycon.packet.*
|
||||
import net.shadowfacts.phycon.util.ActivationMode
|
||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
||||
import net.shadowfacts.phycon.util.GhostInv
|
||||
import 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 {
|
||||
ClientConfigurableDevice,
|
||||
GhostInv {
|
||||
|
||||
companion object {
|
||||
val SLEEP_TIME = 40L
|
||||
|
@ -40,6 +44,9 @@ class InserterBlockEntity: DeviceBlockEntity(PhyBlockEntities.INSERTER),
|
|||
private var inventory: ItemInsertable? = null
|
||||
private var currentRequest: PendingExtractRequest? = null
|
||||
var stackToExtract: ItemStack = ItemStack.EMPTY
|
||||
override var ghostSlotStack: ItemStack
|
||||
get() = stackToExtract
|
||||
set(value) { stackToExtract = value }
|
||||
var amountToExtract = 1
|
||||
override val controller = ActivationController(SLEEP_TIME, this)
|
||||
|
||||
|
@ -127,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")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -37,7 +37,7 @@ class InserterScreenHandler(
|
|||
|
||||
init {
|
||||
// fake slot
|
||||
addSlot(GhostSlot(inserter::stackToExtract, 31, 20))
|
||||
addSlot(GhostSlot(inserter, 31, 20))
|
||||
|
||||
// player inv
|
||||
for (y in 0 until 3) {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -16,7 +16,7 @@ import net.minecraft.world.World
|
|||
import net.shadowfacts.phycon.api.packet.Packet
|
||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
||||
import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity
|
||||
import net.shadowfacts.phycon.block.terminal.AbstractTerminalBlockEntity
|
||||
import net.shadowfacts.phycon.component.*
|
||||
import net.shadowfacts.phycon.packet.*
|
||||
import net.shadowfacts.phycon.util.ActivationMode
|
||||
|
@ -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,
|
||||
|
@ -39,7 +39,7 @@ class MinerBlockEntity: DeviceBlockEntity(PhyBlockEntities.MINER),
|
|||
private val invProxy = MinerInvProxy(this)
|
||||
|
||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||
override val dispatchStackTimeout = TerminalBlockEntity.INSERTION_TIMEOUT
|
||||
override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT
|
||||
override val controller = ActivationController(40L, this)
|
||||
override var providerPriority = 0
|
||||
|
||||
|
@ -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!!
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
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
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER),
|
||||
ClientConfigurableDevice {
|
||||
class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state),
|
||||
ClientConfigurableDevice,
|
||||
GhostInv {
|
||||
|
||||
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
||||
var cachedEmittedPower: Int = 0
|
||||
private set
|
||||
|
||||
var stackToMonitor: ItemStack = ItemStack.EMPTY
|
||||
override var ghostSlotStack: ItemStack
|
||||
get() = stackToMonitor
|
||||
set(value) { stackToMonitor = value }
|
||||
var maxAmount = 64
|
||||
var mode = Mode.ANALOG
|
||||
set(value) {
|
||||
|
@ -35,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()
|
||||
}
|
||||
|
@ -63,7 +70,7 @@ class RedstoneEmitterBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_EM
|
|||
}
|
||||
|
||||
private fun updateInventories() {
|
||||
sendPacket(RequestInventoryPacket(ipAddress))
|
||||
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
|
||||
}
|
||||
|
||||
private fun recalculateRedstone() {
|
||||
|
@ -95,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"))
|
||||
}
|
||||
|
@ -122,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()}")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -129,10 +130,9 @@ class RedstoneEmitterScreen(
|
|||
inv.leftAnchor equalTo (minX + 8)
|
||||
inv.topAnchor equalTo (minY + 72)
|
||||
|
||||
// offset by 1 on each edge to account for MC border
|
||||
field.widthAnchor equalTo 82
|
||||
field.heightAnchor equalTo 11
|
||||
field.leftAnchor equalTo (minX + 56)
|
||||
field.leftAnchor equalTo (minX + 57)
|
||||
field.topAnchor equalTo (minY + 23)
|
||||
|
||||
hStack.centerXAnchor equalTo view.centerXAnchor
|
||||
|
|
|
@ -36,7 +36,7 @@ class RedstoneEmitterScreenHandler(
|
|||
|
||||
init {
|
||||
// fake slot
|
||||
addSlot(GhostSlot(emitter::stackToMonitor, 31, 20))
|
||||
addSlot(GhostSlot(emitter, 31, 20))
|
||||
|
||||
// player inv
|
||||
for (y in 0 until 3) {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import alexiil.mc.lib.attributes.AttributeList
|
||||
import alexiil.mc.lib.attributes.AttributeProvider
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Material
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemPlacementContext
|
||||
import net.minecraft.sound.BlockSoundGroup
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.ItemScatterer
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||
import net.shadowfacts.phycon.block.DeviceBlock
|
||||
import java.util.EnumSet
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBlock<T>(
|
||||
Settings.of(Material.METAL)
|
||||
.strength(1.5f)
|
||||
.sounds(BlockSoundGroup.METAL)
|
||||
),
|
||||
NetworkComponentBlock,
|
||||
AttributeProvider {
|
||||
|
||||
companion object {
|
||||
val FACING = Properties.FACING
|
||||
}
|
||||
|
||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||
val set = EnumSet.allOf(Direction::class.java)
|
||||
set.remove(state[FACING])
|
||||
return set
|
||||
}
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
super.appendProperties(builder)
|
||||
builder.add(FACING)
|
||||
}
|
||||
|
||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
||||
}
|
||||
|
||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
||||
getBlockEntity(world, pos)!!.onActivate(player)
|
||||
return ActionResult.SUCCESS
|
||||
}
|
||||
|
||||
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
||||
if (!state.isOf(newState.block)) {
|
||||
val be = getBlockEntity(world, pos)!!
|
||||
be.dropItems()
|
||||
|
||||
super.onStateReplaced(state, world, pos, newState, moved)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
||||
to.offer(getBlockEntity(world, pos))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
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.minecraft.block.BlockState
|
||||
import net.minecraft.block.entity.BlockEntityType
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.inventory.Inventory
|
||||
import net.minecraft.inventory.InventoryChangedListener
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.util.ItemScatterer
|
||||
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.block.DeviceBlockEntity
|
||||
import net.shadowfacts.phycon.component.*
|
||||
import net.shadowfacts.phycon.frame.NetworkSplitFrame
|
||||
import net.shadowfacts.phycon.packet.*
|
||||
import net.shadowfacts.phycon.util.NetworkUtil
|
||||
import 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<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state),
|
||||
InventoryChangedListener,
|
||||
ItemStackPacketHandler,
|
||||
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
|
||||
|
||||
companion object {
|
||||
// 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 // ticks
|
||||
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
|
||||
}
|
||||
|
||||
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
||||
val internalBuffer = TerminalBufferInventory(18)
|
||||
|
||||
protected val pendingRequests = LinkedList<StackLocateRequest>()
|
||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
||||
|
||||
val cachedNetItems = ItemStackCollections.intMap()
|
||||
private var requestInventoryTimestamp: Long? = null
|
||||
|
||||
// todo: multiple players could have the terminal open simultaneously
|
||||
var netItemObserver: WeakReference<NetItemObserver>? = null
|
||||
|
||||
init {
|
||||
internalBuffer.addListener(this)
|
||||
}
|
||||
|
||||
override fun findDestination(): Interface? {
|
||||
for (dir in Direction.values()) {
|
||||
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
||||
if (itf != null) {
|
||||
return itf
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun handleNetworkSplit() {
|
||||
super.handleNetworkSplit()
|
||||
inventoryCache.clear()
|
||||
}
|
||||
|
||||
override fun handle(packet: Packet) {
|
||||
when (packet) {
|
||||
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
|
||||
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
||||
is StackLocationPacket -> handleStackLocation(packet)
|
||||
is ItemStackPacket -> handleItemStack(packet)
|
||||
is CapacityPacket -> handleCapacity(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
|
||||
inventoryCache[packet.source] = packet.inventory
|
||||
updateAndSync()
|
||||
}
|
||||
|
||||
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
||||
inventoryCache.remove(packet.source)
|
||||
updateAndSync()
|
||||
}
|
||||
|
||||
private fun handleStackLocation(packet: StackLocationPacket) {
|
||||
val request = pendingRequests.firstOrNull {
|
||||
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
|
||||
}
|
||||
if (request != null) {
|
||||
request.results.add(packet.amount to packet.stackProvider)
|
||||
if (request.isFinishable(counter)) {
|
||||
stackLocateRequestCompleted(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||
val mode =
|
||||
if (packet.bounceCount > 0) {
|
||||
// if this stack bounced from an inventory, that means we previously tried to send it to the network, so retry
|
||||
TerminalBufferInventory.Mode.TO_NETWORK
|
||||
} else {
|
||||
TerminalBufferInventory.Mode.FROM_NETWORK
|
||||
}
|
||||
|
||||
val remaining = internalBuffer.insert(packet.stack, mode)
|
||||
|
||||
// this happens outside the normal update loop because by receiving the item stack packet
|
||||
// we "know" how much the count in the source inventory has changed
|
||||
updateAndSync()
|
||||
|
||||
return remaining
|
||||
}
|
||||
|
||||
protected fun updateAndSync() {
|
||||
updateNetItems()
|
||||
// syncs the internal buffer to the client
|
||||
markUpdate()
|
||||
// syncs the open container (if any) to the client
|
||||
netItemObserver?.get()?.netItemsChanged()
|
||||
}
|
||||
|
||||
private fun updateNetItems() {
|
||||
cachedNetItems.clear()
|
||||
for (inventory in inventoryCache.values) {
|
||||
for (stack in inventory.storedStacks) {
|
||||
val amount = inventory.getAmount(stack)
|
||||
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun beginInsertions() {
|
||||
if (world!!.isClient) return
|
||||
|
||||
for (slot in 0 until internalBuffer.size()) {
|
||||
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
||||
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
||||
val stack = internalBuffer.getStack(slot)
|
||||
dispatchItemStack(stack) { insertion ->
|
||||
insertion.bufferSlot = slot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishPendingRequests() {
|
||||
if (world!!.isClient) return
|
||||
if (pendingRequests.isEmpty()) return
|
||||
|
||||
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
||||
// stackLocateRequestCompleted removes the object from pendingRequests
|
||||
finishable.forEach(::stackLocateRequestCompleted)
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
|
||||
if (!world!!.isClient) {
|
||||
finishPendingRequests()
|
||||
finishTimedOutPendingInsertions()
|
||||
|
||||
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) {
|
||||
val request = StackLocateRequest(stack, amount, counter)
|
||||
pendingRequests.add(request)
|
||||
// locate packets are sent immediately instead of being added to a queue
|
||||
// otherwise the terminal UI feels sluggish and unresponsive
|
||||
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||
}
|
||||
|
||||
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||
pendingRequests.remove(request)
|
||||
|
||||
val sortedResults = request.results.toMutableList()
|
||||
sortedResults.sortWith { a, b ->
|
||||
// sort results first by provider priority, and then by the count that it can provide
|
||||
if (a.second.providerPriority == b.second.providerPriority) {
|
||||
b.first - a.first
|
||||
} else {
|
||||
b.second.providerPriority - a.second.providerPriority
|
||||
}
|
||||
}
|
||||
var amountRequested = 0
|
||||
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
||||
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
||||
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
||||
amountRequested += amountToRequest
|
||||
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||
|
||||
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
||||
val remaining = super.finishInsertion(insertion)
|
||||
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
||||
|
||||
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
||||
updateAndSync()
|
||||
|
||||
// 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) {
|
||||
markUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
open fun dropItems() {
|
||||
ItemScatterer.spawn(world, pos, internalBuffer)
|
||||
}
|
||||
|
||||
override fun toCommonTag(tag: NbtCompound) {
|
||||
super.toCommonTag(tag)
|
||||
tag.put("InternalBuffer", internalBuffer.toTag())
|
||||
}
|
||||
|
||||
override fun fromCommonTag(tag: NbtCompound) {
|
||||
super.fromCommonTag(tag)
|
||||
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
||||
}
|
||||
|
||||
interface NetItemObserver {
|
||||
fun netItemsChanged()
|
||||
}
|
||||
|
||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||
var bufferSlot by Delegates.notNull<Int>()
|
||||
}
|
||||
|
||||
open class StackLocateRequest(
|
||||
val stack: ItemStack,
|
||||
val amount: Int,
|
||||
val timestamp: Long,
|
||||
) {
|
||||
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 >= LOCATE_REQUEST_TIMEOUT
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
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.render.GameRenderer
|
||||
import net.minecraft.client.render.Tessellator
|
||||
import net.minecraft.client.render.VertexConsumerProvider
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
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.Text
|
||||
import net.minecraft.util.Identifier
|
||||
import net.shadowfacts.cacao.CacaoHandledScreen
|
||||
import net.shadowfacts.cacao.window.ScreenHandlerWindow
|
||||
import net.shadowfacts.cacao.window.Window
|
||||
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
|
||||
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: AbstractTerminalScreenHandler<BE>>(
|
||||
handler: T,
|
||||
playerInv: PlayerInventory,
|
||||
title: Text,
|
||||
val terminalBackgroundWidth: Int,
|
||||
val terminalBackgroundHeight: Int,
|
||||
): CacaoHandledScreen<T>(handler, playerInv, title) {
|
||||
|
||||
interface SearchQueryListener {
|
||||
fun terminalSearchQueryChanged(newValue: String)
|
||||
fun requestTerminalSearchFieldUpdate(): String?
|
||||
}
|
||||
|
||||
companion object {
|
||||
var searchQueryListener: SearchQueryListener? = null
|
||||
}
|
||||
|
||||
abstract val backgroundTexture: Identifier
|
||||
|
||||
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 {
|
||||
backgroundWidth = terminalBackgroundWidth
|
||||
backgroundHeight = terminalBackgroundHeight
|
||||
|
||||
terminalVC = createViewController()
|
||||
addWindow(ScreenHandlerWindow(handler, terminalVC))
|
||||
|
||||
requestUpdatedItems()
|
||||
}
|
||||
|
||||
abstract fun createViewController(): AbstractTerminalViewController<*, *, *>
|
||||
|
||||
fun requestItem(stack: ItemStack, amount: Int) {
|
||||
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
||||
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
||||
netHandler.sendPacket(packet)
|
||||
}
|
||||
|
||||
fun requestUpdatedItems() {
|
||||
val player = MinecraftClient.getInstance().player!!
|
||||
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchQuery, scrollPosition.toFloat()))
|
||||
}
|
||||
|
||||
private fun showRequestAmountDialog(stack: ItemStack) {
|
||||
val vc = TerminalRequestAmountViewController(this, stack)
|
||||
addWindow(Window(vc))
|
||||
amountVC = vc
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
||||
if (!handler.isBufferSlot(slot.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
||||
val color: UInt = when (mode) {
|
||||
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
||||
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
||||
else -> return
|
||||
}
|
||||
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
||||
}
|
||||
|
||||
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) {
|
||||
val amount = stack.count
|
||||
val s = when {
|
||||
amount < 1_000 -> amount.toString()
|
||||
amount < 1_000_000 -> {
|
||||
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
|
||||
format.format(amount / 1_000.0) + "K"
|
||||
}
|
||||
amount < 1_000_000_000 -> {
|
||||
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
|
||||
format.format(amount / 1_000_000.0) + "M"
|
||||
}
|
||||
else -> {
|
||||
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
|
||||
}
|
||||
}
|
||||
|
||||
// draw damage bar
|
||||
// empty string for label because vanilla renders the count behind the damage bar
|
||||
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
|
||||
|
||||
// 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().positionMatrix, immediate, false, 0, 0xF000F0)
|
||||
RenderSystem.enableDepthTest()
|
||||
immediate.draw()
|
||||
}
|
||||
|
||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
||||
|
||||
drawBackgroundTexture(matrixStack)
|
||||
}
|
||||
|
||||
open fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
||||
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 handledScreenTick() {
|
||||
super.handledScreenTick()
|
||||
|
||||
if (amountVC != null) {
|
||||
amountVC!!.field.tick()
|
||||
} else {
|
||||
terminalVC.searchField.tick()
|
||||
}
|
||||
|
||||
val newSearchQuery = searchQueryListener?.requestTerminalSearchFieldUpdate()
|
||||
if (newSearchQuery != null && searchQuery != newSearchQuery) {
|
||||
searchQuery = newSearchQuery
|
||||
terminalVC.searchField.text = newSearchQuery
|
||||
requestUpdatedItems()
|
||||
}
|
||||
}
|
||||
|
||||
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) && handler.cursorStack.isEmpty) {
|
||||
val stack = slot.stack
|
||||
|
||||
if (type == SlotActionType.QUICK_MOVE) {
|
||||
// shift click, request full stack
|
||||
requestItem(stack, min(stack.count, stack.maxCount))
|
||||
} else if (type == SlotActionType.PICKUP) {
|
||||
if (clickData == 1) {
|
||||
// right click, request half stack
|
||||
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
||||
} else {
|
||||
showRequestAmountDialog(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import net.minecraft.screen.slot.Slot
|
||||
import net.minecraft.screen.slot.SlotActionType
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.entity.player.PlayerInventory
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.screen.ScreenHandler
|
||||
import net.minecraft.screen.ScreenHandlerType
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.registry.Registry
|
||||
import net.shadowfacts.phycon.DefaultPlugin
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import net.shadowfacts.phycon.init.PhyBlocks
|
||||
import net.shadowfacts.phycon.init.PhyScreens
|
||||
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
||||
import net.shadowfacts.phycon.util.SortMode
|
||||
import net.shadowfacts.phycon.util.TerminalSettings
|
||||
import net.shadowfacts.phycon.util.copyWithCount
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
|
||||
handlerType: ScreenHandlerType<*>,
|
||||
syncId: Int,
|
||||
val playerInv: PlayerInventory,
|
||||
val terminal: T,
|
||||
): ScreenHandler(handlerType, syncId),
|
||||
AbstractTerminalBlockEntity.NetItemObserver {
|
||||
|
||||
private val rowsDisplayed = 6
|
||||
|
||||
private val fakeInv = FakeInventory(this)
|
||||
private var searchQuery: String = ""
|
||||
private var settings = TerminalSettings()
|
||||
var totalEntries = 0
|
||||
private set
|
||||
var scrollPosition = 0f
|
||||
private var itemEntries = listOf<Entry>()
|
||||
set(value) {
|
||||
field = value
|
||||
if (terminal.world!!.isClient) {
|
||||
itemsForDisplay = value.map {
|
||||
it.stack.copyWithCount(it.amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
var itemsForDisplay = listOf<ItemStack>()
|
||||
private set
|
||||
|
||||
open val xOffset: Int = 0
|
||||
|
||||
init {
|
||||
if (!terminal.world!!.isClient) {
|
||||
assert(terminal.netItemObserver?.get() === null)
|
||||
terminal.netItemObserver = WeakReference(this)
|
||||
// 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, 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, 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, xOffset + 66 + x * 18, 140 + y * 18))
|
||||
}
|
||||
}
|
||||
// hotbar
|
||||
for (x in 0 until 9) {
|
||||
addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198))
|
||||
}
|
||||
}
|
||||
|
||||
override fun netItemsChanged() {
|
||||
val player = playerInv.player
|
||||
assert(player is ServerPlayerEntity)
|
||||
|
||||
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
||||
if (searchQuery.isBlank()) return@filter true
|
||||
if (searchQuery.startsWith('@')) {
|
||||
val unprefixed = searchQuery.drop(1)
|
||||
val key = Registry.ITEM.getKey(it.key.item)
|
||||
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
||||
return@filter true
|
||||
}
|
||||
}
|
||||
it.key.name.string.contains(searchQuery, true)
|
||||
}
|
||||
|
||||
totalEntries = filtered.size
|
||||
|
||||
val sorted =
|
||||
when (settings[DefaultPlugin.SORT_MODE]) {
|
||||
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
||||
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
||||
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
||||
}
|
||||
|
||||
|
||||
val offsetInItems = currentScrollOffsetInItems()
|
||||
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
||||
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
||||
|
||||
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
||||
|
||||
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries))
|
||||
}
|
||||
|
||||
fun totalRows(): Int {
|
||||
return ceil(totalEntries / 9f).toInt()
|
||||
}
|
||||
|
||||
fun maxScrollOffsetInRows(): Int {
|
||||
return totalRows() - rowsDisplayed
|
||||
}
|
||||
|
||||
fun currentScrollOffsetInRows(): Int {
|
||||
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
||||
}
|
||||
|
||||
fun currentScrollOffsetInItems(): Int {
|
||||
return currentScrollOffsetInRows() * 9
|
||||
}
|
||||
|
||||
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) {
|
||||
this.searchQuery = query
|
||||
this.settings = settings
|
||||
this.scrollPosition = scrollPosition
|
||||
netItemsChanged()
|
||||
}
|
||||
|
||||
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
|
||||
assert(playerInv.player.world.isClient)
|
||||
|
||||
this.searchQuery = query
|
||||
this.scrollPosition = scrollPosition
|
||||
this.totalEntries = totalEntries
|
||||
itemEntries = entries
|
||||
}
|
||||
|
||||
override fun canUse(player: PlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun close(player: PlayerEntity) {
|
||||
super.close(player)
|
||||
|
||||
terminal.netItemObserver = null
|
||||
}
|
||||
|
||||
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) && !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)
|
||||
}
|
||||
}
|
||||
super.onSlotClick(slotId, clickData, actionType, player)
|
||||
}
|
||||
|
||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||
if (isNetworkSlot(slotId)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
val slot = slots[slotId]
|
||||
if (!slot.hasStack()) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
val result = slot.stack.copy()
|
||||
|
||||
if (isBufferSlot(slotId)) {
|
||||
// last boolean param is fromLast
|
||||
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
if (slot.stack.isEmpty) {
|
||||
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
||||
}
|
||||
} else if (isPlayerSlot(slotId)) {
|
||||
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
||||
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
|
||||
if (slotsInsertedInto.isEmpty()) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
||||
val slotsInsertedInto = mutableListOf<Int>()
|
||||
for (index in slots) {
|
||||
if (stack.isEmpty) break
|
||||
if (!slotPredicate(index)) continue
|
||||
|
||||
val slot = this.slots[index]
|
||||
val slotStack = slot.stack
|
||||
if (slotStack.isEmpty) {
|
||||
slot.stack = stack.copy()
|
||||
stack.count = 0
|
||||
|
||||
slot.markDirty()
|
||||
slotsInsertedInto.add(index)
|
||||
} 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)
|
||||
stack.decrement(toMove)
|
||||
|
||||
slot.markDirty()
|
||||
slotsInsertedInto.add(index)
|
||||
}
|
||||
}
|
||||
return slotsInsertedInto
|
||||
}
|
||||
|
||||
val networkSlotsStart = 0
|
||||
val networkSlotsEnd = 54
|
||||
val bufferSlotsStart = 54
|
||||
val bufferSlotsEnd = 72
|
||||
val playerSlotsStart = 72
|
||||
val playerSlotsEnd = 72 + 36
|
||||
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
||||
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
||||
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
||||
|
||||
data class Entry(val stack: ItemStack, val amount: Int)
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import net.minecraft.text.TranslatableText
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import net.shadowfacts.cacao.geometry.Axis
|
||||
import net.shadowfacts.cacao.geometry.Point
|
||||
import net.shadowfacts.cacao.util.Color
|
||||
import net.shadowfacts.cacao.util.LayoutGuide
|
||||
import net.shadowfacts.cacao.util.MouseButton
|
||||
import net.shadowfacts.cacao.view.Label
|
||||
import net.shadowfacts.cacao.view.StackView
|
||||
import net.shadowfacts.cacao.view.View
|
||||
import net.shadowfacts.cacao.view.textfield.TextField
|
||||
import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||
import net.shadowfacts.kiwidsl.dsl
|
||||
import net.shadowfacts.phycon.client.screen.ScrollTrackView
|
||||
import net.shadowfacts.phycon.util.TerminalSettings
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S: AbstractTerminalScreen<BE, H>, H: AbstractTerminalScreenHandler<BE>>(
|
||||
val screen: S,
|
||||
val handler: H,
|
||||
val terminal: BE = handler.terminal,
|
||||
): ViewController() {
|
||||
|
||||
private lateinit var scrollTrack: ScrollTrackView
|
||||
lateinit var settingsView: View
|
||||
private set
|
||||
lateinit var searchField: TextField
|
||||
private set
|
||||
|
||||
lateinit var pane: LayoutGuide
|
||||
private set
|
||||
lateinit var buffer: LayoutGuide
|
||||
private set
|
||||
lateinit var network: LayoutGuide
|
||||
private set
|
||||
lateinit var playerInv: LayoutGuide
|
||||
private set
|
||||
|
||||
lateinit var networkLabel: View
|
||||
private set
|
||||
lateinit var playerInvLabel: View
|
||||
private set
|
||||
lateinit var bufferLabel: View
|
||||
private set
|
||||
|
||||
override fun loadView() {
|
||||
view = ScrollHandlingView(this)
|
||||
}
|
||||
|
||||
override fun viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
pane = view.addLayoutGuide()
|
||||
view.solver.dsl {
|
||||
pane.centerXAnchor equalTo view.centerXAnchor
|
||||
pane.centerYAnchor equalTo view.centerYAnchor
|
||||
pane.widthAnchor equalTo screen.terminalBackgroundWidth
|
||||
pane.heightAnchor equalTo screen.terminalBackgroundHeight
|
||||
}
|
||||
|
||||
buffer = view.addLayoutGuide()
|
||||
view.solver.dsl {
|
||||
buffer.leftAnchor equalTo (pane.leftAnchor + 7 + handler.xOffset)
|
||||
buffer.topAnchor equalTo (pane.topAnchor + 17)
|
||||
buffer.widthAnchor equalTo (18 * 3)
|
||||
buffer.heightAnchor equalTo (18 * 6)
|
||||
}
|
||||
|
||||
network = view.addLayoutGuide()
|
||||
view.solver.dsl {
|
||||
network.leftAnchor equalTo (pane.leftAnchor + 65 + handler.xOffset)
|
||||
network.topAnchor equalTo buffer.topAnchor
|
||||
network.widthAnchor equalTo (18 * 9)
|
||||
network.heightAnchor equalTo (18 * 6)
|
||||
}
|
||||
|
||||
playerInv = view.addLayoutGuide()
|
||||
view.solver.dsl {
|
||||
playerInv.leftAnchor equalTo network.leftAnchor
|
||||
playerInv.topAnchor equalTo (pane.topAnchor + 139)
|
||||
playerInv.widthAnchor equalTo (18 * 9)
|
||||
playerInv.heightAnchor equalTo 76
|
||||
}
|
||||
|
||||
networkLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_network"))).apply {
|
||||
textColor = Color.TEXT
|
||||
}
|
||||
playerInvLabel = view.addSubview(Label(handler.playerInv.displayName)).apply {
|
||||
textColor = Color.TEXT
|
||||
}
|
||||
bufferLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_buffer"))).apply {
|
||||
textColor = Color.TEXT
|
||||
}
|
||||
|
||||
searchField = view.addSubview(TerminalSearchField()).apply {
|
||||
handler = ::searchFieldChanged
|
||||
drawBackground = false
|
||||
}
|
||||
|
||||
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
|
||||
|
||||
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
|
||||
settingsView = settingsStack
|
||||
TerminalSettings.allKeys.sortedByDescending { it.priority }.forEach { key ->
|
||||
val button = SettingButton(key)
|
||||
button.handler = { settingsChanged() }
|
||||
settingsStack.addArrangedSubview(button)
|
||||
}
|
||||
|
||||
view.solver.dsl {
|
||||
networkLabel.leftAnchor equalTo network.leftAnchor
|
||||
networkLabel.topAnchor equalTo (pane.topAnchor + 6)
|
||||
|
||||
bufferLabel.leftAnchor equalTo buffer.leftAnchor
|
||||
bufferLabel.topAnchor equalTo networkLabel.topAnchor
|
||||
|
||||
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
|
||||
playerInvLabel.topAnchor equalTo (pane.topAnchor + 128)
|
||||
|
||||
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 + handler.xOffset)
|
||||
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
|
||||
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
|
||||
scrollTrack.widthAnchor equalTo 12
|
||||
|
||||
settingsStack.leftAnchor equalTo (pane.rightAnchor + 4)
|
||||
settingsStack.topAnchor equalTo pane.topAnchor
|
||||
}
|
||||
}
|
||||
|
||||
override fun viewWillAppear() {
|
||||
super.viewWillAppear()
|
||||
|
||||
searchField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
private fun searchFieldChanged(field: TextField) {
|
||||
screen.searchQuery = field.text
|
||||
screen.requestUpdatedItems()
|
||||
}
|
||||
|
||||
private fun scrollPositionChanged(track: ScrollTrackView) {
|
||||
val oldOffset = handler.currentScrollOffsetInRows()
|
||||
|
||||
handler.scrollPosition = track.scrollPosition.toFloat()
|
||||
screen.scrollPosition = track.scrollPosition
|
||||
|
||||
if (handler.currentScrollOffsetInRows() != oldOffset) {
|
||||
screen.requestUpdatedItems()
|
||||
}
|
||||
}
|
||||
|
||||
private fun settingsChanged() {
|
||||
screen.requestUpdatedItems()
|
||||
}
|
||||
|
||||
class TerminalSearchField: TextField("") {
|
||||
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>): View() {
|
||||
override fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
|
||||
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())
|
||||
if (newOffsetInRows != vc.handler.currentScrollOffsetInRows()) {
|
||||
val newScrollPosition = newOffsetInRows / vc.handler.maxScrollOffsetInRows().toDouble()
|
||||
vc.screen.scrollPosition = newScrollPosition
|
||||
vc.scrollTrack.scrollPosition = newScrollPosition
|
||||
vc.screen.requestUpdatedItems()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import net.shadowfacts.cacao.geometry.Point
|
||||
import net.shadowfacts.cacao.geometry.Size
|
||||
import net.shadowfacts.cacao.util.EnumHelper
|
||||
import net.shadowfacts.cacao.util.MouseButton
|
||||
import net.shadowfacts.cacao.util.texture.Texture
|
||||
import net.shadowfacts.cacao.view.TextureView
|
||||
import net.shadowfacts.cacao.view.button.AbstractButton
|
||||
import net.shadowfacts.phycon.PhysicalConnectivityClient
|
||||
import net.shadowfacts.phycon.api.TerminalSetting
|
||||
import net.shadowfacts.phycon.util.TerminalSettings
|
||||
import java.util.EnumMap
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class SettingButton<E>(
|
||||
val key: TerminalSettings.SettingKey<E>,
|
||||
): AbstractButton<SettingButton<E>>(
|
||||
TextureView(null).apply {
|
||||
intrinsicContentSize = Size(16.0, 16.0)
|
||||
},
|
||||
padding = 2.0
|
||||
) where E: Enum<E>, E: TerminalSetting {
|
||||
|
||||
private val textureCache = EnumMap<E, Texture>(key.clazz)
|
||||
|
||||
private val textureView: TextureView
|
||||
get() = content as TextureView
|
||||
|
||||
init {
|
||||
update()
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
textureView.texture = textureCache.getOrPut(key.value) {
|
||||
val uv = key.value.uv
|
||||
Texture(key.value.iconTexture, uv[0], uv[1])
|
||||
}
|
||||
tooltip = key.value.tooltip
|
||||
}
|
||||
|
||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||
if (!disabled) {
|
||||
val newValue = when (mouseButton) {
|
||||
MouseButton.LEFT -> EnumHelper.next(key.value)
|
||||
MouseButton.RIGHT -> EnumHelper.previous(key.value)
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalConnectivityClient.terminalSettings[key] = newValue
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
return super.mouseClicked(point, mouseButton)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,80 +1,19 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import alexiil.mc.lib.attributes.AttributeList
|
||||
import alexiil.mc.lib.attributes.AttributeProvider
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.block.Material
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemPlacementContext
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.sound.BlockSoundGroup
|
||||
import net.minecraft.state.StateManager
|
||||
import net.minecraft.state.property.Properties
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.ItemScatterer
|
||||
import net.minecraft.util.hit.BlockHitResult
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.Direction
|
||||
import net.minecraft.world.BlockView
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.WorldAccess
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||
import net.shadowfacts.phycon.block.DeviceBlock
|
||||
import java.util.EnumSet
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TerminalBlock: DeviceBlock<TerminalBlockEntity>(
|
||||
Settings.of(Material.METAL)
|
||||
.strength(1.5f)
|
||||
.sounds(BlockSoundGroup.METAL)
|
||||
),
|
||||
NetworkComponentBlock,
|
||||
AttributeProvider {
|
||||
class TerminalBlock: AbstractTerminalBlock<TerminalBlockEntity>() {
|
||||
|
||||
companion object {
|
||||
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
|
||||
val FACING = Properties.FACING
|
||||
}
|
||||
|
||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||
val set = EnumSet.allOf(Direction::class.java)
|
||||
set.remove(state[FACING])
|
||||
return set
|
||||
}
|
||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = TerminalBlockEntity(pos, state)
|
||||
|
||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||
super.appendProperties(builder)
|
||||
builder.add(FACING)
|
||||
}
|
||||
|
||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
||||
}
|
||||
|
||||
override fun createBlockEntity(world: BlockView) = TerminalBlockEntity()
|
||||
|
||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
||||
getBlockEntity(world, pos)!!.onActivate(player)
|
||||
return ActionResult.SUCCESS
|
||||
}
|
||||
|
||||
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
||||
if (!state.isOf(newState.block)) {
|
||||
val be = getBlockEntity(world, pos)!!
|
||||
ItemScatterer.spawn(world, pos, be.internalBuffer)
|
||||
|
||||
super.onStateReplaced(state, world, pos, newState, moved)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
||||
to.offer(getBlockEntity(world, pos))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,189 +1,26 @@
|
|||
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.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.util.Tickable
|
||||
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.minecraft.util.math.BlockPos
|
||||
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.packet.*
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
import kotlin.properties.Delegates
|
||||
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
|
||||
InventoryChangedListener,
|
||||
BlockEntityClientSerializable,
|
||||
Tickable,
|
||||
ItemStackPacketHandler,
|
||||
NetworkStackDispatcher<TerminalBlockEntity.PendingInsertion> {
|
||||
class TerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTerminalBlockEntity(PhyBlockEntities.TERMINAL, pos, state) {
|
||||
|
||||
companion object {
|
||||
// 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
|
||||
}
|
||||
|
||||
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
||||
val internalBuffer = TerminalBufferInventory(18)
|
||||
|
||||
private val pendingRequests = LinkedList<StackLocateRequest>()
|
||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
||||
|
||||
private var observers = 0
|
||||
val cachedNetItems = ItemStackCollections.intMap()
|
||||
|
||||
var netItemObserver: WeakReference<NetItemObserver>? = null
|
||||
|
||||
init {
|
||||
internalBuffer.addListener(this)
|
||||
}
|
||||
|
||||
override fun findDestination(): Interface? {
|
||||
for (dir in Direction.values()) {
|
||||
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
||||
if (itf != null) {
|
||||
return itf
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun handle(packet: Packet) {
|
||||
when (packet) {
|
||||
is ReadInventoryPacket -> handleReadInventory(packet)
|
||||
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
||||
is StackLocationPacket -> handleStackLocation(packet)
|
||||
is ItemStackPacket -> handleItemStack(packet)
|
||||
is CapacityPacket -> handleCapacity(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReadInventory(packet: ReadInventoryPacket) {
|
||||
inventoryCache[packet.source] = packet.inventory
|
||||
updateAndSync()
|
||||
}
|
||||
|
||||
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
||||
inventoryCache.remove(packet.source)
|
||||
updateAndSync()
|
||||
}
|
||||
|
||||
private fun handleStackLocation(packet: StackLocationPacket) {
|
||||
val request = pendingRequests.firstOrNull {
|
||||
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
|
||||
}
|
||||
if (request != null) {
|
||||
request.results.add(packet.amount to packet.stackProvider)
|
||||
if (request.isFinishable(counter)) {
|
||||
stackLocateRequestCompleted(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||
val remaining = internalBuffer.insertFromNetwork(packet.stack)
|
||||
|
||||
// this happens outside the normal update loop because by receiving the item stack packet
|
||||
// we "know" how much the count in the source inventory has changed
|
||||
updateAndSync()
|
||||
|
||||
return remaining
|
||||
}
|
||||
|
||||
private fun updateAndSync() {
|
||||
updateNetItems()
|
||||
sync()
|
||||
netItemObserver?.get()?.netItemsChanged()
|
||||
}
|
||||
|
||||
private fun updateNetItems() {
|
||||
cachedNetItems.clear()
|
||||
for (inventory in inventoryCache.values) {
|
||||
for (stack in inventory.storedStacks) {
|
||||
val amount = inventory.getAmount(stack)
|
||||
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun beginInsertions() {
|
||||
if (world!!.isClient) return
|
||||
|
||||
for (slot in 0 until internalBuffer.size()) {
|
||||
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
||||
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
||||
val stack = internalBuffer.getStack(slot)
|
||||
dispatchItemStack(stack) { insertion ->
|
||||
insertion.bufferSlot = slot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishPendingRequests() {
|
||||
if (world!!.isClient) return
|
||||
if (pendingRequests.isEmpty()) return
|
||||
|
||||
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
||||
// stackLocateRequestCompleted removes the object from pendingRequests
|
||||
finishable.forEach(::stackLocateRequestCompleted)
|
||||
}
|
||||
|
||||
fun addObserver() {
|
||||
observers++
|
||||
}
|
||||
|
||||
fun removeObserver() {
|
||||
observers--
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
override fun onActivate(player: PlayerEntity) {
|
||||
super.onActivate(player)
|
||||
|
||||
if (!world!!.isClient) {
|
||||
finishPendingRequests()
|
||||
finishTimedOutPendingInsertions()
|
||||
}
|
||||
|
||||
if (counter % 20 == 0L && !world!!.isClient) {
|
||||
beginInsertions()
|
||||
|
||||
if (observers > 0) {
|
||||
updateAndSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onActivate(player: PlayerEntity) {
|
||||
if (!world!!.isClient) {
|
||||
updateAndSync()
|
||||
|
||||
inventoryCache.clear()
|
||||
sendPacket(RequestInventoryPacket(ipAddress))
|
||||
val factory = object: ExtendedScreenHandlerFactory {
|
||||
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
|
||||
return TerminalScreenHandler(syncId, playerInv, this@TerminalBlockEntity)
|
||||
|
@ -197,88 +34,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
|
|||
}
|
||||
player.openHandledScreen(factory)
|
||||
}
|
||||
addObserver()
|
||||
}
|
||||
|
||||
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
||||
val request = StackLocateRequest(stack, amount, counter)
|
||||
pendingRequests.add(request)
|
||||
// locate packets are sent immediately instead of being added to a queue
|
||||
// otherwise the terminal UI feels sluggish and unresponsive
|
||||
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||
}
|
||||
|
||||
private fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||
pendingRequests.remove(request)
|
||||
|
||||
val sortedResults = request.results.toMutableList()
|
||||
sortedResults.sortWith { a, b ->
|
||||
// sort results first by provider priority, and then by the count that it can provide
|
||||
if (a.second.providerPriority == b.second.providerPriority) {
|
||||
b.first - a.first
|
||||
} else {
|
||||
b.second.providerPriority - a.second.providerPriority
|
||||
}
|
||||
}
|
||||
var amountRequested = 0
|
||||
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
||||
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
||||
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
||||
amountRequested += amountToRequest
|
||||
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||
|
||||
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
||||
val remaining = super.finishInsertion(insertion)
|
||||
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
||||
|
||||
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
||||
updateAndSync()
|
||||
|
||||
return remaining
|
||||
}
|
||||
|
||||
override fun onInventoryChanged(inv: Inventory) {
|
||||
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
||||
markDirty()
|
||||
sync()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toCommonTag(tag: CompoundTag) {
|
||||
super.toCommonTag(tag)
|
||||
tag.put("InternalBuffer", internalBuffer.toTag())
|
||||
}
|
||||
|
||||
override fun fromCommonTag(tag: CompoundTag) {
|
||||
super.fromCommonTag(tag)
|
||||
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
||||
}
|
||||
interface NetItemObserver {
|
||||
fun netItemsChanged()
|
||||
}
|
||||
|
||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||
var bufferSlot by Delegates.notNull<Int>()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class StackLocateRequest(
|
||||
val stack: ItemStack,
|
||||
val amount: Int,
|
||||
val timestamp: Long,
|
||||
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 >= TerminalBlockEntity.LOCATE_REQUEST_TIMEOUT
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,25 +21,25 @@ 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] }
|
||||
}
|
||||
|
||||
fun insertFromNetwork(stack: ItemStack): ItemStack {
|
||||
fun insert(stack: ItemStack, mode: Mode): ItemStack {
|
||||
var remaining = stack.copy()
|
||||
for (slot in 0 until size()) {
|
||||
if (modes[slot] == Mode.TO_NETWORK) continue
|
||||
if (modes[slot] != mode && modes[slot] != Mode.UNASSIGNED) continue
|
||||
|
||||
remaining = insertFromNetwork(stack, slot)
|
||||
remaining = tryInsert(stack, slot, mode)
|
||||
|
||||
if (remaining.isEmpty) {
|
||||
break
|
||||
|
@ -48,19 +48,17 @@ class TerminalBufferInventory(size: Int): SimpleInventory(size) {
|
|||
return remaining
|
||||
}
|
||||
|
||||
private fun insertFromNetwork(stack: ItemStack, slot: Int): ItemStack {
|
||||
val mode = modes[slot]
|
||||
if (mode == Mode.TO_NETWORK) return stack
|
||||
private fun tryInsert(stack: ItemStack, slot: Int, mode: Mode): ItemStack {
|
||||
val current = getStack(slot)
|
||||
if (current.isEmpty) {
|
||||
setStack(slot, stack)
|
||||
modes[slot] = Mode.FROM_NETWORK
|
||||
markSlot(slot, mode)
|
||||
return ItemStack.EMPTY
|
||||
} else if (ItemStackUtil.areEqualIgnoreAmounts(stack, current)) {
|
||||
val toTransfer = min(current.maxCount - current.count, stack.count)
|
||||
current.count += toTransfer
|
||||
stack.count -= toTransfer
|
||||
modes[slot] = Mode.FROM_NETWORK
|
||||
markSlot(slot, mode)
|
||||
return stack
|
||||
} else {
|
||||
return stack
|
||||
|
|
|
@ -23,7 +23,7 @@ class TerminalFakeSlot(fakeInv: FakeInventory, slot: Int, x: Int, y: Int): Slot(
|
|||
|
||||
}
|
||||
|
||||
class FakeInventory(val screenHandler: TerminalScreenHandler): Inventory {
|
||||
class FakeInventory(val screenHandler: AbstractTerminalScreenHandler<*>): Inventory {
|
||||
override fun getStack(slot: Int): ItemStack {
|
||||
if (slot >= screenHandler.itemsForDisplay.size) return ItemStack.EMPTY
|
||||
return screenHandler.itemsForDisplay[slot]
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.util.Identifier
|
||||
import net.shadowfacts.cacao.util.KeyModifiers
|
||||
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.cacao.view.textfield.NumberField
|
||||
import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||
import net.shadowfacts.kiwidsl.dsl
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TerminalRequestAmountViewController(
|
||||
val screen: AbstractTerminalScreen<*, *>,
|
||||
val stack: ItemStack,
|
||||
): ViewController() {
|
||||
|
||||
companion object {
|
||||
private val BACKGROUND = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png"), 0, 0)
|
||||
}
|
||||
|
||||
lateinit var field: NumberField
|
||||
private set
|
||||
|
||||
override fun viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
val pane = view.addLayoutGuide()
|
||||
view.solver.dsl {
|
||||
pane.widthAnchor equalTo 158
|
||||
pane.heightAnchor equalTo 62
|
||||
pane.centerXAnchor equalTo view.centerXAnchor
|
||||
pane.centerYAnchor equalTo view.centerYAnchor
|
||||
}
|
||||
|
||||
val background = view.addSubview(TextureView(BACKGROUND)).apply {
|
||||
zIndex = -1.0
|
||||
}
|
||||
|
||||
field = view.addSubview(AmountField(this)).apply {
|
||||
drawBackground = false
|
||||
}
|
||||
field.becomeFirstResponder()
|
||||
|
||||
val requestLabel = Label("Request", shadow = true)
|
||||
val request = view.addSubview(Button(requestLabel) {
|
||||
doRequest()
|
||||
})
|
||||
|
||||
val plusOneLabel = Label("+1", shadow = true)
|
||||
val plusOne = view.addSubview(Button(plusOneLabel) {
|
||||
field.number = (field.number ?: 1) + 1
|
||||
})
|
||||
val plusTenLabel = Label("+10", shadow = true)
|
||||
val plusTen = view.addSubview(Button(plusTenLabel) {
|
||||
val old = field.number ?: 1
|
||||
field.number = ceil((old + 1) / 10.0).toInt() * 10
|
||||
})
|
||||
val plusHundredLabel = Label("+100", shadow = true)
|
||||
val plusHundred = view.addSubview(Button(plusHundredLabel) {
|
||||
val old = field.number ?: 1
|
||||
field.number = ceil((old + 1) / 100.0).toInt() * 100
|
||||
})
|
||||
|
||||
val minusOneLabel = Label("-1", shadow = true)
|
||||
val minusOne = view.addSubview(Button(minusOneLabel) {
|
||||
field.number = (field.number ?: 1) - 1
|
||||
})
|
||||
val minusTenLabel = Label("-10", shadow = true)
|
||||
val minusTen = view.addSubview(Button(minusTenLabel) {
|
||||
val old = field.number ?: 1
|
||||
field.number = floor((old - 1) / 10.0).toInt() * 10
|
||||
})
|
||||
val minusHundredLabel = Label("-100", shadow = true)
|
||||
val minusHundred = view.addSubview(Button(minusHundredLabel) {
|
||||
val old = field.number ?: 1
|
||||
field.number = floor((old - 1) / 100.0).toInt() * 100
|
||||
})
|
||||
|
||||
view.solver.dsl {
|
||||
background.leftAnchor equalTo pane.leftAnchor
|
||||
background.rightAnchor equalTo pane.rightAnchor
|
||||
background.topAnchor equalTo pane.topAnchor
|
||||
background.bottomAnchor equalTo pane.bottomAnchor
|
||||
|
||||
field.leftAnchor equalTo (pane.leftAnchor + 8)
|
||||
field.topAnchor equalTo (pane.topAnchor + 27)
|
||||
field.widthAnchor equalTo 80
|
||||
field.heightAnchor equalTo 9
|
||||
|
||||
request.leftAnchor equalTo (pane.leftAnchor + 101)
|
||||
request.centerYAnchor equalTo field.centerYAnchor
|
||||
request.widthAnchor equalTo 50
|
||||
request.heightAnchor equalTo 20
|
||||
|
||||
plusOne.leftAnchor equalTo (pane.leftAnchor + 7)
|
||||
plusTen.leftAnchor equalTo (plusOne.rightAnchor + 3)
|
||||
plusHundred.leftAnchor equalTo (plusTen.rightAnchor + 3)
|
||||
plusHundred.rightAnchor equalTo (pane.leftAnchor + 97)
|
||||
plusOne.widthAnchor equalTo plusTen.widthAnchor
|
||||
plusOne.widthAnchor equalTo plusHundred.widthAnchor
|
||||
|
||||
plusOne.topAnchor equalTo (pane.topAnchor + 7)
|
||||
plusTen.topAnchor equalTo (pane.topAnchor + 7)
|
||||
plusHundred.topAnchor equalTo (pane.topAnchor + 7)
|
||||
plusOne.heightAnchor equalTo 14
|
||||
plusTen.heightAnchor equalTo 14
|
||||
plusHundred.heightAnchor equalTo 14
|
||||
|
||||
minusOne.leftAnchor equalTo (pane.leftAnchor + 7)
|
||||
minusTen.leftAnchor equalTo (minusOne.rightAnchor + 3)
|
||||
minusHundred.leftAnchor equalTo (minusTen.rightAnchor + 3)
|
||||
minusHundred.rightAnchor equalTo (pane.leftAnchor + 97)
|
||||
minusOne.widthAnchor equalTo minusTen.widthAnchor
|
||||
minusOne.widthAnchor equalTo minusHundred.widthAnchor
|
||||
|
||||
minusOne.topAnchor equalTo (pane.topAnchor + 41)
|
||||
minusTen.topAnchor equalTo (pane.topAnchor + 41)
|
||||
minusHundred.topAnchor equalTo (pane.topAnchor + 41)
|
||||
minusOne.heightAnchor equalTo 14
|
||||
minusTen.heightAnchor equalTo 14
|
||||
minusHundred.heightAnchor equalTo 14
|
||||
}
|
||||
}
|
||||
|
||||
private fun doRequest() {
|
||||
screen.requestItem(stack, field.number ?: 1)
|
||||
window!!.removeFromScreen()
|
||||
screen.amountVC = null
|
||||
}
|
||||
|
||||
class AmountField(val vc: TerminalRequestAmountViewController): NumberField(1) {
|
||||
override fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
||||
return if (keyCode == GLFW.GLFW_KEY_ENTER) {
|
||||
vc.doRequest()
|
||||
true
|
||||
} else {
|
||||
super.keyPressed(keyCode, modifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,495 +1,33 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.DrawableHelper
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen
|
||||
import net.minecraft.client.gui.widget.AbstractButtonWidget
|
||||
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
|
||||
import net.minecraft.client.gui.widget.ButtonWidget
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
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.text.TranslatableText
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
|
||||
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
|
||||
import net.shadowfacts.phycon.util.SortMode
|
||||
import net.shadowfacts.phycon.util.next
|
||||
import net.shadowfacts.phycon.util.prev
|
||||
import org.lwjgl.glfw.GLFW
|
||||
import java.lang.NumberFormatException
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
// todo: translate title
|
||||
class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen<TerminalScreenHandler>(handler, playerInv, title) {
|
||||
class TerminalScreen(
|
||||
handler: TerminalScreenHandler,
|
||||
playerInv: PlayerInventory,
|
||||
title: Text,
|
||||
): AbstractTerminalScreen<TerminalBlockEntity, TerminalScreenHandler>(
|
||||
handler,
|
||||
playerInv,
|
||||
title,
|
||||
252,
|
||||
222
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
|
||||
private val DIALOG = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png")
|
||||
}
|
||||
|
||||
private lateinit var searchBox: TextFieldWidget
|
||||
private lateinit var sortButton: SortButton
|
||||
var sortButtonX: Int = 0
|
||||
private set
|
||||
var sortButtonY = 0
|
||||
private set
|
||||
private lateinit var amountBox: TextFieldWidget
|
||||
private var dialogStack = ItemStack.EMPTY
|
||||
private var showingAmountDialog = false
|
||||
set(value) {
|
||||
val oldValue = field
|
||||
field = value
|
||||
for (e in dialogChildren) {
|
||||
e.visible = value
|
||||
}
|
||||
amountBox.isVisible
|
||||
searchBox.setSelected(!value)
|
||||
amountBox.setSelected(value)
|
||||
if (value && !oldValue) {
|
||||
amountBox.text = "1"
|
||||
}
|
||||
updateFocusedElement()
|
||||
}
|
||||
private var dialogChildren = mutableListOf<AbstractButtonWidget>()
|
||||
override val backgroundTexture = BACKGROUND
|
||||
|
||||
private var scrollPosition = 0f
|
||||
private var isDraggingScrollThumb = false
|
||||
private val trackMinY = 18
|
||||
private val trackHeight = 106
|
||||
private val thumbHeight = 15
|
||||
private val thumbWidth = 12
|
||||
private val scrollThumbTop: Int
|
||||
get() = trackMinY + (scrollPosition * (trackHeight - thumbHeight)).roundToInt()
|
||||
|
||||
private val dialogWidth = 158
|
||||
private val dialogHeight = 62
|
||||
|
||||
init {
|
||||
backgroundWidth = 252
|
||||
backgroundHeight = 222
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
|
||||
children.clear()
|
||||
dialogChildren.clear()
|
||||
|
||||
client!!.keyboard.setRepeatEvents(true)
|
||||
|
||||
searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search"))
|
||||
searchBox.setMaxLength(50)
|
||||
// setHasBorder is actually setDrawsBackground
|
||||
searchBox.setHasBorder(false)
|
||||
searchBox.isVisible = true
|
||||
searchBox.setSelected(true)
|
||||
searchBox.setEditableColor(0xffffff)
|
||||
addChild(searchBox)
|
||||
|
||||
sortButtonX = x + 256
|
||||
sortButtonY = y
|
||||
sortButton = SortButton(sortButtonX, sortButtonY, handler.sortMode, {
|
||||
requestUpdatedItems()
|
||||
}, ::renderTooltip)
|
||||
addButton(sortButton)
|
||||
|
||||
val dialogMinX = width / 2 - dialogWidth / 2
|
||||
val dialogMinY = height / 2 - dialogHeight / 2
|
||||
amountBox = TextFieldWidget(textRenderer, dialogMinX + 8, dialogMinY + 27, 80, 9, LiteralText("Amount"))
|
||||
amountBox.setHasBorder(false)
|
||||
amountBox.isVisible = false
|
||||
amountBox.setSelected(false)
|
||||
amountBox.setEditableColor(0xffffff)
|
||||
amountBox.setTextPredicate {
|
||||
if (it.isEmpty()) {
|
||||
true
|
||||
} else {
|
||||
try {
|
||||
Integer.parseInt(it) > 0
|
||||
} catch (e: NumberFormatException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
dialogChildren.add(amountBox)
|
||||
|
||||
val plusOne = SmallButton(dialogMinX + 7, dialogMinY + 7, 28, LiteralText("+1")) {
|
||||
amountBox.intValue += 1
|
||||
}
|
||||
dialogChildren.add(plusOne)
|
||||
|
||||
val plusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 7, 28, LiteralText("+10")) {
|
||||
amountBox.intValue = ceil((amountBox.intValue + 1) / 10.0).toInt() * 10
|
||||
}
|
||||
dialogChildren.add(plusTen)
|
||||
|
||||
val plusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 7, 28, LiteralText("+100")) {
|
||||
amountBox.intValue = ceil((amountBox.intValue + 1) / 100.0).toInt() * 100
|
||||
}
|
||||
dialogChildren.add(plusHundred)
|
||||
|
||||
val minusOne = SmallButton(dialogMinX + 7, dialogMinY + 39, 28, LiteralText("-1")) {
|
||||
amountBox.intValue -= 1
|
||||
}
|
||||
dialogChildren.add(minusOne)
|
||||
|
||||
val minusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 39, 28, LiteralText("-10")) {
|
||||
amountBox.intValue = floor((amountBox.intValue - 1) / 10.0).toInt() * 10
|
||||
}
|
||||
dialogChildren.add(minusTen)
|
||||
|
||||
val minusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 39, 28, LiteralText("-100")) {
|
||||
amountBox.intValue = floor((amountBox.intValue - 1) / 100.0).toInt() * 100
|
||||
}
|
||||
dialogChildren.add(minusHundred)
|
||||
|
||||
// 101,25
|
||||
val request = ButtonWidget(dialogMinX + 101, dialogMinY + 21, 50, 20, LiteralText("Request")) {
|
||||
doDialogRequest()
|
||||
}
|
||||
dialogChildren.add(request)
|
||||
|
||||
updateFocusedElement()
|
||||
requestUpdatedItems()
|
||||
}
|
||||
|
||||
private fun updateFocusedElement() {
|
||||
focused = if (showingAmountDialog) {
|
||||
amountBox
|
||||
} else if (searchBox.isFocused) {
|
||||
searchBox
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestUpdatedItems() {
|
||||
val player = MinecraftClient.getInstance().player!!
|
||||
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchBox.text, sortButton.mode, scrollPosition))
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
searchBox.tick()
|
||||
amountBox.tick()
|
||||
}
|
||||
|
||||
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
|
||||
textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
|
||||
textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
|
||||
textRenderer.draw(matrixStack, TranslatableText("gui.phycon.terminal_buffer"), 7f, 6f, 0x404040)
|
||||
}
|
||||
|
||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||
// if the dialog is open, the background gradient will be drawn in front of the main terminal gui
|
||||
if (!showingAmountDialog) {
|
||||
renderBackground(matrixStack)
|
||||
}
|
||||
|
||||
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||
client!!.textureManager.bindTexture(BACKGROUND)
|
||||
val x = (width - backgroundWidth) / 2
|
||||
val y = (height - backgroundHeight) / 2
|
||||
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
||||
|
||||
// scroll thumb
|
||||
drawTexture(matrixStack, x + 232, y + scrollThumbTop, 52, 230, thumbWidth, thumbHeight)
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if (showingAmountDialog) {
|
||||
RenderSystem.pushMatrix()
|
||||
// items are rendered at some stupidly high z offset. item amounts at an even higher one
|
||||
RenderSystem.translatef(0f, 0f, -350f)
|
||||
|
||||
// fake the mouse x/y while showing a dialog so slot mouseover highlights aren't drawn
|
||||
super.render(matrixStack, -1, -1, delta)
|
||||
|
||||
RenderSystem.popMatrix()
|
||||
} else {
|
||||
super.render(matrixStack, mouseX, mouseY, delta)
|
||||
}
|
||||
|
||||
searchBox.render(matrixStack, mouseX, mouseY, delta)
|
||||
|
||||
if (showingAmountDialog) {
|
||||
renderBackground(matrixStack)
|
||||
|
||||
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||
client!!.textureManager.bindTexture(DIALOG)
|
||||
val dialogMinX = width / 2 - dialogWidth / 2
|
||||
val dialogMinY = height / 2 - dialogHeight / 2
|
||||
drawTexture(matrixStack, dialogMinX, dialogMinY, 0, 0, dialogWidth, dialogHeight)
|
||||
|
||||
for (e in dialogChildren) {
|
||||
e.render(matrixStack, mouseX, mouseY, delta)
|
||||
}
|
||||
} else {
|
||||
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
||||
if (!handler.isBufferSlot(slot.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
||||
val color: UInt = when (mode) {
|
||||
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
||||
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
||||
else -> return
|
||||
}
|
||||
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
||||
}
|
||||
|
||||
private fun isPointInsScrollThumb(mouseX: Double, mouseY: Double): Boolean {
|
||||
val x = (width - backgroundWidth) / 2
|
||||
val y = (height - backgroundHeight) / 2
|
||||
val thumbMinX = x + 232
|
||||
val thumbMaxX = thumbMinX + thumbWidth
|
||||
val thumbMinY = y + scrollThumbTop
|
||||
val thumbMaxY = thumbMinY + thumbHeight
|
||||
return mouseX >= thumbMinX && mouseX < thumbMaxX && mouseY >= thumbMinY && mouseY < thumbMaxY
|
||||
}
|
||||
|
||||
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
|
||||
super.onMouseClick(slot, invSlot, clickData, type)
|
||||
|
||||
updateFocusedElement()
|
||||
|
||||
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
|
||||
val stack = slot.stack
|
||||
|
||||
if (type == SlotActionType.QUICK_MOVE) {
|
||||
// shift click, request full stack
|
||||
requestItem(stack, min(stack.count, stack.maxCount))
|
||||
} else if (type == SlotActionType.PICKUP) {
|
||||
if (clickData == 1) {
|
||||
// right click, request half stack
|
||||
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
||||
} else {
|
||||
dialogStack = stack
|
||||
showingAmountDialog = true
|
||||
searchBox.setSelected(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
for (e in dialogChildren) {
|
||||
if (e.mouseClicked(mouseX, mouseY, button)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
if (isPointInsScrollThumb(mouseX, mouseY)) {
|
||||
isDraggingScrollThumb = true
|
||||
return true
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
return false
|
||||
} else if (isDraggingScrollThumb) {
|
||||
scrollPosition = (mouseY.toFloat() - (y + trackMinY) - 7.5f) / (trackHeight - 15)
|
||||
scrollPosition = MathHelper.clamp(scrollPosition, 0f, 1f)
|
||||
requestUpdatedItems()
|
||||
return true
|
||||
} else {
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseMoved(d: Double, e: Double) {
|
||||
if (showingAmountDialog) {
|
||||
} else {
|
||||
super.mouseMoved(d, e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseReleased(d: Double, e: Double, i: Int): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
return false
|
||||
} else {
|
||||
isDraggingScrollThumb = false
|
||||
|
||||
return super.mouseReleased(d, e, i)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
return false
|
||||
} else {
|
||||
var newOffsetInRows = handler.currentScrollOffsetInRows() - amount.toInt()
|
||||
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, handler.maxScrollOffsetInRows())
|
||||
val newScrollPosition = newOffsetInRows / handler.maxScrollOffsetInRows().toFloat()
|
||||
scrollPosition = newScrollPosition
|
||||
requestUpdatedItems()
|
||||
|
||||
return super.mouseScrolled(mouseX, mouseY, amount)
|
||||
}
|
||||
}
|
||||
|
||||
override fun charTyped(c: Char, i: Int): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
return amountBox.charTyped(c, i)
|
||||
} else {
|
||||
val oldText = searchBox.text
|
||||
if (searchBox.charTyped(c, i)) {
|
||||
if (searchBox.text != oldText) {
|
||||
scrollPosition = 0f
|
||||
requestUpdatedItems()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return super.charTyped(c, i)
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
|
||||
if (showingAmountDialog) {
|
||||
return when (key) {
|
||||
GLFW.GLFW_KEY_ESCAPE -> {
|
||||
showingAmountDialog = false
|
||||
true
|
||||
}
|
||||
GLFW.GLFW_KEY_ENTER -> {
|
||||
doDialogRequest()
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
amountBox.keyPressed(key, j, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val oldText = searchBox.text
|
||||
if (searchBox.keyPressed(key, j, k)) {
|
||||
if (searchBox.text != oldText) {
|
||||
scrollPosition = 0f
|
||||
requestUpdatedItems()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
|
||||
true
|
||||
} else {
|
||||
super.keyPressed(key, j, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doDialogRequest() {
|
||||
showingAmountDialog = false
|
||||
requestItem(dialogStack, amountBox.intValue)
|
||||
}
|
||||
|
||||
private fun requestItem(stack: ItemStack, amount: Int) {
|
||||
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
||||
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
||||
netHandler.sendPacket(packet)
|
||||
}
|
||||
|
||||
private var TextFieldWidget.intValue: Int
|
||||
get() = if (text.isEmpty()) 0 else Integer.parseInt(text)
|
||||
set(value) {
|
||||
text = value.toString()
|
||||
setSelected(true)
|
||||
}
|
||||
|
||||
class SmallButton(x: Int, y: Int, width: Int, title: Text, action: PressAction): ButtonWidget(x, y, width, 14, title, action) {
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val client = MinecraftClient.getInstance()
|
||||
client.textureManager.bindTexture(DIALOG)
|
||||
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||
val v = if (isHovered) 142 else 128
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
RenderSystem.enableDepthTest()
|
||||
drawTexture(matrixStack, x, y, 0, v, width / 2, height)
|
||||
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, v, width / 2, height)
|
||||
drawCenteredText(matrixStack, client.textRenderer, message, x + width / 2, y + (height - 8) / 2, 0xffffffffu.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
class SortButton(
|
||||
x: Int,
|
||||
y: Int,
|
||||
var mode: SortMode,
|
||||
val onChange: (SortMode) -> Unit,
|
||||
val doRenderTooltip: (MatrixStack, Text, Int, Int) -> Unit
|
||||
): AbstractPressableButtonWidget(x, y, 20, 20, LiteralText("")) {
|
||||
override fun onPress() {}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
if ((button == 0 || button == 1) && clicked(mouseX, mouseY)) {
|
||||
val newVal = if (button == 0) mode.next else mode.prev
|
||||
mode = newVal
|
||||
onChange(mode)
|
||||
|
||||
playDownSound(MinecraftClient.getInstance().soundManager)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
val client = MinecraftClient.getInstance()
|
||||
RenderSystem.color4f(1f, 1f, 1f, 1f)
|
||||
RenderSystem.enableBlend()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
RenderSystem.enableDepthTest()
|
||||
|
||||
client.textureManager.bindTexture(WIDGETS_LOCATION)
|
||||
val k = getYImage(isHovered)
|
||||
drawTexture(matrixStack, x, y, 0, 46 + k * 20, width / 2, height)
|
||||
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, 46 + k * 20, width / 2, height)
|
||||
|
||||
client.textureManager.bindTexture(BACKGROUND)
|
||||
val u: Int = when (mode) {
|
||||
SortMode.COUNT_HIGH_FIRST -> 0
|
||||
SortMode.COUNT_LOW_FIRST -> 16
|
||||
SortMode.ALPHABETICAL -> 32
|
||||
}
|
||||
drawTexture(matrixStack, x + 2, y + 2, u, 230, 16, 16)
|
||||
|
||||
if (isHovered) {
|
||||
renderToolTip(matrixStack, mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderToolTip(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
|
||||
val text = LiteralText("")
|
||||
text.append("Sort by: ")
|
||||
text.append(mode.tooltip)
|
||||
doRenderTooltip(matrixStack, text, mouseX, mouseY)
|
||||
}
|
||||
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
|
||||
return TerminalViewController(this, handler)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,247 +1,24 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
import net.minecraft.screen.slot.Slot
|
||||
import net.minecraft.screen.slot.SlotActionType
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.entity.player.PlayerInventory
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.network.PacketByteBuf
|
||||
import net.minecraft.screen.ScreenHandler
|
||||
import net.minecraft.server.network.ServerPlayerEntity
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.registry.Registry
|
||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||
import net.shadowfacts.phycon.init.PhyBlocks
|
||||
import net.shadowfacts.phycon.init.PhyScreens
|
||||
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
||||
import net.shadowfacts.phycon.util.SortMode
|
||||
import net.shadowfacts.phycon.util.copyWithCount
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL, syncId),
|
||||
TerminalBlockEntity.NetItemObserver {
|
||||
|
||||
companion object {
|
||||
val ID = Identifier(PhysicalConnectivity.MODID, "terminal")
|
||||
}
|
||||
|
||||
private val rowsDisplayed = 6
|
||||
|
||||
private val fakeInv = FakeInventory(this)
|
||||
private var searchQuery: String = ""
|
||||
var sortMode = SortMode.COUNT_HIGH_FIRST
|
||||
private set
|
||||
var totalEntries = 0
|
||||
private set
|
||||
private var scrollPosition = 0f
|
||||
private var itemEntries = listOf<Entry>()
|
||||
set(value) {
|
||||
field = value
|
||||
if (terminal.world!!.isClient) {
|
||||
itemsForDisplay = value.map {
|
||||
it.stack.copyWithCount(it.amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
var itemsForDisplay = listOf<ItemStack>()
|
||||
private set
|
||||
class TerminalScreenHandler(
|
||||
syncId: Int,
|
||||
playerInv: PlayerInventory,
|
||||
terminal: TerminalBlockEntity,
|
||||
): AbstractTerminalScreenHandler<TerminalBlockEntity>(PhyScreens.TERMINAL, syncId, playerInv, terminal) {
|
||||
|
||||
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
||||
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
|
||||
this(
|
||||
syncId,
|
||||
playerInv,
|
||||
PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||
)
|
||||
|
||||
init {
|
||||
if (!terminal.world!!.isClient) {
|
||||
terminal.netItemObserver = WeakReference(this)
|
||||
netItemsChanged()
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
// hotbar
|
||||
for (x in 0 until 9) {
|
||||
addSlot(Slot(playerInv, x, 66 + x * 18, 198))
|
||||
}
|
||||
}
|
||||
|
||||
override fun netItemsChanged() {
|
||||
val player = playerInv.player
|
||||
assert(player is ServerPlayerEntity)
|
||||
|
||||
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
||||
if (searchQuery.isBlank()) return@filter true
|
||||
if (searchQuery.startsWith('@')) {
|
||||
val unprefixed = searchQuery.drop(1)
|
||||
val key = Registry.ITEM.getKey(it.key.item)
|
||||
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
||||
return@filter true
|
||||
}
|
||||
}
|
||||
it.key.name.string.contains(searchQuery, true)
|
||||
}
|
||||
|
||||
totalEntries = filtered.size
|
||||
|
||||
val sorted =
|
||||
when (sortMode) {
|
||||
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
||||
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
||||
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
||||
}
|
||||
|
||||
|
||||
val offsetInItems = currentScrollOffsetInItems()
|
||||
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
||||
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
||||
|
||||
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
||||
|
||||
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode, scrollPosition, totalEntries))
|
||||
}
|
||||
|
||||
fun totalRows(): Int {
|
||||
return ceil(totalEntries / 9f).toInt()
|
||||
}
|
||||
|
||||
fun maxScrollOffsetInRows(): Int {
|
||||
return totalRows() - rowsDisplayed
|
||||
}
|
||||
|
||||
fun currentScrollOffsetInRows(): Int {
|
||||
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
||||
}
|
||||
|
||||
fun currentScrollOffsetInItems(): Int {
|
||||
return currentScrollOffsetInRows() * 9
|
||||
}
|
||||
|
||||
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode, scrollPosition: Float) {
|
||||
this.searchQuery = query
|
||||
this.sortMode = sortMode
|
||||
this.scrollPosition = scrollPosition
|
||||
netItemsChanged()
|
||||
}
|
||||
|
||||
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, sortMode: SortMode, scrollPosition: Float, totalEntries: Int) {
|
||||
assert(playerInv.player.world.isClient)
|
||||
|
||||
this.searchQuery = query
|
||||
this.sortMode = sortMode
|
||||
this.scrollPosition = scrollPosition
|
||||
this.totalEntries = totalEntries
|
||||
itemEntries = entries
|
||||
}
|
||||
|
||||
override fun canUse(player: PlayerEntity): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun close(player: PlayerEntity) {
|
||||
super.close(player)
|
||||
|
||||
terminal.removeObserver()
|
||||
}
|
||||
|
||||
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
|
||||
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) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||
if (isNetworkSlot(slotId)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
val slot = slots[slotId]
|
||||
if (!slot.hasStack()) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
val result = slot.stack.copy()
|
||||
|
||||
if (isBufferSlot(slotId)) {
|
||||
// last boolean param is fromLast
|
||||
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
if (slot.stack.isEmpty) {
|
||||
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
||||
}
|
||||
} else if (isPlayerSlot(slotId)) {
|
||||
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
||||
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
|
||||
if (slot.stack.isEmpty) {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
||||
val slotsInsertedInto = mutableListOf<Int>()
|
||||
for (index in slots) {
|
||||
if (stack.isEmpty) break
|
||||
if (!slotPredicate(index)) continue
|
||||
|
||||
val slot = this.slots[index]
|
||||
val slotStack = slot.stack
|
||||
if (slotStack.isEmpty) {
|
||||
slot.stack = stack.copy()
|
||||
stack.count = 0
|
||||
|
||||
slot.markDirty()
|
||||
slotsInsertedInto.add(index)
|
||||
} else if (canStacksCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
|
||||
val maxToMove = slotStack.maxCount - slotStack.count
|
||||
val toMove = min(maxToMove, stack.count)
|
||||
slotStack.increment(toMove)
|
||||
stack.decrement(toMove)
|
||||
|
||||
slot.markDirty()
|
||||
slotsInsertedInto.add(index)
|
||||
}
|
||||
}
|
||||
return slotsInsertedInto
|
||||
}
|
||||
|
||||
val bufferSlotsStart = 54
|
||||
val playerSlotsStart = 72
|
||||
val playerSlotsEnd = 72 + 36
|
||||
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
||||
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
||||
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
||||
|
||||
data class Entry(val stack: ItemStack, val amount: Int)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package net.shadowfacts.phycon.block.terminal
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class TerminalViewController(
|
||||
screen: TerminalScreen,
|
||||
handler: TerminalScreenHandler,
|
||||
): AbstractTerminalViewController<TerminalBlockEntity, TerminalScreen, TerminalScreenHandler>(
|
||||
screen,
|
||||
handler,
|
||||
) {
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package net.shadowfacts.phycon.client
|
||||
|
||||
import net.fabricmc.fabric.api.client.model.ModelProviderContext
|
||||
import net.fabricmc.fabric.api.client.model.ModelResourceProvider
|
||||
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.ScreenDeviceModel
|
||||
|
||||
/**
|
||||
* @author shadowfacts
|
||||
*/
|
||||
class PhyExtendedModelProvider(resourceManager: ResourceManager): ModelResourceProvider {
|
||||
|
||||
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 -> ScreenDeviceModel(TERMINAL)
|
||||
CRAFTING_TERMINAL -> ScreenDeviceModel(CRAFTING_TERMINAL)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue