diff --git a/NOTICE.md b/NOTICE.md index c1b372998..4cbfe4855 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,4 +1,4 @@ ### Licensing exclusions/External copyrighted material -Shockwave landing sound: [ULTRAKILL Game](https://store.steampowered.com/app/1229490/), ARR, got permission to use in mod +Shockwave landing and projectile punch sounds: [ULTRAKILL Game](https://store.steampowered.com/app/1229490/), ARR, got permission to use in mod diff --git a/README.md b/README.md index c153ece08..50a8b01e7 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,21 @@ Minecraft mod with science fiction style, about matter, and energy, combined. ### Required mods -* [Kotlin for Forge](https://www.curseforge.com/minecraft/mc-mods/kotlin-for-forge) or have Kotlin standard library in classpath +* [Kotlin for Forge](https://www.curseforge.com/minecraft/mc-mods/kotlin-for-forge) or have Kotlin standard library in classpath (at least 1.8.0 is required) ### Recommended mods * [Ferrite Core](https://www.curseforge.com/minecraft/mc-mods/ferritecore), reduces memory usage * In case of Overdrive That Matters, ***greatly*** reduces JVM heap bloat caused by model data being duplicated -### Supported mods +* Particle Collider, reduces world join time + +### Mods with special compatibility code -* [Mekanism](https://www.curseforge.com/minecraft/mc-mods/Mekanism) (full duplex Mekanism Joules support, QIO) -* [Curios](https://www.curseforge.com/minecraft/mc-mods/curios) (GUI support, technical inventory access) -* [Cosmetic Armor Reworked](https://www.curseforge.com/minecraft/mc-mods/cosmetic-armor-reworked) (GUI support, technical inventory access) * [JEI](https://www.curseforge.com/minecraft/mc-mods/jei) +* [Mekanism](https://www.curseforge.com/minecraft/mc-mods/Mekanism) +* [Curios](https://www.curseforge.com/minecraft/mc-mods/curios) +* [Cosmetic Armor Reworked](https://www.curseforge.com/minecraft/mc-mods/cosmetic-armor-reworked) ---- diff --git a/build.gradle.kts b/build.gradle.kts index cbf11166a..9ad1213ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,8 @@ -import groovy.lang.Closure + import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.util.Date import java.text.SimpleDateFormat import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import kotlin.text.Regex -import java.util.UUID -import org.spongepowered.asm.gradle.plugins.MixinExtension val mod_version: String by project val mc_version: String by project @@ -13,7 +10,7 @@ val forge_version: String by project val mod_id: String by project val handle_deps: String by project val use_commit_hash_in_version: String by project -val handleDeps = handle_deps == "true" +val handleDeps = handle_deps.toBoolean() plugins { java @@ -23,13 +20,7 @@ plugins { id("org.spongepowered.mixin") } -configurations { - create("library") // non-mod libraries - create("klibrary") // kotlin libs - get("implementation").extendsFrom(get("library"), get("klibrary")) -} - -data class GitInfo(val version: String, val count: String, val tag: String) { +data class GitInfo(val version: String, val tag: String, val buildNumber: String) { // val tagIsVersion: Boolean get() = tag != "" && tag.matches(Regex("v[0-9]+\\.[0-9]\\.[0-9]")) val publishVersion: String get() { @@ -40,28 +31,23 @@ data class GitInfo(val version: String, val count: String, val tag: String) { } val jarName: String get() { + val buildNumber = if (buildNumber != "") "-$buildNumber" else "" + if (tag != "") - return "$mod_version-$version" + return "$mod_version-$version$buildNumber" // if (count != "") // return "$mod_version-SNAPSHOT-${version}_$count" if (version != "") { - return "$mod_version-SNAPSHOT-$version" + return "$mod_version-SNAPSHOT-$version$buildNumber" } else { - return "$mod_version-SNAPSHOT" + return "$mod_version-SNAPSHOT$buildNumber" } } val modVersion: String get() { - if (tag != "") - return mod_version - - if (version != "") { - return "$mod_version-SNAPSHOT-$version" - } else { - return "$mod_version-SNAPSHOT" - } + return if (buildNumber != "") "$mod_version.$buildNumber" else mod_version } } @@ -74,7 +60,6 @@ fun getCommitVersion(): GitInfo? { try { val versionStream = FastByteArrayOutputStream() val tagStream = FastByteArrayOutputStream() - val countStream = FastByteArrayOutputStream() val gotVersion = exec { commandLine("git", "rev-parse", "--short", "HEAD") @@ -82,27 +67,20 @@ fun getCommitVersion(): GitInfo? { standardOutput = versionStream }.exitValue == 0 - val gotCount = exec { - commandLine("git", "rev-list", "--count", "HEAD") - workingDir(".") - standardOutput = countStream - }.exitValue == 0 - val gotTag = exec { commandLine("git", "tag", "--points-at", "HEAD") workingDir(".") standardOutput = tagStream }.exitValue == 0 - if (!gotVersion || !gotCount || !gotTag) { + if (!gotVersion || !gotTag) { return null } val version = versionStream.array.copyOfRange(0, versionStream.length).toString(Charsets.UTF_8).trim() val tag = tagStream.array.copyOfRange(0, tagStream.length).toString(Charsets.UTF_8).trim() - val count = countStream.array.copyOfRange(0, countStream.length).toString(Charsets.UTF_8).trim() - return GitInfo(version, count, tag) + return GitInfo(version, tag, System.getenv("BUILD_NUMBER") ?: "") } catch(err: Throwable) { println("Error getting git version") println(err) @@ -143,10 +121,7 @@ tasks.test { dependencies { val jupiter_version: String by project - val kotlin_version: String by project val kotlin_for_forge_version: String by project - val kotlin_coroutines_version: String by project - val kotlin_serialization_version: String by project val mixin_version: String by project minecraft("net.minecraftforge:forge:$mc_version-$forge_version") @@ -154,72 +129,74 @@ dependencies { implementation("thedarkcolour:kotlinforforge:$kotlin_for_forge_version") - fun library(notation: Any) { this.add("library", notation) } - fun klibrary(notation: Any) { this.add("klibrary", notation) } - - val excludeKGroup = closureOf { - (this as ExternalModuleDependency).exclude(group = "org.jetbrains", module = "annotations") - } as Closure - - // Add everything to the classpath correctly - klibrary(create("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", excludeKGroup)) - klibrary(create("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version", excludeKGroup)) - klibrary(create("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version", excludeKGroup)) - klibrary(create("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutines_version", excludeKGroup)) - klibrary(create("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialization_version", excludeKGroup)) - - // Real mod deobf dependency examples - these get remapped to your current mappings - // compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency - // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency - // implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // Adds registrate as a dependency - - // Examples using mod jars from ./libs compileOnly("yalter.mousetweaks:MouseTweaks:2.23:api") - annotationProcessor("org.spongepowered:mixin:${mixin_version}:processor") - // compile against the JEI API but do not include it at runtime - //compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") - // at runtime, use the full JEI jar - //runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") - if (handleDeps) { val jei_version: String by project - val mekanism_version: String by project - val curios_version: String by project - val cosmetic_armor_reworked_version: String by project + val cosmetic_armor_reworked_id: String by project val jade_id: String by project val configured_id: String by project - val worldedit_fileid: String by project - val more_overlays_version: String by project + val curios_version: String by project + val jei_mc_version: String by project + val resourceful_lib_id: String by project + val resourceful_config_id: String by project + val botarium_id: String by project + val ad_astra_id: String by project - implementation(fg.deobf("top.theillusivec4.curios:curios-forge:${mc_version}-${curios_version}")) - compileOnly(fg.deobf("lain.mods.cos:CosmeticArmorReworked:${mc_version}-${cosmetic_armor_reworked_version}")) + compileOnly(fg.deobf("curse.maven:curios-309927:${curios_version}")) + compileOnly(fg.deobf("curse.maven:cosmetic-armor-reworked-237307:$cosmetic_armor_reworked_id")) - compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")) - compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}")) - runtimeOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}")) - // implementation("mcjty:theoneprobe:${mc_version}-${the_one_probe_version}:deobf") + compileOnly(fg.deobf("mezz.jei:jei-${jei_mc_version}-common-api:${jei_version}")) + compileOnly(fg.deobf("mezz.jei:jei-${jei_mc_version}-forge-api:${jei_version}")) + // runtimeOnly(fg.deobf("mezz.jei:jei-${jei_mc_version}-forge:${jei_version}")) + + // runtimeOnly(fg.deobf("ru.dbotthepony:particle-collider:0.4.5")) + + compileOnly(fg.deobf("curse.maven:jade-324717:${jade_id}")) + //runtimeOnly(fg.deobf("curse.maven:configured-457570:${configured_id}")) + + compileOnly(fg.deobf("curse.maven:resourceful-lib-570073:${resourceful_lib_id}")) + compileOnly(fg.deobf("curse.maven:resourceful-config-714059:${resourceful_config_id}")) + compileOnly(fg.deobf("curse.maven:botarium-704113:${botarium_id}")) + compileOnly(fg.deobf("curse.maven:ad-astra-635042:${ad_astra_id}")) - runtimeOnly(fg.deobf("curse.maven:jade-324717:${jade_id}")) - runtimeOnly(fg.deobf("curse.maven:configured-457570:${configured_id}")) // runtimeOnly(fg.deobf("curse.maven:worldedit-225608:${worldedit_fileid}")) // runtimeOnly(fg.deobf("at.ridgo8.moreoverlays:MoreOverlays-updated:${more_overlays_version}")) - compileOnly(fg.deobf("mekanism:Mekanism:${mc_version}-${mekanism_version}:all")) + // runtimeOnly(fg.deobf("curse.maven:cyclops-core-232758:4392602")) + // runtimeOnly(fg.deobf("curse.maven:integrated-dynamics-236307:4391535")) + // runtimeOnly(fg.deobf("curse.maven:integrated-crafting-287357:4391487")) + // runtimeOnly(fg.deobf("curse.maven:integrated-terminals-295910:4400924")) + // runtimeOnly(fg.deobf("curse.maven:common-capabilities-247007:4391468")) + // runtimeOnly(fg.deobf("curse.maven:integrated-tunnels-251389:4344632")) } } configurations { getByName("dataImplementation").extendsFrom(getByName("implementation")) - getByName("library").resolutionStrategy.cacheChangingModulesFor(10, "minutes") } minecraft { mappings("official", mc_version) + + copyIdeResources.set(true) + accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg")) runs { + configureEach { + workingDirectory = project.file("run").absolutePath + + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + property("forge.logging.markers", "REGISTRIES") + + // Log4j console level + property("forge.logging.console.level", "debug") + } + create("client") { mods { create(mod_id) { @@ -239,25 +216,13 @@ minecraft { val originalUsername = usernameStream.array.copyOfRange(0, usernameStream.length).toString(Charsets.UTF_8).trim() if (originalUsername.isNotEmpty()) { - var username = originalUsername - var speculatedUUID = UUID.nameUUIDFromBytes("OfflinePlayer:$username".toByteArray(Charsets.UTF_8)) - var counter = 1 - - while (speculatedUUID.hashCode() and 1 == 1) { - username = originalUsername + "_".repeat(counter) - speculatedUUID = UUID.nameUUIDFromBytes("OfflinePlayer:$username".toByteArray(Charsets.UTF_8)) - counter++ - } - - args("--username", username) + args("--username", originalUsername) } else { args("--username", "Dev_${System.getProperty("user.name")}") } } else { args("--username", "Dev_${System.getProperty("user.name")}") } - - args("-mixin.config=$mod_id.mixins.json") } create("server") { @@ -267,7 +232,7 @@ minecraft { } } - args("nogui", "-mixin.config=$mod_id.mixins.json") + args("nogui") } create("data") { @@ -278,8 +243,6 @@ minecraft { sources(sourceSets["main"], sourceSets["data"]) } } - - forceExit(false) } } } @@ -287,28 +250,7 @@ minecraft { mixin { add(sourceSets.main.get(), "$mod_id.refmap.json") config("$mod_id.mixins.json") -} - -minecraft.runs.all { - workingDirectory = project.file("run").absolutePath - - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property("forge.logging.markers", "REGISTRIES") - - // Log4j console level - property("forge.logging.console.level", "debug") - - lazyToken("minecraft_classpath") { - configurations["library"] - .copyRecursive() - .resolve() - .map { it.absolutePath } - .toMutableList() - .also { it.addAll(configurations["klibrary"].copyRecursive().resolve().map { it.absolutePath }) } - .joinToString(File.pathSeparator) - } + config("$mod_id.ad_astra.mixins.json") } repositories { @@ -321,11 +263,11 @@ repositories { url = uri("https://maven.dbotthepony.ru") content { - includeGroup("top.theillusivec4.curios") includeGroup("yalter.mousetweaks") includeGroup("mekanism") includeGroup("lain.mods.cos") includeGroup("at.ridgo8.moreoverlays") + includeGroup("ru.dbotthepony") } } @@ -349,14 +291,22 @@ repositories { } maven { - name = "Progwml6 maven" - url = uri("https://dvs1.progwml6.com/files/maven/") + name = "Jared's Maven" + url = uri("https://maven.blamejared.com/") content { includeGroup("mezz.jei") } } + maven { + url = uri("https://maven.theillusivec4.top/") + + content { + includeGroup("top.theillusivec4.curios") + } + } + // mavenCentral() } @@ -377,12 +327,24 @@ fun org.gradle.jvm.tasks.Jar.attachManifest() { // Example configuration to allow publishing using the maven-publish plugin // This is the preferred method to reobfuscate your jar file tasks.jar.configure { - from(configurations["library"].map { if (it.isDirectory) it else zipTree(it) }) finalizedBy("reobfJar") attachManifest() archiveVersion.set(gitVersion.jarName) } +tasks.withType(ProcessResources::class.java) { + val replaceProperties = mapOf( + "mc_version" to mc_version, + "mod_id" to mod_id, + "mod_version" to gitVersion.modVersion + ) + inputs.properties(replaceProperties) + + filesMatching(arrayListOf("META-INF/mods.toml", "pack.mcmeta")) { + expand(replaceProperties) + } +} + tasks { create("sourceJar", org.gradle.jvm.tasks.Jar::class.java) { archiveClassifier.set("sources") @@ -391,7 +353,6 @@ tasks { create("deobfJar", org.gradle.jvm.tasks.Jar::class.java) { archiveClassifier.set("deobf") - from(configurations["library"].map { if (it.isDirectory) it else zipTree(it) }) from(sourceSets.main.get().output) attachManifest() } @@ -437,21 +398,3 @@ if (project.hasProperty("mavenUser") && project.hasProperty("mavenPassword") && } } } - -// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing -// publish.dependsOn("reobfJar") - -/* -publishing { - publications { - mavenJava(MavenPublication) { - artifact jar - } - } - repositories { - maven { - url "file://${project.projectDir}/mcmodsrepo" - } - } -} -*/ diff --git a/colomposer.js b/colomposer.js index b65a227b3..6a7a2bb2f 100644 --- a/colomposer.js +++ b/colomposer.js @@ -48,21 +48,24 @@ for (const colorA of colors) { const magick = child_process.spawn('magick', [ 'convert', - '-compose', 'Multiply', '-size', `${width}x${height}`, '(', `${root_main}${texA}.png`, `xc:rgb(${rgbA[0]}, ${rgbA[1]}, ${rgbA[2]})`, + '-compose', 'Multiply', '-composite', ')', '(', `${root_main}${texB}.png`, `xc:rgb(${rgbB[0]}, ${rgbB[1]}, ${rgbB[2]})`, + '-channel', 'rgb', + '-compose', 'Multiply', '-composite', ')', - + + '-channel', 'rgba', '-compose', 'Over', '-composite', diff --git a/gradle.properties b/gradle.properties index 5890f6fe4..d287adf01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,29 +6,31 @@ kotlin.stdlib.default.dependency=false org.gradle.vfs.watch=true mod_id=overdrive_that_matters -mod_version=1.0 +mod_version=1.4 use_commit_hash_in_version=true -mc_version=1.19.2 -forge_gradle_version=5.1.27 -forge_version=43.1.43 -mixingradle_version=0.7.32 +mc_version=1.19.3 +jei_mc_version=1.19.3 +curios_mc_version=1.19.3 + +forge_gradle_version=[6.0.14,6.2) +forge_version=44.1.23 +mixingradle_version=0.7.33 mixin_version=0.8.5 -jei_version=11.3.0.262 -jupiter_version=5.8.2 -mekanism_version=10.3.5.homebaked -curios_version=5.1.1.0 -cosmetic_armor_reworked_version=v1 -jade_id=4010505 -configured_id=4011355 -worldedit_fileid=3922622 -more_overlays_version=1.21.3-mc1.19 +jei_version=12.4.0.22 +jupiter_version=5.9.2 +curios_version=4440173 +cosmetic_armor_reworked_id=4439659 +ad_astra_id=4452072 +botarium_id=4416456 +resourceful_lib_id=4378849 +resourceful_config_id=4441381 +jade_id=4434045 +configured_id=4462894 -kotlin_for_forge_version=3.1.0 -kotlin_version=1.6.10 -kotlin_coroutines_version=1.6.0 -kotlin_serialization_version=1.3.2 +kotlin_for_forge_version=4.7.0 +kotlin_version=1.9.0 handle_deps=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2..943f0cbfa 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102e0..37aef8d3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa5..65dcd68d6 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions $var, ${var}, ${var:-default}, ${var+SET}, -# ${var#prefix}, ${var%suffix}, and $( cmd ); -# * compound commands having a testable exit status, especially case; -# * various built-in commands including command, set, and ulimit. +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..93e3f59f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle.kts b/settings.gradle.kts index 11d08643f..4293ab440 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,21 +2,21 @@ println("Running with Java ${System.getProperty("java.version")} on JVM: ${System.getProperty("java.vendor")} ${System.getProperty("java.vm.version")} (Architecture: ${System.getProperty("os.arch")})") pluginManagement { - val kotlin_version: String by settings - val forge_gradle_version: String by settings - val mixingradle_version: String by settings - - plugins { - kotlin("jvm") version(kotlin_version) - id("net.minecraftforge.gradle") version(forge_gradle_version) - id("org.spongepowered.mixin") version(mixingradle_version) + repositories { + gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention").version("0.5.0") +} + buildscript { repositories { // These repositories are only for Gradle plugins, put any other repositories in the repository block further below maven(url = "https://maven.minecraftforge.net") { + name = "Minecraft Forge" + content { includeGroup("net.minecraftforge.gradle") includeGroup("net.minecraftforge") @@ -24,6 +24,8 @@ buildscript { } maven(url = "https://repo.spongepowered.org/repository/maven-public/") { + name = "Spongepowered" + content { includeGroup("org.spongepowered") } @@ -40,5 +42,7 @@ buildscript { classpath(group = "net.minecraftforge.gradle", name = "ForgeGradle", version = forge_gradle_version) classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}") classpath("org.spongepowered:mixingradle:${mixingradle_version}") + + classpath(group = "org.gradle.toolchains", name = "foojay-resolver", version = "0.5.0") } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt index b58581093..04d2f80eb 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.mc.otm.datagen +import net.minecraft.core.RegistrySetBuilder +import net.minecraft.core.registries.Registries import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.DyeColor import net.minecraft.world.level.block.Block @@ -13,39 +15,36 @@ import net.minecraft.world.level.block.state.properties.DoorHingeSide import net.minecraft.world.level.block.state.properties.DoubleBlockHalf import net.minecraft.world.level.block.state.properties.Half import net.minecraftforge.client.model.generators.ModelFile +import net.minecraftforge.common.data.DatapackBuiltinEntriesProvider +import net.minecraftforge.common.data.ForgeAdvancementProvider import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.fml.common.Mod import net.minecraftforge.data.event.GatherDataEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidResearchDataProvider import ru.dbotthepony.mc.otm.block.* -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth +import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.datagen.blocks.BatteryBankProvider import ru.dbotthepony.mc.otm.datagen.blocks.MatterBankProvider import ru.dbotthepony.mc.otm.datagen.blocks.MatteryBlockStateProvider import ru.dbotthepony.mc.otm.datagen.items.MatteryItemModelProvider import ru.dbotthepony.mc.otm.datagen.lang.AddEnglishLanguage import ru.dbotthepony.mc.otm.datagen.models.MatteryBlockModelProvider -import ru.dbotthepony.mc.otm.datagen.recipes.MatteryRecipeProvider import ru.dbotthepony.mc.otm.registry.* import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.datagen.advancements.AdvancementProvider import ru.dbotthepony.mc.otm.datagen.advancements.addAdvancements import ru.dbotthepony.mc.otm.datagen.advancements.addAndroidAdvancements import ru.dbotthepony.mc.otm.datagen.advancements.addMachineAdvancements import ru.dbotthepony.mc.otm.datagen.blocks.addBlockStates import ru.dbotthepony.mc.otm.datagen.blocks.addComplexBlockStates import ru.dbotthepony.mc.otm.datagen.items.addItemModels +import ru.dbotthepony.mc.otm.datagen.lang.AddRussianLanguage import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.loot.* import ru.dbotthepony.mc.otm.datagen.loot.LootModifiers import ru.dbotthepony.mc.otm.datagen.models.addBlockModels -import ru.dbotthepony.mc.otm.datagen.recipes.addBlastingRecipes -import ru.dbotthepony.mc.otm.datagen.recipes.addCraftingTableRecipes -import ru.dbotthepony.mc.otm.datagen.recipes.addDecorativesRecipes -import ru.dbotthepony.mc.otm.datagen.recipes.addPlatePressRecipes -import ru.dbotthepony.mc.otm.datagen.recipes.addShapelessRecipes -import ru.dbotthepony.mc.otm.datagen.recipes.addOreSmeltingRecipes +import ru.dbotthepony.mc.otm.datagen.recipes.* import ru.dbotthepony.mc.otm.datagen.tags.TagsProvider import ru.dbotthepony.mc.otm.datagen.tags.addTags import ru.dbotthepony.mc.otm.matter.MatterDataProvider @@ -53,7 +52,7 @@ import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock import kotlin.properties.Delegates -fun modLocation(string: String) = ResourceLocation(DataGen.MOD_ID, string) +internal fun modLocation(string: String) = ResourceLocation(DataGen.MOD_ID, string) @Mod.EventBusSubscriber(modid = DataGen.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) object DataGen { @@ -217,6 +216,86 @@ object DataGen { } } + private fun MatteryBlockModelProvider.bars(name: String, parent: String, texture: ResourceLocation): ModelFile { + return withExistingParent(name, "block/$parent") + .texture("bars", texture) + .texture("edge", texture) + .texture("particle", texture) + .renderType("cutout_mipped") + } + + @Suppress("LocalVariableName") + fun bars(block: Block, texture: ResourceLocation) { + val name = block.registryName?.path ?: throw IllegalStateException("Invalid state of glass pane $block") + + val cap = "${name}_cap" + val cap_alt = "${name}_cap_alt" + val post = "${name}_post" + val post_ends = "${name}_post_ends" + val side = "${name}_side" + val side_alt = "${name}_side_alt" + + var mdl_cap by Delegates.notNull() + var mdl_cap_alt by Delegates.notNull() + var mdl_post by Delegates.notNull() + var mdl_post_ends by Delegates.notNull() + var mdl_side by Delegates.notNull() + var mdl_side_alt by Delegates.notNull() + + with(blockModelProvider) { + exec { + mdl_cap = bars(cap, "iron_bars_cap", texture) + mdl_cap_alt = bars(cap_alt, "iron_bars_cap_alt", texture) + mdl_post = bars(post, "iron_bars_post", texture) + mdl_post_ends = bars(post_ends, "iron_bars_post_ends", texture) + mdl_side = bars(side, "iron_bars_side", texture) + mdl_side_alt = bars(side_alt, "iron_bars_side_alt", texture) + } + } + + @Suppress("name_shadowing") + blockStateProvider.exec { + with(blockStateProvider.getMultipartBuilder(block)) { + part().modelFile(mdl_post_ends).addModel() + + part().modelFile(mdl_post).addModel() + .condition(IronBarsBlock.EAST, false) + .condition(IronBarsBlock.WEST, false) + .condition(IronBarsBlock.NORTH, false) + .condition(IronBarsBlock.SOUTH, false) + + part().modelFile(mdl_cap).addModel() + .condition(IronBarsBlock.EAST, false) + .condition(IronBarsBlock.WEST, false) + .condition(IronBarsBlock.NORTH, true) + .condition(IronBarsBlock.SOUTH, false) + + part().modelFile(mdl_cap).rotationY(90).addModel() + .condition(IronBarsBlock.EAST, true) + .condition(IronBarsBlock.WEST, false) + .condition(IronBarsBlock.NORTH, false) + .condition(IronBarsBlock.SOUTH, false) + + part().modelFile(mdl_cap_alt).addModel() + .condition(IronBarsBlock.EAST, false) + .condition(IronBarsBlock.WEST, false) + .condition(IronBarsBlock.NORTH, false) + .condition(IronBarsBlock.SOUTH, true) + + part().modelFile(mdl_cap_alt).rotationY(90).addModel() + .condition(IronBarsBlock.EAST, false) + .condition(IronBarsBlock.WEST, true) + .condition(IronBarsBlock.NORTH, false) + .condition(IronBarsBlock.SOUTH, false) + + part().modelFile(mdl_side).addModel().condition(IronBarsBlock.NORTH, true) + part().modelFile(mdl_side).rotationY(90).addModel().condition(IronBarsBlock.EAST, true) + part().modelFile(mdl_side_alt).addModel().condition(IronBarsBlock.SOUTH, true) + part().modelFile(mdl_side_alt).rotationY(90).addModel().condition(IronBarsBlock.WEST, true) + } + } + } + fun door(block: Block, textureTop: ResourceLocation, textureBottom: ResourceLocation) { var doorBottomLeft by Delegates.notNull() var doorBottomLeftOpen by Delegates.notNull() @@ -271,7 +350,7 @@ object DataGen { } } ).rotationY( - facing.toYRotBlockstate() - 90 + + facing.yRotationBlockstateNorth() - 90 + (if (open) when (hinge) { DoorHingeSide.LEFT -> 90 DoorHingeSide.RIGHT -> -90 @@ -321,7 +400,7 @@ object DataGen { Half.BOTTOM -> trapdoorBottom } ).rotationY( - facing.toYRotBlockstate() + facing.yRotationBlockstateNorth() ).addModel() .condition(TrapDoorBlock.FACING, facing) .condition(TrapDoorBlock.OPEN, open) @@ -406,7 +485,7 @@ object DataGen { val lootModifier = LootModifiers(event.generator) val languageProvider = MatteryLanguageProvider(event.generator) val matterData = MatterDataProvider(event) - val researchProvider = AndroidResearchDataProvider(event.generator).also { it.exec { addResearchData(it, languageProvider) } } + val researchProvider = AndroidResearchDataProvider(event).also { it.exec { addResearchData(it, languageProvider) } } this.blockModelProvider = blockModelProvider this.blockStateProvider = blockStateProvider @@ -419,7 +498,12 @@ object DataGen { this.matterData = matterData val tagsProvider = TagsProvider(event) - val advancementProvider = AdvancementProvider(event) + val advancementProvider = object : ForgeAdvancementProvider(event.generator.packOutput, event.lookupProvider, event.existingFileHelper, listOf( + AdvancementGenerator { registries, saver, existingFileHelper -> + addAdvancements(saver, languageProvider) + addAndroidAdvancements(saver, languageProvider) + } + )) {} addTags(tagsProvider) @@ -436,14 +520,21 @@ object DataGen { event.generator.addProvider(event.includeServer(), advancementProvider) event.generator.addProvider(event.includeServer(), matterData) + val registrySetBuilder = RegistrySetBuilder() + .add(Registries.CONFIGURED_FEATURE, ::registerConfiguredFeatures) + .add(Registries.PLACED_FEATURE, ::registerPlacedFeatures) + + event.generator.addProvider(event.includeServer(), DatapackBuiltinEntriesProvider(event.generator.packOutput, event.lookupProvider, registrySetBuilder, setOf(MOD_ID))) + AddEnglishLanguage(languageProvider) + AddRussianLanguage(languageProvider) for ((color, door) in MBlocks.TRITANIUM_DOOR) door(door, modLocation("block/decorative/tritanium_door_top${color?.name?.lowercase()?.let { "_$it" } ?: ""}"), modLocation("block/decorative/tritanium_door_bottom${color?.name?.lowercase()?.let { "_$it" } ?: ""}")) trapdoor(MBlocks.TRITANIUM_TRAPDOOR[null]!!, modLocation("block/decorative/tritanium_trapdoor")) - for (color in DyeColor.values()) + for (color in DyeColor.entries) trapdoor(MBlocks.TRITANIUM_TRAPDOOR[color]!!, modLocation("block/decorative/tritanium_trapdoor_${color.name.lowercase()}")) addBlockModels(blockModelProvider) @@ -460,17 +551,15 @@ object DataGen { addCraftingTableRecipes(consumer) addBlastingRecipes(consumer) addDecorativesRecipes(recipeProvider, consumer) + addMachineUpgradeRecipes(consumer) addShapelessRecipes(consumer) addOreSmeltingRecipes(consumer) - } - - advancementProvider.exec { it, files -> - addAdvancements(it, files, languageProvider) - addAndroidAdvancements(it, files, languageProvider) - addMachineAdvancements(it, files, languageProvider) + addPainterRecipes(consumer) + addMatterEntanglerRecipes(consumer) } addPlatePressRecipes(recipeProvider) + addMicrowaveRecipes(recipeProvider) lootModifier.lambda { addLootModifiers(it) @@ -479,5 +568,7 @@ object DataGen { languageProvider.registerProviders() addMatterData(matterData) + + tagsProvider.register() } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DecorativeData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DecorativeData.kt index 83e31826e..4a13dd03c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DecorativeData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DecorativeData.kt @@ -1,21 +1,21 @@ package ru.dbotthepony.mc.otm.datagen -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.level.block.PressurePlateBlock +import net.minecraft.world.level.block.AnvilBlock import net.minecraft.world.level.block.SlabBlock import net.minecraft.world.level.block.StairBlock import net.minecraft.world.level.block.WallBlock import net.minecraft.world.level.block.state.properties.BlockStateProperties +import net.minecraftforge.client.model.generators.BlockModelBuilder import net.minecraftforge.client.model.generators.ConfiguredModel import net.minecraftforge.client.model.generators.ModelFile -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.xRotationBlockstateNorth +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.blocks.MatteryBlockStateProvider import ru.dbotthepony.mc.otm.datagen.items.MatteryItemModelProvider import ru.dbotthepony.mc.otm.datagen.models.MatteryBlockModelProvider -import ru.dbotthepony.mc.otm.datagen.models.MatteryModelBuilder import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MNames @@ -37,6 +37,17 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr DataGen.decoratives(MRegistry.TRITANIUM_BLOCK) + for (anvil in MBlocks.TRITANIUM_ANVIL) { + blockStateProvider.exec { + blockStateProvider.getVariantBuilder(anvil).forAllStates { + ConfiguredModel.builder() + .modelFile(blockStateProvider.models().getExistingFile(modLocation("block/${anvil.registryName!!.path}"))) + .rotationY(it[AnvilBlock.FACING].yRotationBlockstateNorth()) + .build() + } + } + } + for ((color, block) in MRegistry.TRITANIUM_STAIRS.allBlocks) { DataGen.decorativeStairs(block as StairBlock, MRegistry.TRITANIUM_BLOCK.allBlocks[color]!!.registryName!!.path, MRegistry.TRITANIUM_BLOCK.allBlocks[color]!!.registryName!!.path) } @@ -129,6 +140,8 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr blockModelProvider.decorativeGlassAll(MRegistry.INDUSTRIAL_GLASS.allBlocks.values) blockStateProvider.simpleBlockM(MRegistry.INDUSTRIAL_GLASS.allBlocks.values) + blockStateProvider.simpleBlockM(MBlocks.FLUID_TANK) + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_BLOCK.blocksWithColor) { DataGen.decorativeColumn( block, @@ -181,17 +194,17 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr DataGen.decorativeCubeAll(MBlocks.DANGER_STRIPE_BLOCK) DataGen.decorativeColumn(MBlocks.METAL_BEAM, "metal_beam_side", "metal_beam_top") - var labLampOn: MatteryModelBuilder? = null - var labLampOff: MatteryModelBuilder? = null + var labLampOn: BlockModelBuilder? = null + var labLampOff: BlockModelBuilder? = null blockModelProvider.exec { - val top = ResourceLocation(DataGen.MOD_ID, "block/decorative/metal_beam_top") + val top = modLocation("block/decorative/metal_beam_top") labLampOn = it.cube( MBlocks.LABORATORY_LAMP.registryName!!.path, top, top, - ResourceLocation(DataGen.MOD_ID, "block/decorative/laboratory_lamp_front"), + modLocation("block/decorative/laboratory_lamp_front"), top, top, top, @@ -201,7 +214,7 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr MBlocks.LABORATORY_LAMP.registryName!!.path + "_unlit", top, top, - ResourceLocation(DataGen.MOD_ID, "block/decorative/laboratory_lamp_front_off"), + modLocation("block/decorative/laboratory_lamp_front_off"), top, top, top, @@ -217,28 +230,28 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr blockStateProvider.getVariantBuilder(MBlocks.LABORATORY_LAMP).forAllStates { return@forAllStates ConfiguredModel.builder() .modelFile(if (it[BlockStateProperties.LIT]) labLampOn!! else labLampOff!!) - .rotationX(it[RotatableMatteryBlock.FACING_FULL].toXRotBlockstate()) - .rotationY(it[RotatableMatteryBlock.FACING_FULL].toYRotBlockstate()) + .rotationX(it[BlockRotationFreedom.DIRECTIONAL.property].front.xRotationBlockstateNorth()) + .rotationY(it[BlockRotationFreedom.DIRECTIONAL.property].front.yRotationBlockstateNorth()) .build() } blockStateProvider.getVariantBuilder(MBlocks.LABORATORY_LAMP_INVERTED).forAllStates { return@forAllStates ConfiguredModel.builder() .modelFile(if (it[BlockStateProperties.LIT]) labLampOn!! else labLampOff!!) - .rotationX(it[RotatableMatteryBlock.FACING_FULL].toXRotBlockstate()) - .rotationY(it[RotatableMatteryBlock.FACING_FULL].toYRotBlockstate()) + .rotationX(it[BlockRotationFreedom.DIRECTIONAL.property].front.xRotationBlockstateNorth()) + .rotationY(it[BlockRotationFreedom.DIRECTIONAL.property].front.yRotationBlockstateNorth()) .build() } } blockModelProvider.exec { for (crate in MRegistry.CARGO_CRATES.blocks.values) { - it.withExistingParent("${crate.registryName!!.path}_closed", ResourceLocation(OverdriveThatMatters.MOD_ID, "${MNames.CARGO_CRATE}_closed")) - .texture("texture", "block/cargo_crates/${crate.registryName!!.path}") + it.withExistingParent("${crate.registryName!!.path}_closed", modLocation("${MNames.CARGO_CRATE}_closed")) + .texture("body", "block/cargo_crates/${crate.registryName!!.path}") .texture("particle", "block/cargo_crates/${crate.registryName!!.path}") - it.withExistingParent("${crate.registryName!!.path}_open", ResourceLocation(OverdriveThatMatters.MOD_ID, "${MNames.CARGO_CRATE}_open")) - .texture("texture", "block/cargo_crates/${crate.registryName!!.path}") + it.withExistingParent("${crate.registryName!!.path}_open", modLocation("${MNames.CARGO_CRATE}_open")) + .texture("body", "block/cargo_crates/${crate.registryName!!.path}") .texture("particle", "block/cargo_crates/${crate.registryName!!.path}") } } @@ -258,8 +271,13 @@ fun addDecorativeData(blockStateProvider: MatteryBlockStateProvider, itemModelPr for ((color, glass) in MRegistry.INDUSTRIAL_GLASS_PANE.allBlocks) { val name = MRegistry.INDUSTRIAL_GLASS.allBlocks[color]!!.registryName!!.path - val textureSide = ResourceLocation(DataGen.MOD_ID, "block/decorative/$name") - val textureRailing = ResourceLocation(DataGen.MOD_ID, "block/decorative/industrial_glass_frame") + val textureSide = modLocation("block/decorative/$name") + val textureRailing = modLocation("block/decorative/industrial_glass_frame") DataGen.pane(glass, textureSide, textureRailing) } + + DataGen.bars(MBlocks.TRITANIUM_BARS, modLocation("block/decorative/tritanium_bars")) + + blockStateProvider.block(MBlocks.ENGINE) + itemModelProvider.block(MItems.ENGINE) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/Ext.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/Ext.kt deleted file mode 100644 index 38dc866bb..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/Ext.kt +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import net.minecraft.core.Direction -import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.block.state.properties.Property -import kotlin.math.roundToInt - -fun Direction.toYRotBlockstate(): Int { - return when (this) { - Direction.DOWN -> 0 - Direction.UP -> 0 - Direction.NORTH -> 0 - Direction.SOUTH -> 180 - Direction.WEST -> -90 - Direction.EAST -> 90 - } -} - -fun Direction.toYRotBlockstateInv(): Int { - return when (this) { - Direction.DOWN -> 0 - Direction.UP -> 0 - Direction.NORTH -> 180 - Direction.SOUTH -> 0 - Direction.WEST -> 90 - Direction.EAST -> -90 - } -} - -fun Direction.toXRotBlockstate(): Int { - return when (this) { - Direction.DOWN -> 90 - Direction.UP -> -90 - Direction.NORTH -> 0 - Direction.SOUTH -> 0 - Direction.WEST -> 0 - Direction.EAST -> 0 - } -} - -fun Direction.toXRotBlockstateInv(): Int { - return when (this) { - Direction.DOWN -> -90 - Direction.UP -> 90 - Direction.NORTH -> 0 - Direction.SOUTH -> 0 - Direction.WEST -> 0 - Direction.EAST -> 0 - } -} - -fun > BlockState.getValueNullable(prop: Property): T? { - if (hasProperty(prop)) { - return getValue(prop) - } - - return null -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/MatterData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/MatterData.kt index f02e26d98..a46c6be0c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/MatterData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/MatterData.kt @@ -4,7 +4,8 @@ import net.minecraft.tags.ItemTags import net.minecraft.world.item.Item import net.minecraft.world.item.Items import net.minecraftforge.common.Tags -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.matter.ComputeAction import ru.dbotthepony.mc.otm.matter.MatterDataProvider import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems @@ -16,6 +17,29 @@ fun addMatterData(provider: MatterDataProvider) { provider.inherit(Items.CHIPPED_ANVIL, Items.ANVIL, 0.75) provider.inherit(Items.DAMAGED_ANVIL, Items.ANVIL, 0.5) + for (i in 1 until MItems.TRITANIUM_ANVIL.size) { + provider.inherit(MItems.TRITANIUM_ANVIL[i], MItems.TRITANIUM_ANVIL[i - 1], 0.85) + } + + provider.inherit(Items.WATER_BUCKET, Items.BUCKET) { + plus(Decimal(1), 20.0) + } + + provider.inherit(Items.LAVA_BUCKET, Items.MAGMA_BLOCK) { + plus(Decimal(0), 666.0) + plus(Items.BUCKET) + } + + provider.inherit(Items.POWDER_SNOW_BUCKET, Items.SNOW_BLOCK) { + multiply(Decimal(0.75), 1.0) + plus(Decimal(0), 200.0) + plus(Items.BUCKET) + } + + provider.inherit(Items.MILK_BUCKET, Items.BUCKET) { + plus(Decimal(7), 480.0) + } + val copper = listOf, Item>>( listOf( Items.EXPOSED_COPPER, @@ -101,7 +125,7 @@ fun addMatterData(provider: MatterDataProvider) { relative(Items.NETHER_STAR, 2000, 1200) - relative(MItems.ZPM_BATTERY, 60000, 7200) + relative(MItems.ZPM_BATTERY, 80000000, 720000) relative(MItems.PILL_HEAL, 14, 8) relative(MItems.PILL_ANDROID, 20, 20) @@ -205,6 +229,7 @@ fun addMatterData(provider: MatterDataProvider) { relative(Items.HONEY_BLOCK, 6, 1.75) relative(Tags.Items.GEMS_PRISMARINE, 6, 2.75) + relative(Items.PRISMARINE_SHARD, 6, 2.75) relative(Tags.Items.RODS_BLAZE, 1.4, 2) relative(Items.GHAST_TEAR, 2.5, 2.5) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/OreGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/OreGen.kt new file mode 100644 index 000000000..2e7de16ef --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/OreGen.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.datagen + +import net.minecraft.core.RegistrySetBuilder +import net.minecraft.core.registries.Registries +import net.minecraft.data.worldgen.BootstapContext +import net.minecraft.resources.ResourceKey +import net.minecraft.tags.BlockTags +import net.minecraft.world.level.levelgen.VerticalAnchor +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature +import net.minecraft.world.level.levelgen.feature.Feature +import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration +import net.minecraft.world.level.levelgen.placement.CountPlacement +import net.minecraft.world.level.levelgen.placement.HeightRangePlacement +import net.minecraft.world.level.levelgen.placement.InSquarePlacement +import net.minecraft.world.level.levelgen.placement.PlacedFeature +import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest +import net.minecraftforge.common.data.DatapackBuiltinEntriesProvider +import net.minecraftforge.data.event.GatherDataEvent +import ru.dbotthepony.mc.otm.registry.MBlocks + +private val oreKey by lazy { ResourceKey.create(Registries.CONFIGURED_FEATURE, modLocation("tritanium_ore")) } + +fun registerConfiguredFeatures(context: BootstapContext>) { + val stone = TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES) + val deepslate = TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES) + + val target = listOf( + OreConfiguration.target(stone, MBlocks.TRITANIUM_ORE.defaultBlockState()), + OreConfiguration.target(deepslate, MBlocks.DEEPSLATE_TRITANIUM_ORE.defaultBlockState()), + ) + + context.register(oreKey, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 9))) +} + +fun registerPlacedFeatures(context: BootstapContext) { + fun location(name: String) = ResourceKey.create(Registries.PLACED_FEATURE, modLocation(name)) + + val configured = context.lookup(Registries.CONFIGURED_FEATURE) + val ore = configured.getOrThrow(oreKey) + + context.register(location("normal_tritanium"), PlacedFeature( + ore, + listOf( + CountPlacement.of(8), + InSquarePlacement.spread(), + HeightRangePlacement.triangle(VerticalAnchor.absolute(0), VerticalAnchor.absolute(50)) + ) + )) + + context.register(location("deep_tritanium"), PlacedFeature( + ore, + listOf( + CountPlacement.of(10), + InSquarePlacement.spread(), + HeightRangePlacement.uniform(VerticalAnchor.aboveBottom(8), VerticalAnchor.absolute(0)) + ) + )) +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt index 0623cfc76..c2e1a2448 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/ResearchData.kt @@ -4,13 +4,9 @@ import net.minecraft.tags.ItemTags import net.minecraft.world.item.Items import net.minecraftforge.common.Tags import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.AndroidResearchDescriptions +import ru.dbotthepony.mc.otm.android.AndroidResearchResults import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature -import ru.dbotthepony.mc.otm.android.feature.FallDampenersFeature -import ru.dbotthepony.mc.otm.android.feature.ItemMagnetFeature -import ru.dbotthepony.mc.otm.android.feature.JumpBoostFeature -import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature -import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent @@ -47,6 +43,45 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang serializer.accept(IMPROVED_LIMBS) + val SWIM_BOOSTERS = AndroidResearchType.Builder(modLocation(MNames.SWIM_BOOSTERS)) + .withExperience(26) + .withDescription(AndroidResearchDescriptions.SWIM_BOOSTERS.Instance(0)) + .addPrerequisite(IMPROVED_LIMBS) + .addPrerequisite(AIR_BAGS) + .addFeatureResult(AndroidFeatures.SWIM_BOOSTERS) + .withIcon(ResearchIcons.ICON_LIMB_OVERCLOCKING) + .addItem(MItemTags.TRITANIUM_PLATES, 2) + .addItem(MItemTags.COPPER_WIRES, 2) + .addItem(MItems.ELECTROMOTOR, 2) + .build() + + serializer.accept(SWIM_BOOSTERS) + + val SWIM_BOOSTERS_2 = AndroidResearchType.Builder(modLocation(MNames.SWIM_BOOSTERS + "_2")) + .withExperience(30) + .withDescription(AndroidResearchDescriptions.SWIM_BOOSTERS.Instance(1)) + .addPrerequisite(SWIM_BOOSTERS) + .addFeatureLevel(AndroidFeatures.SWIM_BOOSTERS) + .withIcon(ResearchIcons.ICON_LIMB_OVERCLOCKING) + .addItem(MItemTags.GOLD_WIRES, 8) + .addItem(MItems.ELECTROMOTOR, 2) + .build() + + serializer.accept(SWIM_BOOSTERS_2) + + val SWIM_BOOSTERS_3 = AndroidResearchType.Builder(modLocation(MNames.SWIM_BOOSTERS + "_3")) + .withExperience(30) + .withDescription(AndroidResearchDescriptions.SWIM_BOOSTERS.Instance(2)) + .addPrerequisite(SWIM_BOOSTERS_2) + .addFeatureLevel(AndroidFeatures.SWIM_BOOSTERS) + .withIcon(ResearchIcons.ICON_LIMB_OVERCLOCKING) + .addItem(MItemTags.CARBON_PLATES, 8) + .addItem(MItemTags.TRITANIUM_NUGGETS, 4) + .addItem(MItemTags.COPPER_WIRES, 2) + .build() + + serializer.accept(SWIM_BOOSTERS_3) + val STEP_ASSIST = AndroidResearchType.Builder(modLocation(MNames.STEP_ASSIST)) .withExperience(24) .addFeatureResult(AndroidFeatures.STEP_ASSIST) @@ -103,7 +138,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang .addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS)) .addFeatureResult(OverdriveThatMatters.loc(MNames.NANOBOTS_ARMOR)) .withIcon(ResearchIcons.ICON_ARMOR) - .addBlocker(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_1), rigid = true) + .addBlocker(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_1), optional = true) .addItem(MItemTags.TRITANIUM_PLATES, 4) .addItem(MItemTags.COPPER_WIRES, 8) .build() @@ -128,15 +163,18 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang TranslatableComponent( "android_research.overdrive_that_matters.limb_overclocking.description", (i + 1) * 8, - (i + 1) * 6 + (i + 1) * 6, + (i + 1) * 20 ) ) .addItem(MItemTags.COPPER_WIRES, 4 + i * 2) - .addFeatureResult(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING), i) if (i > 0) { - research.addPrerequisite(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING_LIST[i - 1]), rigid = true) + research.addFeatureLevel(AndroidFeatures.LIMB_OVERCLOCKING) + research.addPrerequisite(OverdriveThatMatters.loc(MNames.LIMB_OVERCLOCKING_LIST[i - 1]), optional = true) research.addItem(MItemTags.GOLD_WIRES, i * 2) + } else { + research.addFeatureResult(AndroidFeatures.LIMB_OVERCLOCKING) } research.build() @@ -152,21 +190,26 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang .withDescription( TranslatableComponent( "android_research.overdrive_that_matters.attack_boost.description", - (i + 1) * 6 + (i + 1) * 15 ) ) - .addFeatureResult(AndroidFeatures.ATTACK_BOOST, i) + .addItem(MItems.ELECTROMAGNET, 2 + i) + .addItem(MItemTags.GOLD_WIRES, 4 * i + 8) + .addItem(MItemTags.PISTONS) .addBlocker(NANOBOTS_ARMOR) if (i > 0) { - research.addPrerequisite(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_LIST[i - 1]), rigid = true) + research.addFeatureLevel(AndroidFeatures.ATTACK_BOOST) + research.addPrerequisite(OverdriveThatMatters.loc(MNames.ATTACK_BOOST_LIST[i - 1]), optional = true) + } else { + research.addFeatureResult(AndroidFeatures.ATTACK_BOOST) } research.build() }) regenList.add(run { - val regeneration = AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_REGENERATION_LIST[i])) + val research = AndroidResearchType.Builder(modLocation(MNames.NANOBOTS_REGENERATION_LIST[i])) .withExperience(20 + i * 6) .withIconText(TextComponent((i + 1).toString())) .withIcon(ResearchIcons.ICON_NANOBOTS) @@ -179,15 +222,16 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang .addItem(MItems.MATTER_CAPACITOR_PARTS, 1) .addItem(Items.SUGAR, 2 + i * 2) .addItem(Tags.Items.DUSTS_REDSTONE, 2 + i * 2) - .addFeatureResult(AndroidFeatures.NANOBOTS_REGENERATION, i) if (i > 0) { - regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS_REGENERATION_LIST[i - 1]), rigid = true) + research.addFeatureLevel(AndroidFeatures.NANOBOTS_REGENERATION) + research.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS_REGENERATION_LIST[i - 1]), optional = true) } else { - regeneration.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS), rigid = true) + research.addPrerequisite(OverdriveThatMatters.loc(MNames.NANOBOTS), optional = true) + research.addFeatureResult(AndroidFeatures.NANOBOTS_REGENERATION) } - regeneration.build() + research.build() }) } @@ -216,10 +260,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang .addItem(MItemTags.TRITANIUM_PLATES, 2 + i * 2) .addItem(Items.SUGAR, 1 + i) .addItem(MItems.ELECTROMAGNET) - .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0, - transformersUp = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_UP.bind(level)), - transformersDown = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_DOWN.bind(level)), - ) + .addResult(AndroidResearchResults.NANOBOTS_ARMOR_STRENGTH) .build() }) @@ -243,10 +284,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang ) ) .addItem(Tags.Items.DUSTS_REDSTONE, 4 + i * 4) - .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0, - transformersUp = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_UP.bind(level)), - transformersDown = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_DOWN.bind(level)), - ) + .addResult(AndroidResearchResults.NANOBOTS_ARMOR_SPEED) .build() }) } @@ -261,7 +299,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.SHOCKWAVE)) .withExperience(40) .withDescription(0 .. 1) - .appendDescription(ShockwaveFeature.POWER_COST_DESCRIPTION) + .withDescription(AndroidResearchDescriptions.SHOCKWAVE) .withIcon(ResearchIcons.ICON_SHOCKWAVE) .addFeatureResult(AndroidFeatures.SHOCKWAVE) .addPrerequisite(attackBoostList[2]) @@ -276,7 +314,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.ITEM_MAGNET)) .withExperience(28) .withDescription(0 .. 1) - .appendDescription(ItemMagnetFeature.POWER_COST_DESCRIPTION) + .withDescription(AndroidResearchDescriptions.ITEM_MAGNET) .withIcon(ResearchIcons.ICON_ITEM_MAGNET) .addFeatureResult(AndroidFeatures.ITEM_MAGNET) .addPrerequisite(STEP_ASSIST) @@ -292,9 +330,9 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.FALL_DAMPENERS + "_1")) .withExperience(25) .withDescription() - .appendDescription(FallDampenersFeature.DESCRIPTION.bind(1)) + .withDescription(AndroidResearchDescriptions.FALL_DAMPENERS.Instance(1)) .withIcon(ResearchIcons.ICON_FEATHER_FALLING) - .addFeatureResult(AndroidFeatures.FALL_DAMPENERS, 0) + .addFeatureResult(AndroidFeatures.FALL_DAMPENERS) .addPrerequisite(STEP_ASSIST) .addItem(MItems.ELECTROMAGNET, 2) .addItem(ItemTags.WOOL, 2) @@ -305,9 +343,9 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.FALL_DAMPENERS + "_2")) .withExperience(30) .withDescription() - .appendDescription(FallDampenersFeature.DESCRIPTION.bind(2)) + .withDescription(AndroidResearchDescriptions.FALL_DAMPENERS.Instance(2)) .withIcon(ResearchIcons.ICON_FEATHER_FALLING) - .addFeatureResult(AndroidFeatures.FALL_DAMPENERS, 1) + .addFeatureLevel(AndroidFeatures.FALL_DAMPENERS) .addPrerequisite(FALL_DAMPENERS_1) .addItem(MItemTags.GOLD_PLATES, 2) .addItem(MItemTags.COPPER_WIRES, 4) @@ -319,9 +357,9 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.FALL_DAMPENERS + "_3")) .withExperience(35) .withDescription(0 .. 1) - .appendDescription(FallDampenersFeature.DESCRIPTION.bind(3)) + .withDescription(AndroidResearchDescriptions.FALL_DAMPENERS.Instance(3)) .withIcon(ResearchIcons.ICON_FEATHER_FALLING) - .addFeatureResult(AndroidFeatures.FALL_DAMPENERS, 2) + .addFeatureLevel(AndroidFeatures.FALL_DAMPENERS) .addPrerequisite(FALL_DAMPENERS_2) .addItem(MItemTags.ADVANCED_CIRCUIT, 2) .addItem(Tags.Items.GEMS_DIAMOND, 4) @@ -337,7 +375,7 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.ENDER_TELEPORTER)) .withExperience(35) .withDescription() - .appendDescription(EnderTeleporterFeature.POWER_COST_DESCRIPTION) + .withDescription(AndroidResearchDescriptions.ENDER_TELEPORTER) .withIcon(ResearchIcons.ICON_ENDER_TELEPORT) .addFeatureResult(AndroidFeatures.ENDER_TELEPORTER) .addPrerequisite(FALL_DAMPENERS_1) @@ -350,27 +388,13 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang serializer.accept(ENDER_TELEPORTER) - val PHANTOM_ATTRACTOR = - AndroidResearchType.Builder(modLocation(MNames.PHANTOM_ATTRACTOR)) - .withExperience(20) - .withDescription() - .withIcon(ResearchIcons.ICON_PHANTOM_ATTRACTOR) - .addFeatureResult(AndroidFeatures.PHANTOM_ATTRACTOR) - .addPrerequisite(NANOBOTS) - .addItem(MItems.PHANTOM_ATTRACTOR) - .addItem(MItemTags.COPPER_WIRES, 2) - .addItem(MItemTags.TRITANIUM_PLATES, 2) - .build() - - serializer.accept(PHANTOM_ATTRACTOR) - val JUMP_BOOST_1 = AndroidResearchType.Builder(modLocation(MNames.JUMP_BOOST + "_1")) .withExperience(27) .withDescription(0 .. 1) - .appendDescription(JumpBoostFeature.POWER_COST_DESCRIPTION) + .withDescription(AndroidResearchDescriptions.JUMP_BOOST) .withIcon(ResearchIcons.ICON_JUMP_BOOST) - .addFeatureResult(AndroidFeatures.JUMP_BOOST, 0) + .addFeatureResult(AndroidFeatures.JUMP_BOOST) .addItem(MItemTags.PISTONS, 2) .addItem(MItemTags.GOLD_WIRES, 4) .addItem(MItems.ELECTROMAGNET, 2) @@ -382,9 +406,9 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang AndroidResearchType.Builder(modLocation(MNames.JUMP_BOOST + "_2")) .withExperience(34) .withDescription() - .appendDescription(JumpBoostFeature.POWER_COST_DESCRIPTION) + .withDescription(AndroidResearchDescriptions.JUMP_BOOST) .withIcon(ResearchIcons.ICON_JUMP_BOOST) - .addFeatureResult(AndroidFeatures.JUMP_BOOST, 1) + .addFeatureLevel(AndroidFeatures.JUMP_BOOST) .addItem(MItems.ELECTRIC_PARTS, 4) .addItem(MItems.ELECTROMAGNET, 4) .addPrerequisite(JUMP_BOOST_1) @@ -394,74 +418,179 @@ fun addResearchData(serializer: Consumer, lang: MatteryLang serializer.accept(JUMP_BOOST_1) serializer.accept(JUMP_BOOST_2) - with(lang.english) { - misc("fall_dampeners.description", "Reduces fall damage by %s%%") + with(lang) { + misc("fall_dampeners.description", "Reduces fall damage by %s%% and increases fall damage flat resist by %s half a hearts") { + russian("Уменьшает урон от падения на %s%% и повышает сопротивление урону от падения на %s полусердец") + } - add(limbList[0], "Limb Overclocking %s") - add(limbList[0], "description", "Boosts unit's mobility by %s%% and attack speed by %s%%") + add(limbList[0], "Limb Overclocking %s") { + russian("Разгон конечностей %s") + } - add(AIR_BAGS, "Air Bags") - add(NANOBOTS, "Nanobots") - add(AIR_BAGS, "description", "Allows unit to swim in water") - add(NANOBOTS, "description", "Various useful nanobots for doing various tasks") + add(limbList[0], "description", "Boosts mobility by %s%%, attack speed by %s%% and brushing speed by %s%%") { + russian("Увеличивает мобильность на %s%%, скорость атак на %s%% и скорость чистки блоков на %s%%") + } - add(regenList[0], "Regeneration %s") - add(regenList[0], "description", "Nanobots get ability to repair unit's internal systems on the move") - add(regenList[0], "description_improve", "Improves regeneration speed") + add(AIR_BAGS, "Air Bags") { + russian("Воздушные мешки") + } + add(NANOBOTS, "Nanobots") { + russian("Наноботы") + } + add(AIR_BAGS, "description", "Allows to swim in water") { + russian("Позволяет плавать в воде") + } + add(NANOBOTS, "description", "Various useful nanobots for doing various tasks") { + russian("Различные наноботы для различных задач") + } - add(NANOBOTS_ARMOR, "Nanobots Armor") - add(NANOBOTS_ARMOR, "description", "Allows nanobots to align themselves in cell shape, reducing incoming damage by a %% by absorbing impacts") + add(regenList[0], "Regeneration %s") { + russian("Регенерация %s") + } + add(regenList[0], "description", "Nanobots get ability to repair internal Android' systems on the move") { + russian("Наноботы получают возможность чинить внутренние системы Андроида на ходу") + } + add(regenList[0], "description_improve", "Improves health regeneration speed") { + russian("Улучшает скорость регенерации здоровья") + } - add(armorSpeedList[0], "Nanobots Armor Build Speed %s") - add(armorSpeedList[0], "description", "Reduces time required for nanobots to form protection layer") + add(NANOBOTS_ARMOR, "Nanobots Armor") { + russian("Броня из наноботов") + } + add(NANOBOTS_ARMOR, "description", "Allows nanobots to align themselves in cell shape, reducing incoming damage by a %% by absorbing impacts") { + russian("Позволяет наноботам выстраиваться в клеточную структуру, уменьшая внешний урон на определённый проект путём поглощения ударов") + } - add(armorStrengthList[0], "Nanobots Armor Strength %s") - add(armorStrengthList[0], "description", "Increases impact absorption strength of nanobots") + add(armorSpeedList[0], "Nanobots Armor Build Speed %s") { + russian("Скорость аостроения слоя брони наноботов %s") + } + add(armorSpeedList[0], "description", "Reduces time required for nanobots to form protection layer") { + russian("Уменьшает время необходимое наноботам для формирования защитного слоя") + } - add(EXTENDED_REACH, "Extended Reach") - add(EXTENDED_REACH, "description", "Increases block interaction distance") + add(armorStrengthList[0], "Nanobots Armor Strength %s") { + russian("Сила слоя брони наноботов %s") + } + add(armorStrengthList[0], "description", "Increases impact absorption strength of nanobots") { + russian("Увеличивает поглощающею силу брони наноботов") + } - add(IMPROVED_LIMBS, "Improved Limbs") - add(IMPROVED_LIMBS, "description", "Allows limbs to be upgraded") + add(EXTENDED_REACH, "Extended Reach") { + russian("Удлинённые манипуляторы") + } + add(EXTENDED_REACH, "description", "Increases block interaction distance") { + russian("Увеличивает радиус взаимодействия с блоками") + } - add(STEP_ASSIST, "Step Assist") - add(STEP_ASSIST, "description", "Allows unit to step up whole blocks") + add(IMPROVED_LIMBS, "Improved Limbs") { + russian("Улучшенные конечности") + } + add(IMPROVED_LIMBS, "description", "Allows limbs to be upgraded") { + russian("Позволяет улучшать конечности") + } - add(ITEM_MAGNET, "Item Magnet") - add(ITEM_MAGNET, "description0", "Pulls nearby items to unit while active") - add(ITEM_MAGNET, "description1", "Drains energy for each item stack it pulls") + add(SWIM_BOOSTERS, "Swim Boosters") { + russian("Плавательные лопасти") + } + add(SWIM_BOOSTERS, "description", "Increases swimming speed by %s%%") { + russian("Ускоряет скорость плавания на %s%%") + } - add(FALL_DAMPENERS_1, "Fall Dampeners") - add(FALL_DAMPENERS_1, "description", "Installs basic equipment in unit's limbs to negate some fall damage") + add(SWIM_BOOSTERS_2, "Swim Boosters 2") { + russian("Плавательные лопасти 2") + } - add(FALL_DAMPENERS_2, "Fall Dampeners 2") - add(FALL_DAMPENERS_2, "description", "Installs micro displacing and dampening equipment in unit's limbs to negate great deal of fall damage") + add(SWIM_BOOSTERS_3, "Swim Boosters 3") { + russian("Плавательные лопасти 3") + } - add(FALL_DAMPENERS_3, "Fall Dampeners 3") - add(FALL_DAMPENERS_3, "description0", "Installs autonomous calculation matrices and hardening to crucial parts") - add(FALL_DAMPENERS_3, "description1", "of unit's limbs to land on to negate most of fall damage") + add(STEP_ASSIST, "Step Assist") { + russian("Помощь подъёма") + } + add(STEP_ASSIST, "description", "Allows unit to step up whole blocks") { + russian("Позволяет переступать полные блоки") + } - add(SHOCKWAVE, "Shockwave Pulsator") - add(SHOCKWAVE, "description0", "Releases a shockwave around unit, damaging everything in small radius, as unit quickly land on ground") - add(SHOCKWAVE, "description1", "It does not, however, help with ground impact damage!") + add(ITEM_MAGNET, "Item Magnet") { + russian("Предметный магнит") + } + add(ITEM_MAGNET, "description0", "Pulls nearby items while active") { + russian("Притягивает ближайшие предметы пока активен") + } + add(ITEM_MAGNET, "description1", "Drains energy for each item stack it pulls") { + russian("Потребляет энергию за каждую стопку которую притягивает магнит") + } - add(PHANTOM_ATTRACTOR, "Builtin Phantom Attractor") - add(PHANTOM_ATTRACTOR, "description", "Allows unit to attract phantoms while active under same conditions as non-androids") + add(FALL_DAMPENERS_1, "Fall Dampeners") { + russian("Поглотители инерции") + } + add(FALL_DAMPENERS_1, "description", "Installs basic equipment in limbs to negate some fall damage") { + russian("Обустраивает конечности примитивными деталями для небольшого смягчения падения") + } - add(JUMP_BOOST_1, "Jump Boost") - add(JUMP_BOOST_1, "description0", "Allows unit to perform higher jump") - add(JUMP_BOOST_1, "description1", "Can be activated by crouching and jumping at the same time") + add(FALL_DAMPENERS_2, "Fall Dampeners 2") { + russian("Поглотители инерции 2") + } + add(FALL_DAMPENERS_2, "description", "Installs micro displacing and dampening equipment in limbs to negate great deal of fall damage") { + russian("Оборудует конечности микро смещающимися и смягчающим оборудованием, которое поглощает значительный урон от падения") + } - add(JUMP_BOOST_2, "Jump Boost 2") - add(JUMP_BOOST_2, "description", "Allows unit to perform extra higher jump") + add(FALL_DAMPENERS_3, "Fall Dampeners 3") { + russian("Поглотители инерции 3") + } + add(FALL_DAMPENERS_3, "description0", "Installs autonomous fall damage avoidance calculation matrices and hardening to crucial parts") { + russian("Устанавливает автономные матрицы калькуляции избегания урона от падения, а так же усиливает защиту важных деталей") + } + add(FALL_DAMPENERS_3, "description1", "of limbs to land on to negate most of fall damage") { + russian("от урона от падения для почти полного поглощения урона от падения") + } - add(ENDER_TELEPORTER, "Ender Teleporter") - add(ENDER_TELEPORTER, "description", "Allows unit to perform instant, short distance teleports without damage to internal systems") + add(SHOCKWAVE, "Shockwave Pulsator") { + russian("Генератор ударных волн") + } + add(SHOCKWAVE, "description0", "Releases a shockwave, damaging everything in small radius, as you quickly land on ground") { + russian("Вызывает ударную волну при стремительном падении на землю, нанося урон всему, что вас окружает") + } + add(SHOCKWAVE, "description1", "It does not, however, help with ground impact damage!") { + russian("Используйте с осторожностью, так как данная технология сама по себе не поглащает урон от падения!") + } - add(NIGHT_VISION, "Night Vision") - add(NIGHT_VISION, "description", "Allows unit to clearly see in the dark") + add(JUMP_BOOST_1, "Jump Boost") { + russian("Усилитель прыжка") + } + add(JUMP_BOOST_1, "description0", "Allows to perform higher jump") { + russian("Позволяет совершить высокий прыжок") + } + add(JUMP_BOOST_1, "description1", "Can be activated by crouching and jumping at the same time") { + russian("Для прыжка удерживайте кнопку приседания и нажмите кнопку прыжка") + } - add(attackBoostList[0], "Attack Boost %s") - add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%") + add(JUMP_BOOST_2, "Jump Boost 2") { + russian("Усилитель прыжка 2") + } + add(JUMP_BOOST_2, "description", "Allows to perform extra higher jump") { + russian("Позволяет совершить ещё более высокий прыжок") + } + + add(ENDER_TELEPORTER, "Ender Teleporter") { + russian("Телепортатор Края") + } + add(ENDER_TELEPORTER, "description", "Allows to perform instant, short distance teleports without damage to internal systems") { + russian("Позволяет совершать мгновенную телепортацию на короткую дистанцию без нанесения урона внутренним системам") + } + + add(NIGHT_VISION, "Night Vision") { + russian("Ночное зрение") + } + add(NIGHT_VISION, "description", "Allows to clearly see in the dark") { + russian("Позволяет видеть в темноте") + } + + add(attackBoostList[0], "Attack Boost %s") { + russian("Усиление атаки %s") + } + add(attackBoostList[0], "description", "Increases total melee attack strength by %s%%") { + russian("Увеличивает урон в ближнем бою на %s%%") + } } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/SoundDataProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/SoundDataProvider.kt index 6e4c5ac76..79e01991e 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/SoundDataProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/SoundDataProvider.kt @@ -11,7 +11,7 @@ fun SoundDefinition.subtitle(value: SoundEvent): SoundDefinition { return subtitle("otm.sound." + value.location.path) } -class SoundDataProvider(event: GatherDataEvent) : SoundDefinitionsProvider(event.generator, DataGen.MOD_ID, event.existingFileHelper) { +class SoundDataProvider(event: GatherDataEvent) : SoundDefinitionsProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) { override fun registerSounds() { add(MSoundEvents.PLASMA_WEAPON_OVERHEAT, definition().subtitle("otm.sound.plasma_weapon_overheat") @@ -34,6 +34,11 @@ class SoundDataProvider(event: GatherDataEvent) : SoundDefinitionsProvider(event add(MSoundEvents.ANDROID_SHOCKWAVE, definition().subtitle("otm.sound.android.shockwave") .with(SoundDefinition.Sound.sound(modLocation("android/shockwave"), SoundDefinition.SoundType.SOUND))) + + add(MSoundEvents.ANDROID_PROJ_PARRY, + definition().subtitle("otm.sound.android.projectile_parry") + .with(SoundDefinition.Sound.sound(modLocation("android/punch_projectile"), SoundDefinition.SoundType.SOUND)) + ) } private inline fun add(value: SoundEvent, block: SoundDefinition.() -> Unit) { diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt index cfbbeefee..4513da0a9 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementData.kt @@ -1,14 +1,11 @@ package ru.dbotthepony.mc.otm.datagen.advancements -import net.minecraft.advancements.Advancement import net.minecraft.advancements.AdvancementRewards import net.minecraft.advancements.FrameType import net.minecraft.advancements.RequirementsStrategy import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.data.ExistingFileHelper -import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation @@ -16,17 +13,22 @@ import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger +import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger import java.util.function.Consumer -fun addAdvancements(serializer: Consumer, existingFileHelper: ExistingFileHelper, lang: MatteryLanguageProvider) { - val translation = lang.english.Prepended("otm.advancements.regular") +typealias Strategy = RequirementsStrategy + +fun addAdvancements(serializer: Consumer, lang: MatteryLanguageProvider) { + val translation = lang.MultiBuilder("otm.advancements.regular") val root = AdvancementBuilder() - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .display( itemStack = ItemStack(MItems.TRITANIUM_INGOT), - title = TranslatableComponent(translation.add("root", "Overdrive That Matters")), - description = TranslatableComponent(translation.add("root.desc", "Its all about things that matter")), + title = translation.add("root", "Overdrive That Matters"), + description = translation.add("root.desc", "It's all about things that matter") { + russian("Мод про все штуки которые материальны") + }, showToast = false, announceChat = false, background = modLocation("textures/block/decorative/tritanium_block_gray.png") @@ -34,283 +36,401 @@ fun addAdvancements(serializer: Consumer, existingFileHelper: Exist .addCriterion("has_tritanium_ore", criterion(MItemTags.TRITANIUM_ORES)) .addCriterion("has_tritanium_ore_clump", criterion(MItemTags.TRITANIUM_ORE_CLUMPS)) .addCriterion("has_tritanium_ingot", InventoryChangeTrigger.TriggerInstance.hasItems(MItems.TRITANIUM_INGOT)) - .save(serializer, modLocation("regular/root"), existingFileHelper) + .save(serializer, modLocation("regular/root")) + + addMachineAdvancements(serializer, lang, root) val crude = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.BATTERY_CRUDE), - title = TranslatableComponent(translation.add("crude_battery", "Potato Power!")), - description = TranslatableComponent(translation.add("crude_battery.desc", "Put together a Crude Battery. Better hope for getter better power source soon")), + title = translation.add("crude_battery", "Potato Power!") { + russian("Сила картофеля!") + }, + description = translation.add("crude_battery.desc", "Put together a Crude Battery. Better hope for getting better power source soon") { + russian("Создайте Простой Аккумулятор. Надо бы поторопиться с созданием более продвинутого аккумулятора") + }, ) .addCriterion("has_item", criterion(MItems.BATTERY_CRUDE)) - .save(serializer, modLocation("regular/crude_battery"), existingFileHelper) + .save(serializer, modLocation("regular/crude_battery")) val normal = AdvancementBuilder() .parent(crude) .display( itemStack = ItemStack(MItems.BATTERY_NORMAL), - title = TranslatableComponent(translation.add("normal_battery", "Power Storage")), - description = TranslatableComponent(translation.add("normal_battery.desc", "Put together an Ordinary Battery")), + title = translation.add("normal_battery", "Power Storage") { + russian("Хранилище энергии") + }, + description = translation.add("normal_battery.desc", "Put together a Battery") { + russian("Создайте аккумулятор") + }, ) .addCriterion("has_item", criterion(MItems.BATTERY_NORMAL)) - .save(serializer, modLocation("regular/normal_battery"), existingFileHelper) + .save(serializer, modLocation("regular/normal_battery")) AdvancementBuilder() .parent(normal) .display( itemStack = ItemStack(MItems.BATTERY_DENSE), - title = TranslatableComponent(translation.add("dense_battery", "Extra Space Battery")), - description = TranslatableComponent(translation.add("dense_battery.desc", "Put together a Dense Battery. Better not to expose it to fires!")), + title = translation.add("dense_battery", "Extra Space Battery") { + russian("Больше места для энергии") + }, + description = translation.add("dense_battery.desc", "Put together a Dense Battery. Better not to expose it to fires!") { + russian("Создайте плотный аккумулятор. Держите подальше от огня!") + }, ) .addCriterion("has_item", criterion(MItems.BATTERY_DENSE)) - .save(serializer, modLocation("regular/dense_battery"), existingFileHelper) + .save(serializer, modLocation("regular/dense_battery")) val capacitor = AdvancementBuilder() .parent(normal) .display( itemStack = ItemStack(MItems.BATTERY_CAPACITOR), - title = TranslatableComponent(translation.add("capacitor_battery", "Supercapacitor")), - description = TranslatableComponent(translation.add("capacitor_battery.desc", "Put together a Capacitor Battery. Surely, you gonna need them somewhere...")), + title = translation.add("capacitor_battery", "Supercapacitor") { + russian("Сверхконденсатор") + }, + description = translation.add("capacitor_battery.desc", "Put together a Capacitor Battery. Surely, you gonna need them somewhere...") { + russian("Создайте аккумулятор-конденсатор. Очень вероятно, что вы найдете ему применение...") + }, ) .addCriterion("has_item", criterion(MItems.BATTERY_CAPACITOR)) - .save(serializer, modLocation("regular/capacitor_battery"), existingFileHelper) + .save(serializer, modLocation("regular/capacitor_battery")) AdvancementBuilder() .parent(capacitor) .display( itemStack = ItemStack(MItems.ENERGY_SWORD), - title = TranslatableComponent(translation.add("energy_sword", "Self Sharpening Blade")), - description = TranslatableComponent(translation.add("energy_sword.desc", "Wield a High-Frequency Blade, a melee weapon intended to slice Creepers into creep-cakes. Rational folks won't attempt to slice their Android fellows...")), + title = translation.add("energy_sword", "Self Sharpening Blade") { + russian("Клинок с самозаточкой") + }, + description = translation.add("energy_sword.desc", "Wield a High-Frequency Blade, a melee weapon intended to slice Creepers into creep-cakes") { + russian("Получите высокочастотный клинок, оружие ближнего боя предназначенное для нарезания Криперов на крипо-тортики") + }, frameType = FrameType.GOAL ) .addCriterion("has_item", criterion(MItems.ENERGY_SWORD)) - .save(serializer, modLocation("regular/energy_sword"), existingFileHelper) + .save(serializer, modLocation("regular/energy_sword")) AdvancementBuilder() .parent(normal) .display( itemStack = ItemStack(MItems.QUANTUM_BATTERY), - title = TranslatableComponent(translation.add("quantum_battery", "Power, in Superposition")), - description = TranslatableComponent(translation.add("quantum_battery.desc", "Put together a Quantum Battery, powered by Ender technologies")), + title = translation.add("quantum_battery", "Power, in Superposition") { + russian("Энергия, в суперпозиции") + }, + description = translation.add("quantum_battery.desc", "Put together a Quantum Battery, powered by Ender technologies") { + russian("Создайте квантовый аккумулятор, пропитанную технологиями Края") + }, frameType = FrameType.GOAL ) .rewards(AdvancementRewards.Builder.experience(50)) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .addCriterion("has_item0", criterion(MItems.QUANTUM_BATTERY)) .addCriterion("has_item1", criterion(MItems.QUANTUM_CAPACITOR)) - .save(serializer, modLocation("regular/quantum_battery"), existingFileHelper) + .save(serializer, modLocation("regular/quantum_battery")) - AdvancementBuilder() + val zpm = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.ZPM_BATTERY), - title = TranslatableComponent(translation.add("zpm_battery", "Pocket Universe, as Power Source")), - description = TranslatableComponent(translation.add("zpm_battery.desc", "Find Zero Point Module, something from different universe of ours, created using technologies lost in time in all possible multiverses")), + title = translation.add("zpm_battery", "Pocket Universe, as Power Source") { + russian("Карманная вселенная, как источник питания") + }, + description = translation.add("zpm_battery.desc", "Find Zero Point Module, something from different multiverse of ours, created using technologies lost in time in all possible multiverses") { + russian("Найдите модуль нулевой точки, вещь из другой мультивселенной, созданная с использованием технологий, потерянных во времени во всех возможных мультивслеленных") + }, frameType = FrameType.CHALLENGE, hidden = true ) .rewards(AdvancementRewards.Builder.experience(800)) .addCriterion("has_item", criterion(MItems.ZPM_BATTERY)) - .save(serializer, modLocation("regular/zpm_battery"), existingFileHelper) + .save(serializer, modLocation("regular/zpm_battery")) + + addExopackAdvancements(serializer, lang, root, zpm) val blackhole = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.BLACK_HOLE), - title = TranslatableComponent(translation.add("black_hole_pull", "Something Massive and Something Close")), - description = TranslatableComponent(translation.add("black_hole_pull.desc", "Experience Singularity's gravitational force, better not to get closer")), + title = translation.add("black_hole_pull", "Something Massive and Something Close") { + russian("Что то массивное и близкое") + }, + description = translation.add("black_hole_pull.desc", "Experience Singularity's gravitational force, better not to get closer") { + russian("Испытайте на себе гравитационную силу сингулярности, лучше не подходить ближе") + }, hidden = true ) - .addCriterion("pulled_by_black_hole", BlackHoleTrigger.Instance) - .save(serializer, modLocation("regular/black_hole"), existingFileHelper) + .addCriterion("pulled_by_black_hole", BlackHoleTrigger.criterion) + .save(serializer, modLocation("regular/black_hole")) AdvancementBuilder() .parent(blackhole) .display( itemStack = ItemStack(MItems.BLACK_HOLE_SCANNER), - title = TranslatableComponent(translation.add("black_hole_scanner", "Determining the Mass")), - description = TranslatableComponent(translation.add("black_hole_scanner.desc", "Craft the Singularity Scanner, to determine mass of Singularity from safe distance")), + title = translation.add("black_hole_scanner", "Determining the Mass") { + russian("Определяем массу") + }, + description = translation.add("black_hole_scanner.desc", "Craft the Singularity Scanner, to determine mass of... Singularities, from safe distance") { + russian("Создайте сканер сингулярностей, для определения массы... Сингулярностей на безопасном расстоянии") + }, ) .addCriterion("has_item", criterion(MItems.BLACK_HOLE_SCANNER)) - .save(serializer, modLocation("regular/black_hole_scanner"), existingFileHelper) + .save(serializer, modLocation("regular/black_hole_scanner")) AdvancementBuilder() .parent(blackhole) .display( itemStack = ItemStack(MItems.GRAVITATION_STABILIZER), - title = TranslatableComponent(translation.add("stabilizer", "Reducing the Impact")), - description = TranslatableComponent(translation.add("stabilizer.desc", "Put together a device that defy physical laws and also defy gravity of Singularities. Better hope it does not cause any side effects")), + title = translation.add("stabilizer", "Reducing the Impact") { + russian("Уменьшаем воздействие") + }, + description = translation.add("stabilizer.desc", "Put together a device that defy physical laws and also defy gravity of Singularities. Better hope it does not cause any side effects") { + russian("Создайте устройство, которое смеётся перед законами физики, а так же смеётся перед силами гравитации сингулярностей. Надо надеется, что это не принесёт никаких последствий") + }, ) .addCriterion("has_item", criterion(MItems.GRAVITATION_STABILIZER)) - .save(serializer, modLocation("regular/stabilizer"), existingFileHelper) + .save(serializer, modLocation("regular/stabilizer")) AdvancementBuilder() .parent(blackhole) .display( itemStack = ItemStack(MItems.PORTABLE_GRAVITATION_STABILIZER), - title = TranslatableComponent(translation.add("portable_stabilizer", "Local Gravity Field")), - description = TranslatableComponent(translation.add("portable_stabilizer.desc", "Protect yourself from gravitational effect of Singularity using Portable Gravitation Stabilizer")), + title = translation.add("portable_stabilizer", "Local Gravity Field") { + russian("Локальное поле гравитации") + }, + description = translation.add("portable_stabilizer.desc", "Protect yourself from gravitational effect of Singularity using Portable Space-Time Equalizer") { + russian("Защитите себя от гравитационных эффектов Сингулярностей используя Портативный") + }, ) .addCriterion("has_item", criterion(MItems.PORTABLE_GRAVITATION_STABILIZER)) - .save(serializer, modLocation("regular/portable_stabilizer"), existingFileHelper) + .save(serializer, modLocation("regular/portable_stabilizer")) val ore = AdvancementBuilder() .parent(root) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .display( itemStack = ItemStack(MItems.TRITANIUM_ORE_CLUMP), - title = TranslatableComponent(translation.add("ore", "Blue Metal Discovery")), - description = TranslatableComponent(translation.add("ore.desc", "Mine some Tritanium")), + title = translation.add("ore", "Blue Metal Discovery") { + russian("Открытие синего металла") + }, + description = translation.add("ore.desc", "Mine some Tritanium") { + russian("Добудьте немного тритана") + }, ) .addCriterion("has_tritanium_ore", criterion(MItemTags.TRITANIUM_ORES)) .addCriterion("has_tritanium_ore_clump", criterion(MItemTags.TRITANIUM_ORE_CLUMPS)) - .save(serializer, modLocation("regular/ore"), existingFileHelper) + .save(serializer, modLocation("regular/ore")) val ingot = AdvancementBuilder() .parent(ore) .display( itemStack = ItemStack(MItems.TRITANIUM_INGOT), - title = TranslatableComponent(translation.add("ingot", "Acquire Harder-ware")), - description = TranslatableComponent(translation.add("ingot.desc", "Smelt a Tritanium ingot")), + title = translation.add("ingot", "Acquire Harder-ware") { + russian("Куй сильнее...") + }, + description = translation.add("ingot.desc", "Smelt a Tritanium ingot") { + russian("Выплавьте тритановый слиток") + }, ) .addCriterion("has_tritanium_ingot", criterion(MItemTags.TRITANIUM_INGOTS)) - .save(serializer, modLocation("regular/ingot"), existingFileHelper) + .save(serializer, modLocation("regular/ingot")) AdvancementBuilder() .parent(ingot) .display( itemStack = ItemStack(MItems.TRITANIUM_PICKAXE), - title = TranslatableComponent(translation.add("pickaxe", "A Tool for Patient Miners")), - description = TranslatableComponent(translation.add("pickaxe.desc", "Craft a Tritanium Pickaxe")), + title = translation.add("pickaxe", "A Tool for Patient Miners") { + russian("Инструмент для неспешных шахтёров") + }, + description = translation.add("pickaxe.desc", "Craft a Tritanium Pickaxe") { + russian("Создайте тритановую кирку") + }, ) .addCriterion("has_tritanium_pickaxe", criterion(MItems.TRITANIUM_PICKAXE)) - .save(serializer, modLocation("regular/pickaxe"), existingFileHelper) + .save(serializer, modLocation("regular/pickaxe")) AdvancementBuilder() .parent(ingot) .display( itemStack = ItemStack(MItems.TRITANIUM_HOE), - title = TranslatableComponent(translation.add("hoe", "A Tool for Farmers")), - description = TranslatableComponent(translation.add("hoe.desc", "Tritanium is a very good choice for making a sturdy Hoe")), + title = translation.add("hoe", "A Tool for Farmers") { + russian("Инструмент для фермеров") + }, + description = translation.add("hoe.desc", "Tritanium is a very good choice for making a sturdy Hoe") { + russian("Тритан - очень хороший выбор для создания прочной Мотыги") + }, hidden = true ) .addCriterion("hoe", criterion(MItems.TRITANIUM_HOE)) - .save(serializer, modLocation("regular/hoe"), existingFileHelper) + .save(serializer, modLocation("regular/hoe")) val plate = AdvancementBuilder() .parent(ingot) .display( itemStack = ItemStack(MItems.TRITANIUM_PLATE), - title = TranslatableComponent(translation.add("plate", "Hard Plating")), - description = TranslatableComponent(translation.add("plate.desc", "Roll down some Tritanium using a Plate Press")) + title = translation.add("plate", "Hard Plating") { + russian("Прочные пластины") + }, + description = translation.add("plate.desc", "Roll down some Tritanium using a Plate Press") { + russian("Раскатайте немного тритана используя пресс пластин") + } ) .addCriterion("has_item", criterion(MItemTags.TRITANIUM_PLATES)) - .save(serializer, modLocation("regular/plate"), existingFileHelper) + .save(serializer, modLocation("regular/plate")) AdvancementBuilder() .parent(plate) .display( itemStack = ItemStack(MItems.TRITANIUM_CHESTPLATE), - title = TranslatableComponent(translation.add("armor", "Composite Armor")), - description = TranslatableComponent(translation.add("armor.desc", "Bend some Tritanium plates into simple yet sturdy armor")) + title = translation.add("armor", "Composite Armor") { + russian("Композитная броня") + }, + description = translation.add("armor.desc", "Bend some Tritanium Plates and Carbon Mesh into incredibly sturdy armor") { + russian("Согните немного тритановых пластин вместе с углеродной сеткой в невероятно прочную броню") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .addCriterion("has_item0", criterion(MItems.TRITANIUM_HELMET)) .addCriterion("has_item1", criterion(MItems.TRITANIUM_CHESTPLATE)) .addCriterion("has_item2", criterion(MItems.TRITANIUM_PANTS)) .addCriterion("has_item3", criterion(MItems.TRITANIUM_BOOTS)) - .save(serializer, modLocation("regular/armor"), existingFileHelper) + .save(serializer, modLocation("regular/armor")) + + AdvancementBuilder() + .parent(ingot) + .display( + itemStack = ItemStack(MItems.SIMPLE_TRITANIUM_CHESTPLATE), + title = translation.add("simple_armor", "Sturdy Armor") { + russian("Прочная броня") + }, + description = translation.add("simple_armor.desc", "Put togeher Simple Tritanium Armor from Tritanium Ingots, simple and effective") { + russian("Создайте простую тритановую броню из слитков, просто и эффективно") + } + ) + .requirements(Strategy.OR) + .addCriterion("has_item0", criterion(MItems.SIMPLE_TRITANIUM_HELMET)) + .addCriterion("has_item1", criterion(MItems.SIMPLE_TRITANIUM_CHESTPLATE)) + .addCriterion("has_item2", criterion(MItems.SIMPLE_TRITANIUM_PANTS)) + .addCriterion("has_item3", criterion(MItems.SIMPLE_TRITANIUM_BOOTS)) + .save(serializer, modLocation("regular/simple_armor")) val glass = AdvancementBuilder() .parent(plate) .display( itemStack = ItemStack(MRegistry.INDUSTRIAL_GLASS.item), - title = TranslatableComponent(translation.add("industrial_glass", "Extra Hard Glass")), - description = TranslatableComponent(translation.add("industrial_glass.desc", "Manual says it should be bulletproof.")) + title = translation.add("industrial_glass", "Extra Hard Glass") { + russian("Дополнительно прочное стекло") + }, + description = translation.add("industrial_glass.desc", "Manual says it should be bulletproof.") { + russian("В инструкции указано что оно должно быть пуленепробиваемо.") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .also { advancement -> MRegistry.INDUSTRIAL_GLASS.allItems.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/industrial_glass"), existingFileHelper) + .save(serializer, modLocation("regular/industrial_glass")) AdvancementBuilder() .parent(glass) .display( itemStack = ItemStack(MRegistry.INDUSTRIAL_GLASS.getItem(DyeColor.GREEN)), - title = TranslatableComponent(translation.add("industrial_glass2", "Glass-tacular Artist")), - description = TranslatableComponent(translation.add("industrial_glass2.desc", "Paint Industrial Glass all possible colors")), + title = translation.add("industrial_glass2", "Glass-Tacular Artist") { + russian("Стекло-чаровательный артист") + }, + description = translation.add("industrial_glass2.desc", "Paint Industrial Glass all possible colors") { + russian("Покрасьте промышленное стекло во все возможные цвета") + }, frameType = FrameType.GOAL ) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .also { advancement -> MRegistry.INDUSTRIAL_GLASS.allItems.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/industrial_glass2"), existingFileHelper) + .save(serializer, modLocation("regular/industrial_glass2")) val cargoCrate = AdvancementBuilder() .parent(plate) .display( itemStack = ItemStack(MRegistry.CARGO_CRATES.item), - title = TranslatableComponent(translation.add("cargo_crate", "Sturdy Item Stash")), - description = TranslatableComponent(translation.add("cargo_crate.desc", "Cargo Crates, like Double Chest, but Single.")) + title = translation.add("cargo_crate", "Sturdy Item Stash") { + russian("Прочное хранилище предметов") + }, + description = translation.add("cargo_crate.desc", "Cargo Crates, like Double Chest, but Single") { + russian("Грузовые ящики, будто двойные сундуки, но одинарные.") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .also { advancement -> MRegistry.CARGO_CRATES.allItems.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/cargo_crate"), existingFileHelper) + .save(serializer, modLocation("regular/cargo_crate")) val cargoCrateInMinecart = AdvancementBuilder() .parent(cargoCrate) .display( itemStack = ItemStack(MItems.CARGO_CRATE_MINECARTS[null]!!), - title = TranslatableComponent(translation.add("cargo_crate_minecart", "Crate On a Rail")), - description = TranslatableComponent(translation.add("cargo_crate_minecart.desc", "Drop a Cargo Crate onto Minecart and see how it goes")) + title = translation.add("cargo_crate_minecart", "Crate On a Rail") { + russian("Ящик на рельсах") + }, + description = translation.add("cargo_crate_minecart.desc", "Drop a Cargo Crate onto Minecart and see how it goes") { + russian("Сбросьте грузовой ящик в вагонетку и посмотрите, что получится") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .also { advancement -> MItems.CARGO_CRATE_MINECARTS.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/cargo_crate_minecart"), existingFileHelper) + .save(serializer, modLocation("regular/cargo_crate_minecart")) AdvancementBuilder() .parent(cargoCrateInMinecart) .display( itemStack = ItemStack(MItems.CARGO_CRATE_MINECARTS[DyeColor.GREEN]!!), - title = TranslatableComponent(translation.add("cargo_crate_minecart2", "A Motley Train")), - description = TranslatableComponent(translation.add("cargo_crate_minecart2.desc", "Have all color variants of Minecarts with Cargo Crates")), + title = translation.add("cargo_crate_minecart2", "A Motley Train") { + russian("Пёстрый поезд") + }, + description = translation.add("cargo_crate_minecart2.desc", "Have all color variants of Minecarts with Cargo Crates") { + russian("Создайте все варианты покрасок вагонеток с грузовыми Ящиками") + }, frameType = FrameType.GOAL ) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .also { advancement -> MItems.CARGO_CRATE_MINECARTS.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/cargo_crate_minecart2"), existingFileHelper) + .save(serializer, modLocation("regular/cargo_crate_minecart2")) AdvancementBuilder() .parent(cargoCrate) .display( itemStack = ItemStack(MRegistry.CARGO_CRATES.item), - title = TranslatableComponent(translation.add("cargo_crate2", "Colorful Warehouse")), - description = TranslatableComponent(translation.add("cargo_crate2.desc", "Craft all color variants of Cargo Crates")), + title = translation.add("cargo_crate2", "Colorful Warehouse") { + russian("Разноцветный склад") + }, + description = translation.add("cargo_crate2.desc", "Craft all color variants of Cargo Crates") { + russian("Покрасьте грузовые ящики во все возможные цвета") + }, frameType = FrameType.GOAL ) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .also { advancement -> MRegistry.CARGO_CRATES.allItems.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/cargo_crate2"), existingFileHelper) + .save(serializer, modLocation("regular/cargo_crate2")) val tritaniumBlock = AdvancementBuilder() - .parent(plate) + .parent(ingot) .display( itemStack = ItemStack(MRegistry.TRITANIUM_BLOCK.item), - title = TranslatableComponent(translation.add("tritanium_block", "Cold, Impregnable Wall")), - description = TranslatableComponent(translation.add("tritanium_block.desc", "Coat stones in Tritanium Plates, a cheap yet incredibly sturdy material")) + title = translation.add("tritanium_block", "Cold, Impregnable Wall") { + russian("Холодная, неприступная стена") + }, + description = translation.add("tritanium_block.desc", "Coat stones in Tritanium, a cheap yet incredibly sturdy material") { + russian("Покройте булыжник в тритане, дешёвый, но невероятно прочный материал") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .also { advancement -> MRegistry.TRITANIUM_BLOCK.allItems.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MRegistry.TRITANIUM_STRIPED_BLOCK.flatItems.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } @@ -319,30 +439,38 @@ fun addAdvancements(serializer: Consumer, existingFileHelper: Exist MItems.TRITANIUM_STRIPED_BLOCK.also { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MItems.TRITANIUM_STRIPED_STAIRS.also { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/tritanium_block"), existingFileHelper) + .save(serializer, modLocation("regular/tritanium_block")) AdvancementBuilder() .parent(tritaniumBlock) .display( itemStack = ItemStack(MItems.TRITANIUM_STRIPED_BLOCK), - title = TranslatableComponent(translation.add("striped_tritanium_block", "Old Fashion Color Touch")), - description = TranslatableComponent(translation.add("striped_tritanium_block.desc", "Pale Blue coat with Yellow stripe, I bet you know whose design is this")) + title = translation.add("striped_tritanium_block", "Old Fashion Color Touch") { + russian("Старомодная цветовая отделка") + }, + description = translation.add("striped_tritanium_block.desc", "Pale Blue coat with Yellow stripe, I bet you know whose design is this") { + russian("Бледно синяя покраска с жёлтой полоской, я готов поспорить вы знаете чей это дизайн") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .addCriterion("has_item", criterion(MItems.TRITANIUM_STRIPED_BLOCK)) .addCriterion("has_item1", criterion(MItems.TRITANIUM_STRIPED_STAIRS)) .addCriterion("has_item2", criterion(MItems.TRITANIUM_STRIPED_SLAB)) .addCriterion("has_item3", criterion(MItems.TRITANIUM_STRIPED_WALL)) - .save(serializer, modLocation("regular/striped_tritanium_block"), existingFileHelper) + .save(serializer, modLocation("regular/striped_tritanium_block")) val colorTritaniumBlock = AdvancementBuilder() .parent(tritaniumBlock) .display( itemStack = ItemStack(MRegistry.TRITANIUM_BLOCK.getItem(DyeColor.GREEN)), - title = TranslatableComponent(translation.add("tritanium_block2", "Colorful Fortress")), - description = TranslatableComponent(translation.add("tritanium_block2.desc", "Put some paint over Tritanium Block to make it look fabulous")) + title = translation.add("tritanium_block2", "Colorful Fortress") { + russian("Разноцветная крепость") + }, + description = translation.add("tritanium_block2.desc", "Put some paint over Tritanium Block to make it look fabulous") { + russian("Покрасьте тритановый блок для придания ему сказочных оттенков") + } ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .also { advancement -> MRegistry.TRITANIUM_BLOCK.items.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MRegistry.TRITANIUM_STAIRS.items.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } @@ -351,68 +479,114 @@ fun addAdvancements(serializer: Consumer, existingFileHelper: Exist MItems.TRITANIUM_STRIPED_BLOCK.also { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MItems.TRITANIUM_STRIPED_STAIRS.also { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/tritanium_block2"), existingFileHelper) + .save(serializer, modLocation("regular/tritanium_block2")) val colorfulTritaniumBlock = AdvancementBuilder() .parent(colorTritaniumBlock) .display( itemStack = ItemStack(MRegistry.TRITANIUM_BLOCK.getItem(DyeColor.BLACK)), - title = TranslatableComponent(translation.add("tritanium_block3", "Paint Me A Castle")), - description = TranslatableComponent(translation.add("tritanium_block3.desc", "Craft all color variants of Tritanium Blocks")), + title = translation.add("tritanium_block3", "Paint Me A Castle") { + russian("Разукрась мне замок") + }, + description = translation.add("tritanium_block3.desc", "Craft all color variants of Tritanium Blocks") { + russian("Создайте все варианты покрасок тритановых Блоков") + }, frameType = FrameType.GOAL ) .rewards(AdvancementRewards.Builder.loot(modLocation("tritanium_block3")).addExperience(100)) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .also { advancement -> MRegistry.TRITANIUM_BLOCK.items.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/tritanium_block3"), existingFileHelper) + .save(serializer, modLocation("regular/tritanium_block3")) AdvancementBuilder() .parent(colorfulTritaniumBlock) .display( itemStack = ItemStack(MRegistry.TRITANIUM_STRIPED_BLOCK.getItem(DyeColor.BLACK, DyeColor.WHITE)), - title = TranslatableComponent(translation.add("tritanium_block4", "All The Colors")), - description = TranslatableComponent(translation.add("tritanium_block4.desc", "Craft ALL color variants of Tritanium Blocks including striped ones")), + title = translation.add("tritanium_block4", "All The Colors") { + russian("Все цвета всё сюда") + }, + description = translation.add("tritanium_block4.desc", "Craft ALL color variants of Tritanium Blocks including striped ones") { + russian("Создайте АБСОЛЮТНО ВСЕ варианты покрасок тритановых блоков, включая с полосками") + }, frameType = FrameType.CHALLENGE ) .rewards(AdvancementRewards.Builder.loot(modLocation("tritanium_block4")).addExperience(400)) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .also { advancement -> MRegistry.TRITANIUM_BLOCK.items.values.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MRegistry.TRITANIUM_STRIPED_BLOCK.flatItems.forEach { advancement.addCriterion(it.registryName!!.path, criterion(it)) } MItems.TRITANIUM_STRIPED_BLOCK.also { advancement.addCriterion(it.registryName!!.path, criterion(it)) } } - .save(serializer, modLocation("regular/tritanium_block4"), existingFileHelper) + .save(serializer, modLocation("regular/tritanium_block4")) val pill = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.PILL_ANDROID), - title = TranslatableComponent(translation.add("pill", "Side Colored Mystery")), - description = TranslatableComponent(translation.add("pill.desc", "Find some of those mysterious pills. Consult with Cleric before trying to ingest them.")), + title = translation.add("pill", "Side Colored Mystery") { + russian("Мистика с цветными краями") + }, + description = translation.add("pill.desc", "Find some of those mysterious pills") { + russian("Найдите одну из этих ваших мистических пилюль") + }, ) - .requirements(RequirementsStrategy.OR) + .requirements(Strategy.OR) .addCriterion("pill1", criterion(MItems.PILL_ANDROID)) .addCriterion("pill2", criterion(MItems.PILL_HEAL)) .addCriterion("pill3", criterion(MItems.PILL_HUMANE)) .addCriterion("pill4", criterion(MItems.PILL_OBLIVION)) - .save(serializer, modLocation("regular/pill"), existingFileHelper) + .save(serializer, modLocation("regular/pill")) AdvancementBuilder() .parent(pill) .display( itemStack = ItemStack(MItems.PILL_HEAL), - title = TranslatableComponent(translation.add("all_pills", "Take Your Meds")), - description = TranslatableComponent(translation.add("all_pills.desc", "Find all possible pill types")), + title = translation.add("all_pills", "Take Your Meds") { + russian("Пей таблетки") + }, + description = translation.add("all_pills.desc", "Find all possible pill types") { + russian("Найдите всевозможные варианты пилюль") + }, frameType = FrameType.CHALLENGE, hidden = true ) .rewards(AdvancementRewards.Builder.experience(200)) - .requirements(RequirementsStrategy.AND) + .requirements(Strategy.AND) .addCriterion("pill1", criterion(MItems.PILL_ANDROID)) .addCriterion("pill2", criterion(MItems.PILL_HEAL)) .addCriterion("pill3", criterion(MItems.PILL_HUMANE)) .addCriterion("pill4", criterion(MItems.PILL_OBLIVION)) - .save(serializer, modLocation("regular/all_pills"), existingFileHelper) + .save(serializer, modLocation("regular/all_pills")) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(MItems.ESSENCE_CAPSULE), + title = translation.add("essence_capsule", "Forgot To Forget") { + russian("Я забыл забыть") + }, + description = translation.add("essence_capsule.desc", "Recover Essence Capsule. Memories...") { + russian("Верните капсулу эссенции. Воспоминания...") + }, + ) + .requirements(Strategy.OR) + .addCriterion("essence1", criterion(MItems.ESSENCE_CAPSULE)) + .addCriterion("essence2", criterion(MItems.ESSENCE_DRIVE)) + .save(serializer, modLocation("regular/essence_capsule")) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) }, + title = translation.add("explosive_hammer", "I Did It Like This") { + russian("Я сделал это вот так") + }, + description = translation.add("explosive_hammer.desc", "Nail down something (or someone)") { + russian("Пригвоздите что-либо (или кого-либо)") + } + ) + .addCriterion("damage", NailedEntityTrigger.Instance().criterion()) + .save(serializer, modLocation("regular/explosive_hammer")) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementProvider.kt deleted file mode 100644 index e2afbc95d..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AdvancementProvider.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen.advancements - -import net.minecraft.advancements.Advancement -import net.minecraftforge.common.data.ExistingFileHelper -import net.minecraftforge.data.event.GatherDataEvent -import java.util.LinkedList -import java.util.function.Consumer - -class AdvancementProvider(event: GatherDataEvent) : net.minecraft.data.advancements.AdvancementProvider(event.generator, event.existingFileHelper) { - private val callbacks = LinkedList<(Consumer, ExistingFileHelper) -> Unit>() - - fun exec(callback: (Consumer, ExistingFileHelper) -> Unit) { - callbacks.add(callback) - } - - fun exec(callback: (Consumer) -> Unit) { - callbacks.add { it, _ -> - callback.invoke(it) - } - } - - override fun registerAdvancements(consumer: Consumer, fileHelper: ExistingFileHelper) { - for (callback in callbacks) { - callback.invoke(consumer, fileHelper) - } - } -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt index 8103ac23a..591f3a24e 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/AndroidAdvancementsData.kt @@ -1,266 +1,310 @@ package ru.dbotthepony.mc.otm.datagen.advancements -import net.minecraft.advancements.Advancement import net.minecraft.advancements.AdvancementRewards import net.minecraft.advancements.FrameType -import net.minecraft.advancements.RequirementsStrategy -import net.minecraft.advancements.critereon.DamagePredicate -import net.minecraft.advancements.critereon.DamageSourcePredicate import net.minecraft.advancements.critereon.EntityPredicate -import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.advancements.critereon.ItemPredicate import net.minecraft.advancements.critereon.MinMaxBounds.Doubles -import net.minecraft.advancements.critereon.PlayerHurtEntityTrigger -import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.EntityType import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import net.minecraftforge.common.data.ExistingFileHelper -import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation -import ru.dbotthepony.mc.otm.registry.MItemTags +import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger -import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger +import java.util.* import java.util.function.Consumer -fun addAndroidAdvancements(serializer: Consumer, existingFileHelper: ExistingFileHelper, lang: MatteryLanguageProvider) { - val translation = lang.english.Prepended("otm.advancements.android") +fun addAndroidAdvancements(serializer: Consumer, lang: MatteryLanguageProvider) { + val translation = lang.MultiBuilder("otm.advancements.android") val root = AdvancementBuilder() .display( itemStack = ItemStack(MItems.PILL_ANDROID), - title = TranslatableComponent(translation.add("root", "Androids and Humans")), - description = TranslatableComponent(translation.add("root.desc", "Can you make out who is cruel machine and who can love?")), + title = translation.add("root", "Androids and Humans") { + russian("Андроиды и люди") + }, + description = translation.add("root.desc", "Can you make out who is cruel machine and who shows empathy?") { + russian("Сможете ли вы отличить бездушную машину от того, кто показывает сочувствие?") + }, showToast = false, announceChat = false, background = modLocation("textures/block/decorative/metal_beam_top.png") ) - .addCriterion("became_android", BecomeAndroidTrigger.Instance) - .save(serializer, modLocation("android/root"), existingFileHelper) + .addCriterion("became_android", BecomeAndroidTrigger.criterion) + .save(serializer, modLocation("android/root")) AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.ZPM_BATTERY), - title = TranslatableComponent(translation.add("zpm", "Fully Autonomous")), - description = TranslatableComponent(translation.add("zpm.desc", "Use Zero Point Module as internal battery power source. Only time is your enemy now")), + title = translation.add("zpm", "Fully Autonomous") { + russian("Полностью автономный") + }, + description = translation.add("zpm.desc", "Use Zero Point Module as internal battery power source. Only eternity is your enemy now") { + russian("Используйте модуль нулевой точки как внутренний источник питания. Теперь только вечность будет вашим злейшим врагом") + }, hidden = true, frameType = FrameType.CHALLENGE ) - .addCriterion("item", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.ZPM_BATTERY).build())) - .save(serializer, modLocation("android/zpm"), existingFileHelper) + .addCriterion("item", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.ZPM_BATTERY).build()).criterion()) + .save(serializer, modLocation("android/zpm")) AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.QUANTUM_BATTERY), - title = TranslatableComponent(translation.add("quantum_battery", "Wireless Charged")), - description = TranslatableComponent(translation.add("quantum_battery.desc", "Use Quantum Battery as internal battery power source, might as well have Fission Reactor charge other side of the link")), + title = translation.add("quantum_battery", "Wireless Charged") { + russian("Беспроводная зарядка") + }, + description = translation.add("quantum_battery.desc", "Use Quantum Battery as internal battery power source, might as well have Fission Reactor charge other side of the link") { + russian("Используйте Квантовый Аккумулятор как внутренний источник питания, можно даже подключить другой конец к Реактору Распада") + }, hidden = true, frameType = FrameType.GOAL ) - .requirements(RequirementsStrategy.OR) - .addCriterion("item0", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.QUANTUM_BATTERY).build())) - .addCriterion("item1", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.QUANTUM_CAPACITOR).build())) - .save(serializer, modLocation("android/quantum_battery"), existingFileHelper) + .requirements(Strategy.OR) + .addCriterion("item0", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.QUANTUM_BATTERY).build()).criterion()) + .addCriterion("item1", AndroidBatteryTrigger.Instance(ItemPredicate.Builder.item().of(MItems.QUANTUM_CAPACITOR).build()).criterion()) + .save(serializer, modLocation("android/quantum_battery")) AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.PILL_ANDROID), - title = TranslatableComponent(translation.add("normal", "Last Sweet Dreams")), - description = TranslatableComponent(translation.add("normal.desc", "Become an Android in your dreams, a soulless machine... Or is it?")), + title = translation.add("normal", "Last Sweet Dreams") { + russian("Последние сновидения") + }, + description = translation.add("normal.desc", "Become an Android in your dreams, a soulless machine... Or is it?") { + russian("Превратитесь в андроида во сне, в бездушную машину... Или нет?") + }, hidden = true, ) - .addCriterion("became_android", BecomeAndroidSleepTrigger.Instance) - .save(serializer, modLocation("android/become_thru_sleep"), existingFileHelper) + .addCriterion("became_android", BecomeAndroidSleepTrigger.criterion) + .save(serializer, modLocation("android/become_thru_sleep")) AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.PILL_ANDROID), - title = TranslatableComponent(translation.add("death", "The Old Way")), - description = TranslatableComponent(translation.add("death.desc", "Become an Android in event of death, veteran's favorite")), + title = translation.add("death", "The Old Way") { + russian("Изготовленный по старинке") + }, + description = translation.add("death.desc", "In event of death, become an Android; Veteran's favorite") { + russian("Станьте андроидом, будучи умерев; Ветераны оценят") + }, hidden = true, ) - .addCriterion("became_android", BecomeAndroidDeathTrigger.Instance) - .save(serializer, modLocation("android/become_thru_death"), existingFileHelper) + .addCriterion("became_android", BecomeAndroidDeathTrigger.criterion) + .save(serializer, modLocation("android/become_thru_death")) AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.PILL_HUMANE), - title = TranslatableComponent(translation.add("unandroid", "Feel Humane Again")), - description = TranslatableComponent(translation.add("unandroid.desc", "Become fleshy after being a machine, yet something is still missing that you had before...")), + title = translation.add("unandroid", "Feel Humane Again") { + russian("Вкусить человечность вновь") + }, + description = translation.add("unandroid.desc", "Become fleshy after being a machine, yet something is still missing that you had before...") { + russian("Вновь обретите плоть после своей жизни как набор гаек и болтов, но вот чего-то всё равно не хватает, что было при вас с самого начала...") + }, hidden = true, frameType = FrameType.GOAL ) - .addCriterion("become_humane", BecomeHumaneTrigger.Instance) - .save(serializer, modLocation("android/become_humane"), existingFileHelper) - - val phantoms = AdvancementBuilder() - .parent(root) - .display( - itemStack = ItemStack(Items.PHANTOM_MEMBRANE), - title = TranslatableComponent(translation.add("phantom_spawn_denied", "Insomnia Immune")), - description = TranslatableComponent(translation.add("phantom_spawn_denied.desc", "Have Phantoms not come after you because you are not someone who needs to sleep")), - hidden = true - ) - .addCriterion("phantom_spawn_denied", PhantomSpawnDeniedTrigger.Instance) - .save(serializer, modLocation("android/phantom_spawn_denied"), existingFileHelper) + .addCriterion("become_humane", BecomeHumaneTrigger.criterion) + .save(serializer, modLocation("android/become_humane")) val attractor = AdvancementBuilder() - .parent(phantoms) + .parent(root) .display( itemStack = ItemStack(MItems.PHANTOM_ATTRACTOR), - title = TranslatableComponent(translation.add("phantom_attractor", "Eversleeping Decoy")), - description = TranslatableComponent(translation.add("phantom_attractor.desc", "Put together a Phantom Attractor, to be able to fight Phantoms as Android again")), + title = translation.add("phantom_attractor", "Eversleeping Decoy") { + russian("Фантоматичная приманка") + }, + description = translation.add("phantom_attractor.desc", "Put together a Phantom Attractor, to be able to fight Phantoms as Android again") { + russian("Создайте приманщик фантомов, для привлечения фантомов вновь, будучи андроидом") + }, ) .addCriterion("has_item", criterion(MItems.PHANTOM_ATTRACTOR)) - .save(serializer, modLocation("regular/phantom_attractor"), existingFileHelper) - - AdvancementBuilder() - .parent(attractor) - .display( - itemStack = ItemStack(MItems.PHANTOM_ATTRACTOR), - title = TranslatableComponent(translation.add("phantom_attractor_research", "Deception of Phantoms")), - description = TranslatableComponent(translation.add("phantom_attractor_research.desc", "Research into how to attract Phantoms the same way as the ones who need to sleep")), - ) - .addCriterion("researched", AndroidResearchTrigger.Instance(modLocation(MNames.PHANTOM_ATTRACTOR))) - .save(serializer, modLocation("regular/phantom_attractor_research"), existingFileHelper) + .save(serializer, modLocation("regular/phantom_attractor")) val researchAnything = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.ANDROID_STATION), - title = TranslatableComponent(translation.add("research_anything", "New Trick")), - description = TranslatableComponent(translation.add("research_anything.desc", "Research anything as Android")), + title = translation.add("research_anything", "New Trick") { + russian("Новый фокус") + }, + description = translation.add("research_anything.desc", "Research anything as Android") { + russian("Исследуйте что либо за андроида") + }, ) - .addCriterion("research_anything", AndroidResearchTrigger.Instance(null)) - .save(serializer, modLocation("android/research_anything"), existingFileHelper) + .addCriterion("research_anything", AndroidResearchTrigger.Instance(Optional.empty(), Optional.empty()).criterion()) + .save(serializer, modLocation("android/research_anything")) AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(Items.WATER_BUCKET), - title = TranslatableComponent(translation.add("air_bags", "Patching Up Wooden Vessel")), - description = TranslatableComponent(translation.add("air_bags.desc", "Research Air Bags as Android, to float in water")), + title = translation.add("air_bags", "Patching Up Wooden Vessel") { + russian("В бочку - затычку") + }, + description = translation.add("air_bags.desc", "Research Air Bags as Android, to float in water") { + russian("Исследуйте воздушные мешки, дабы быть вновь поплавком в воде") + }, ) - .addCriterion("air_bags", AndroidResearchTrigger.Instance(modLocation(MNames.AIR_BAGS))) - .save(serializer, modLocation("android/research_air_bags"), existingFileHelper) + .addCriterion("air_bags", AndroidResearchTrigger.Instance(modLocation(MNames.AIR_BAGS)).criterion()) + .save(serializer, modLocation("android/research_air_bags")) AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(Items.ENDER_EYE), - title = TranslatableComponent(translation.add("night_vision", "Second Pair of Eyes")), - description = TranslatableComponent(translation.add("night_vision.desc", "Research Night Vision as Android, to see in the dark")), + title = translation.add("night_vision", "Second Pair of Eyes") { + russian("Вторая пара глаз") + }, + description = translation.add("night_vision.desc", "Research Night Vision as Android, to see in the dark") { + russian("Исследуйте ночное зрение за андроида, дабы видеть во темноте") + }, ) - .addCriterion("night_vision", AndroidResearchTrigger.Instance(modLocation(MNames.NIGHT_VISION))) - .save(serializer, modLocation("android/research_night_vision"), existingFileHelper) + .addCriterion("night_vision", AndroidResearchTrigger.Instance(modLocation(MNames.NIGHT_VISION)).criterion()) + .save(serializer, modLocation("android/research_night_vision")) val nanobots = AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(MItems.MATTER_TRANSFORM_MATRIX), - title = TranslatableComponent(translation.add("nanobots", "Nanomachines, Son!")), - description = TranslatableComponent(translation.add("nanobots.desc", "Research Nanobots as Android, to unlock potent research that come after it")), + title = translation.add("nanobots", "Nanomachines, son.") { + russian("Наномашины, сынок.") + }, + description = translation.add("nanobots.desc", "Research Nanobots as Android, to unlock potent research that come after it") { + russian("Исследуйте наномашины за андроида, для разблокировки очень больших возможностей") + }, hidden = true ) - .addCriterion("nanobots", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS))) - .save(serializer, modLocation("android/research_nanobots"), existingFileHelper) + .addCriterion("nanobots", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS)).criterion()) + .save(serializer, modLocation("android/research_nanobots")) val shielding = AdvancementBuilder() .parent(nanobots) .display( itemStack = ItemStack(Items.SHIELD), - title = TranslatableComponent(translation.add("nanobots_armor_deflect", "Like a Concrete Wall")), - description = TranslatableComponent(translation.add("nanobots_armor_deflect.desc", "Have Nanobots absorb more than 5 hears of damage while you are left still functioning")), + title = translation.add("nanobots_armor_deflect", "Like a Concrete Wall") { + russian("Как за каменной стеной") + }, + description = translation.add("nanobots_armor_deflect.desc", "Have Nanobots absorb more than 5 hears of damage while you are left still functioning") { + russian("Дайте наноботам поглотить 5 сердец урона, не отключившись насовсем") + }, hidden = true, frameType = FrameType.GOAL ) - .addCriterion("damage", NanobotsArmorTrigger.Instance(Doubles.atLeast(10.0))) - .save(serializer, modLocation("android/nanobots_armor_deflect"), existingFileHelper) + .addCriterion("damage", NanobotsArmorTrigger.Instance(Doubles.atLeast(10.0)).criterion()) + .save(serializer, modLocation("android/nanobots_armor_deflect")) AdvancementBuilder() .parent(shielding) .display( itemStack = ItemStack(Items.SHIELD), - title = TranslatableComponent(translation.add("nanobots_armor_deflect2", "Unstoppable Force vs Immovable Object")), - description = TranslatableComponent(translation.add("nanobots_armor_deflect2.desc", "Have Nanobots absorb more than 10 hears of damage while you are left still functioning")), + title = translation.add("nanobots_armor_deflect2", "Unstoppable Force vs Immovable Object") { + russian("Неостановимая сила против недвижимого объекта") + }, + description = translation.add("nanobots_armor_deflect2.desc", "Have Nanobots absorb more than 10 hears of damage while you are left still functioning") { + russian("Дайте наноботам поглотить 10 сердец урона, не отключившись насовсем") + }, hidden = true, frameType = FrameType.CHALLENGE ) - .addCriterion("damage", NanobotsArmorTrigger.Instance(Doubles.atLeast(20.0))) - .save(serializer, modLocation("android/nanobots_armor_deflect2"), existingFileHelper) + .addCriterion("damage", NanobotsArmorTrigger.Instance(Doubles.atLeast(20.0)).criterion()) + .save(serializer, modLocation("android/nanobots_armor_deflect2")) AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(Items.FEATHER), - title = TranslatableComponent(translation.add("fall_dampeners_save", "Lucky Landing")), - description = TranslatableComponent(translation.add("fall_dampeners_save.desc", "Survive fall that would have otherwise be fatal without Fall Dampeners research")), + title = translation.add("fall_dampeners_save", "Lucky Landing") { + russian("Удачное приземление") + }, + description = translation.add("fall_dampeners_save.desc", "Survive fall that would have otherwise be fatal without Fall Dampeners") { + russian("Выживите после падения, которое было бы фатальным без поглотителей инерции") + }, frameType = FrameType.GOAL ) - .addCriterion("saved", FallDampenersSaveTrigger.Instance) - .save(serializer, modLocation("android/fall_dampeners_save"), existingFileHelper) + .addCriterion("saved", FallDampenersSaveTrigger.criterion) + .save(serializer, modLocation("android/fall_dampeners_save")) AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(Items.SKELETON_SKULL), - title = TranslatableComponent(translation.add("ender_teleport_fall_death", "Navigation Error")), - description = TranslatableComponent(translation.add("ender_teleport_fall_death.desc", "Fall to your death moments after Teleporting")), + title = translation.add("ender_teleport_fall_death", "Navigation Error") { + russian("Ошибка навигации") + }, + description = translation.add("ender_teleport_fall_death.desc", "Fall to your demise moments after Teleporting as Android") { + russian("Разбейтесь насмерть через мгновения после телепортации за андроида") + }, frameType = FrameType.GOAL, hidden = true ) - .addCriterion("death", EnderTeleporterFallDeathTrigger.Instance) - .save(serializer, modLocation("android/ender_teleport_fall_death"), existingFileHelper) + .addCriterion("death", EnderTeleporterFallDeathTrigger.criterion) + .save(serializer, modLocation("android/ender_teleport_fall_death")) val regen = AdvancementBuilder() .parent(nanobots) .display( itemStack = ItemStack(Items.GOLDEN_APPLE), - title = TranslatableComponent(translation.add("regen", "Field Repair Done Easy")), - description = TranslatableComponent(translation.add("regen.desc", "Research Nanobots Regeneration as Android")), + title = translation.add("regen", "Field Repair Done Easy") { + russian("Починка на ходу - легко") + }, + description = translation.add("regen.desc", "Research Nanobots Regeneration as Android") { + russian("Исследуйте регенерацию наноботов за андроида") + }, ) - .addCriterion("regen0", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_1))) - .save(serializer, modLocation("android/regen"), existingFileHelper) + .addCriterion("regen0", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_1)).criterion()) + .save(serializer, modLocation("android/regen")) AdvancementBuilder() .parent(regen) .display( itemStack = ItemStack(Items.ENCHANTED_GOLDEN_APPLE), - title = TranslatableComponent(translation.add("regen_all", "Field Repair Done Effortless")), - description = TranslatableComponent(translation.add("regen_all.desc", "Max out Nanobots Regeneration research")), + title = translation.add("regen_all", "Field Repair Done Effortless") { + russian("Починка на ходу - без усилий") + }, + description = translation.add("regen_all.desc", "Max out Nanobots Regeneration research") { + russian("Полностью исследуйте регенерацию наноботов за андроида") + }, frameType = FrameType.GOAL, ) - .addCriterion("regen0", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_1))) - .addCriterion("regen1", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_2))) - .addCriterion("regen2", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_3))) - .addCriterion("regen3", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_4))) - .save(serializer, modLocation("android/regen_all"), existingFileHelper) + .addCriterion("regen0", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_1)).criterion()) + .addCriterion("regen1", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_2)).criterion()) + .addCriterion("regen2", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_3)).criterion()) + .addCriterion("regen3", AndroidResearchTrigger.Instance(modLocation(MNames.NANOBOTS_REGENERATION_4)).criterion()) + .save(serializer, modLocation("android/regen_all")) AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(MItems.ANDROID_STATION), - title = TranslatableComponent(translation.add("research_all", "Mecha-agnomination")), - description = TranslatableComponent(translation.add("research_all.desc", "Research everything (that don't block any other research)")), + title = translation.add("research_all", "Mecha-Agnomination") { + russian("Меха-зумие") + }, + description = translation.add("research_all.desc", "Research everything as Android (that don't block or get blocked by any other research)") { + russian("Исследуйте все технологии за андроида (которые не блокируют и не блокируются другими технологиями)") + }, frameType = FrameType.CHALLENGE ) .rewards(AdvancementRewards.Builder.experience(400).addLootTable(modLocation("research_all_android"))) @@ -268,29 +312,106 @@ fun addAndroidAdvancements(serializer: Consumer, existingFileHelper DataGen.researchProvider.generatedView.stream() .filter { it.allBlockedBy.isEmpty() && it.allBlocking.isEmpty() } .forEach { - advancement.addCriterion(it.id.toString(), AndroidResearchTrigger.Instance(it)) + advancement.addCriterion(it.id.toString(), AndroidResearchTrigger.Instance(it).criterion()) } } - .save(serializer, modLocation("android/research_everything"), existingFileHelper) + .save(serializer, modLocation("android/research_everything")) val shockwave = AdvancementBuilder() .parent(researchAnything) .display( itemStack = ItemStack(Items.PISTON), - title = TranslatableComponent(translation.add("shockwave", "Supersonic Landing")), - description = TranslatableComponent(translation.add("shockwave.desc", "Perform a Shockwave upon landing")), + title = translation.add("shockwave", "Supersonic Landing") { + russian("Сверхзвуковое приземление") + }, + description = translation.add("shockwave.desc", "Make a Shockwave upon landing") { + russian("Вызовите ударную волну при приземлении") + }, ) - .addCriterion("shockwave", ShockwaveTrigger.Instance) - .save(serializer, modLocation("android/shockwave"), existingFileHelper) + .addCriterion("shockwave", ShockwaveTrigger.criterion) + .save(serializer, modLocation("android/shockwave")) AdvancementBuilder() .parent(shockwave) .display( itemStack = ItemStack(Items.WARDEN_SPAWN_EGG), - title = TranslatableComponent(translation.add("shockwave_warden", "Music To Their Ears")), - description = TranslatableComponent(translation.add("shockwave_warden.desc", "Hurt Warden using Shockwave ability")), + title = translation.add("shockwave_warden", "Music to their Ears") { + russian("Музыка для их ушей") + }, + description = translation.add("shockwave_warden.desc", "Hurt Warden using Shockwave ability") { + russian("Нанесите хранителю урон используя ударную волну") + }, frameType = FrameType.GOAL ) - .addCriterion("shockwave_warden", ShockwaveDamageMobTrigger.Instance(EntityPredicate.Builder.entity().of(EntityType.WARDEN).build().wrap())) - .save(serializer, modLocation("android/shockwave_warden"), existingFileHelper) + .addCriterion("shockwave_warden", ShockwaveDamageMobTrigger.Instance(Optional.of(EntityPredicate.Builder.entity().of(EntityType.WARDEN).build().wrap())).criterion()) + .save(serializer, modLocation("android/shockwave_warden")) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.WITHER_SKELETON_SKULL), + title = translation.add("wither", "Not Quite Alive, Not Quite Undead") { + russian("Ни живой, ни мёртвый") + }, + description = translation.add("wither.desc", "Defeat The Wither as Android. The Wither was surely confused over kind of thing you are") { + russian("Победите Иссушителя будучи андроидом. Наверняка Иссушитель был ошеломлён таким раскладом дел") + }, + frameType = FrameType.GOAL, + hidden = true + ) + .addCriterion("kill_wither", KillAsAndroidTrigger.Instance( + predicate = Optional.of(EntityPredicate.Builder.entity().of(EntityType.WITHER).build().wrap()), + ).criterion()) + .save(serializer, modLocation("android/wither")) + + val underwater = AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.TURTLE_SPAWN_EGG), + title = translation.add("travel_underwater", "Underwater Walk") { + russian("Подводная прогулка") + }, + description = translation.add("travel_underwater.desc", "Travel at least 200 meters underwater as Android without Air Bags research. This reminds us of someone...") { + russian("Преодолейте как минимум 200 метров под водой будучи андроидом без исследования воздушных мешков. Кого-то это нам напоминает...") + }, + frameType = FrameType.GOAL, + hidden = true + ) + .addCriterion("travel", AndroidTravelUnderwater.Instance(200.0).criterion()) + .save(serializer, modLocation("android/underwater")) + + AdvancementBuilder() + .parent(underwater) + .display( + itemStack = ItemStack(Items.TURTLE_SPAWN_EGG), + title = translation.add("travel_underwater2", "Underwater Travel") { + russian("Подводная прогулка") + }, + description = translation.add("travel_underwater2.desc", "Travel at least 1046 meters underwater as Android without Air Bags research, like someone else did so") { + russian("Преодолейте как минимум 1046 метров под водой будучи андроидом без исследования воздушных мешков, прям как тот, кто так однажды так и сделал") + }, + frameType = FrameType.CHALLENGE, + hidden = true + ) + .addCriterion("travel", AndroidTravelUnderwater.Instance(1046.0).criterion()) + .save(serializer, modLocation("android/underwater2")) + + AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(Items.PRISMARINE_CRYSTALS), + title = translation.add("elder_guardian", "Drowned, but Still Determined") { + russian("Затонувший, но всё ещё целеустремлённый") + }, + description = translation.add("elder_guardian.desc", "Slay Elder Guardian as Android without Air Bags researched") { + russian("Победите Древнего стража будучи андроидом без исследования воздушных мешков") + }, + frameType = FrameType.CHALLENGE, + hidden = true + ) + .addCriterion("kill_elder_guardian", KillAsAndroidTrigger.Instance( + predicate = Optional.of(EntityPredicate.Builder.entity().of(EntityType.ELDER_GUARDIAN).build().wrap()), + featurePredicate = KillAsAndroidTrigger.Not(KillAsAndroidTrigger.Has(AndroidFeatures.AIR_BAGS.registryName!!)) + ).criterion()) + .save(serializer, modLocation("android/elder_guardian")) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/ExopackAdvancementsData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/ExopackAdvancementsData.kt new file mode 100644 index 000000000..8f228548e --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/ExopackAdvancementsData.kt @@ -0,0 +1,262 @@ +package ru.dbotthepony.mc.otm.datagen.advancements + +import net.minecraft.advancements.FrameType +import net.minecraft.advancements.critereon.ItemPredicate +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider +import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedCraftingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedEnderAccessTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger +import java.util.function.Consumer + +fun addExopackAdvancements(serializer: Consumer, lang: MatteryLanguageProvider, root: AdvancementHolder, zpm: AdvancementHolder) { + val translation = lang.MultiBuilder("otm.advancements.exopack") + + AdvancementBuilder() + .parent(zpm) + .display( + hidden = true, + itemStack = ItemStack(MItems.ZPM_BATTERY), + title = translation.add("zpm_battery", "At Maximum Battery Capacity") { + russian("Достигнут максимальный заряд батареи") + }, + description = translation.add("zpm_battery.desc", "Use Zero Point Module as power source in Exopack") { + russian("Используйте модуль нулевой точки как источник питания в Экзопаке") + }, + frameType = FrameType.GOAL + ) + .addCriterion("zpm_battery", ExopackBatterySlotTrigger.Instance(ItemPredicate.Builder.item().of(MItems.ZPM_BATTERY).build()).criterion()) + .save(serializer, modLocation("exopack/zpm_battery")) + + val obtained = AdvancementBuilder() + .parent(root) + .display( + itemStack = ItemStack(MItems.EXOPACK_PROBE), + title = translation.add("obtained", "One Dimension Bigger") { + russian("Больше на одно измерение") + }, + description = translation.add("obtained.desc", "Obtain an Exopack, a mysterious, semi-present, light as a feather, stuff storage on your back") { + russian("Получите Экзопак, загадочное, полу-присутствующее, лёгкое как пёрышко, хранилище штуковин на вашей спине") + }, + frameType = FrameType.GOAL + ) + .addCriterion("obtained", ExopackObtainedTrigger.criterion) + .save(serializer, modLocation("exopack/obtained")) + + AdvancementBuilder() + .parent(obtained) + .display( + itemStack = ItemStack(Items.CRAFTING_TABLE), + title = translation.add("crafting", "Crafting on Go") { + russian("Крафт по пути") + }, + description = translation.add("crafting.desc", "Install Crafting Upgrade in your Exopack, allowing to craft 3x3 recipes") { + russian("Установите улучшение сетки крафта в ваш Экзопаке, который позволяет создавать предметы, требующие сетку крафта рабочего стола") + }, + ) + .addCriterion("crafting", ExopackGainedCraftingTrigger.criterion) + .save(serializer, modLocation("exopack/crafting")) + + AdvancementBuilder() + .parent(obtained) + .display( + itemStack = ItemStack(Items.FURNACE), + title = translation.add("smelting", "Pocket Furnace") { + russian("Печь в кармане") + }, + description = translation.add("smelting.desc", "Install Smelting Module in your Exopack, allowing to smelt items right inside your inventory") { + russian("Установите модуль переплавки в ваш Экзопак, позволяющий переплавлять предметы прямо у вас в инвентаре") + }, + ) + .addCriterion("smelting", ExopackGainedSmeltingTrigger.criterion) + .save(serializer, modLocation("exopack/smelting")) + + AdvancementBuilder() + .parent(obtained) + .display( + itemStack = ItemStack(Items.ENDER_CHEST), + title = translation.add("ender_access", "Ender-ious Access") { + russian("Эендер-иумый Доступ") + }, + description = translation.add("ender_access.desc", "Gain direct access to your Ender Chest out of your Exopack") { + russian("Получите прямой доступ к содержимому вашего сундука края прямо из Экзопака") + }, + ) + .addCriterion("ender_access", ExopackGainedEnderAccessTrigger.criterion) + .save(serializer, modLocation("ender_access/smelting")) + + var size = AdvancementBuilder() + .parent(obtained) + .display( + itemStack = ItemStack(Items.CHEST), + title = translation.add("size0", "Closet Upgrade") { + russian("Обновление чуланчика") + }, + description = translation.add("size0.desc", "Upgrade Exopack storage") { + russian("Улучшите размер инвентаря Экзопака") + }, + ) + .addCriterion("size0", ExopackSlotsExpandedTrigger.Instance(minTotal = 1).criterion()) + .save(serializer, modLocation("exopack/size0")) + + val size0 = size + + size = AdvancementBuilder() + .parent(size) + .display( + itemStack = ItemStack(Items.CHEST), + title = translation.add("size1", "Double the Capacity") { + russian("Двойной объём") + }, + description = translation.add("size1.desc", "Reach 27 slots in your Exopack storage") { + russian("Достигните 27 слотов хранилища Экзопака") + }, + ) + .addCriterion("size1", ExopackSlotsExpandedTrigger.Instance(minTotal = 27).criterion()) + .save(serializer, modLocation("exopack/size1")) + + size = AdvancementBuilder() + .parent(size) + .display( + itemStack = ItemStack(Items.CHEST), + hidden = true, + title = translation.add("size2", "Pack Rat") { + russian("Воришка") + }, + description = translation.add("size2.desc", "Reach 54 slots in your Exopack storage") { + russian("Достигните 54 слотов хранилища Экзопака") + }, + ) + .addCriterion("size2", ExopackSlotsExpandedTrigger.Instance(minTotal = 54).criterion()) + .save(serializer, modLocation("exopack/size2")) + + size = AdvancementBuilder() + .parent(size) + .display( + itemStack = ItemStack(Items.CHEST), + hidden = true, + title = translation.add("size3", "Its Getting Big In Here") { + russian("Тут становится просторно") + }, + description = translation.add("size3.desc", "Reach 108 slots in your Exopack storage") { + russian("Достигните 108 слотов хранилища Экзопака") + }, + frameType = FrameType.GOAL + ) + .addCriterion("size3", ExopackSlotsExpandedTrigger.Instance(minTotal = 108).criterion()) + .save(serializer, modLocation("exopack/size3")) + + size = AdvancementBuilder() + .parent(size) + .display( + itemStack = ItemStack(Items.ENDER_CHEST), + hidden = true, + title = translation.add("size4", "Warehousing Pioneer") { + russian("Пионер складских решений") + }, + description = translation.add("size4.desc", "Reach 432 slots in your Exopack storage. You could fit a house in there") { + russian("Достигните 432 слотов хранилища Экзопака. Туда можно уже впихнуть целый дом") + }, + frameType = FrameType.CHALLENGE + ) + .addCriterion("size4", ExopackSlotsExpandedTrigger.Instance(minTotal = 432).criterion()) + .save(serializer, modLocation("exopack/size4")) + + AdvancementBuilder() + .parent(size) + .display( + itemStack = ItemStack(Items.ENDER_CHEST), + hidden = true, + title = translation.add("size5", "With Volume Like This...") { + russian("С таким объёмом...") + }, + description = translation.add("size5.desc", "Reach 1728 slots in your Exopack storage. Why would you need to go any bigger?!") { + russian("Достигните 1728 слотов хранилища Экзопака. Куда вам столько?!") + }, + frameType = FrameType.CHALLENGE + ) + .addCriterion("size5", ExopackSlotsExpandedTrigger.Instance(minTotal = 1728).criterion()) + .save(serializer, modLocation("exopack/size5")) + + var once = AdvancementBuilder() + .parent(size0) + .display( + itemStack = ItemStack(Items.CHEST), + title = translation.add("once0", "One Module - One Row") { + russian("Один модуль - одна строка") + }, + description = translation.add("once0.desc", "Upgrade your Exopack storage with 9 slots using one module") { + russian("Улучшите хранилище Экзопака модулем на 9 слотов") + }, + ) + .addCriterion("once0", ExopackSlotsExpandedTrigger.Instance(minGained = 9).criterion()) + .save(serializer, modLocation("exopack/once0")) + + once = AdvancementBuilder() + .parent(once) + .display( + itemStack = ItemStack(Items.CHEST), + title = translation.add("once1", "One Module - One Chest") { + russian("Один модуль - один сундук") + }, + description = translation.add("once1.desc", "Upgrade your Exopack storage with 27 slots using one module") { + russian("Улучшите хранилище Экзопака модулем на 27 слотов") + }, + ) + .addCriterion("once1", ExopackSlotsExpandedTrigger.Instance(minGained = 27).criterion()) + .save(serializer, modLocation("exopack/once1")) + + once = AdvancementBuilder() + .parent(once) + .display( + itemStack = ItemStack(Items.CHEST), + hidden = true, + title = translation.add("once2", "One Module - Two Chests?") { + russian("Один модуль - два сундука?") + }, + description = translation.add("once2.desc", "Upgrade your Exopack storage with 54 slots using one module") { + russian("Улучшите хранилище Экзопака модулем на 54 слотов") + }, + frameType = FrameType.GOAL + ) + .addCriterion("once2", ExopackSlotsExpandedTrigger.Instance(minGained = 54).criterion()) + .save(serializer, modLocation("exopack/once2")) + + once = AdvancementBuilder() + .parent(once) + .display( + itemStack = ItemStack(Items.ENDER_CHEST), + hidden = true, + title = translation.add("once3", "Storage Housing Construct") { + russian("Хранилище быстрого приготовления") + }, + description = translation.add("once3.desc", "Upgrade your Exopack storage with 90 slots using one module") { + russian("Улучшите хранилище Экзопака модулем на 90 слотов") + }, + frameType = FrameType.GOAL + ) + .addCriterion("once3", ExopackSlotsExpandedTrigger.Instance(minGained = 90).criterion()) + .save(serializer, modLocation("exopack/once3")) + + AdvancementBuilder() + .parent(once) + .display( + itemStack = ItemStack(Items.ENDER_CHEST), + hidden = true, + title = translation.add("once4", "Non-Euclidean Wardrobe") { + russian("Неевклидов Шкаф") + }, + description = translation.add("once4.desc", "Upgrade your Exopack storage with 150 slots using one module. After you open one, tens meters long racks roll out of it!") { + russian("Улучшите хранилище Экзопака модулем на 150 слотов. Открой один - и покатились стеллажи на десятки метров!") + }, + frameType = FrameType.CHALLENGE + ) + .addCriterion("once4", ExopackSlotsExpandedTrigger.Instance(minGained = 150).criterion()) + .save(serializer, modLocation("exopack/once4")) +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/Helpers.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/Helpers.kt index 4a5289f30..bfb78c050 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/Helpers.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/Helpers.kt @@ -14,6 +14,7 @@ import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.level.ItemLike import ru.dbotthepony.mc.otm.core.TextComponent +import java.util.function.Consumer fun AdvancementBuilder(): Advancement.Builder = Advancement.Builder.advancement() @@ -69,3 +70,5 @@ fun criterion(item: ItemLike): CriterionTriggerInstance { } fun EntityPredicate.wrap(): EntityPredicate.Composite = EntityPredicate.Composite.wrap(this) + +fun Advancement.Builder.save(advancement: Consumer, name: ResourceLocation) = save(advancement, name.toString()) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/MachineAdvancementsData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/MachineAdvancementsData.kt index 6cb958c8b..0181180fd 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/MachineAdvancementsData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/advancements/MachineAdvancementsData.kt @@ -1,85 +1,193 @@ package ru.dbotthepony.mc.otm.datagen.advancements import net.minecraft.advancements.Advancement -import net.minecraft.advancements.RequirementsStrategy +import net.minecraft.advancements.FrameType +import net.minecraft.advancements.critereon.ItemPredicate import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.data.ExistingFileHelper +import net.minecraft.world.item.Items import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.key import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.lang.MatteryLanguageProvider import ru.dbotthepony.mc.otm.datagen.modLocation -import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.triggers.TakeItemOutOfReplicatorTrigger import java.util.function.Consumer -private data class CraftEntry(val item: Item, val englishName: String, val englishSuffix: String? = null) +typealias AdvancementHolder = Advancement -fun addMachineAdvancements(serializer: Consumer, existingFileHelper: ExistingFileHelper, lang: MatteryLanguageProvider) { - val translation = lang.english.Prepended("otm.advancements.machine") +private data class CraftEntry( + val item: Item, + val englishName: String, + val englishSuffix: String? = null, - val root = AdvancementBuilder() - .requirements(RequirementsStrategy.OR) - .display( - itemStack = ItemStack(MItems.CHEMICAL_GENERATOR), - title = TranslatableComponent(translation.add("root", "Tritanium Empowered Machinery")), - description = TranslatableComponent(translation.add("root.desc", "Do not drop in anything if you want the latter preserved intact")), - showToast = false, - announceChat = false, - background = modLocation("textures/block/decorative/floor_tiles_gray.png") - ) - .addCriterion("has_machine", criterion(MItemTags.MACHINES)) - .addCriterion("has_tritanium_ingot", criterion(MItemTags.TRITANIUM_INGOTS)) - .addCriterion("has_tritanium_plate_somehow", criterion(MItemTags.TRITANIUM_PLATES)) - .save(serializer, modLocation("machines/root"), existingFileHelper) + val russianName: String? = null, + val russianSuffix: String? = null, +) { + fun make(serializer: Consumer, parent: AdvancementHolder, translation: MatteryLanguageProvider.MultiBuilder): AdvancementHolder { + val path = item.registryName!!.path + + val translated = translation.add("$path.desc", "Craft a %s%s") { + russian("Создайте %s%s") + } + + val translatedSuffix = translation.add("$path.suffix", if (englishSuffix != null) ". $englishSuffix" else "") { + russian(if (russianSuffix != null) ". $russianSuffix" else "") + } + + return AdvancementBuilder() + .parent(parent) + .display( + itemStack = ItemStack(item), + title = translation.add(path, englishName) { + if (russianName != null) { + russian(russianName) + } + }, + description = TranslatableComponent(translated.contents.key, item.description, translatedSuffix), + ) + .addCriterion("has_machine", criterion(item)) + .save(serializer, modLocation("machines/$path")) + } +} + +fun addMachineAdvancements(serializer: Consumer, lang: MatteryLanguageProvider, root: AdvancementHolder) { + val translation = lang.MultiBuilder("otm.advancements.machine") val chem = AdvancementBuilder() .parent(root) .display( itemStack = ItemStack(MItems.CHEMICAL_GENERATOR), - title = TranslatableComponent(translation.add("chemical_generator", "Burning the Organics")), - description = TranslatableComponent(translation.add("chemical_generator.desc", "Craft a Chemical Generator, better to put it outside")), + title = translation.add("chemical_generator", "Burning the Organics") { + russian("Сжигание органики") + }, + description = translation.add("chemical_generator.desc", "Craft a Chemical Generator, better to put it outside") { + russian("Создайте химический генератор. Лучше установить его снаружи") + }, ) .addCriterion("has_machine", criterion(MItems.CHEMICAL_GENERATOR)) - .save(serializer, modLocation("machines/chemical_generator"), existingFileHelper) + .save(serializer, modLocation("machines/chemical_generator")) val press = AdvancementBuilder() .parent(chem) .display( itemStack = ItemStack(MItems.PLATE_PRESS), - title = TranslatableComponent(translation.add("plate_press", "Bending the Material")), - description = TranslatableComponent(translation.add("plate_press.desc", "Craft a Plate Press, avoid having limbs inside during operation")), + title = translation.add("plate_press", "Bending the Material") { + russian("Раскатка металла") + }, + description = translation.add("plate_press.desc", "Craft a Plate Press, avoid having limbs inside at all times") { + russian("Создайте пресс пластин, не суйте свои или чужие конечности внутрь") + }, ) .addCriterion("has_machine", criterion(MItems.PLATE_PRESS)) - .save(serializer, modLocation("machines/plate_press"), existingFileHelper) + .save(serializer, modLocation("machines/plate_press")) - val entries = listOf( - CraftEntry(MItems.MATTER_SCANNER, "Scanning Things that Matter"), - CraftEntry(MItems.PATTERN_STORAGE, "Digital Knowledge Library"), - CraftEntry(MItems.MATTER_DECOMPOSER, "Decaying the Atoms", "Keep your limbs outside of the working chamber at all times"), - CraftEntry(MItems.MATTER_PANEL, "Indexing the Library"), - CraftEntry(MItems.MATTER_REPLICATOR, "Local Bakery", "Now let's bake some perfect bread"), - CraftEntry(MItems.MATTER_BOTTLER, "Transfusing Pure Matter", "For those who loved to play with water in their childhood"), - CraftEntry(MItems.MATTER_RECYCLER, "Refine and Redefine", "This is what waste recycling should look like"), - CraftEntry(MItems.MATTER_CAPACITOR_BANK, "Modular Matter Tank"), + CraftEntry(MItems.TWIN_PLATE_PRESS, "Twice the Thud", + russianName = "Двойной стук").make(serializer, press, translation) - CraftEntry(MItems.ENERGY_COUNTER, "Visualize Power Burn"), - CraftEntry(MItems.BATTERY_BANK, "Batteries Not Included", "By all means avoid the urge to hammer incompatible batteries into the power bus."), - ) + val scanner = CraftEntry(MItems.MATTER_SCANNER, "Scanning Things that Matter", + russianName = "Сканируем вещи которые материальны") + val decomposer = CraftEntry(MItems.MATTER_DECOMPOSER, "Decaying the Atoms", "Keep your limbs outside of the working chamber at all times", + russianName = "Разлагаем атомы", russianSuffix = "Во всех ситуациях держите свои конечности вне рабочей камеры") + val panel = CraftEntry(MItems.MATTER_PANEL, "Indexing the Library", + russianName = "Индексируем библиотеку") + val replicator = CraftEntry(MItems.MATTER_REPLICATOR, "Cook with (Im)Perfection", "Now let's bake some perfect bread", + russianName = "Повар с (не) идеальностями", russianSuffix = "А теперь давайте выпечем немного идеального хлеба") + val bottler = CraftEntry(MItems.MATTER_BOTTLER, "Transfusing Pure Matter", "For those who loved to play with water in their childhood", + russianName = "Переливаем чистую материю", russianSuffix = "Для тех, кто любил играться в воде в детстве") + val recycler = CraftEntry(MItems.MATTER_RECYCLER, "Refine and Redefine", "This is what waste recycling should look like", + russianName = "Переработка и перегонка", russianSuffix = "Вот он, пик переработки отходов") + val capacitor = CraftEntry(MItems.MATTER_CAPACITOR_BANK, "Modular Matter Tank", + russianName = "Модульный бак материи") - val built = mutableMapOf() + val counter = CraftEntry(MItems.ENERGY_COUNTER, "Visualize Power Burn", + russianName = "Визуализация сжигания энергии") + val battery = CraftEntry(MItems.BATTERY_BANK, "Batteries Not Included", "By all means avoid the urge to hammer incompatible batteries into the power bus.", + russianName = "Батарейки в комплект не входят", russianSuffix = "Пожалуйста, воздержитесь от вбивания кувалдой несовместимых батарей в энергетическую шину.") - for (entry in entries) { - val path = entry.item.registryName!!.path + val pattern = CraftEntry(MItems.PATTERN_STORAGE, "Digital Knowledge Library", + russianName = "Цифровая библиотека знаний") - built[entry.item] = AdvancementBuilder() - .parent(press) - .display( - itemStack = ItemStack(entry.item), - title = TranslatableComponent(translation.add(path, entry.englishName)), - description = TranslatableComponent(translation.add("$path.desc", "Craft a %s%s"), entry.item.description, if (entry.englishSuffix != null) ". " + entry.englishSuffix else ""), - ) - .addCriterion("has_machine", criterion(entry.item)) - .save(serializer, modLocation("machines/$path"), existingFileHelper) + val reconstructor = CraftEntry(MItems.MATTER_RECONSTRUCTOR, "Flipping Hourglass", + russianName = "Переворачиваем песочные часы") + + decomposer.make(serializer, press, translation).also { + pattern.make(serializer, it, translation).also { + scanner.make(serializer, it, translation) + panel.make(serializer, it, translation) + + replicator.make(serializer, it, translation).also { + AdvancementBuilder() + .parent(it) + .display( + itemStack = ItemStack(Items.BREAD), + title = translation.add("replicate_something", "Local Bakery") { + russian("Местная выпечка") + }, + description = translation.add("replicate_something.desc", "Replicate something using Matter Replicator. If you replicated some food, be first to taste it among your company") { + russian("Среплицируйте что либо используя репликатор материи. Если это еда, то не стесняйтесь быть первым, кто попробует её на вкус среди вашей компании") + }, + frameType = FrameType.GOAL + ) + .addCriterion("replicate_something", TakeItemOutOfReplicatorTrigger.Instance(ItemPredicate.Builder.item().of(MItems.MATTER_DUST).build(), true).criterion()) + .save(serializer, modLocation("machines/replicate_something")) + + AdvancementBuilder() + .parent(it) + .display( + hidden = true, + itemStack = ItemStack(MItems.MATTER_DUST), + title = translation.add("replicate_failure", "Unhealthy Flavor") { + russian("Вредная посыпка") + }, + description = translation.add("replicate_failure.desc", "Experience replication failure and have your thing turn into matter dust") { + russian("Наблюдайте неудачный результат репликации, где ваш заказ рассыпался в материальную труху") + }, + ) + .addCriterion("replicate_failure", TakeItemOutOfReplicatorTrigger.Instance(ItemPredicate.Builder.item().of(MItems.MATTER_DUST).build()).criterion()) + .save(serializer, modLocation("machines/replicate_failure")) + } + + reconstructor.make(serializer, it, translation) + } + + bottler.make(serializer, it, translation) + recycler.make(serializer, it, translation) + capacitor.make(serializer, it, translation) + } + + counter.make(serializer, press, translation).also { + battery.make(serializer, it, translation) + } + + val station = CraftEntry(MItems.ANDROID_STATION, "Android Home Page", + russianName = "Домашняя страница андроидов", + russianSuffix = "Только пользоваться этим устройством могут вёдра с болтами", + englishSuffix = "Except only buckets of bolts can use this thing") + + val charger = CraftEntry(MItems.ANDROID_CHARGER, "Android Home Router", + russianName = "Домашняя страница андроидов") + + station.make(serializer, press, translation).also { + charger.make(serializer, it, translation) + } + + CraftEntry(MItems.COBBLESTONE_GENERATOR, "Cobblestone: Infinity + 1", + russianName = "Булыжник: бесконечность + 1", + russianSuffix = "Смотрите, чтоб он не просыпался во все сундуки", + englishSuffix = "Watch for not to spill it over all your chests").make(serializer, press, translation) + + CraftEntry(MItems.POWERED_FURNACE, "One Big Resistor", + russianName = "Один большой резистор", + russianSuffix = "Каждый элемент электрической цепи способен испускать свет и тепло, единожды.", + englishSuffix = "Any electrical element can emit light and heat, once.") + .make(serializer, press, translation) + .also { + CraftEntry(MItems.POWERED_BLAST_FURNACE, "Big Microwave Oven", + russianName = "Большая микроволновая печь").make(serializer, it, translation) + + CraftEntry(MItems.POWERED_SMOKER, "Small Microwave Oven", + russianName = "Маленькая микроволновая печь").make(serializer, it, translation) } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/Banks.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/Banks.kt index 6fc3c2bb1..7302eeb0c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/Banks.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/Banks.kt @@ -1,13 +1,14 @@ package ru.dbotthepony.mc.otm.datagen.blocks -import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.block.Block import net.minecraftforge.client.model.generators.BlockStateProvider +import net.minecraftforge.client.model.generators.ConfiguredModel import net.minecraftforge.data.event.GatherDataEvent -import ru.dbotthepony.mc.otm.block.BatteryBankBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth import ru.dbotthepony.mc.otm.datagen.DataGen -import ru.dbotthepony.mc.otm.datagen.toYRotBlockstate +import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MBlocks private fun nothingOrNumber(input: Int): String { @@ -17,29 +18,25 @@ private fun nothingOrNumber(input: Int): String { return (input - 1).toString() } -open class BatteryBankProvider(event: GatherDataEvent) : BlockStateProvider(event.generator, DataGen.MOD_ID, event.existingFileHelper) { +open class BatteryBankProvider(event: GatherDataEvent) : BlockStateProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) { protected var block = "battery_bank" protected var batteryPath = "block/battery/battery" protected var registry: Block = MBlocks.BATTERY_BANK override fun registerStatesAndModels() { - with(getMultipartBuilder(registry)) { - val battery_bank = models().getExistingFile(ResourceLocation("overdrive_that_matters:block/$block")) - - RotatableMatteryBlock.FACING.possibleValues.forEach { - part().modelFile(battery_bank).rotationY(it.toYRotBlockstate()).addModel().condition( - RotatableMatteryBlock.FACING, it) - - for (i in 0 .. 11) { - part().modelFile( - models().getExistingFile(ResourceLocation("overdrive_that_matters:$batteryPath$i")) - ).rotationY(it.toYRotBlockstate()).addModel() - .condition(RotatableMatteryBlock.FACING, it) - .condition(BatteryBankBlock.BATTERY_SLOTS_PROPS[i], true) - } + with(getVariantBuilder(registry)) { + forAllStates { + ConfiguredModel.builder() + .modelFile(models().getExistingFile(modLocation("block/$block"))) + .rotationY(it[BlockRotationFreedom.HORIZONTAL.property].front.yRotationBlockstateNorth()) + .build() } } } + + override fun getName(): String { + return "Battery Bank Model Provider" + } } class MatterBankProvider(event: GatherDataEvent) : BatteryBankProvider(event) { @@ -48,4 +45,8 @@ class MatterBankProvider(event: GatherDataEvent) : BatteryBankProvider(event) { batteryPath = "block/battery/matter_capacitor" registry = MBlocks.MATTER_CAPACITOR_BANK } + + override fun getName(): String { + return "Matter Bank Model Provider" + } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/BlockStates.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/BlockStates.kt index e4db7d8c1..780b7f7ac 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/BlockStates.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/BlockStates.kt @@ -1,21 +1,20 @@ package ru.dbotthepony.mc.otm.datagen.blocks -import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.block.state.properties.BlockStateProperties import net.minecraft.world.level.block.state.properties.DoubleBlockHalf import net.minecraftforge.client.model.generators.ConfiguredModel -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.block.CableBlock -import ru.dbotthepony.mc.otm.block.CargoCrateBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock +import ru.dbotthepony.mc.otm.block.tech.AndroidChargerBlock +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.xRotationBlockstateNorth +import ru.dbotthepony.mc.otm.core.math.xRotationBlockstateSouth +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateSouth import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.modLocation -import ru.dbotthepony.mc.otm.datagen.toXRotBlockstate -import ru.dbotthepony.mc.otm.datagen.toXRotBlockstateInv -import ru.dbotthepony.mc.otm.datagen.toYRotBlockstate -import ru.dbotthepony.mc.otm.datagen.toYRotBlockstateInv import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.registry.MRegistry @@ -28,48 +27,63 @@ fun addBlockStates(provider: MatteryBlockStateProvider) { provider.ore(MBlocks.TRITANIUM_ORE) provider.ore(MBlocks.TRITANIUM_RAW_BLOCK) provider.block(MBlocks.TRITANIUM_INGOT_BLOCK) + provider.block(MBlocks.METAL_MESH) provider.block(MBlocks.CHEMICAL_GENERATOR) provider.block(MBlocks.MATTER_SCANNER) provider.block(MBlocks.ITEM_MONITOR) + provider.block(MBlocks.HOLO_SIGN) provider.exec { with(provider.getMultipartBuilder(MBlocks.PHANTOM_ATTRACTOR)) { - for (dir in RotatableMatteryBlock.FACING.possibleValues) { + for (dir in BlockRotationFreedom.HORIZONTAL.possibleValues) { part().modelFile(provider.models().getExistingFile(modLocation("block/${MNames.PHANTOM_ATTRACTOR}"))) - .rotationY(dir.toYRotBlockstate()) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() .condition(BlockStateProperties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER) - .condition(RotatableMatteryBlock.FACING, dir) + .condition(BlockRotationFreedom.HORIZONTAL.property, dir) .end() } } - with(provider.getMultipartBuilder(MBlocks.MATTER_BOTTLER)) { - for (dir in RotatableMatteryBlock.FACING.possibleValues) { - for (enum in WorkerState.SEMI_WORKER_STATE.possibleValues) { - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_bottler_${enum.name.lowercase()}"))) - .rotationY(dir.toYRotBlockstate()) + with(provider.getMultipartBuilder(MBlocks.ANDROID_CHARGER)) { + for (dir in BlockRotationFreedom.HORIZONTAL.possibleValues) { + for (part in AndroidChargerBlock.PART.possibleValues) { + part().modelFile(provider.models().getExistingFile(modLocation("block/${MNames.ANDROID_CHARGER}_${part.serializedName}"))) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, dir) + .condition(AndroidChargerBlock.PART, part) + .condition(BlockRotationFreedom.HORIZONTAL.property, dir) + .end() + } + } + } + + with(provider.getMultipartBuilder(MBlocks.MATTER_BOTTLER)) { + for (dir in BlockRotationFreedom.HORIZONTAL.possibleValues) { + for (enum in WorkerState.SEMI_WORKER_STATE.possibleValues) { + part().modelFile(provider.models().getExistingFile(modLocation("matter_bottler_${enum.name.lowercase()}"))) + .rotationY(dir.front.yRotationBlockstateNorth()) + .addModel() + .condition(BlockRotationFreedom.HORIZONTAL.property, dir) .condition(WorkerState.WORKER_STATE, enum) .end() } } - for (dir in RotatableMatteryBlock.FACING.possibleValues) { + for (dir in BlockRotationFreedom.HORIZONTAL.possibleValues) { for (enum in MatterBottlerBlock.SLOT_PROPERTIES) { - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_bottler_${enum.name}_open"))) - .rotationY(dir.toYRotBlockstate()) + part().modelFile(provider.models().getExistingFile(modLocation("matter_bottler_${enum.name}_open"))) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, dir) + .condition(BlockRotationFreedom.HORIZONTAL.property, dir) .condition(enum, false) .end() - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_bottler_${enum.name}_closed"))) - .rotationY(dir.toYRotBlockstate()) + part().modelFile(provider.models().getExistingFile(modLocation("matter_bottler_${enum.name}_closed"))) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, dir) + .condition(BlockRotationFreedom.HORIZONTAL.property, dir) .condition(enum, true) .end() } @@ -80,12 +94,18 @@ fun addBlockStates(provider: MatteryBlockStateProvider) { provider.block(MBlocks.MATTER_DECOMPOSER) provider.block(MBlocks.MATTER_REPLICATOR) provider.block(MBlocks.PLATE_PRESS) + provider.block(MBlocks.TWIN_PLATE_PRESS) provider.block(MBlocks.GRAVITATION_STABILIZER) provider.block(MBlocks.GRAVITATION_STABILIZER_LENS) + provider.block(MBlocks.POWERED_BLAST_FURNACE) + provider.block(MBlocks.POWERED_FURNACE) provider.block(MBlocks.STORAGE_POWER_SUPPLIER) provider.block(MBlocks.MATTER_RECYCLER) + provider.block(MBlocks.MATTER_RECONSTRUCTOR) provider.block(MBlocks.ENERGY_SERVO) + provider.block(MBlocks.COBBLESTONE_GENERATOR) + provider.block(MBlocks.ESSENCE_STORAGE) provider.exec { for (crate in MRegistry.CARGO_CRATES.allBlocks.values) { @@ -93,76 +113,75 @@ fun addBlockStates(provider: MatteryBlockStateProvider) { return@forAllStates arrayOf( ConfiguredModel.builder() .modelFile(provider.models().getExistingFile( - ResourceLocation( - OverdriveThatMatters.MOD_ID, "${crate.registryName!!.path}_${if (it.getValue( + modLocation("${crate.registryName!!.path}_${if (it.getValue( CargoCrateBlock.IS_OPEN)) "open" else "closed"}") )) - .rotationY(it.getValue(RotatableMatteryBlock.FACING).toYRotBlockstate()) + .rotationY(it.getValue(BlockRotationFreedom.HORIZONTAL.property).front.yRotationBlockstateNorth()) .buildLast() ) } } with(provider.getMultipartBuilder(MBlocks.STORAGE_BUS)) { - for (dir in net.minecraft.core.Direction.values()) { - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_bus"))) - .rotationX(dir.toXRotBlockstate()) - .rotationY(dir.toYRotBlockstate()) + for (dir in BlockRotationFreedom.DIRECTIONAL.possibleValues) { + part().modelFile(provider.models().getExistingFile(modLocation("storage_bus"))) + .rotationX(dir.front.xRotationBlockstateNorth()) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING_FULL, dir) + .condition(BlockRotationFreedom.DIRECTIONAL.property, dir) .end() - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_connection"))) - .rotationX(dir.toXRotBlockstateInv()) - .rotationY(dir.toYRotBlockstateInv()) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_connection"))) + .rotationX(dir.front.xRotationBlockstateSouth()) + .rotationY(dir.front.yRotationBlockstateSouth()) .addModel() - .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.ordinal], true) + .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.front]!!, true) .end() } - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_core"))) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_core"))) .addModel().end() } with(provider.getMultipartBuilder(MBlocks.STORAGE_IMPORTER)) { - for (dir in net.minecraft.core.Direction.values()) { - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_importer"))) - .rotationX(dir.toXRotBlockstate()) - .rotationY(dir.toYRotBlockstate()) + for (dir in BlockRotationFreedom.DIRECTIONAL.possibleValues) { + part().modelFile(provider.models().getExistingFile(modLocation("storage_importer"))) + .rotationX(dir.front.xRotationBlockstateNorth()) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING_FULL, dir) + .condition(BlockRotationFreedom.DIRECTIONAL.property, dir) .end() - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_connection"))) - .rotationX(dir.toXRotBlockstateInv()) - .rotationY(dir.toYRotBlockstateInv()) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_connection"))) + .rotationX(dir.front.xRotationBlockstateSouth()) + .rotationY(dir.front.yRotationBlockstateSouth()) .addModel() - .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.ordinal], true) + .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.front]!!, true) .end() } - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_core"))) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_core"))) .addModel().end() } with(provider.getMultipartBuilder(MBlocks.STORAGE_EXPORTER)) { - for (dir in net.minecraft.core.Direction.values()) { - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_exporter"))) - .rotationX(dir.toXRotBlockstate()) - .rotationY(dir.toYRotBlockstate()) + for (dir in BlockRotationFreedom.DIRECTIONAL.possibleValues) { + part().modelFile(provider.models().getExistingFile(modLocation("storage_exporter"))) + .rotationX(dir.front.xRotationBlockstateNorth()) + .rotationY(dir.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING_FULL, dir) + .condition(BlockRotationFreedom.DIRECTIONAL.property, dir) .end() - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_connection"))) - .rotationX(dir.toXRotBlockstateInv()) - .rotationY(dir.toYRotBlockstateInv()) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_connection"))) + .rotationX(dir.front.xRotationBlockstateSouth()) + .rotationY(dir.front.yRotationBlockstateSouth()) .addModel() - .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.ordinal], true) + .condition(CableBlock.MAPPING_CONNECTION_PROP[dir.front]!!, true) .end() } - part().modelFile(provider.models().getExistingFile(ResourceLocation(OverdriveThatMatters.MOD_ID, "storage_cable_core"))) + part().modelFile(provider.models().getExistingFile(modLocation("storage_cable_core"))) .addModel().end() } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/ComplexBlockStates.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/ComplexBlockStates.kt index be4fc41e2..dbadd096c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/ComplexBlockStates.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/ComplexBlockStates.kt @@ -1,52 +1,50 @@ package ru.dbotthepony.mc.otm.datagen.blocks import net.minecraft.core.Direction -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.block.EnergyCounterBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.matter.PatternStorageBlock import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock -import ru.dbotthepony.mc.otm.datagen.DataGen -import ru.dbotthepony.mc.otm.datagen.DataGen.MOD_ID -import ru.dbotthepony.mc.otm.datagen.toYRotBlockstate +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth +import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MBlocks fun addComplexBlockStates(provider: MatteryBlockStateProvider) { with(provider.getMultipartBuilder(MBlocks.DRIVE_VIEWER)) { - for (facing in RotatableMatteryBlock.FACING.possibleValues) { + for (facing in BlockRotationFreedom.HORIZONTAL.possibleValues) { part() - .modelFile(provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/drive_viewer_drive_part"))) - .rotationY(facing.toYRotBlockstate()) + .modelFile(provider.models().getExistingFile(modLocation("block/drive_viewer_drive_part"))) + .rotationY(facing.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, facing) + .condition(BlockRotationFreedom.HORIZONTAL.property, facing) .condition(DriveViewerBlock.DRIVE_PRESENT, true) for (workState in WorkerState.SEMI_WORKER_STATE.possibleValues) { part() - .modelFile(provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/drive_viewer_${workState.name.lowercase()}"))) - .rotationY(facing.toYRotBlockstate()) + .modelFile(provider.models().getExistingFile(modLocation("block/drive_viewer_${workState.name.lowercase()}"))) + .rotationY(facing.front.yRotationBlockstateNorth()) .addModel() .condition(WorkerState.SEMI_WORKER_STATE, workState) - .condition(RotatableMatteryBlock.FACING, facing) + .condition(BlockRotationFreedom.HORIZONTAL.property, facing) } } } with(provider.getMultipartBuilder(MBlocks.PATTERN_STORAGE)) { - for (facing in RotatableMatteryBlock.FACING.possibleValues) { + for (facing in BlockRotationFreedom.HORIZONTAL.possibleValues) { part() - .modelFile(provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/pattern_storage"))) - .rotationY(facing.toYRotBlockstate()) + .modelFile(provider.models().getExistingFile(modLocation("block/pattern_storage"))) + .rotationY(facing.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, facing) + .condition(BlockRotationFreedom.HORIZONTAL.property, facing) for (i in 0 .. 7) { part() - .modelFile(provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/pattern/model$i"))) - .rotationY(facing.toYRotBlockstate()) + .modelFile(provider.models().getExistingFile(modLocation("block/pattern/model$i"))) + .rotationY(facing.front.yRotationBlockstateNorth()) .addModel() - .condition(RotatableMatteryBlock.FACING, facing) + .condition(BlockRotationFreedom.HORIZONTAL.property, facing) .condition(PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[i], true) } } @@ -54,24 +52,24 @@ fun addComplexBlockStates(provider: MatteryBlockStateProvider) { with(provider.getMultipartBuilder(MBlocks.ENERGY_COUNTER)) { // даваааййй - val up = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_up")) - val down = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_down")) - val west = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_west")) - val east = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_east")) + val up = provider.models().getExistingFile(modLocation("block/energy_counter_up")) + val down = provider.models().getExistingFile(modLocation("block/energy_counter_down")) + val west = provider.models().getExistingFile(modLocation("block/energy_counter_west")) + val east = provider.models().getExistingFile(modLocation("block/energy_counter_east")) // ДАААА ДАВАЙЙ ДАААВАААЙЙЙЙЙЙ - val north = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_north")) - val northDown = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_north_down")) - val northEast = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_north_east")) - val northWest = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_north_west")) - val south = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_south")) - val southDown = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_south_down")) - val southEast = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_south_east")) - val southWest = provider.models().getExistingFile(ResourceLocation(MOD_ID, "block/energy_counter_south_west")) + val north = provider.models().getExistingFile(modLocation("block/energy_counter_north")) + val northDown = provider.models().getExistingFile(modLocation("block/energy_counter_north_down")) + val northEast = provider.models().getExistingFile(modLocation("block/energy_counter_north_east")) + val northWest = provider.models().getExistingFile(modLocation("block/energy_counter_north_west")) + val south = provider.models().getExistingFile(modLocation("block/energy_counter_south")) + val southDown = provider.models().getExistingFile(modLocation("block/energy_counter_south_down")) + val southEast = provider.models().getExistingFile(modLocation("block/energy_counter_south_east")) + val southWest = provider.models().getExistingFile(modLocation("block/energy_counter_south_west")) for (dir in arrayOf(Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.NORTH)) { - part().modelFile(down).rotationY(dir.toYRotBlockstate()).addModel().condition(EnergyCounterBlock.INPUT_DIRECTION, Direction.UP).condition(EnergyCounterBlock.IF_DIRECTION, dir) - part().modelFile(up).rotationY(dir.toYRotBlockstate()).addModel().condition(EnergyCounterBlock.INPUT_DIRECTION, Direction.DOWN).condition(EnergyCounterBlock.IF_DIRECTION, dir) + part().modelFile(down).rotationY(dir.yRotationBlockstateNorth()).addModel().condition(EnergyCounterBlock.INPUT_DIRECTION, Direction.UP).condition(EnergyCounterBlock.IF_DIRECTION, dir) + part().modelFile(up).rotationY(dir.yRotationBlockstateNorth()).addModel().condition(EnergyCounterBlock.INPUT_DIRECTION, Direction.DOWN).condition(EnergyCounterBlock.IF_DIRECTION, dir) } // низкий поклон за полностью рабочий поворот вокруг оси Z @@ -94,4 +92,4 @@ fun addComplexBlockStates(provider: MatteryBlockStateProvider) { part().modelFile(mdl).rotationX(90).addModel().condition(EnergyCounterBlock.INPUT_DIRECTION, dir).condition(EnergyCounterBlock.IF_DIRECTION, Direction.DOWN) } } -} \ No newline at end of file +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/MatteryBlockStateProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/MatteryBlockStateProvider.kt index 441942236..4aa2544be 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/MatteryBlockStateProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/blocks/MatteryBlockStateProvider.kt @@ -1,18 +1,18 @@ package ru.dbotthepony.mc.otm.datagen.blocks -import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.client.model.generators.BlockStateProvider import net.minecraftforge.client.model.generators.ConfiguredModel import net.minecraftforge.data.event.GatherDataEvent -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.core.getValueNullable +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.xRotationBlockstateNorth +import ru.dbotthepony.mc.otm.core.math.yRotationBlockstateNorth import ru.dbotthepony.mc.otm.datagen.DataGen -import ru.dbotthepony.mc.otm.datagen.getValueNullable -import ru.dbotthepony.mc.otm.datagen.toXRotBlockstate -import ru.dbotthepony.mc.otm.datagen.toYRotBlockstate import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.datagen.modLocation import java.util.LinkedList typealias BlockStateTransform = (state: BlockState, builder: ConfiguredModel.Builder<*>, path: String) -> String? @@ -21,13 +21,18 @@ private val EMPTY: BlockStateTransform = { _, _, _ -> null } private fun initialTransform(it: BlockState, modelPath: String, builder: ConfiguredModel.Builder<*>): String { @Suppress("NAME_SHADOWING") var modelPath = modelPath - it.getValueNullable(RotatableMatteryBlock.FACING)?.let { - builder.rotationY(it.toYRotBlockstate()) + it.getValueNullable(BlockRotationFreedom.HORIZONTAL.property)?.let { + builder.rotationY(it.front.yRotationBlockstateNorth()) } - it.getValueNullable(RotatableMatteryBlock.FACING_FULL)?.let { - builder.rotationY(it.toYRotBlockstate()) - builder.rotationX(it.toXRotBlockstate()) + it.getValueNullable(BlockRotationFreedom.DIRECTIONAL.property)?.let { + builder.rotationY(it.front.yRotationBlockstateNorth()) + builder.rotationX(it.front.xRotationBlockstateNorth()) + } + + it.getValueNullable(BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION.property)?.let { + builder.rotationY(it.front.yRotationBlockstateNorth() + it.top.yRotationBlockstateNorth()) + builder.rotationX(it.front.xRotationBlockstateNorth()) } it.getValueNullable(WorkerState.WORKER_STATE)?.let { @@ -41,7 +46,7 @@ private fun initialTransform(it: BlockState, modelPath: String, builder: Configu return modelPath } -class MatteryBlockStateProvider(event: GatherDataEvent) : BlockStateProvider(event.generator, DataGen.MOD_ID, event.existingFileHelper) { +class MatteryBlockStateProvider(event: GatherDataEvent) : BlockStateProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) { private val callbacks = LinkedList<() -> Unit>() fun exec(lambda: () -> Unit): MatteryBlockStateProvider { @@ -55,7 +60,7 @@ class MatteryBlockStateProvider(event: GatherDataEvent) : BlockStateProvider(eve var modelPath = initialTransform(it, "block/${block.registryName!!.path}", builder) modelPath = func(it, builder, modelPath) ?: modelPath - builder.modelFile(models().getExistingFile(ResourceLocation(DataGen.MOD_ID, modelPath))) + builder.modelFile(models().getExistingFile(modLocation(modelPath))) return@forAllStates builder.build() } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/ItemModels.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/ItemModels.kt index 3b8e3a921..8ef92f7c8 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/ItemModels.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/ItemModels.kt @@ -1,9 +1,7 @@ package ru.dbotthepony.mc.otm.datagen.items -import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.DyeColor import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry @@ -22,6 +20,9 @@ fun addItemModels(provider: MatteryItemModelProvider) { } provider.block(MItems.CARBON_FIBRE_BLOCK) + provider.block(MItems.METAL_JUNK) + provider.block(MItems.METAL_MESH) + provider.generatedTranslucent(MItems.TRITANIUM_BARS, modLocation("block/decorative/tritanium_bars")) provider.block(MItems.DEEPSLATE_TRITANIUM_ORE) provider.block(MItems.TRITANIUM_ORE) provider.block(MItems.TRITANIUM_STRIPED_BLOCK) @@ -29,6 +30,7 @@ fun addItemModels(provider: MatteryItemModelProvider) { provider.block(MItems.TRITANIUM_INGOT_BLOCK) provider.block(MItems.ITEM_MONITOR) provider.block(MItems.PHANTOM_ATTRACTOR) + provider.block(MItems.HOLO_SIGN) MRegistry.VENT.allItems.values.forEach(provider::block) MRegistry.VENT_ALTERNATIVE.allItems.values.forEach(provider::block) @@ -40,7 +42,7 @@ fun addItemModels(provider: MatteryItemModelProvider) { } for ((color, glass) in MRegistry.INDUSTRIAL_GLASS_PANE.allItems) { - provider.generatedTranslucent(glass, ResourceLocation(DataGen.MOD_ID, "block/decorative/${MRegistry.INDUSTRIAL_GLASS.allItems[color]!!.registryName!!.path}")) + provider.generatedTranslucent(glass, modLocation("block/decorative/${MRegistry.INDUSTRIAL_GLASS.allItems[color]!!.registryName!!.path}")) } provider.blocks(MRegistry.DECORATIVE_CRATE.allItems.values) @@ -53,6 +55,10 @@ fun addItemModels(provider: MatteryItemModelProvider) { provider.generated(MItems.PILL_HEAL) provider.generated(MItems.NUTRIENT_PASTE) + provider.generated(MItems.ESSENCE_DRIVE) + provider.generated(MItems.ESSENCE_CAPSULE) + provider.generated(MItems.ESSENCE_SERVO) + for (item in MItems.ExopackUpgrades.INVENTORY_UPGRADES) { provider.generated(item, modLocation("item/exosuit_inventory_upgrade")) } @@ -69,24 +75,76 @@ fun addItemModels(provider: MatteryItemModelProvider) { provider.generated(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE) provider.generated(MItems.ExopackUpgrades.CRAFTING_UPGRADE) + provider.generated(MItems.ExopackUpgrades.SMELTING_UPGRADE) + provider.generated(MItems.ExopackUpgrades.ENDER_UPGRADE) provider.component(MItems.TRITANIUM_DUST) provider.component(MItems.TRITANIUM_INGOT) + provider.component(MItems.TRITANIUM_NUGGET) provider.resource(MItems.TRITANIUM_ORE_CLUMP) provider.generated(MItems.EXOPACK_PROBE) provider.handheld(MItems.TRITANIUM_TOOLS) - provider.generated(MItems.TRITANIUM_ARMOR) + provider.armorColored(MItems.TRITANIUM_ARMOR) + provider.generated(MItems.SIMPLE_TRITANIUM_ARMOR) + + provider.handheld(MItems.CHEST_UPGRADER) provider.generatedTiered(MItems.BATTERIES, "battery_tier") provider.generated(MItems.BATTERY_CREATIVE) + provider.generated(MItems.PROCEDURAL_BATTERY, modLocation("item/battery_procedural")) - provider.generated(MItems.MATTER_CAPACITOR_BASIC, ResourceLocation(DataGen.MOD_ID, "item/matter_capacitor_tier1")) - provider.generated(MItems.MATTER_CAPACITOR_NORMAL, ResourceLocation(DataGen.MOD_ID, "item/matter_capacitor_tier2")) - provider.generated(MItems.MATTER_CAPACITOR_DENSE, ResourceLocation(DataGen.MOD_ID, "item/matter_capacitor_tier3")) + provider.generated(MItems.MATTER_CAPACITOR_BASIC, modLocation("item/matter_capacitor_tier1")) + provider.generated(MItems.MATTER_CAPACITOR_NORMAL, modLocation("item/matter_capacitor_tier2")) + provider.generated(MItems.MATTER_CAPACITOR_DENSE, modLocation("item/matter_capacitor_tier3")) provider.generated(MItems.MATTER_CAPACITOR_CREATIVE) + provider.generated(MItems.MachineUpgrades.Basic.BLANK, modLocation("item/machine_upgrade_tier1")) + provider.upgrade(MItems.MachineUpgrades.Basic.SPEED, "speed", "tier1") + provider.upgrade(MItems.MachineUpgrades.Basic.ENERGY_CONSUMPTION, "energy", "tier1") + provider.upgrade(MItems.MachineUpgrades.Basic.FAILSAFE, "failure", "tier1") + provider.upgrade(MItems.MachineUpgrades.Basic.ENERGY_STORAGE, "capacity", "tier1") + provider.upgrade(MItems.MachineUpgrades.Basic.MATTER_STORAGE, "matter", "tier1") + provider.upgrade(MItems.MachineUpgrades.Basic.PROCESSING_ITEMS, "processing", "tier1") + + provider.generated(MItems.MachineUpgrades.Normal.BLANK, modLocation("item/machine_upgrade_tier2")) + provider.upgrade(MItems.MachineUpgrades.Normal.SPEED, "speed", "tier2") + provider.upgrade(MItems.MachineUpgrades.Normal.ENERGY_CONSUMPTION, "energy", "tier2") + provider.upgrade(MItems.MachineUpgrades.Normal.FAILSAFE, "failure", "tier2") + provider.upgrade(MItems.MachineUpgrades.Normal.ENERGY_STORAGE, "capacity", "tier2") + provider.upgrade(MItems.MachineUpgrades.Normal.MATTER_STORAGE, "matter", "tier2") + provider.upgrade(MItems.MachineUpgrades.Normal.PROCESSING_ITEMS, "processing", "tier2") + + provider.generated(MItems.MachineUpgrades.Advanced.BLANK, modLocation("item/machine_upgrade_tier3")) + provider.upgrade(MItems.MachineUpgrades.Advanced.SPEED, "speed", "tier3") + provider.upgrade(MItems.MachineUpgrades.Advanced.ENERGY_CONSUMPTION, "energy", "tier3") + provider.upgrade(MItems.MachineUpgrades.Advanced.FAILSAFE, "failure", "tier3") + provider.upgrade(MItems.MachineUpgrades.Advanced.ENERGY_STORAGE, "capacity", "tier3") + provider.upgrade(MItems.MachineUpgrades.Advanced.MATTER_STORAGE, "matter", "tier3") + provider.upgrade(MItems.MachineUpgrades.Advanced.PROCESSING_ITEMS, "processing", "tier3") + + provider.upgrade(MItems.MachineUpgrades.Creative.SPEED, "speed", "creative") + + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_CONSUMPTION, "energy", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT, "energy", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT, "energy", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT_SMALL, "energy", "creative") + + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_STORAGE, "capacity", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT, "capacity", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT_SMALL, "capacity", "creative") + + provider.upgrade(MItems.MachineUpgrades.Creative.FAILSAFE, "failure", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.FAILURE, "failure", "creative") + + provider.upgrade(MItems.MachineUpgrades.Creative.PROCESSING_ITEMS, "processing", "creative") + + provider.upgrade(MItems.MachineUpgrades.Creative.MATTER_STORAGE, "matter", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT, "matter", "creative") + provider.upgrade(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT_SMALL, "matter", "creative") + + provider.generated(MItems.QUANTUM_BATTERY) provider.generated(MItems.QUANTUM_CAPACITOR) provider.generated(MItems.QUANTUM_BATTERY_CREATIVE) @@ -100,6 +158,7 @@ fun addItemModels(provider: MatteryItemModelProvider) { provider.block(MItems.TRITANIUM_TRAPDOOR[null]!!, "tritanium_trapdoor_bottom") + for (color in DyeColor.values()) provider.block(MItems.TRITANIUM_TRAPDOOR[color]!!, "tritanium_trapdoor_${color.name.lowercase()}_bottom") @@ -113,15 +172,25 @@ fun addItemModels(provider: MatteryItemModelProvider) { provider.block(MItems.MATTER_CABLE, "matter_cable_core") provider.block(MItems.MATTER_DECOMPOSER, "matter_decomposer_working") provider.block(MItems.ENERGY_SERVO, "energy_servo") + provider.block(MItems.ESSENCE_STORAGE, "essence_storage") + provider.block(MItems.MATTER_RECONSTRUCTOR, "matter_reconstructor") + provider.block(MItems.POWERED_BLAST_FURNACE, "powered_blast_furnace_working") + provider.block(MItems.POWERED_FURNACE, "powered_furnace_working") provider.block(MItems.PLATE_PRESS, "plate_press_idle") + provider.block(MItems.TWIN_PLATE_PRESS, "twin_plate_press_idle") provider.block(MItems.STORAGE_POWER_SUPPLIER, "storage_power_supplier") provider.block(MItems.MATTER_RECYCLER, "matter_recycler_working") + provider.block(MItems.COBBLESTONE_GENERATOR, "cobblestone_generator") provider.block(MItems.STORAGE_BUS) provider.block(MItems.STORAGE_IMPORTER) provider.block(MItems.STORAGE_EXPORTER) + for (item in MItems.TRITANIUM_ANVIL) { + provider.block(item) + } + for ((color, item) in MItems.CARGO_CRATE_MINECARTS) { provider.generated(item) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/MatteryItemModelProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/MatteryItemModelProvider.kt index 985082d3d..7c5a3f187 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/MatteryItemModelProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/items/MatteryItemModelProvider.kt @@ -1,15 +1,20 @@ package ru.dbotthepony.mc.otm.datagen.items +import net.minecraft.data.models.ItemModelGenerators import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.PackType +import net.minecraft.world.item.ArmorItem import net.minecraft.world.item.Item import net.minecraftforge.client.model.generators.ItemModelProvider +import net.minecraftforge.client.model.generators.ModelBuilder import net.minecraftforge.data.event.GatherDataEvent import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.datagen.DataGen +import ru.dbotthepony.mc.otm.datagen.modLocation import java.util.LinkedList -class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event.generator, DataGen.MOD_ID, event.existingFileHelper) { +class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) { private val callbacks = LinkedList<() -> Unit>() fun exec(func: () -> Unit): MatteryItemModelProvider { @@ -23,8 +28,8 @@ class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event } } - fun block(item: Item) = exec { withExistingParent(item.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/${item.registryName!!.path}")) } - fun block(item: Item, path: String) = exec { withExistingParent(item.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/$path")) } + fun block(item: Item) = exec { withExistingParent(item.registryName!!.path, modLocation("block/${item.registryName!!.path}")) } + fun block(item: Item, path: String) = exec { withExistingParent(item.registryName!!.path, modLocation("block/$path")) } fun blocks(vararg items: Item) = items.forEach(this::block) fun blocks(items: Collection) = items.forEach(this::block) @@ -40,18 +45,18 @@ class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event withExistingParent(item.registryName!!.path, HANDHELD).texture("layer0", texture) } - fun generated(item: Item) = generated(item, ResourceLocation(DataGen.MOD_ID, "item/${item.registryName!!.path}")) - fun generated(vararg items: Item) = items.forEach { generated(it, ResourceLocation(DataGen.MOD_ID, "item/${it.registryName!!.path}")) } - fun KOT(vararg items: Item) = items.forEach { generated(it, ResourceLocation(DataGen.MOD_ID, "block/ph_kitty")) } - fun generated(items: Collection) = items.forEach { generated(it, ResourceLocation(DataGen.MOD_ID, "item/${it.registryName!!.path}")) } - fun generatedBlock(vararg items: Item) = items.forEach { generated(it, ResourceLocation(DataGen.MOD_ID, "block/${it.registryName!!.path}")) } - fun generatedBlockDecorative(vararg items: Item) = items.forEach { generated(it, ResourceLocation(DataGen.MOD_ID, "block/decorative/${it.registryName!!.path}")) } - fun handheld(vararg items: Item) = items.forEach { handheld(it, ResourceLocation(DataGen.MOD_ID, "item/${it.registryName!!.path}")) } - fun handheld(items: Collection) = items.forEach { handheld(it, ResourceLocation(DataGen.MOD_ID, "item/${it.registryName!!.path}")) } + fun generated(item: Item) = generated(item, modLocation("item/${item.registryName!!.path}")) + fun generated(vararg items: Item) = items.forEach { generated(it, modLocation("item/${it.registryName!!.path}")) } + fun KOT(vararg items: Item) = items.forEach { generated(it, modLocation("block/ph_kitty")) } + fun generated(items: Collection) = items.forEach { generated(it, modLocation("item/${it.registryName!!.path}")) } + fun generatedBlock(vararg items: Item) = items.forEach { generated(it, modLocation("block/${it.registryName!!.path}")) } + fun generatedBlockDecorative(vararg items: Item) = items.forEach { generated(it, modLocation("block/decorative/${it.registryName!!.path}")) } + fun handheld(vararg items: Item) = items.forEach { handheld(it, modLocation("item/${it.registryName!!.path}")) } + fun handheld(items: Collection) = items.forEach { handheld(it, modLocation("item/${it.registryName!!.path}")) } - fun generated(item: Item, prefix: String) = generated(item, ResourceLocation(DataGen.MOD_ID, "item/${prefix}${item.registryName!!.path}")) - fun generatedStrict(item: Item, path: String) = generated(item, ResourceLocation(DataGen.MOD_ID, "item/$path")) - fun handheld(item: Item, prefix: String) = handheld(item, ResourceLocation(DataGen.MOD_ID, "item/${prefix}${item.registryName!!.path}")) + fun generated(item: Item, prefix: String) = generated(item, modLocation("item/${prefix}${item.registryName!!.path}")) + fun generatedStrict(item: Item, path: String) = generated(item, modLocation("item/$path")) + fun handheld(item: Item, prefix: String) = handheld(item, modLocation("item/${prefix}${item.registryName!!.path}")) fun component(item: Item) = generated(item, "component/") fun components(vararg items: Item) = items.forEach(this::component) @@ -63,7 +68,7 @@ class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event var i = 0 for (item in items) { - generated(item, ResourceLocation(DataGen.MOD_ID, "item/$prefix$i")) + generated(item, modLocation("item/$prefix$i")) i++ } } @@ -72,12 +77,27 @@ class MatteryItemModelProvider(event: GatherDataEvent) : ItemModelProvider(event var i = 0 for (item in items) { - generated(item, ResourceLocation(DataGen.MOD_ID, "item/$prefix$i")) + generated(item, modLocation("item/$prefix$i")) i++ } } + fun armorColored(item: Item) = exec { + withExistingParent(item.registryName!!.path, GENERATED) + .texture("layer0", modLocation("item/${item.registryName!!.path}_base")) + .texture("layer1", modLocation("item/${item.registryName!!.path}_overlay")) + } + fun armorColored(vararg items: Item) = items.forEach { armorColored(it) } + fun armorColored(items: Collection) = items.forEach { armorColored(it) } + + fun upgrade(item: Item, upgradeType: String, tier: String = "tier0") = exec { + withExistingParent(item.registryName!!.path, GENERATED) + .texture("layer0", modLocation("item/machine_upgrade_$tier")) + .texture("layer1", modLocation("item/machine_upgrade_icon_$upgradeType")) + } + companion object { + val ARMOR_TRIM_MATERIALS = listOf("quartz", "iron", "netherite", "redstone", "copper", "gold", "emerald", "diamond", "lapis", "amethyst") val GENERATED = ResourceLocation("minecraft", "item/generated") val HANDHELD = ResourceLocation("minecraft", "item/handheld") private val LOGGER = LogManager.getLogger() diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 912815145..ab1f5619f 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -3,23 +3,25 @@ package ru.dbotthepony.mc.otm.datagen.lang import ru.dbotthepony.mc.otm.registry.* private fun decoratives(provider: MatteryLanguageProvider) { - provider.englishColors.add(MRegistry.VENT, "%s Vent") - provider.englishColors.add(MRegistry.VENT_ALTERNATIVE, "%s Alternative Vent") + with(provider.englishColors) { + add(MRegistry.VENT, "%s Vent") + add(MRegistry.VENT_ALTERNATIVE, "%s Alternative Vent") - provider.englishColors.add(MRegistry.TRITANIUM_BLOCK, "%s Tritanium Block") - provider.englishColors.add(MRegistry.TRITANIUM_STAIRS, "%s Tritanium Stairs") - provider.englishColors.add(MRegistry.TRITANIUM_SLAB, "%s Tritanium Slab") - provider.englishColors.add(MRegistry.TRITANIUM_WALL, "%s Tritanium Wall") - provider.englishColors.add(MRegistry.FLOOR_TILES, "%s Floor Tiles") - provider.englishColors.add(MRegistry.FLOOR_TILES_STAIRS, "%s Floor Tiles Stairs") - provider.englishColors.add(MRegistry.FLOOR_TILES_SLAB, "%s Floor Tiles Slab") - provider.englishColors.add(MRegistry.UNREFINED_FLOOR_TILES, "Unrefined %s Floor Tiles") + add(MRegistry.TRITANIUM_BLOCK, "%s Tritanium Block") + add(MRegistry.TRITANIUM_STAIRS, "%s Tritanium Stairs") + add(MRegistry.TRITANIUM_SLAB, "%s Tritanium Slab") + add(MRegistry.TRITANIUM_WALL, "%s Tritanium Wall") + add(MRegistry.FLOOR_TILES, "%s Floor Tiles") + add(MRegistry.FLOOR_TILES_STAIRS, "%s Floor Tiles Stairs") + add(MRegistry.FLOOR_TILES_SLAB, "%s Floor Tiles Slab") + add(MRegistry.UNREFINED_FLOOR_TILES, "Unrefined %s Floor Tiles") - provider.englishColors.add(MRegistry.INDUSTRIAL_GLASS, "%s Stained Industrial Glass") - provider.englishColors.add(MRegistry.INDUSTRIAL_GLASS_PANE, "%s Stained Industrial Glass Pane") + add(MRegistry.INDUSTRIAL_GLASS, "%s Stained Industrial Glass") + add(MRegistry.INDUSTRIAL_GLASS_PANE, "%s Stained Industrial Glass Pane") - provider.englishColors.add(MRegistry.CARGO_CRATES, "%s Cargo Crate") - provider.englishColors.add(MRegistry.DECORATIVE_CRATE, "%s Container Block") + add(MRegistry.CARGO_CRATES, "%s Cargo Crate") + add(MRegistry.DECORATIVE_CRATE, "%s Container Block") + } with (provider.english) { for ((color, name) in provider.englishColors.dyeClassMapped) { @@ -46,10 +48,10 @@ private fun decoratives(provider: MatteryLanguageProvider) { add(MRegistry.TRITANIUM_PRESSURE_PLATE.block, "description1", "High blast resistance") } - provider.english.add(MItems.CARGO_CRATE_MINECARTS[null]!!, "Minecart with Cargo Crate") - provider.english.add(MEntityTypes.CARGO_CRATE_MINECARTS[null]!!, "Minecart with Cargo Crate") - with(provider.english) { + add(MItems.CARGO_CRATE_MINECARTS[null]!!, "Minecart with Cargo Crate") + add(MEntityTypes.CARGO_CRATE_MINECARTS[null]!!, "Minecart with Cargo Crate") + add(MRegistry.CARGO_CRATES.block, "Cargo Crate") add(MRegistry.TRITANIUM_BLOCK.block, "Tritanium Block") add(MRegistry.TRITANIUM_STAIRS.block, "Tritanium Stairs") @@ -64,6 +66,7 @@ private fun decoratives(provider: MatteryLanguageProvider) { add(MRegistry.VENT.block, "Vent") add(MRegistry.VENT_ALTERNATIVE.block, "Alternative Vent") + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_BLOCK.blocksWithColor) { val (base, stripe) = colors @@ -114,6 +117,17 @@ private fun sounds(provider: MatteryLanguageProvider) { private fun misc(provider: MatteryLanguageProvider) { with(provider.english) { + gui("help.slot_filters", "Hold CTRL to setup slot filters") + gui("help.slot_charging", "Hold ALT to switch slot charging") + + gui("needs", "Needs %s") + gui("needs_x", "Needs %s x%d") + + misc("needs_no_power", "Requires no power to operate") + + gui("lock_holo_screen", "Lock contents") + gui("lock_holo_screen.tip", "Locking and unlocking contents is only possible in creative.\nWhen locked, text boundaries are removed.") + gui("ticks", "Ticks") gui("power_cost_per_use", "Power cost per use: %s") @@ -125,24 +139,34 @@ private fun misc(provider: MatteryLanguageProvider) { gui("recipe.ticks", "%s Ticks") gui("exopack", "Exopack Inventory") + + gui("exopack.customize", "Customize Exopack appearance") + gui("exopack.customization", "Exopack appearance settings") + gui("exopack.go_back", "Open vanilla inventory") gui("exopack.go_in", "Open Exopack inventory") - gui("exopack.toggle_visibility", "Toggle Exopack visibility") + gui("exopack.toggle_visibility", "Visibile on player") + gui("exopack.toggle_glow", "Glows in dark") + gui("exopack.change_color", "Customize color") + gui("exopack.change_color2", "Remove color") + gui("exopack.go_curios", "Open Curios inventory") gui("exopack.probe1", "This little device feels unnatural to touch, it is almost certainly resilient to any possible attempt to break it open.") gui("exopack.probe2", "There is fingerprint reader built into one of sides which gently glow when touched.") gui("exopack.probe3", "It seems this box will unlock once you strongly press fingerprint reader, and you feel what's inside will affect you without any way back!") - gui("exopack.already_activated", "You already have an Exopack on you!") + gui("exopack.already_activated", "You already have an Exopack!") gui("exopack_upgrades.no_exopack", "This piece of technology seems to be of no use to you.... Or does it?!") gui("exopack_upgrades.already_activated", "Upgrade is already active!") gui("exopack_upgrades.slots_upgrade", "Using this will permanently grant %s slots in your Exopack inventory.") gui("exopack_upgrades.crafting_upgrade", "Using this will permanently grant 3x3 crafting grid in your Exopack inventory.") + gui("exopack_upgrades.smelting_upgrade", "Using this will permanently grant smelter in your Exopack inventory.") + gui("exopack_upgrades.ender_access_upgrade", "Using this will permanently grant Ender Chest access in your Exopack inventory.") gui("crude_battery.replace_in_world", "Simplistic nature of this battery allows to replace your energy source in the field without using Android Station.") - gui("crude_battery.replace_in_world_warning", "This operation is very unstable and causes serious damage to your systems!") + gui("crude_battery.replace_in_world_warning", "This operation is very unstable and can cause extreme damage to your systems!") gui("power_supplier.active_nodes", "Currently demanding nodes: %s") @@ -150,15 +174,16 @@ private fun misc(provider: MatteryLanguageProvider) { misc("exopack_upgrades.slots_upgraded", "Your Exopack has permanently gained %s slots") misc("exopack_upgrades.crafting_upgraded", "Your Exopack has permanently gained 3x3 crafting grid") + misc("exopack_upgrades.smelting_installed", "Your Exopack has permanently gained smelting module") + misc("exopack_upgrades.ender_access_installed", "Your Exopack has permanently gained access to Ender Chest contents") - misc("exopack.granted1", "As you keep pressing on the fingerprint reader, you are getting hurt in finger.") - misc("exopack.granted2", "After you raise your finger, fingerprint reader glows very bright.") - misc("exopack.granted3", "Then, the fingerprint reader fades, leaving faint trace not of your finger, but of your very soul.") - misc("exopack.granted4", "The device opens... And whatever was inside it shroud you, yet you feel nothing, as it wasn't even there.") - misc("exopack.granted5", "As whatever shrouded you takes final form, you feel it binds to your soul.") - misc("exopack.granted6", "INITIALIZATION SEQUENCE COMPLETE. WELCOME, USER %s") - misc("exopack.granted7", "You are now permanently equipped with four dimensional omni-present Exopack.") - misc("exopack.granted8", "As of now, this Exopack is not much, but it's built-in AI hints there are upgrade modules out there somewhere...") + misc("exopack.granted1", "As you keep pressing on the fingerprint reader, probe disappears.") + misc("exopack.granted2", "After a moment, you are getting pierced into back multiple times...") + misc("exopack.granted3", "As pain signals ease away, you find a new 'friend' on your back: the Exopack.") + misc("exopack.granted4", "This device provides access to personal pocket dimension in 4th space.") + misc("exopack.granted5", "Scavenge for or craft modules to upgrade your Exopack.") + misc("exopack.granted6", "The Exopack itself also exists in 4th dimension, it does not prevent wearing armor.") + misc("exopack.granted7", "However, despite existing in 4th dimension, items stored in it are still dropped upon unfortunate event.") misc("dumping_matter_registry", "Dumping matter registry to %s") misc("dumped_matter_registry", "Dumped matter registry to %s") @@ -166,10 +191,11 @@ private fun misc(provider: MatteryLanguageProvider) { misc("iteration", "Iteration %s") misc("death_reason", "Decommissioned!") - misc("item.blackhole_immunity", "Negates gravitational effects of singularities") + misc("item.blackhole_immunity", "Negates spacetime dilating effect of singularities") misc("suffix.merge", "%s %s") + misc("suffix.none", "%s %s") misc("suffix.kilo", "%s k%s") misc("suffix.mega", "%s M%s") misc("suffix.giga", "%s G%s") @@ -190,6 +216,27 @@ private fun misc(provider: MatteryLanguageProvider) { misc("suffix.zepto", "%s z%s") misc("suffix.yocto", "%s y%s") + misc("suffix_concise.none", "%s") + misc("suffix_concise.kilo", "%sk") + misc("suffix_concise.mega", "%sM") + misc("suffix_concise.giga", "%sG") + misc("suffix_concise.tera", "%sT") + misc("suffix_concise.peta", "%sP") + misc("suffix_concise.exa", "%sE") + misc("suffix_concise.zetta", "%sZ") + misc("suffix_concise.yotta", "%sY") + + misc("suffix_concise.deci", "%sd") + misc("suffix_concise.centi", "%sc") + misc("suffix_concise.milli", "%sm") + misc("suffix_concise.micro", "%sμ") + misc("suffix_concise.nano", "%sn") + misc("suffix_concise.pico", "%sp") + misc("suffix_concise.femto", "%sf") + misc("suffix_concise.atto", "%sa") + misc("suffix_concise.zepto", "%sz") + misc("suffix_concise.yocto", "%sy") + misc("suffix_raw.kilo", "k") misc("suffix_raw.mega", "M") misc("suffix_raw.giga", "G") @@ -209,31 +256,24 @@ private fun misc(provider: MatteryLanguageProvider) { misc("suffix_raw.zepto", "z") misc("suffix_raw.yocto", "y") - misc("container.matter_panel.increase_by", "+%s") - misc("container.matter_panel.decrease_by", "-%s") - misc("container.matter_panel.send", "Send") - misc("container.matter_panel.close", "Close") - misc("container.matter_panel.cancel_task", "Cancel task") - misc("container.matter_panel.cancel", "Cancel") - misc("container.matter_panel.label", "Replication request") - misc("container.matter_panel.task", "Ongoing replication task") - misc("container.matter_panel.task_line", "%s: %s | %s / %s") - - misc("container.matter_panel.tasks", "Tasks") - misc("container.matter_panel.patterns", "Patterns") - - misc("item.power.infinite.storage", "Stored energy: Infinity / Infinity") - misc("item.power.infinite.throughput", "Max I/O Infinite / Infinite") + misc("item.power.infinite.storage", "Stored energy: ∞ / ∞") + misc("item.power.infinite.throughput", "Max I/O: ∞ / ∞") misc("item.power.passed", "Passed energy: %s") misc("item.power.received", "Received energy: %s") misc("item.power.average", "Average throughput: %s/t") misc("item.power.last_20_ticks", "Last second: %s") misc("item.power.last_tick", "Last tick: %s") + gui("power.passed", "Total passed energy:") + gui("power.average", "Average throughput per tick:") + gui("power.last_20_ticks", "Last second:") + gui("power.last_tick", "Last tick:") + misc("item.power.storage", "Stored energy: %s / %s") - misc("item.power.throughput", "Max throughput: %s / %s") - misc("item.power.throughput_mono", "Max throughput: %s") - misc("item.power.infinity", "Infinity MtJ") + misc("item.power.storage0", "Stored energy: %s") + misc("item.power.throughput", "Max I/O: %s / %s") + misc("item.power.throughput_mono", "Max I/O: %s") + misc("item.power.infinity", "∞ MtJ") misc("item.worker.work_ticks_mono", "Work ticks: %s") misc("item.worker.work_ticks", "Work ticks: %s / %s") @@ -241,11 +281,14 @@ private fun misc(provider: MatteryLanguageProvider) { misc("item.block.stored_battery", "Battery: %s") misc("item.pattern.stored", "Stored patterns: %s / %s") - misc("item.pattern.infinite.stored", "Stored patterns %s") + misc("item.pattern.infinite.stored", "Stored patterns: %s") misc("item.pattern.line", "%s [%s%%]") misc("item.pattern.research", "Researched: %s%%") + misc("item.pattern.research.item_count", "Items: %s / %s") + misc("item.pattern.research.advance", "Progress per item: %s%%") - misc("item.matter.infinite", "Stored matter: Infinity / Infinity") + + misc("item.matter.infinite", "Stored matter: ∞ / ∞") misc("item.matter.normal", "Stored matter: %s / %s") misc("gui.matter_task.total", "Total: %s") @@ -264,42 +307,47 @@ private fun misc(provider: MatteryLanguageProvider) { misc("pill.heal", "Instantly restores 4 hearts upon ingestion, provides 2 min Absorption V and 8 seconds Regeneration III.") misc("pill.heal_android", "Does nothing to androids.") - misc("pill.message", "Nothing happened, but you feel exhausted?.. Maybe get rest.") - misc("pill.message_finish", "§kONE OF US ONE OF US ONE OF US ONE OF US ONE OF US") + misc("pill.message", "Nothing happened, but you feel... exhausted?.. Maybe get rest.") + misc("pill.message_finish", "§kONE OF US ONE OF US ONE OF US") - misc("gui.power.percentage_level", "Energy level: %s%%") - misc("gui.level", "%s / %s") - misc("gui.power.name", "MtJ") + gui("power.percentage_level", "Energy level: %s%%") + gui("level", "%s / %s") + gui("diff", "%s (%s / %s)") + gui("power.name", "MtJ") + gui("fluid.name", "B") + gui("fluid.level", "%s / %s of %s") - misc("gui.power.burn_time", "Burn time left: %s ticks") + gui("empty", "Empty") - misc("gui.progress_widget", "Progress: %s%%") - misc("gui.progress_widget_stuck", "The machine can not work, check configuration") + gui("power.burn_time", "Burn time left: %s ticks") - misc("gui.total_raw", "Total:") + gui("progress_widget", "Progress: %s%%") + gui("progress_widget_stuck", "The machine can not work, check configuration") - misc("gui.matter.percentage_level", "Matter level: %s%%") - misc("gui.matter.format", "Matter: %s") - misc("gui.matter.format_and_complexity", "%s / Complexity: %s") - misc("gui.matter.format_and_complexity2", "%s (%s) / Complexity: %s (%s)") - misc("gui.matter.name", "MtU") + gui("total_raw", "Total:") - misc("gui.filter.is_whitelist", "Is Whitelist") - misc("gui.filter.match_nbt", "Match NBT") - misc("gui.filter.match_tag", "Match Tag") + gui("matter.percentage_level", "Matter level: %s%%") + gui("matter.format", "Matter: %s") + gui("matter.format_and_complexity", "%s / Complexity: %s") + gui("matter.format_and_complexity2", "%s (%s) / Complexity: %s (%s)") + gui("matter.name", "MtU") - misc("gui.android_research", "Research Tree") + gui("filter.is_whitelist", "Is Whitelist") + gui("filter.match_nbt", "Match NBT") + gui("filter.match_tag", "Match Tag") - misc("gui.pattern.percentage_level", "Fill level: %s%%") - misc("gui.pattern.format", "Stored patterns: %s / %s") + gui("android_research", "Research Tree") - misc("gui.redstone.ignored", "Redstone mode: Ignored") - misc("gui.redstone.low", "Redstone mode: Low") - misc("gui.redstone.high", "Redstone mode: High") + gui("pattern.percentage_level", "Fill level: %s%%") + gui("pattern.format", "Stored patterns: %s / %s") - misc("gui.redstone.ignored.description", "Redstone signal does not affect machine's function") - misc("gui.redstone.low.description", "Machine work if no redstone signal is present") - misc("gui.redstone.high.description", "Machine work only if any redstone signal is present") + gui("redstone.ignored", "Redstone mode: Ignored") + gui("redstone.low", "Redstone mode: Low") + gui("redstone.high", "Redstone mode: High") + + gui("redstone.ignored.description", "Redstone signal does not affect machine's function") + gui("redstone.low.description", "Machine work if no redstone signal is present") + gui("redstone.high.description", "Machine work only if any redstone signal is present") misc("3d2d.gravitation_stabilizer.mass", "Singularity mass: %s") misc("3d2d.gravitation_stabilizer.strength", "Gravitation strength: %s") @@ -324,14 +372,12 @@ private fun misc(provider: MatteryLanguageProvider) { misc("matter_bottler.switch_mode", "Switch work mode") - misc("container.matter_panel.number_input", "Input replication task count") - misc("item.quantum_battery.creative", "Fill this to win Minecraft.") misc("item.quantum_battery.creative2", "See ya after millions of stars burn out.") - misc("item.quantum_battery.creative_power", "Stored energy: %s / Infinity") + misc("item.quantum_battery.creative_power", "Stored energy: %s / ∞") misc("item.quantum_link_id", "Quantum link ID: %s") - misc("item.quantum_description", "This item is sharing it's contents with other similar items using quantum entanglement") + misc("item.quantum_description", "This item is sharing its contents with other similar items using quantum entanglement") } } @@ -342,22 +388,31 @@ private fun death(provider: MatteryLanguageProvider) { death("otm_event_horizon", "%1\$s never crossed the event horizon") death("otm_hawking_radiation", "%1\$s discovered Hawking radiation") death("otm_emp", "%1\$s electronics' fried") + death("otm_cosmic_rays", "%1\$s electronics' got scrambled by cosmic radiation") + death("otm_android_discharge", "%1\$s ran out of power") death("otm_become_android.player", "%1\$s lost their humanity whilst %2\$s tried to reason with them") death("otm_become_humane.player", "%1\$s gained their humanity whilst %2\$s tried to reason with them") death("otm_event_horizon.player", "%1\$s tried to cross the event horizon whilst trying to escape %2\$s") death("otm_hawking_radiation.player", "%1\$s disintegrated whilst fighting %2\$s") - death("otm_emp.player", "%1\$s blew fuzes of %2\$s") - death("otm_emp.player.item", "%1\$s blew fuzes of %2\$s using %3\$s") + death("otm_emp.player", "%2\$s blew fuzes of %1\$s") + death("otm_emp.player.item", "%2\$s blew fuzes of %1\$s using %3\$s") - death(MRegistry.DAMAGE_EXOPACK_PROBE_ID, "%1 couldn't handle spinal surgery") - death("${MRegistry.DAMAGE_EXOPACK_PROBE_ID}.player", "%1 couldn't handle spinal surgery whilst fighting %2\$s") + death("otm_explosive_hammer", "%1\$s's fun time with hammer is over") + death("otm_hammer_nail", "%1\$s got nailed") + death("otm_hammer_nail" + ".player", "%1\$s got nailed by %\$2") + death("otm_hammer_nail" + ".player.item", "%1\$s got nailed by %2\$s using %3\$s") + + death("otm_exopack_probe", "%1\$s couldn't handle spinal surgery") + death("otm_exopack_probe.player", "%1\$s couldn't handle spinal surgery whilst fighting %2\$s") } } private fun blocks(provider: MatteryLanguageProvider) { with(provider.english) { add(MBlocks.ANDROID_STATION, "Android Station") + add(MBlocks.ANDROID_CHARGER, "Wireless Charger") + add(MBlocks.ANDROID_CHARGER, "desc", "Charges nearby androids and exopacks") add(MBlocks.BATTERY_BANK, "Battery Bank") add(MBlocks.MATTER_DECOMPOSER, "Matter Decomposer") add(MBlocks.MATTER_CAPACITOR_BANK, "Matter Capacitor Bank") @@ -368,7 +423,24 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.MATTER_REPLICATOR, "Matter Replicator") add(MBlocks.MATTER_BOTTLER, "Matter Bottler") add(MBlocks.DRIVE_VIEWER, "Drive Viewer") - add(MBlocks.BLACK_HOLE, "Local Anomalous Singular Gravitation Field") + add(MBlocks.BLACK_HOLE, "Local Anomalous Spacetime Dilation Singular Point") + add(MBlocks.COBBLESTONE_GENERATOR, "Cobblestone Generator") + add(MBlocks.INFINITE_WATER_SOURCE, "Infinite Water Source") + add(MBlocks.ESSENCE_STORAGE, "Essence Storage") + add(MBlocks.ESSENCE_STORAGE, "desc", "Allows to store and retrieve experience levels") + add(MBlocks.MATTER_RECONSTRUCTOR, "Matter Reconstructor") + add(MBlocks.MATTER_RECONSTRUCTOR, "desc", "Repairs tools using matter") + add(MBlocks.DEV_CHEST, "Dev Chest") + add(MBlocks.DEV_CHEST, "desc", "Contains all items present in game") + add(MBlocks.PAINTER, "Painting Table") + add(MBlocks.MATTER_ENTANGLER, "Matter Entangler") + + add(MBlocks.FLUID_TANK, "Fluid Tank") + add(MBlocks.FLUID_TANK, "named", "Fluid Tank (%s)") + + add(MBlocks.ENGINE, "Ship Engine") + add(MBlocks.ENGINE, "desc", "Unfortunately, it doesn't seem to be functional anymore.") + add(MBlocks.HOLO_SIGN, "Holo Sign") add(MBlocks.TRITANIUM_INGOT_BLOCK, "Tritanium Plating Block") @@ -381,12 +453,20 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.DRIVE_RACK, "Condensation Drive Rack") add(MBlocks.ITEM_MONITOR, "Item Monitor") add(MBlocks.PLATE_PRESS, "Plate Press") + add(MBlocks.TWIN_PLATE_PRESS, "Twin Plate Press") + + add(MBlocks.POWERED_FURNACE, "Electric Furnace") + add(MBlocks.POWERED_SMOKER, "Microwave Oven") + add(MBlocks.POWERED_BLAST_FURNACE, "Induction Furnace") add(MBlocks.MATTER_RECYCLER, "Matter Recycler") add(MBlocks.ENERGY_SERVO, "Energy Servo") add(MBlocks.ENERGY_SERVO, "desc", "Charges, Discharges or Exchanges energy of items") - add(MBlocks.CARBON_FIBRE_BLOCK, "Carbon fibre Block") + add(MBlocks.CARBON_FIBRE_BLOCK, "Carbon Fibre Block") + add(MBlocks.METAL_JUNK, "Metal Junk Block") + add(MBlocks.METAL_JUNK, "desc", "Useless junk, or is it?") + add(MBlocks.METAL_MESH, "Metal Mesh") add(MBlocks.TRITANIUM_STRIPED_BLOCK, "Tritanium Striped Block") add(MBlocks.TRITANIUM_STRIPED_STAIRS, "Tritanium Striped Stairs") @@ -402,16 +482,14 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.STORAGE_IMPORTER, "Storage Importer") add(MBlocks.STORAGE_EXPORTER, "Storage Exporter") - add(MBlocks.GRAVITATION_STABILIZER, "Gravitation Stabilizer") - add(MBlocks.GRAVITATION_STABILIZER_LENS, "Gravitation Stabilizer Lens") - add(MBlocks.GRAVITATION_STABILIZER, "desc", "Reduces gravitation effects of singularities") - add(MBlocks.GRAVITATION_STABILIZER, "desc2", "Requires no power to operate") - add(MBlocks.GRAVITATION_STABILIZER, "desc3", "Keep in mind the effect of multiple stabilizers produce exponentially increasing result") - add(MBlocks.GRAVITATION_STABILIZER, "desc4", "Too weak gravitation field will cause singularity to melt and evaporate away very fast") + add(MBlocks.GRAVITATION_STABILIZER, "Spacetime Normalizer") + add(MBlocks.GRAVITATION_STABILIZER_LENS, "Spacetime Normalizer Lens") + add(MBlocks.GRAVITATION_STABILIZER, "desc", "Reduces spacetime distortion effects of singularities") + add(MBlocks.GRAVITATION_STABILIZER, "desc2", "Keep in mind the effect of multiple stabilizers produce exponentially increasing result") + add(MBlocks.GRAVITATION_STABILIZER, "desc3", "Too weak gravitation field will cause singularity to 'evaporate' really fast!") add(MBlocks.PHANTOM_ATTRACTOR, "Phantom Attractor") add(MBlocks.PHANTOM_ATTRACTOR, "desc", "Attracts Phantoms when it is night time") - add(MBlocks.PHANTOM_ATTRACTOR, "desc2", "Requires no power to operate") add(MBlocks.LABORATORY_LAMP, "Laboratory Lamp") add(MBlocks.LABORATORY_LAMP, "description", "Provides directional light with redstone switch") @@ -426,37 +504,62 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "Tritanium Trapdoor") add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "description0", "High blast resistance door with redstone latch...") add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "description1", "...feeling safe now?") + + add(MBlocks.TRITANIUM_BARS, "Tritanium Bars") + + for (block in MBlocks.TRITANIUM_ANVIL) + add(block, "Tritanium Anvil") } } private fun items(provider: MatteryLanguageProvider) { with(provider.english) { + add(MItems.PROCEDURAL_BATTERY, "Mythical Battery") + add(MItems.PROCEDURAL_BATTERY, "desc", "These batteries are found in dungeons with randomized stats") + + add(MItems.EXPLOSIVE_HAMMER, "Explosive Hammer") + add(MItems.EXPLOSIVE_HAMMER, "desc", "For those who feel bored") + add(MItems.EXPLOSIVE_HAMMER, "primed", "Primed!") + add(MItems.EXPLOSIVE_HAMMER, "not_primed0", "Not primed") + add(MItems.EXPLOSIVE_HAMMER, "not_primed1", "Prime at crafting table with little of gunpowder and nugget payload") + add(MItems.EXOPACK_PROBE, "Exopack Probe") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE, "Creative Exopack Inventory Upgrade") add(MItems.ExopackUpgrades.CRAFTING_UPGRADE, "Exopack Crafting Upgrade") + add(MItems.ExopackUpgrades.SMELTING_UPGRADE, "Exopack Smelting Module") + add(MItems.ExopackUpgrades.ENDER_UPGRADE, "Exopack Ender Access Module") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_WITHER, "Superdense Packing Upgrade") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_WITHER, "description", "Utilizes similar principle that exhibit Nether Stars") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON, "Ender Link Pocket Dimension Upgrade") - add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON, "description", "Allows to store items in portable pocket dimension") + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON, "description", "Allows to store items in portable pocket dimension. Also grants access to Ender Chest contents.") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "Indescribable Exopack Inventory Upgrade") add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "description", "They normally generate in dungeons with appropriate NBT tag attached") - add(MItems.NUTRIENT_PASTE, "Nutrient paste") + add(MItems.ESSENCE_CAPSULE, "Essence Capsule") + add(MItems.ESSENCE_DRIVE, "Essence Memory Holotape") + add(MItems.ESSENCE_SERVO, "Essence Servo") + add(MItems.ESSENCE_SERVO, "desc", "Allows to 'pump' essence involving fleshy humanoids") + add(MItems.ESSENCE_SERVO, "desc2", "Can be used standalone, or as tool inside Essence Servo") + + add(MItems.NUTRIENT_PASTE, "Nutrient Paste") + + add(MItems.FLUID_CAPSULE, "Fluid Capsule") + add(MItems.FLUID_CAPSULE, "named", "Fluid Capsule (%s)") add(MItems.BLACK_HOLE_SCANNER, "Singularity Scanner") add(MItems.BLACK_HOLE_SCANNER, "desc", "Scans singularities for their properties") add(MItems.BLACK_HOLE_SCANNER, "desc2", "Hold in hand to determine mass of singularities") - add(MItems.GRAVITATION_FIELD_LIMITER, "Gravitation Field Limiter") - add(MItems.GRAVITATION_FIELD_SENSOR, "Gravitation Field Sensor") - add(MItems.PORTABLE_GRAVITATION_STABILIZER, "Portable Gravitation Stabilizer") + add(MItems.GRAVITATION_FIELD_LIMITER, "Spacetime Distortion Limiter") + add(MItems.GRAVITATION_FIELD_SENSOR, "Spacetime Sensor") + add(MItems.PORTABLE_GRAVITATION_STABILIZER, "Portable Spacetime Normalizer") add(MItems.BATTERY_CRUDE, "Crude Battery") add(MItems.BATTERY_BASIC, "Basic Battery") - add(MItems.BATTERY_NORMAL, "Ordinary Battery") + add(MItems.BATTERY_NORMAL, "Battery") add(MItems.BATTERY_DENSE, "Dense Battery") add(MItems.BATTERY_CAPACITOR, "Capacitor Battery") add(MItems.BATTERY_CREATIVE, "Creative Battery") @@ -470,14 +573,22 @@ private fun items(provider: MatteryLanguageProvider) { add(MItems.TRITANIUM_SHOVEL, "Tritanium Shovel") add(MItems.TRITANIUM_AXE, "Tritanium Axe") add(MItems.TRITANIUM_HOE, "Tritanium Hoe") + add(MItems.TRITANIUM_SHEARS, "Tritanium Shears") + add(MItems.TRITANIUM_SHIELD, "Tritanium Shield") add(MItems.TRITANIUM_HELMET, "Tritanium Helmet") add(MItems.TRITANIUM_CHESTPLATE, "Tritanium Chestplate") add(MItems.TRITANIUM_PANTS, "Tritanium Leggings") add(MItems.TRITANIUM_BOOTS, "Tritanium Boots") + add(MItems.SIMPLE_TRITANIUM_HELMET, "Simple Tritanium Helmet") + add(MItems.SIMPLE_TRITANIUM_CHESTPLATE, "Simple Tritanium Chestplate") + add(MItems.SIMPLE_TRITANIUM_PANTS, "Simple Tritanium Leggings") + add(MItems.SIMPLE_TRITANIUM_BOOTS, "Simple Tritanium Boots") + add(MItems.TRITANIUM_DUST, "Tritanium Dust") add(MItems.TRITANIUM_INGOT, "Tritanium Ingot") + add(MItems.TRITANIUM_NUGGET, "Tritanium Nugget") add(MItems.MATTER_IO_PORT, "Matter IO Port") add(MItems.MATTER_TRANSFORM_MATRIX, "Matter Transformation Matrix") add(MItems.ENERGY_BUS, "Energy Bus") @@ -495,14 +606,18 @@ private fun items(provider: MatteryLanguageProvider) { add(MItems.ADVANCED_CONTROL_CIRCUIT, "Advanced Control Circuit") add(MItems.QUANTUM_TRANSCEIVER, "Quantum Transceiver") add(MItems.ELECTROMAGNET, "Electromagnet") + add(MItems.ELECTROMOTOR, "Electromotor") add(MItems.MIRROR_COMPOUND, "Mirror Compound") add(MItems.MIRROR, "Mirror") - add(MItems.MIRROR, "description", "You can clearly see your reflection in this thing") + add(MItems.MIRROR, "description", "I can clearly see my own reflection in this mirror") + add(MItems.REINFORCED_TRITANIUM_PLATE, "Reinforced Tritanium Plate") + add(MItems.REINFORCED_TRITANIUM_PLATE, "description", "An armor plate, reinforced to withstand great kinetic forces") + add(MItems.CARBON_MESH, "Carbon Mesh") - add(MItems.GRAVITATIONAL_DISRUPTOR, "Gravitational Disruptor") + add(MItems.GRAVITATIONAL_DISRUPTOR, "Spacetime Equalizer") - add(MItems.GRAVITATIONAL_DISRUPTOR, "description", "Once within close proximity of supermassive body, suppresses any gravity in it's radius") - add(MItems.GRAVITATIONAL_DISRUPTOR, "description2", "Allows collapse of black holes") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description", "Once within close proximity of massive spacetime dilation anomaly, equalizes spacetime in it's radius") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description2", "Allows collapse of singularities") add(MItems.GRAVITATIONAL_DISRUPTOR, "description3", "Doesn't destroy any of mass singularity had acquired, which result in violent explosion of matter!") add(MItems.GRAVITATIONAL_DISRUPTOR, "description4", "The explosion %s be contained by %s. Do not even attempt to contain it.") add(MItems.GRAVITATIONAL_DISRUPTOR, "description4_clarification", "can not") @@ -543,6 +658,49 @@ private fun items(provider: MatteryLanguageProvider) { add(MItems.ZPM_BATTERY, "Zero Point Module") add(MItems.ZPM_BATTERY, "description", "Can be found in hands of those who travel between dimensions, if they ever reached different reality of origin of these constructs...") + + add(MItems.MachineUpgrades.Basic.BLANK, "Basic Upgrade Template") + add(MItems.MachineUpgrades.Basic.SPEED, "Basic Speed Upgrade") + add(MItems.MachineUpgrades.Basic.ENERGY_CONSUMPTION, "Basic Energy Consumption Upgrade") + add(MItems.MachineUpgrades.Basic.FAILSAFE, "Basic Failsafe Upgrade") + add(MItems.MachineUpgrades.Basic.ENERGY_STORAGE, "Basic Energy Storage Upgrade") + add(MItems.MachineUpgrades.Basic.MATTER_STORAGE, "Basic Matter Storage Upgrade") + add(MItems.MachineUpgrades.Basic.PROCESSING_ITEMS, "Basic Item Processing Upgrade") + + add(MItems.MachineUpgrades.Normal.BLANK, "Upgrade Template") + add(MItems.MachineUpgrades.Normal.SPEED, "Speed Upgrade") + add(MItems.MachineUpgrades.Normal.ENERGY_CONSUMPTION, "Energy Consumption Upgrade") + add(MItems.MachineUpgrades.Normal.FAILSAFE, "Failsafe Upgrade") + add(MItems.MachineUpgrades.Normal.ENERGY_STORAGE, "Energy Storage Upgrade") + add(MItems.MachineUpgrades.Normal.MATTER_STORAGE, "Matter Storage Upgrade") + add(MItems.MachineUpgrades.Normal.PROCESSING_ITEMS, "Item Processing Upgrade") + + add(MItems.MachineUpgrades.Advanced.BLANK, "Advanced Upgrade Template") + add(MItems.MachineUpgrades.Advanced.SPEED, "Advanced Speed Upgrade") + add(MItems.MachineUpgrades.Advanced.ENERGY_CONSUMPTION, "Advanced Energy Consumption Upgrade") + add(MItems.MachineUpgrades.Advanced.FAILSAFE, "Advanced Failsafe Upgrade") + add(MItems.MachineUpgrades.Advanced.ENERGY_STORAGE, "Advanced Energy Storage Upgrade") + add(MItems.MachineUpgrades.Advanced.MATTER_STORAGE, "Advanced Matter Storage Upgrade") + add(MItems.MachineUpgrades.Advanced.PROCESSING_ITEMS, "Advanced Item Processing Upgrade") + + add(MItems.MachineUpgrades.Creative.SPEED, "Creative Speed Upgrade") + add(MItems.MachineUpgrades.Creative.ENERGY_CONSUMPTION, "Creative Energy Consumption Upgrade") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE, "Creative Energy Storage Upgrade") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT, "Creative Energy Storage Upgrade (Flat)") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT_SMALL, "Creative Energy Storage Upgrade (Flat Small)") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE, "Creative Matter Storage Upgrade") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT, "Creative Matter Storage Upgrade (Flat)") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT_SMALL, "Creative Matter Storage Upgrade (Flat Small)") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT, "Creative Energy Throughput Upgrade") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT, "Creative Energy Throughput Upgrade (Flat)") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT_SMALL, "Creative Energy Throughput Upgrade (Flat Small)") + add(MItems.MachineUpgrades.Creative.FAILSAFE, "Creative Failsafe Upgrade") + add(MItems.MachineUpgrades.Creative.FAILURE, "Creative Failure Upgrade") + add(MItems.MachineUpgrades.Creative.PROCESSING_ITEMS, "Creative Item Processing Upgrade") + + add(MItems.CHEST_UPGRADER, "Crate-r") + add(MItems.CHEST_UPGRADER, "desc", "Replaces placed chests and barrels with cargo crates while keeping storage contents") + add(MItems.CHEST_UPGRADER, "desc2", "Hold desired crates in the opposite hand") } } @@ -576,8 +734,8 @@ private fun androidFeatures(provider: MatteryLanguageProvider) { add(AndroidFeatures.NIGHT_VISION, "Night Vision") add(AndroidFeatures.NANOBOTS_ARMOR, "Nanobots Armor") add(AndroidFeatures.ITEM_MAGNET, "Item Magnet") + add(AndroidFeatures.SWIM_BOOSTERS, "Swim Boosters") add(AndroidFeatures.STEP_ASSIST, "Step Assist") - add(AndroidFeatures.PHANTOM_ATTRACTOR, "Phantom Attractor") add(AndroidFeatures.JUMP_BOOST, "Jump Boost") add(AndroidFeatures.ENDER_TELEPORTER, "Ender Teleporter") } @@ -585,6 +743,35 @@ private fun androidFeatures(provider: MatteryLanguageProvider) { private fun gui(provider: MatteryLanguageProvider) { with(provider.english) { + gui("quicksearch", "Quick search...") + + gui("painter.is_bulk", "Bulk painting") + gui("painter.is_bulk.desc", "Input slot will be automatically refilled from your inventory") + gui("painter.is_bulk.desc2", "Quick moving result will paint as many items as possible from your inventory") + + gui("energy_required", "Energy required: %s") + + gui("insert_priority", "Insert priority") + gui("extract_priority", "Extract priority") + gui("increase", "Increase") + gui("decrease", "Decrease") + + gui("color_picker", "Color Picker") + + gui("color.short.red", "R") + gui("color.short.green", "G") + gui("color.short.blue", "B") + gui("color.short.hue", "H") + gui("color.short.saturation", "S") + gui("color.short.value", "V") + + gui("color.full.red", "Red") + gui("color.full.green", "Green") + gui("color.full.blue", "Blue") + gui("color.full.hue", "Hue") + gui("color.full.saturation", "Saturation") + gui("color.full.value", "Value") + gui("item_monitor.refill_source.desc", "Controls from where to take items for slot auto refill") gui("item_monitor.refill_source.system", "System only. Crafting grid will be auto refilled only from storage system. This is the behavior you see in AE2 and Refined Storage") gui("item_monitor.refill_source.inventory", "Inventory only. Crafting grid will be auto refilled only from player's inventory") @@ -603,6 +790,130 @@ private fun gui(provider: MatteryLanguageProvider) { gui("item_monitor.amount.full", "Stack of ingredients. Craft until reaching stack size of one of ingredients. If at least one of inputs is not stackable then 'one stack' mode is used instead") gui("stored_amount", "Exact amount stored: %s") + + gui("sides.item_config", "Item Configuration") + gui("sides.energy_config", "Energy Configuration") + gui("sides.fluid_config", "Fluid Configuration") + + gui("sides.pull_help", "Hold Shift to cycle pull mode") + gui("sides.push_help", "Hold Ctrl to cycle push mode") + + gui("sides.top", "Top") + gui("sides.bottom", "Bottom") + gui("sides.front", "Front") + gui("sides.back", "Back") + gui("sides.left", "Left") + gui("sides.right", "Right") + + gui("side_mode.disabled", "Disabled") + gui("side_mode.input", "Input only") + gui("side_mode.output", "Output only") + gui("side_mode.input_output", "Input/Output") + gui("side_mode.battery", "Battery") + + gui("side_mode.pull", "Pull") + gui("side_mode.push", "Push") + + gui("upgrades", "Upgrades") + gui("upgrades.current", "Active upgrades:") + gui("upgrade.speed", "Operation speed: %s%%") + gui("upgrade.processing_items", "Items processed per cycle: +%s") + gui("upgrade.energy_storage_flat", "Energy capacity: +%s") + gui("upgrade.matter_storage_flat", "Matter capacity: +%s") + gui("upgrade.energy_storage", "Energy capacity: +%s%%") + gui("upgrade.matter_storage", "Matter capacity: +%s%%") + gui("upgrade.energy_consumed", "Energy consumption: %s%%") + gui("upgrade.energy_throughput_flat", "Energy throughput: +%s") + gui("upgrade.energy_throughput", "Energy throughput: +%s%%") + gui("upgrade.failsafe", "Failure chance: %s%%") + + gui("upgrade_type.list", "Upgrade classification:") + gui("upgrade_type.allowed", "Allowed upgrades:") + gui("upgrade_type.allowed_none", "No possible upgrades at the moment.") + gui("upgrade_type.speed", "Speed") + gui("upgrade_type.processing", "Processing") + gui("upgrade_type.matter_storage", "Matter Storage") + gui("upgrade_type.energy_storage", "Energy Storage") + gui("upgrade_type.energy_consumption", "Energy Consumption") + gui("upgrade_type.energy_throughput", "Energy Throughput") + gui("upgrade_type.failsafe", "Failsafe") + + gui("balance_inputs", "Balance input slots") + + gui("sorting.sort_now", "Sort") + gui("sorting.sort_settings", "Right click to show settings") + gui("sorting.default", "Default sorting") + gui("sorting.name", "Sort by name") + gui("sorting.id", "Sort by ID") + gui("sorting.modid", "Sort by Namespace (mod) ID") + gui("sorting.count", "Sort by quantity") + gui("sorting.ascending", "Ascending") + gui("sorting.descending", "Descending") + gui("sorting.matter_value", "Matter value") + gui("sorting.matter_complexity", "Replication complexity") + + gui("matter_panel.increase_by", "+%s") + gui("matter_panel.decrease_by", "-%s") + gui("matter_panel.send", "Send") + gui("matter_panel.close", "Close") + gui("matter_panel.cancel_task", "Cancel task") + gui("matter_panel.cancel", "Cancel") + gui("matter_panel.label", "Replication request") + gui("matter_panel.task", "Ongoing replication task") + gui("matter_panel.task_line", "%s: %s | %s / %s") + gui("matter_panel.is_providing_tasks", "Tasks are dispatched to replicators") + gui("matter_panel.not_providing_tasks", "Tasks are NOT dispatched to replicators") + gui("matter_panel.cancel_all", "Cancel all tasks") + gui("matter_panel.cancel_all.desc", "Do you really want to cancel all replication tasks?") + + gui("matter_panel.tasks", "Tasks") + gui("matter_panel.patterns", "Patterns") + + gui("matter_panel.number_input", "Input replication task count") + + gui("matter_panel.matter_stored", "Matter stored: %s") + gui("matter_panel.matter_required", "Matter required: %s") + gui("matter_panel.complexity", "Total complexity: %s") + + gui("experience", "%s experience points") + gui("experience_levels", "%s experience levels") + + gui("experience.store", "Store %s experience levels") + gui("experience.store_all", "Store all experience levels") + gui("experience.dispense", "Dispense %s experience levels") + gui("experience.dispense_all", "Dispense all experience levels") + gui("experience.set_exact", "Set your experience level to %s") + + gui("essence_capsule", "(Almost) Everything you ever knew is within") + gui("essence_capsule2", "This item can be recycled at Essence Servo") + gui("essence_capsule3", "Use to break free most of stored knowledge") + gui("essence_capsule.digital", "Use to scan memories stored within") + + gui("slot_filter.filtered", "This slot is filtered for automation") + gui("slot_filter.forbidden", "This slot is forbidden for automation mechanisms") + gui("slot_filter.hint", "To remove slot filter press Ctrl + LMB") + + gui("tooltip.enum.active", "> %s") + + gui("debug.tags.help", "Hold Shift to display tags") + gui("debug.tags.item.title", "Item tags:") + gui("debug.tags.block.title", "Block tags:") + gui("debug.tags.tag.entry", " - %s") + } +} + +private fun jade(provider: MatteryLanguageProvider) { + with(provider.english) { + jadeloc("matter_storage", "Matter Storage") + jadeloc("mattery_energy", "Energy Storage") + jadeloc("mattery_worker", "Machine Job Progress") + jadeloc("matter_bottler", "Matter Bottler Mode") + jadeloc("matter_reconstructor", "Matter Reconstructor Progress") + + jade("mode", "Mode: %s") + + jade("matter_bottler.mode.fill", "Filling") + jade("matter_bottler.mode.drain", "Emptying") } } @@ -620,6 +931,8 @@ fun AddEnglishLanguage(provider: MatteryLanguageProvider) { research(provider) gui(provider) + jade(provider) + with(provider.english) { add("itemGroup.otm", "Overdrive That Matters") add("itemGroup.otm_decorative", "Overdrive That Matters Decorative") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt index 2b84bf75b..9da3b9f2a 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/MatteryLanguageProvider.kt @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import net.minecraft.data.DataGenerator +import net.minecraft.network.chat.Component import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.sounds.SoundEvent @@ -17,7 +18,10 @@ import net.minecraft.world.level.block.Block import net.minecraftforge.common.data.LanguageProvider import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidFeatureType +import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchType +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock private fun researchString(key: AndroidResearchType): String { @@ -35,7 +39,7 @@ private fun researchString(key: AndroidResearchType): String { } class MatteryLanguageProvider(private val gen: DataGenerator) { - private inner class Slave(language: String) : LanguageProvider(gen, OverdriveThatMatters.MOD_ID, language) { + private inner class Slave(language: String) : LanguageProvider(gen.packOutput, OverdriveThatMatters.MOD_ID, language) { override fun addTranslations() {} } @@ -47,6 +51,30 @@ class MatteryLanguageProvider(private val gen: DataGenerator) { } } + inner class MultiBuilder(path: String) { + val path = "$path." + + constructor(vararg path: String) : this(path.joinToString(".")) + constructor(path: Collection) : this(path.joinToString(".")) + + inner class Path(path: String) { + val fullPath = this@MultiBuilder.path + path + fun english(value: String) = english.add(fullPath, value) + fun russian(value: String) = russian.add(fullPath, value) + } + + inline fun add(key: String, configurator: Path.() -> Unit): Component { + return TranslatableComponent(Path(key).also(configurator).fullPath) + } + + inline fun add(key: String, english: String, configurator: Path.() -> Unit = {}): Component { + return add(key) { + english(english) + configurator.invoke(this) + } + } + } + @Suppress("unused") inner class Builder(language: String) { val slave: LanguageProvider by lazy { slaves.computeIfAbsent(language, ::Slave) } @@ -77,6 +105,9 @@ class MatteryLanguageProvider(private val gen: DataGenerator) { fun sound(key: String, value: String) = slave.add("otm.sound.$key", value) fun sound(key: SoundEvent, value: String) = slave.add("otm.sound.${key.location.path}", value) + fun jade(key: String, value: String) = slave.add("otm.jade.$key", value) + fun jadeloc(key: String, value: String) = slave.add("config.jade.plugin_${DataGen.MOD_ID}.$key", value) + inner class Prepended(path: String) { val path = "$path." constructor(vararg path: String) : this(path.joinToString(".")) @@ -382,8 +413,57 @@ class MatteryLanguageProvider(private val gen: DataGenerator) { "Black", ) + val russianColors = Colors("ru_ru", + "Белый", + "Оранжевый", + "Маджентовый", + "Светло Синий", + "Жёлтый", + "Лаймовый", + "Розовый", + "Серый", + "Светло Серый", + "Циановый", + "Пурпурный", + "Синий", + "Коричневый", + "Зелёный", + "Красный", + "Чёрный", + ) + val english by lazy { Builder("en_us") } val russian by lazy { Builder("ru_ru") } + inner class Single(val key: String) { + fun english(value: String) = english.add(key, value) + fun russian(value: String) = russian.add(key, value) + } + + inline fun add(key: String, configurator: Single.() -> Unit) = Single(key).also(configurator) + fun add(key: AndroidResearchType, configurator: Single.() -> Unit) = Single(researchString(key)).also(configurator) + + inline fun add(key: String, english: String, configurator: Single.() -> Unit = {}) = add(key) { english(english); configurator.invoke(this) } + fun add(key: AndroidResearchType, english: String, configurator: Single.() -> Unit = {}) = add(key) { english(english); configurator.invoke(this) } + fun add(key: AndroidResearchType, suffix: String, english: String, configurator: Single.() -> Unit = {}) = add(researchString(key) + ".$suffix") { english(english); configurator.invoke(this) } + + inline fun death(key: String, configurator: Single.() -> Unit) = Single("death.attack.$key").also(configurator) + inline fun death(key: String, english: String, configurator: Single.() -> Unit = {}) = death(key) { english(english); configurator.invoke(this) } + + inline fun research(key: String, configurator: Single.() -> Unit) = Single("android_research.overdrive_that_matters.$key").also(configurator) + inline fun research(key: String, english: String, configurator: Single.() -> Unit = {}) = research(key) { english(english); configurator.invoke(this) } + inline fun research(key: String, suffix: String, english: String, configurator: Single.() -> Unit = {}) = research("$key.$suffix") { english(english); configurator.invoke(this) } + + inline fun misc(key: String, configurator: Single.() -> Unit) = Single("otm.$key").also(configurator) + inline fun misc(key: String, english: String, configurator: Single.() -> Unit = {}) = misc(key) { english(english); configurator.invoke(this) } + + inline fun gui(key: String, configurator: Single.() -> Unit) = Single("otm.gui.$key").also(configurator) + inline fun gui(key: String, english: String, configurator: Single.() -> Unit = {}) = gui(key) { english(english); configurator.invoke(this) } + + inline fun sound(key: String, configurator: Single.() -> Unit) = Single("otm.sound.$key").also(configurator) + inline fun sound(key: String, english: String, configurator: Single.() -> Unit = {}) = sound(key) { english(english); configurator.invoke(this) } + inline fun sound(key: SoundEvent, configurator: Single.() -> Unit) = Single("otm.sound.${key.location.path}").also(configurator) + inline fun sound(key: SoundEvent, english: String, configurator: Single.() -> Unit = {}) = sound(key) { english(english); configurator.invoke(this) } + fun getProvider(language: String): LanguageProvider = slaves.computeIfAbsent(language, ::Slave) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt new file mode 100644 index 000000000..9fbc08869 --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -0,0 +1,928 @@ +package ru.dbotthepony.mc.otm.datagen.lang + +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.MBlocks +import ru.dbotthepony.mc.otm.registry.MEntityTypes +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.registry.MSoundEvents + +private const val HIGH_BLAST_RESISTANCE = "Высокая взрывоустойчивость" +private const val HIGH_BLAST_RESISTANCE_DOOR = "Взрывоустойчивая дверь с засовом красного камня..." +private const val FEELING_SAFE_NOW = "...ощущаете ли вы себя теперь в безопасности?" + +private fun decoratives(provider: MatteryLanguageProvider) { + with(provider.russianColors) { + add(MRegistry.VENT, "%s Вентиляция") + add(MRegistry.VENT_ALTERNATIVE, "%s Альтернативная Вентиляция") + + add(MRegistry.TRITANIUM_BLOCK, "%s тритановый блок") + add(MRegistry.TRITANIUM_STAIRS, "%s Тритановые ступеньки") + add(MRegistry.TRITANIUM_SLAB, "%s Тритановая плита") + add(MRegistry.TRITANIUM_WALL, "%s тритановая ограда") + add(MRegistry.FLOOR_TILES, "%s керамическая плитка") + add(MRegistry.FLOOR_TILES_STAIRS, "%s ступеньки из керамической плитки") + add(MRegistry.FLOOR_TILES_SLAB, "%s плита из керамической плитки") + add(MRegistry.UNREFINED_FLOOR_TILES, "Необработанная %s кирамическая Плитка") + + add(MRegistry.INDUSTRIAL_GLASS, "%s окрашенное промышленное стекло") + add(MRegistry.INDUSTRIAL_GLASS_PANE, "%s окрашенная промышленная стеклянная панель") + + add(MRegistry.CARGO_CRATES, "%s грузовой ящик") + add(MRegistry.DECORATIVE_CRATE, "%s блок контейнера") + } + + with (provider.russian) { + for ((color, name) in provider.russianColors.dyeClassMapped) { + add(MItems.CARGO_CRATE_MINECARTS[color]!!, "Вагонетка с $name грузовым ящиком") + add(MEntityTypes.CARGO_CRATE_MINECARTS[color]!!, "Вагонетка с $name грузовым ящиком") + + add(MRegistry.TRITANIUM_PRESSURE_PLATE.getBlock(color), "$name тритановая нажимная пластина") + add(MRegistry.TRITANIUM_PRESSURE_PLATE.getBlock(color), "description0", "Активируется только при наступлении игрока на неё") + add(MRegistry.TRITANIUM_PRESSURE_PLATE.getBlock(color), "description1", HIGH_BLAST_RESISTANCE) + + add(MBlocks.TRITANIUM_DOOR[color]!!, "$name тритановая дверь") + add(MBlocks.TRITANIUM_DOOR[color]!!, "description0", HIGH_BLAST_RESISTANCE_DOOR) + add(MBlocks.TRITANIUM_DOOR[color]!!, "description1", FEELING_SAFE_NOW) + add(MBlocks.TRITANIUM_DOOR[color]!!, "description2", "Данный вариант выкрашен в $name") + + add(MBlocks.TRITANIUM_TRAPDOOR[color]!!, "$name тритановый люк") + add(MBlocks.TRITANIUM_TRAPDOOR[color]!!, "description0", HIGH_BLAST_RESISTANCE_DOOR) + add(MBlocks.TRITANIUM_TRAPDOOR[color]!!, "description1", FEELING_SAFE_NOW) + add(MBlocks.TRITANIUM_TRAPDOOR[color]!!, "description2", "Данный вариант выкрашен в $name") + } + + add(MRegistry.TRITANIUM_PRESSURE_PLATE.block, "Тритановая нажимная пластина") + add(MRegistry.TRITANIUM_PRESSURE_PLATE.block, "description0", "Активируется только при наступлении игрока на неё") + add(MRegistry.TRITANIUM_PRESSURE_PLATE.block, "description1", HIGH_BLAST_RESISTANCE) + } + + with(provider.russian) { + add(MItems.CARGO_CRATE_MINECARTS[null]!!, "Вагонетка с грузовым ящиком") + add(MEntityTypes.CARGO_CRATE_MINECARTS[null]!!, "Вагонетка с грузовым ящиком") + + add(MRegistry.CARGO_CRATES.block, "Грузовой ящик") + add(MRegistry.TRITANIUM_BLOCK.block, "Тритановый блок") + add(MRegistry.TRITANIUM_STAIRS.block, "Тритановые ступеньки") + add(MRegistry.TRITANIUM_SLAB.block, "Тритановая плита") + add(MRegistry.TRITANIUM_WALL.block, "Тритановая ограда") + + add(MRegistry.INDUSTRIAL_GLASS.block, "Промышленное стекло") + add(MRegistry.INDUSTRIAL_GLASS_PANE.block, "Панель промышенного стекла") + + add(MRegistry.DECORATIVE_CRATE.block, "Ржавый грузовой контейнер") + + add(MRegistry.VENT.block, "Вентиляция") + add(MRegistry.VENT_ALTERNATIVE.block, "Альтернаятивная вентиляция") + + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_BLOCK.blocksWithColor) { + val (base, stripe) = colors + + val baseName = provider.russianColors.dyeClassMapped[base]!! + val stripeName = provider.russianColors.dyeClassMapped[stripe]!! + + add(block, "$baseName-окрашенный $stripeName-ополосаченный тритановый блок") + } + + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_STAIRS.blocksWithColor) { + val (base, stripe) = colors + + val baseName = provider.russianColors.dyeClassMapped[base]!! + val stripeName = provider.russianColors.dyeClassMapped[stripe]!! + + add(block, "$baseName-окрашенные $stripeName-ополосаченные тритановые ступеньки") + } + + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_SLAB.blocksWithColor) { + val (base, stripe) = colors + + val baseName = provider.russianColors.dyeClassMapped[base]!! + val stripeName = provider.russianColors.dyeClassMapped[stripe]!! + + add(block, "$baseName-окрашенная $stripeName-ополосаченная тритановая Плита") + } + + for ((block, colors) in MRegistry.TRITANIUM_STRIPED_WALL.blocksWithColor) { + val (base, stripe) = colors + + val baseName = provider.russianColors.dyeClassMapped[base]!! + val stripeName = provider.russianColors.dyeClassMapped[stripe]!! + + add(block, "$baseName-окрашенная $stripeName-ополосаченная тритановая ограда") + } + } +} + +private fun sounds(provider: MatteryLanguageProvider) { + with(provider.russian) { + sound("rifle_shot", "Выстрел плазменной винтовки") + sound("plasma_weapon_overheat", "Плазменное оружие перегрелось") + sound("player_become_android", "Игрок превратился в андроида") + + sound(MSoundEvents.CARGO_CRATE_OPEN, "Открыт грузовой ящик") + } +} + +private fun misc(provider: MatteryLanguageProvider) { + with(provider.russian) { + gui("help.slot_filters", "Удерживайте CTRL для настройки фильтрации слотов") + gui("help.slot_charging", "Удерживайте ALT для переключения зарядки слотов") + + gui("needs", "Требуется %s") + gui("needs_x", "Требуется %s x%d") + + misc("needs_no_power", "Не требует энергии для работы") + + gui("lock_holo_screen", "Заблокировать содержимое") + gui("lock_holo_screen.tip", "Блокировка и разблокировка содержимого возможна только в режиме творчества.\nКогда заблокировано, границы ввода текста отключены.") + + gui("ticks", "Тиков") + + gui("power_cost_per_use", "Энергии на операцию: %s") + gui("power_cost_per_tick", "Энергии на тик: %s") + + gui("cancel", "Отмена") + gui("confirm", "Подтвердить") + + gui("recipe.ticks", "%s Тиков") + + gui("exopack", "Инвентарь Экзопака") + + gui("exopack.customize", "Изменить внешний вид Экзопака") + gui("exopack.customization", "Внешний вид Экзопака") + + gui("exopack.go_back", "Открыть обычный инвентарь") + gui("exopack.go_in", "Открыть инвентарь Экзопака") + gui("exopack.toggle_visibility", "Отображать на игроке") + gui("exopack.toggle_glow", "Свечение в темноте") + gui("exopack.change_color", "Изменить окраску") + gui("exopack.change_color2", "Убрать окраску") + gui("exopack.go_curios", "Открыть инвентарь Curios") + + gui("exopack.probe1", "Данное маленькое устройство необычно на ощупь, а так же неприступно для любых попыток вскрыть.") + gui("exopack.probe2", "На одной из сторон данного устройства находится сканер отпечатка, который тускло загорается при касании.") + gui("exopack.probe3", "Вероятно, устройство откроется если достаточно сильно нажать на сканер отпечатка, и вы чувствуете, что последствия будут необратимы!") + + gui("exopack.already_activated", "У вас уже есть Экзопак!") + + gui("exopack_upgrades.no_exopack", "Данный пазл технологии кажется для вас бесполезным... Или..?!") + + gui("exopack_upgrades.already_activated", "Улучшение уже активно!") + gui("exopack_upgrades.slots_upgrade", "Активируя данное улучшение даст %s слотов в вашем Экзопаке.") + gui("exopack_upgrades.crafting_upgrade", "Активация данного улучшения даст 3x3 сетку создания в вашем Экзопаке.") + gui("exopack_upgrades.smelting_upgrade", "Активация данного улучшения даст плавильню в вашем Экзопаке.") + gui("exopack_upgrades.ender_access_upgrade", "Активация данного улучшения даст доступ к сундуку края в вашем Экзопаке.") + + gui("crude_battery.replace_in_world", "Простота устройства данного аккумулятора позволяет вам заменить .") + gui("crude_battery.replace_in_world_warning", "Данная операция крайне рискованная и может нанести огромный урон вашим системам!") + + gui("power_supplier.active_nodes", "Узлы сети, требующие питания: %s") + + misc("battery.single_use", "Единоразовая батарейка, не может быть перезаряжена.") + + misc("exopack_upgrades.slots_upgraded", "Ваш Экзопак был расширен на %s слотов") + misc("exopack_upgrades.crafting_upgraded", "Ваш Экзопак получил 3x3 сетку создания") + misc("exopack_upgrades.smelting_installed", "Ваш Экзопак получил плавильню") + misc("exopack_upgrades.ender_access_installed", "Ваш Экзопак получил доступ к содержимому сундука края") + + misc("exopack.granted1", "После некоторого времени нажатия на сканер отпечатка, маяк исчезает.") + misc("exopack.granted2", "Через мгновение, вашу спину пронзают множество раз...") + misc("exopack.granted3", "Как только боль утихает, вы обнаруживаете нового 'друга' на вашей спине: Экзопак.") + misc("exopack.granted4", "Данное устройство предоставляет вам доступ к карманному измерению в 4том измерении.") + misc("exopack.granted5", "Ищите или создавайте модули для улучшения Экзопака.") + misc("exopack.granted6", "Так же сам Экзопак существует в 4том измерении, что не мешает носить любую броню.") + misc("exopack.granted7", "Но не смотря на то, что экзопак существует в 4том измерении, предметы из него всё равно высыпаются при смерти.") + + misc("dumping_matter_registry", "Выгружаю реестр материи в %s") + misc("dumped_matter_registry", "Выгрузил реестр материи в %s") + + misc("iteration", "Итерация %s") + misc("death_reason", "Списан!") + + misc("item.blackhole_immunity", "Нейтрализует искажение пространства-времени сингулярностями") + + misc("suffix.merge", "%s %s") + + misc("suffix.none", "%s %s") + misc("suffix.kilo", "%s к%s") + misc("suffix.mega", "%s М%s") + misc("suffix.giga", "%s Г%s") + misc("suffix.tera", "%s Т%s") + misc("suffix.peta", "%s П%s") + misc("suffix.exa", "%s Э%s") + misc("suffix.zetta", "%s З%s") + misc("suffix.yotta", "%s И%s") + + misc("suffix.deci", "%s д%s") + misc("suffix.centi", "%s с%s") + misc("suffix.milli", "%s м%s") + misc("suffix.micro", "%s мк%s") + misc("suffix.nano", "%s н%s") + misc("suffix.pico", "%s п%s") + misc("suffix.femto", "%s ф%s") + misc("suffix.atto", "%s а%s") + misc("suffix.zepto", "%s з%s") + misc("suffix.yocto", "%s и%s") + + misc("suffix_concise.none", "%s") + misc("suffix_concise.kilo", "%sк") + misc("suffix_concise.mega", "%sМ") + misc("suffix_concise.giga", "%sГ") + misc("suffix_concise.tera", "%sТ") + misc("suffix_concise.peta", "%sП") + misc("suffix_concise.exa", "%sЭ") + misc("suffix_concise.zetta", "%sЗ") + misc("suffix_concise.yotta", "%sИ") + misc("suffix_concise.deci", "%sд") + misc("suffix_concise.centi", "%sс") + misc("suffix_concise.milli", "%sм") + misc("suffix_concise.micro", "%s к") + misc("suffix_concise.nano", "%sн") + misc("suffix_concise.pico", "%sп") + misc("suffix_concise.femto", "%sф") + misc("suffix_concise.atto", "%sа") + misc("suffix_concise.zepto", "%sз") + misc("suffix_concise.yocto", "%sи") + + misc("suffix_raw.kilo", "к") + misc("suffix_raw.mega", "М") + misc("suffix_raw.giga", "Г") + misc("suffix_raw.tera", "Т") + misc("suffix_raw.peta", "П") + misc("suffix_raw.exa", "Э") + misc("suffix_raw.zetta", "З") + misc("suffix_raw.yotta", "И") + misc("suffix_raw.deci", "д") + misc("suffix_raw.centi", "с") + misc("suffix_raw.milli", "м") + misc("suffix_raw.micro", "мк") + misc("suffix_raw.nano", "н") + misc("suffix_raw.pico", "п") + misc("suffix_raw.femto", "ф") + misc("suffix_raw.atto", "а") + misc("suffix_raw.zepto", "з") + misc("suffix_raw.yocto", "и") + + misc("item.power.infinite.storage", "Хранимая энергия неиссякаема") + misc("item.power.infinite.throughput", "Максимальный ввод/вывод неограничен") + misc("item.power.passed", "Переданная энергия: %s") + misc("item.power.received", "Принятая энергия: %s") + misc("item.power.average", "Среднее: %s в тик") + misc("item.power.last_20_ticks", "Последняя секунда: %s") + misc("item.power.last_tick", "Последний тик: %s") + + gui("power.passed", "Всего передано энергии:") + gui("power.average", "Средняя передача в тик:") + gui("power.last_20_ticks", "Последняя секунда:") + gui("power.last_tick", "Последний тик:") + + misc("item.power.storage", "Хранимая энергия: %s / %s") + misc("item.power.throughput", "Максимальная пропускная способность: %s / %s") + misc("item.power.throughput_mono", "Максимальная пропускная способность: %s") + misc("item.power.infinity", "∞ МтДж") + + misc("item.worker.work_ticks_mono", "Рабочих тиков: %s") + misc("item.worker.work_ticks", "Рабочих тиков: %s / %s") + + misc("item.block.stored_battery", "Батарея: %s") + + misc("item.pattern.stored", "Хранимые шаблоны: %s / %s") + misc("item.pattern.infinite.stored", "Хранимые шаблоны: %s") + misc("item.pattern.research", "Исследовано: %s%%") + misc("item.pattern.research.item_count", "Предметы: %s / %s") + misc("item.pattern.research.advance", "Исследование за предмет: %s%%") + + misc("item.matter.infinite", "Хранимая материя неиссякаема") + misc("item.matter.normal", "Хранимая материя: %s / %s") + + misc("gui.matter_task.total", "Всего: %s") + misc("gui.matter_task.required", "Осталось исполнить: %s") + misc("gui.matter_task.in_progress", "В процессе: %s") + misc("gui.matter_task.finished", "Завершено: %s") + + misc("pill.warning", "ВНИМАНИЕ: Это МГНОВЕННО спишет вас при употреблении!") + misc("pill.android", "Примешь эту таблетку - войдешь в страну чудес, потеряв то, что удерживает тебя здесь.") + + misc("pill.humane", "Примешь эту таблетку - проснешься в своей постели и поверишь, что это был сон.") + + misc("pill.oblivion", "Предметы и Очки опыта, потраченные на исследования полностью возвращаются.") + misc("pill.message_oblivion", "Все возможности андроида были удалены, а потраченные на исследования предметы и опыт возвращены.") + + misc("pill.heal", "Мгновенно восполняет 4 сердца при употреблении, а так же даёт на 2 минуты поглощение V и на 8 секунд регенерацию III.") + misc("pill.heal_android", "Не действует на андроидов.") + + misc("pill.message", "Ничего не произошло, но вы чувствуете... себя уставшим?.. Возможно надо отдохнуть.") + misc("pill.message_finish", "§kОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС ОДИН ИЗ НАС") + + gui("power.percentage_level", "Уровень энергии: %s%%") + gui("power.name", "МтДж") + gui("fluid.name", "В") + gui("fluid.level", "%s / %s с %s") + + gui("empty", "Пусто") + + gui("power.burn_time", "Оставшееся время горения: %s тиков") + + gui("progress_widget", "Прогресс: %s%%") + gui("progress_widget_stuck", "Это устройство не может продолжить работу, проверьте конфигурацию") + + gui("total_raw", "Всего:") + + gui("matter.percentage_level", "Уровень материи: %s%%") + gui("matter.format", "Материя: %s") + gui("matter.format_and_complexity", "%s / Сложность: %s") + gui("matter.format_and_complexity2", "%s (%s) / Сложность: %s (%s)") + gui("matter.name", "МтЕд") + + gui("filter.is_whitelist", "Белый список") + gui("filter.match_nbt", "Сравнивать NBT") + gui("filter.match_tag", "Сравнить Теги") + + gui("android_research", "Дерево Исследований") + + gui("pattern.percentage_level", "Уровень заполнения: %s%%") + gui("pattern.format", "Хранимые шаблоны: %s / %s") + + gui("redstone.ignored", "Режим красного камня: Игнорирование") + gui("redstone.low", "Режим красного камня: Нет сигнала") + gui("redstone.high", "Режим красного камня: Есть сигнал") + + gui("redstone.ignored.description", "Сигнал красного камня не влияет на работу устройства") + gui("redstone.low.description", "Устройство работает если нет сигнала красного камня") + gui("redstone.high.description", "Устройство работает если есть сигнал красного камня") + + misc("3d2d.gravitation_stabilizer.mass", "Масса сингулярности: %s") + misc("3d2d.gravitation_stabilizer.strength", "Гравитационная сила: %s") + + misc("android_station.research.low_power", "Мало питания у станции андроидов! Исследования недоступны!") + misc("android_station.research.researched", "Исследовано!") + misc("android_station.research.can_be_researched", "Готово к исследованию!") + misc("android_station.research.can_not_be_researched", "Нельзя исследовать!") + misc("android_station.research.xp_cost", "Требуется %s уровней Опыта") + misc("android_station.research.item", "Требует %s %s шт.") + misc("android_station.research.missing_predecessors", "Сначала надо изучить %s") + + misc("android_station.research.confirm", "Подтвердите исследование %s") + misc("android_station.research.research_will_be_blocked", "Следующие исследования станут недоступны:") + + misc("android_station.low_power_0", "Недостаточно питания; Невозможно менять части андроида") + misc("android_station.low_power_1", "Слабое питание; Невозможно исследовать") + misc("android_station.power_ok", "Питание в норме") + + misc("filter.yes", "Да") + misc("filter.no", "Нет") + + misc("matter_bottler.switch_mode", "Переключить режим работы") + + misc("item.quantum_battery.creative", "Наполните этот аккумулятор чтоб выиграть Minecraft.") + misc("item.quantum_battery.creative2", "Встретимся после выгорания миллионов звёзд.") + misc("item.quantum_battery.creative_power", "Хранимая энергия: %s / ∞") + + misc("item.quantum_link_id", "ID Квантового соединения: %s") + misc("item.quantum_description", "Содержимое данного предмета связано с другими предметами посредством квантовой запутанности") + } +} + +private fun death(provider: MatteryLanguageProvider) { + with(provider.russian) { + death("otm_become_android", "%1\$s потерял свою человечность") + death("otm_become_humane", "%1\$s восстановил свою человечность") + death("otm_event_horizon", "%1\$s никогда не пересёк горизонт событий") + death("otm_hawking_radiation", "%1\$s открыл излучение Хокинга") + death("otm_emp", "Электроника %1\$s перегорела") + death("otm_cosmic_rays", "Электроника %1\$s была ошеломлена космическим излучением") + death("otm_android_discharge", "В аккумуляторах %1\$s закончился заряд") + + death("otm_become_android.player", "%1\$s потерял свою человечность, когда %2\$s пытался образумить их") + death("otm_become_humane.player", "%1\$s восстановил свою человечность, когда %2\$s пытался образумить их") + death("otm_event_horizon.player", "%1\$s попытался пересечь горизонт событий, спасаясь от %2\$s") + death("otm_hawking_radiation.player", "%1\$s распался пока сражался с %2\$s") + death("otm_emp.player", "%2\$s выбил все предохранители %1\$s") + death("otm_emp.player.item", "%2\$s выбил все предохранители %1\$s используя %3\$s") + + death("otm_explosive_hammer", "Время развлечений у %1\$s с молотком подошло к концу") + death("otm_hammer_nail", "%1\$s был пригвождён") + death("otm_hammer_nail" + ".player", "%1\$s был пригвождён %2\$s") + death("otm_hammer_nail" + ".player.item", "%1\$s был пригвождён %2\$s используя %3\$s") + + death("otm_exopack_probe", "%1\$s не выдержал спинную хирургию") + death("otm_exopack_probe.player", "%1\$s не выдержал спинную хирургию пока сражался с %2\$s") + } +} + +private fun blocks(provider: MatteryLanguageProvider) { + with(provider.russian) { + add(MBlocks.ANDROID_STATION, "Станция андроидов") + add(MBlocks.ANDROID_CHARGER, "Беспроводной зарядник") + add(MBlocks.ANDROID_CHARGER, "desc", "Заряжает ближайших андроидов и экзопаки") + add(MBlocks.BATTERY_BANK, "Банк аккумуляторов") + add(MBlocks.MATTER_DECOMPOSER, "Декомпозитор материи") + add(MBlocks.MATTER_CAPACITOR_BANK, "Банк накопителей материи") + add(MBlocks.MATTER_CABLE, "Кабель сети материи") + add(MBlocks.PATTERN_STORAGE, "Хранилище шаблонов") + add(MBlocks.MATTER_SCANNER, "Сканер материи") + add(MBlocks.MATTER_PANEL, "Монитор шаблонов") + add(MBlocks.MATTER_REPLICATOR, "Репликатор материи") + add(MBlocks.MATTER_BOTTLER, "Бутилировщик материи") + add(MBlocks.DRIVE_VIEWER, "Просмотрщик дисков конденсации") + add(MBlocks.BLACK_HOLE, "Локализированная сингулярная точка аномального искажения пространства-времени") + add(MBlocks.COBBLESTONE_GENERATOR, "Генератор булыжника") + add(MBlocks.INFINITE_WATER_SOURCE, "Неиссякаемый источник воды") + add(MBlocks.ESSENCE_STORAGE, "Хранилище эссенции") + add(MBlocks.ESSENCE_STORAGE, "desc", "Позволяет хранить очки опыта") + add(MBlocks.MATTER_RECONSTRUCTOR, "Материальный реконструктор") + add(MBlocks.MATTER_RECONSTRUCTOR, "desc", "Чинит инструменты используя материю") + add(MBlocks.DEV_CHEST, "Сундук разработчика") + add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре") + add(MBlocks.PAINTER, "Стол маляра") + add(MBlocks.MATTER_ENTANGLER, "Квантовый запутыватель материи") + + add(MBlocks.FLUID_TANK, "Жидкостный бак") + add(MBlocks.FLUID_TANK, "named", "Жидкостный бак (%s)") + + add(MBlocks.ENGINE, "Двигатель корабля") + add(MBlocks.ENGINE, "desc", "К сожалению, он больше не выглядит рабочим.") + add(MBlocks.HOLO_SIGN, "Голографическая табличка") + + add(MBlocks.TRITANIUM_INGOT_BLOCK, "Блок слитков тритана") + + add(MBlocks.ENERGY_COUNTER, "Счётчик энергии") + add(MBlocks.ENERGY_COUNTER, "Facing", "сторона входа: %s") + add(MBlocks.ENERGY_COUNTER, "Switch", "сменить сторону входа") + add(MBlocks.ENERGY_COUNTER, "Limit", "лимит ввода/вывода. -1 для отключения лимитов") + + add(MBlocks.CHEMICAL_GENERATOR, "Химический генератор") + add(MBlocks.DRIVE_RACK, "Стеллаж дисков конденсации") + add(MBlocks.ITEM_MONITOR, "Монитор предметов") + add(MBlocks.PLATE_PRESS, "Пресс пластин") + add(MBlocks.TWIN_PLATE_PRESS, "Двойной пресс пластин") + + add(MBlocks.POWERED_FURNACE, "Электрическая печь") + add(MBlocks.POWERED_BLAST_FURNACE, "Индукционная печь") + add(MBlocks.POWERED_SMOKER, "Микроволновая печь") + + add(MBlocks.MATTER_RECYCLER, "Перерабатыватель материи") + add(MBlocks.ENERGY_SERVO, "Энергетическая помпа") + add(MBlocks.ENERGY_SERVO, "Desc", "заряжает, разряжает и передаёт энергию между предметами") + + add(MBlocks.CARBON_FIBRE_BLOCK, "Блок углеродных трубок") + add(MBlocks.METAL_MESH, "Блок металлической сетки") + add(MBlocks.METAL_JUNK, "Металлический хлам") + add(MBlocks.METAL_JUNK, "desc", "Бесполезный хлам, или нет?") + + add(MBlocks.TRITANIUM_STRIPED_BLOCK, "Тритановый блок с полоской") + add(MBlocks.TRITANIUM_STRIPED_STAIRS, "Тритановые ступеньки с полоской") + add(MBlocks.TRITANIUM_STRIPED_SLAB, "Тритановая плита с полоской") + add(MBlocks.TRITANIUM_STRIPED_WALL, "Тритановая ограда с полоской") + add(MBlocks.TRITANIUM_ORE, "Тритановая руда") + add(MBlocks.DEEPSLATE_TRITANIUM_ORE, "Тританоносный глубинный сланец") + add(MBlocks.TRITANIUM_RAW_BLOCK, "Блок рудного тритана") + + add(MBlocks.STORAGE_CABLE, "Кабель хранилища") + add(MBlocks.STORAGE_POWER_SUPPLIER, "Подстанция сети хранилища") + add(MBlocks.STORAGE_BUS, "Шина хранилища") + add(MBlocks.STORAGE_IMPORTER, "Импортер хранилища") + add(MBlocks.STORAGE_EXPORTER, "Экспортер хранилища") + + add(MBlocks.GRAVITATION_STABILIZER, "Стабилизатор пространства-времени") + add(MBlocks.GRAVITATION_STABILIZER_LENS, "Линза стабилизатора пространства-времени") + add(MBlocks.GRAVITATION_STABILIZER, "Desc", "Уменьшает искажение пространства-времени сингулярностей") + add(MBlocks.GRAVITATION_STABILIZER, "Desc2", "Имейте ввиду, что несколько стабилизаторов создают экспоненциальный эффект") + add(MBlocks.GRAVITATION_STABILIZER, "Desc3", "Слишком слабое искажение пространства-времени приведёт к быстрому 'испарению' сингулярности!") + + add(MBlocks.PHANTOM_ATTRACTOR, "Приманщик фантомов") + add(MBlocks.PHANTOM_ATTRACTOR, "Desc", "приманивает фантомов в ночное время") + + add(MBlocks.LABORATORY_LAMP, "Лабораторная лампа") + add(MBlocks.LABORATORY_LAMP, "Description", "освещает на несколько блоков в направлении своей лампы, с переключателем красного камня") + add(MBlocks.LABORATORY_LAMP_INVERTED, "Лабораторная лампа (инвентированный сигнал)") + add(MBlocks.DANGER_STRIPE_BLOCK, "Полоски 'опасность'") + add(MBlocks.METAL_BEAM, "Металлическая опора") + + add(MBlocks.TRITANIUM_DOOR[null]!!, "Тритановая дверь") + add(MBlocks.TRITANIUM_DOOR[null]!!, "description0", "Взрывоустойчивая дверь с засовом красного камня...") + add(MBlocks.TRITANIUM_DOOR[null]!!, "description1", FEELING_SAFE_NOW) + + add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "Тритановый люк") + add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "description0", "Взрывоустойчивая дверь с засовом красного камня...") + add(MBlocks.TRITANIUM_TRAPDOOR[null]!!, "description1", FEELING_SAFE_NOW) + + add(MBlocks.TRITANIUM_BARS, "Тритановая решётка") + + for (block in MBlocks.TRITANIUM_ANVIL) + add(block, "Тритановая наковальня") + } +} + +private fun items(provider: MatteryLanguageProvider) { + with(provider.russian) { + add(MItems.PROCEDURAL_BATTERY, "Загадочный аккумулятор") + add(MItems.PROCEDURAL_BATTERY, "desc", "Данные аккумуляторы можно найти в подземельях, со случайными характеристиками") + + add(MItems.EXPLOSIVE_HAMMER, "Молоток-убийца") + add(MItems.EXPLOSIVE_HAMMER, "desc", "Для тех, кому стало скучно") + add(MItems.EXPLOSIVE_HAMMER, "primed", "Заряжен!") + add(MItems.EXPLOSIVE_HAMMER, "not_primed0", "Не заряжен") + add(MItems.EXPLOSIVE_HAMMER, "not_primed1", "Для зарядки добавьте немного пороха и наконечник-самородок") + + add(MItems.EXOPACK_PROBE, "Маяк экзопака") + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE, "Творческое обновление инвентаря экзопака") + add(MItems.ExopackUpgrades.CRAFTING_UPGRADE, "Обновление сетки крафта экзопака") + add(MItems.ExopackUpgrades.SMELTING_UPGRADE, "Модуль плавильни экзопака") + add(MItems.ExopackUpgrades.ENDER_UPGRADE, "Модуль доступа к сундуку края экзопака") + + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_WITHER, "Обновление сверхмассивной упаковки") + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_WITHER, "description", "Использует те же принципы, которыми обладают звёзды незера") + + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON, "Обновление соединения края экзопака") + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON, "description", "Позволяет хранить предметы в карманном измерении. А так же даёт доступ к содержимому сундука края.") + + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "Неописуемое обновление инвентаря экзопака") + add(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, "description", "В нормальных условиях, они появляются в сундуках") + + add(MItems.ESSENCE_CAPSULE, "Капсула эссенции") + add(MItems.ESSENCE_DRIVE, "Голодиск воспоминаний андроида") + add(MItems.ESSENCE_SERVO, "Помпа эссенции") + add(MItems.ESSENCE_SERVO, "desc", "Позволяет 'перекачивать' эссенцию гуманоидов из плоти") + add(MItems.ESSENCE_SERVO, "desc2", "Может быть использовано напрямую, или как инструмент внутри хранилища эссенции") + + add(MItems.NUTRIENT_PASTE, "Питательная паста") + + add(MItems.FLUID_CAPSULE, "Жидкостная капсула") + add(MItems.FLUID_CAPSULE, "named", "Жидкостная капсула (%s)") + + add(MItems.BLACK_HOLE_SCANNER, "Сканер ёярностей") + add(MItems.BLACK_HOLE_SCANNER, "desc", "Сканирует сингулярности и считывает их свойства") + add(MItems.BLACK_HOLE_SCANNER, "desc2", "Держите в любой их рук для считывания массы сингулярностей") + + add(MItems.GRAVITATION_FIELD_LIMITER, "Ограничитель искажения пространства-времени") + add(MItems.GRAVITATION_FIELD_SENSOR, "Сенсор искажения пространства-времени") + add(MItems.PORTABLE_GRAVITATION_STABILIZER, "Портативный стабилизатор пространства-времени") + + add(MItems.BATTERY_CRUDE, "Самобытный аккумулятор") + add(MItems.BATTERY_BASIC, "Простой аккумулятор") + add(MItems.BATTERY_NORMAL, "Аккумулятор") + add(MItems.BATTERY_DENSE, "Плотный аккумулятор") + add(MItems.BATTERY_CAPACITOR, "Аккумулятор-конденсатор") + add(MItems.BATTERY_CREATIVE, "Творческий аккумулятор") + + add(MItems.QUANTUM_BATTERY, "Квантовый аккумулятор") + add(MItems.QUANTUM_CAPACITOR, "Квантовый аккумулятор-накопитель") + add(MItems.QUANTUM_BATTERY_CREATIVE, "Творческий квантовый аккумулятор") + + add(MItems.TRITANIUM_SWORD, "Тритановый меч") + add(MItems.TRITANIUM_PICKAXE, "Тритановая кирка") + add(MItems.TRITANIUM_SHOVEL, "Тритановая лопата") + add(MItems.TRITANIUM_AXE, "Тритановый топор") + add(MItems.TRITANIUM_HOE, "Тритановая мотыга") + add(MItems.TRITANIUM_SHEARS, "Тритановые ножницы") + add(MItems.TRITANIUM_SHIELD, "Тритановый щит") + + add(MItems.TRITANIUM_HELMET, "Тритановый шлем") + add(MItems.TRITANIUM_CHESTPLATE, "Тритановый нагрудник") + add(MItems.TRITANIUM_PANTS, "Тритановые поножи") + add(MItems.TRITANIUM_BOOTS, "Тритановые ботинки") + + add(MItems.SIMPLE_TRITANIUM_HELMET, "Простой тритановый шлем") + add(MItems.SIMPLE_TRITANIUM_CHESTPLATE, "Простой тритановый нагрудник") + add(MItems.SIMPLE_TRITANIUM_PANTS, "Простые тритановые поножи") + add(MItems.SIMPLE_TRITANIUM_BOOTS, "Простые тритановые ботинки") + + add(MItems.TRITANIUM_DUST, "Тритановая пыль") + add(MItems.TRITANIUM_INGOT, "Тритановый слиток") + add(MItems.TRITANIUM_NUGGET, "Тритановый самородок") + add(MItems.MATTER_IO_PORT, "Порт ввода/вывода материи") + add(MItems.MATTER_TRANSFORM_MATRIX, "Матрица преобразования материи") + add(MItems.ENERGY_BUS, "Шина питания") + add(MItems.ELECTRIC_PARTS, "Электрические части") + add(MItems.MACHINE_FRAME, "Каркас механизма") + add(MItems.TRITANIUM_PLATE, "Тритановая пластина") + add(MItems.IRON_PLATE, "Железная пластина") + add(MItems.GOLD_PLATE, "Золотая пластина") + add(MItems.COPPER_WIRING, "Медная проволока") + add(MItems.GOLD_WIRING, "Золотая проволока") + add(MItems.PORTABLE_CONDENSATION_DRIVE_CASING, "Каркас портативного диска конденсации") + add(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE_CASING, "Каркас плотного портативного диска конденсации") + add(MItems.CIRCUIT_PLATING, "Плата электроники") + add(MItems.BASIC_CONTROL_CIRCUIT, "Простая схема управления") + add(MItems.ADVANCED_CONTROL_CIRCUIT, "Продвинутая схема управления") + add(MItems.QUANTUM_TRANSCEIVER, "Квантовый передатчик") + add(MItems.ELECTROMAGNET, "Электромагнит") + add(MItems.ELECTROMOTOR, "Электромотор") + add(MItems.MIRROR_COMPOUND, "Набор выплавки зеркала") + add(MItems.MIRROR, "Зеркало") + add(MItems.MIRROR, "description", "Я могу очень отчётливо видеть своё отражение в этом зеркале") + add(MItems.REINFORCED_TRITANIUM_PLATE, "Укреплённая тритановая пластина") + add(MItems.REINFORCED_TRITANIUM_PLATE, "description", "Бронированная пластина, усиленная что бы выдержать большие кинетические силы") + add(MItems.CARBON_MESH, "Углеродная сетка") + + add(MItems.GRAVITATIONAL_DISRUPTOR, "Маяк уравнения пространства-времени") + + add(MItems.GRAVITATIONAL_DISRUPTOR, "description", "Будучи находясь около точки искажения пространства-времени, создаёт противоположное искажение пространства-времени") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description2", "Позволяет 'уничтожать' сингулярности") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description3", "Никак не влияет на материю, которую накопила сингулярность, что вызывает неописуемо сильный взрыв материи!") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description4", "Взрыв %s сдержан %s. Даже не пытайтесь его сдержать.") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description4_clarification", "не может быть") + add(MItems.GRAVITATIONAL_DISRUPTOR, "description4_clarification2", "ничем") + + add(MItems.MATTER_DUST, "Пыль материи") + add(MItems.MATTER_DUST, "desc", "Данный предмет является результатом неудачной попытки декомпозиции или репликации") + add(MItems.MATTER_DUST, "desc2", "Закиньте в Материальный Переработчик для переработки обратно в чистую материю!") + add(MItems.MATTER_DUST, "desc3", "Не нюхать, не кидать на других и не сыпать на пончики") + + add(MItems.PILL_ANDROID, "Пилюля андроида") + add(MItems.PILL_HUMANE, "Пилюля человечности") + add(MItems.PILL_OBLIVION, "Пилюля сброса андроида до заводских настроек") + add(MItems.PILL_HEAL, "Медицинская пилюля") + + add(MItems.MATTER_CAPACITOR_PARTS, "Части накопителя материи") + add(MItems.MATTER_CAPACITOR_BASIC, "Простой накопитель материи") + add(MItems.MATTER_CAPACITOR_NORMAL, "Накопитель материи") + add(MItems.MATTER_CAPACITOR_DENSE, "Плотный накопитель материи") + add(MItems.MATTER_CAPACITOR_CREATIVE, "Творческий накопитель материи") + + add(MItems.ENERGY_SWORD, "Высокочастотный клинок") + add(MItems.ENERGY_SWORD, "desc", "Требует энергию для работы") + add(MItems.ENERGY_SWORD, "desc2", "Наносит дополнительный урон андроидам если имеет заряд") + add(MItems.ENERGY_SWORD, "desc3", "Всегда наносит полный урон по площади если имеет заряд") + add(MItems.ENERGY_SWORD, "desc4", "Зачарование 'Разящий клинок' не имеет никакого эффекта на данном оружии") + + add(MItems.PORTABLE_CONDENSATION_DRIVE, "Portable Condensation Drive") + add(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE, "Portable Dense Condensation Drive") + add(MItems.PLASMA_RIFLE, "Плазменная винтовка") + add(MItems.TRITANIUM_ORE_CLUMP, "Рудный тритан") + add(MItems.PATTERN_DRIVE_NORMAL, "Диск шаблонов") + add(MItems.PATTERN_DRIVE_CREATIVE, "Творческий диск шаблонов") + + add(MItems.PATTERN_DRIVE_CREATIVE2, "Вездесущий диск шаблонов") + add(MItems.PATTERN_DRIVE_CREATIVE2, "description1", "Предмет режима творчества") + add(MItems.PATTERN_DRIVE_CREATIVE2, "description2", "Содержит в себе шаблоны всех предметов, которые имеют значение материи") + + add(MItems.ZPM_BATTERY, "Модуль нулевой точки") + add(MItems.ZPM_BATTERY, "description", "Может быть найден у тех, кто путешествует между измерениями, если, конечно, они смогли достигнуть вселенной, где данные устройства были созиданы...") + + add(MItems.MachineUpgrades.Basic.BLANK, "Шаблон простого улучшения") + add(MItems.MachineUpgrades.Basic.SPEED, "Простое улучшение скорости") + add(MItems.MachineUpgrades.Basic.ENERGY_CONSUMPTION, "Простое улучшение энергоэффективности") + add(MItems.MachineUpgrades.Basic.FAILSAFE, "Простое улучшение отказоустойчивости") + add(MItems.MachineUpgrades.Basic.ENERGY_STORAGE, "Простое улучшение энергохранилища") + add(MItems.MachineUpgrades.Basic.MATTER_STORAGE, "Простое улучшение хранилища материи") + add(MItems.MachineUpgrades.Basic.PROCESSING_ITEMS, "Простое улучшение обработки") + + add(MItems.MachineUpgrades.Normal.BLANK, "Шаблон улучшения") + add(MItems.MachineUpgrades.Normal.SPEED, "Улучшение скорости") + add(MItems.MachineUpgrades.Normal.ENERGY_CONSUMPTION, "Улучшение энергоэффективности") + add(MItems.MachineUpgrades.Normal.FAILSAFE, "Улучшение отказоустойчивости") + add(MItems.MachineUpgrades.Normal.ENERGY_STORAGE, "Улучшение энергохранилища") + add(MItems.MachineUpgrades.Normal.MATTER_STORAGE, "Улучшение хранилища материи") + add(MItems.MachineUpgrades.Normal.PROCESSING_ITEMS, "Улучшение обработки") + + add(MItems.MachineUpgrades.Advanced.BLANK, "Шаблон продвинутого улучшения") + add(MItems.MachineUpgrades.Advanced.SPEED, "Продвинутое улучшение скорости") + add(MItems.MachineUpgrades.Advanced.ENERGY_CONSUMPTION, "Продвинутое улучшение энергоэффективности") + add(MItems.MachineUpgrades.Advanced.FAILSAFE, "Продвинутое улучшение отказоустойчивости") + add(MItems.MachineUpgrades.Advanced.ENERGY_STORAGE, "Продвинутое улучшение энергохранилища") + add(MItems.MachineUpgrades.Advanced.MATTER_STORAGE, "Продвинутое улучшение хранилища материи") + add(MItems.MachineUpgrades.Advanced.PROCESSING_ITEMS, "Продвинутое улучшение обработки") + + add(MItems.MachineUpgrades.Creative.SPEED, "Творческое улучшение скорости") + add(MItems.MachineUpgrades.Creative.ENERGY_CONSUMPTION, "Творческое улучшение энергоэффективности") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE, "Творческое улучшение энергохранилища") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT, "Творческое улучшение энергохранилища (простое)") + add(MItems.MachineUpgrades.Creative.ENERGY_STORAGE_FLAT_SMALL, "Творческое улучшение энергохранилища (малое простое)") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE, "Творческое улучшение хранилища материи") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT, "Творческое улучшение хранилища материи (простое)") + add(MItems.MachineUpgrades.Creative.MATTER_STORAGE_FLAT_SMALL, "Творческое улучшение хранилища материи (малое простое)") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT, "Творческое улучшение энергоканала") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT, "Творческое улучшение энергоканала (простое)") + add(MItems.MachineUpgrades.Creative.ENERGY_THROUGHPUT_FLAT_SMALL, "Творческое улучшение энергоканала (малое простое)") + add(MItems.MachineUpgrades.Creative.FAILSAFE, "Творческое улучшение отказоустойчивости") + add(MItems.MachineUpgrades.Creative.FAILURE, "Творческое улучшение краха") + add(MItems.MachineUpgrades.Creative.PROCESSING_ITEMS, "Творческое улучшение обработки") + + add(MItems.CHEST_UPGRADER, "Ящикатор") + add(MItems.CHEST_UPGRADER, "desc", "Заменяет установленные сундуки и бочки грузовыми ящиками с сохранением содержимого") + add(MItems.CHEST_UPGRADER, "desc2", "Удерживайте необходимые ящики в противоположной руке") + } +} + +private fun stats(provider: MatteryLanguageProvider) { + with(provider.russian) { + stat("health_regenerated", "Здоровья отрегенерировано наноботами") + stat("damage_absorbed", "Урона поглащено наноботами") + stat("power_consumed", "МтДж сожжено за андроида") + } +} + +private fun research(provider: MatteryLanguageProvider) { + with(provider.russian) { + add("android_research.status.requires", "Требует %s для изучения") + add("android_research.status.blocks", "Блокирует %s") + add("android_research.status.blocked_by", "Блокируется %s") + add("android_research.status.requires_item", "Требует %s") + add("android_research.status.requires_item_multiple0", "Требует %s %s шт. (требуется ещё %s шт.)") + add("android_research.status.requires_item_multiple1", "Требует %s %s шт.") + + add("android_research.status.requires_item_any", "Требует любое из %s") + add("android_research.status.requires_item_multiple_any0", "Требует любое из %s %s шт. (требуется ещё %s шт.)") + add("android_research.status.requires_item_multiple_any1", "Требует любое из %s %s шт.") + } +} + +private fun androidFeatures(provider: MatteryLanguageProvider) { + with(provider.russian) { + add(AndroidFeatures.AIR_BAGS, "Воздушные мешки") + add(AndroidFeatures.SHOCKWAVE, "Генератор ударных волн") + add(AndroidFeatures.NIGHT_VISION, "Ночное зрение") + add(AndroidFeatures.NANOBOTS_ARMOR, "Броня из наноботов") + add(AndroidFeatures.ITEM_MAGNET, "Предметный магнит") + add(AndroidFeatures.SWIM_BOOSTERS, "Плавательные лопасти") + add(AndroidFeatures.STEP_ASSIST, "Помощник подъёма") + add(AndroidFeatures.JUMP_BOOST, "Усилитель прыжка") + add(AndroidFeatures.ENDER_TELEPORTER, "Телепортатор края") + } +} + +private fun gui(provider: MatteryLanguageProvider) { + with(provider.russian) { + gui("quicksearch", "Быстрый поиск...") + + gui("painter.is_bulk", "Массовая покраска") + gui("painter.is_bulk.desc", "Слот покраски будет автоматически наполняться из вашего инвентаря") + gui("painter.is_bulk.desc2", "Быстрое перемещение покрасит максимальное количество предметов из вашего инвентаря") + + gui("energy_required", "Требуется энергии: %s") + + gui("insert_priority", "Приоритет вставки") + gui("extract_priority", "Приоритет забора") + gui("increase", "Увеличить") + gui("decrease", "Уменьшить") + + gui("color_picker", "Выбор цвета") + + gui("color.short.red", "К") + gui("color.short.green", "З") + gui("color.short.blue", "С") + gui("color.short.hue", "Ц") + gui("color.short.saturation", "Н") + gui("color.short.value", "Я") + + gui("color.full.red", "Красный") + gui("color.full.green", "Зелёный") + gui("color.full.blue", "Синий") + gui("color.full.hue", "Цвет") + gui("color.full.saturation", "Насыщение") + gui("color.full.value", "Яркость") + + gui("item_monitor.refill_source.desc", "Контролирует источник предметов для заполнения сетки создания") + gui("item_monitor.refill_source.system", "Только система. Сетка создания будет заполняться только из системы предметов. Данный параметр соответствует поведению AE2 и Refined Storage") + gui("item_monitor.refill_source.inventory", "Только инвентарь. Сетка создания будет заполняться только из инвентаря игрока") + gui("item_monitor.refill_source.system_first", "Сначала система. Сетка создания сначала будет заполняться из системы предметов, а затем из инвентаря игрока") + gui("item_monitor.refill_source.inventory_first", "Сначала инвентарь. Сетка создания сначала будет заполняться из инвентаря игрока, а затем из системы предметов") + gui("item_monitor.refill_source.do_not", "Не заполнять автоматически") + + gui("item_monitor.result_target.desc", "Контролирует поведение сетки создания") + gui("item_monitor.result_target.all_system", "Всё в систему. И результат создания и остаток отправляется в систему предметов") + gui("item_monitor.result_target.mixed", "Смешанный. Результат создания кладётся в инвентарь игрока, остаток отправляется в систему предметов") + gui("item_monitor.result_target.all_inventory", "Всё в инвентарь. И результат создания и остаток кладётся в инвентарь игрока") + + gui("item_monitor.amount.desc", "Контролирует сколько предметов создавать при быстром крафте") + gui("item_monitor.amount.one", "Ровно один предмет") + gui("item_monitor.amount.stack", "Ровно одну стопку. Данный параметр соответствует поведению AE2 и Refined Storage") + gui("item_monitor.amount.full", "Стопку ингредиентов. Создание продолжается пока не будет достигнут лимит по стопке одного из ингредиентов. Если хотя бы один из ингредиентов не может быть стопкой, будет использован режим 'одна стопка результата'") + + gui("stored_amount", "Точное количество в хранилище: %s шт.") + + gui("sides.item_config", "Настройка предметов") + gui("sides.energy_config", "Настройка энергии") + gui("sides.fluid_config", "Настройка жидкости") + + gui("sides.pull_help", "Удерживайте Shift для настройки режима забора") + gui("sides.push_help", "Удерживайте Ctrl для настройки режима выталкивания") + + gui("sides.top", "Верхняя сторона") + gui("sides.bottom", "Нижняя сторона") + gui("sides.front", "Передняя сторона") + gui("sides.back", "Задняя сторона") + gui("sides.left", "Левая сторона") + gui("sides.right", "Правая сторона") + + gui("side_mode.disabled", "Отключено") + gui("side_mode.input", "Только вход") + gui("side_mode.output", "Только выход") + gui("side_mode.input_output", "Вход/Выход") + gui("side_mode.battery", "Батарея") + + gui("side_mode.pull", "Автоматическое вытягивание") + gui("side_mode.push", "Автоматическое выталкивание") + + gui("upgrades", "Улучшения") + gui("upgrades.current", "Активные улучшения:") + gui("upgrade.speed", "Скорость работы: %s%%") + gui("upgrade.processing_items", "Работы за цикл: +%s") + gui("upgrade.energy_storage_flat", "Хранилище энергии: +%s") + gui("upgrade.matter_storage_flat", "Хранилище материи: +%s") + gui("upgrade.energy_storage", "Хранилище энергии: +%s%%") + gui("upgrade.matter_storage", "Хранилище материи: +%s%%") + gui("upgrade.energy_consumed", "Потребление энергии: %s%%") + gui("upgrade.energy_throughput_flat", "Пропускная способность энергии: +%s") + gui("upgrade.energy_throughput", "Пропускная способность энергии: +%s%%") + gui("upgrade.failsafe", "Шанс неудачи: %s%%") + + gui("upgrade_type.list", "Классификация улучшения:") + gui("upgrade_type.allowed", "Допустимые улучшения:") + gui("upgrade_type.allowed_none", "На данный момент нет допустимых улучшений.") + gui("upgrade_type.speed", "Скорость") + gui("upgrade_type.processing", "Обработка") + gui("upgrade_type.matter_storage", "Хранилище материи") + gui("upgrade_type.energy_storage", "Энергохранилище") + gui("upgrade_type.energy_consumption", "Энергоэффективность") + gui("upgrade_type.energy_throughput", "Энергоканал") + gui("upgrade_type.failsafe", "Отказоустойчивость") + + gui("balance_inputs", "Балансировать входные слоты") + + gui("sorting.sort_now", "Отсортировать") + gui("sorting.sort_settings", "Нажмите правую кнопку мыши для настроек") + gui("sorting.default", "Сортировка по умолчанию") + gui("sorting.name", "Сортировка по имени") + gui("sorting.id", "Сортировка по ID") + gui("sorting.modid", "Сортировка по пространству имён (моду)") + gui("sorting.count", "Сортировка по количеству") + gui("sorting.ascending", "Возрастающая") + gui("sorting.descending", "Убывающая") + gui("sorting.matter_value", "Значение материи") + gui("sorting.matter_complexity", "Сложность репликации") + + gui("matter_panel.send", "Запросить") + gui("matter_panel.close", "Закрыть") + gui("matter_panel.cancel_task", "Отменить задание") + gui("matter_panel.cancel", "Отмена") + gui("matter_panel.label", "Запрос на репликацию") + gui("matter_panel.task", "Будущий запрос на репликацию") + gui("matter_panel.is_providing_tasks", "Задачи передаются на репликаторы") + gui("matter_panel.not_providing_tasks", "Задачи НЕ передаются на репликаторы") + gui("matter_panel.cancel_all", "Отменить все задачи") + gui("matter_panel.cancel_all.desc", "Вы действительно хотите отменить все задачи репликации?") + + gui("matter_panel.tasks", "Задачи") + gui("matter_panel.patterns", "Шаблоны") + + gui("matter_panel.number_input", "Введите кол-во единиц репликации") + + gui("matter_panel.matter_stored", "Материи хранится: %s") + gui("matter_panel.matter_required", "Материи требуется: %s") + gui("matter_panel.complexity", "Общая сложность: %s") + + gui("experience", "%s очков опыта") + gui("experience_levels", "%s уровней опыта") + + gui("experience.store", "Передать %s уровней опыта") + gui("experience.store_all", "Передать все уровни опыта") + gui("experience.dispense", "Вывести %s уровней опыта") + gui("experience.dispense_all", "Вывести все уровни опыта") + gui("experience.set_exact", "Установить уровень опыта в %s") + + gui("essence_capsule", "(Почти) Всё, что вы знали, хранится внутри") + gui("essence_capsule2", "Данный предмет может быть переработан внутри хранилища эссенции") + gui("essence_capsule3", "Используйте, чтобы высвободить большую часть хранящихся внутри знаний") + gui("essence_capsule.digital", "Используйте, чтобы вернуть ваши воспоминания") + + gui("slot_filter.filtered", "Данный слот отфильтрован для автоматизации") + gui("slot_filter.forbidden", "Данный слот запрещён для взаимодействия средствами автоматизации") + gui("slot_filter.hint", "Для удаления фильтра нажмите Ctrl + ЛКМ") + + gui("debug.tags.help", "Удерживайте Shift для отображения тегов") + gui("debug.tags.item.title", "Теги предмета:") + gui("debug.tags.block.title", "Теги блока:") + } +} + +private fun jade(provider: MatteryLanguageProvider) { + with(provider.russian) { + jadeloc("matter_storage", "Хранилище материи") + jadeloc("mattery_energy", "Хранилище энергии") + jadeloc("mattery_worker", "Прогресс работы механизма") + jadeloc("matter_bottler", "Режим бутилировщика материи") + jadeloc("matter_reconstructor", "Прогресс материального реконструктора") + + jade("mode", "Режим: %s") + + jade("matter_bottler.mode.fill", "Заполнение") + jade("matter_bottler.mode.drain", "Опустошение") + } +} + +fun AddRussianLanguage(provider: MatteryLanguageProvider) { + decoratives(provider) + blocks(provider) + sounds(provider) + misc(provider) + items(provider) + gui(provider) + stats(provider) + research(provider) + death(provider) + androidFeatures(provider) + jade(provider) +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/DSL.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/DSL.kt index 4ca29433b..ecacac754 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/DSL.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/DSL.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.datagen.loot import net.minecraft.advancements.critereon.StatePropertiesPredicate import net.minecraft.util.StringRepresentable -import net.minecraft.world.item.Rarity import net.minecraft.world.level.ItemLike import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.properties.Property @@ -17,7 +16,6 @@ import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.providers.number.ConstantValue import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator import ru.dbotthepony.mc.otm.data.condition.ChanceCondition -import ru.dbotthepony.mc.otm.data.loot.RandomizerFunction inline fun LootTable.Builder.lootPool(configurator: LootPool.Builder.() -> Unit): LootTable.Builder = withPool(LootPool.lootPool().also(configurator)) inline fun LootTable.Builder.singleItem(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit): LootTable.Builder { @@ -28,17 +26,6 @@ inline fun LootTable.Builder.singleItem(item: ItemLike, configurator: LootPoolSi inline fun lootPool(configurator: LootPool.Builder.() -> Unit): LootPool = LootPool.lootPool().also(configurator).build() inline fun singleItem(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit): LootPool = lootPool { item(item, configurator) } -fun singleRandomizedItem(item: ItemLike, rarity: Rarity = Rarity.COMMON, chance: Double? = null): LootPool { - return lootPool { - item(item) { - apply(RandomizerFunction.valueOf(rarity)) - - if (chance != null) { - chanceCondition(chance) - } - } - } -} inline fun LootPool.Builder.item(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit) { add(LootItem.lootTableItem(item).also(configurator)) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiers.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiers.kt index cc119d648..af9dc0737 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiers.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiers.kt @@ -51,7 +51,7 @@ fun PlainLootAppender( vararg items: Pair ) = PlainLootAppender(conditions, Arrays.stream(items)) -class LootModifiers(generator: DataGenerator) : GlobalLootModifierProvider(generator, DataGen.MOD_ID) { +class LootModifiers(generator: DataGenerator) : GlobalLootModifierProvider(generator.packOutput, DataGen.MOD_ID) { private val lambdas = ArrayList<(LootModifiers) -> Unit>() fun lambda(lambda: (LootModifiers) -> Unit) { diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiersData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiersData.kt index 4d9d3b332..3131627ab 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiersData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootModifiersData.kt @@ -1,18 +1,21 @@ package ru.dbotthepony.mc.otm.datagen.loot import net.minecraft.resources.ResourceLocation +import net.minecraft.util.valueproviders.UniformInt import net.minecraft.world.entity.EntityType import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Rarity import net.minecraft.world.level.storage.loot.BuiltInLootTables import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraftforge.common.loot.LootTableIdCondition +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.UniformDecimal import ru.dbotthepony.mc.otm.data.condition.ChanceWithPlaytimeCondition import ru.dbotthepony.mc.otm.data.condition.HasExoPackCondition import ru.dbotthepony.mc.otm.data.condition.ItemInInventoryCondition import ru.dbotthepony.mc.otm.data.condition.KilledByRealPlayerOrIndirectly import ru.dbotthepony.mc.otm.data.loot.LootPoolAppender -import ru.dbotthepony.mc.otm.data.loot.RandomizerFunction +import ru.dbotthepony.mc.otm.item.ProceduralBatteryItem +import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem import ru.dbotthepony.mc.otm.registry.MItems @Suppress("FunctionName") @@ -25,60 +28,102 @@ fun LootTableIdCondition(location: ResourceLocation): LootItemCondition { return LootTableIdCondition.Builder(location).build() } -private fun exosuitModifiers(it: LootModifiers) { - it.add("dungeon_exosuit", LootPoolAppender( +fun addLootModifiers(it: LootModifiers) { + it.add("dungeon_exopack", LootPoolAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.SIMPLE_DUNGEON)), singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { chanceCondition(0.2) - apply(RandomizerFunction.COMMON) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(6, 9))) }, singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { chanceCondition(0.05) - apply(RandomizerFunction.UNCOMMON) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(9, 18))) }, )) - it.add("mineshaft_exosuit", LootPoolAppender( + it.add("mineshaft_additions", LootPoolAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.ABANDONED_MINESHAFT)), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.1), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.1, rarity = Rarity.UNCOMMON) + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.1) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(4, 8))) + }, + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.1) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(4, 10))) + }, + + singleItem(MItems.PROCEDURAL_BATTERY) { + chanceCondition(0.15) + + apply(ProceduralBatteryItem.Randomizer( + maxBatteryLevel = UniformDecimal(Decimal(8_000_000), Decimal(40_000_000)), + batteryLevel = UniformDecimal(Decimal(0), Decimal(20_000_000)), + maxInput = UniformDecimal(Decimal(700), Decimal(4_000)), + )) + } )) it.add("desert_pyramid_exosuit", LootPoolAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.DESERT_PYRAMID)), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.15, rarity = Rarity.UNCOMMON), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.25, rarity = Rarity.COMMON) + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.1) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(12, 18))) + }, + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.25) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(4, 9))) + }, )) it.add("jungle_temple_exosuit", LootPoolAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.JUNGLE_TEMPLE)), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.15, rarity = Rarity.UNCOMMON), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.35, rarity = Rarity.RARE) + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.15) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(9, 18))) + }, + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.35) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(16, 28), UniformInt.of(2, 6))) + }, )) it.add("end_city_exosuit", LootPoolAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.END_CITY_TREASURE)), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.4, rarity = Rarity.UNCOMMON), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.2, rarity = Rarity.RARE), - singleRandomizedItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL, chance = 0.15, rarity = Rarity.EPIC), - )) -} -fun addLootModifiers(it: LootModifiers) { - exosuitModifiers(it) + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.4) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(9, 18))) + }, + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.2) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(14, 27), UniformInt.of(2, 6))) + }, + + singleItem(MItems.ExopackUpgrades.INVENTORY_UPGRADE_PROCEDURAL) { + chanceCondition(0.1) + apply(ProceduralExopackSlotUpgradeItem.Randomizer(UniformInt.of(27, 56), UniformInt.of(2, 6))) + }, + )) it.add("dungeon_pill", PlainLootAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.SIMPLE_DUNGEON)), - ItemStack(MItems.PILL_ANDROID, 1) to 0.4, + ItemStack(MItems.PILL_ANDROID, 1) to 0.1, ItemStack(MItems.PILL_HEAL, 2) to 0.5, ItemStack(MItems.PILL_HEAL, 1) to 0.75, )) it.add("mineshaft_pill", PlainLootAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.ABANDONED_MINESHAFT)), - ItemStack(MItems.PILL_ANDROID, 1) to 0.075, + ItemStack(MItems.PILL_ANDROID, 1) to 0.04, ItemStack(MItems.PILL_HEAL, 2) to 0.1, ItemStack(MItems.PILL_HEAL, 1) to 0.4, )) @@ -92,7 +137,7 @@ fun addLootModifiers(it: LootModifiers) { it.add("desert_pyramid_pill", PlainLootAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.DESERT_PYRAMID)), - ItemStack(MItems.PILL_ANDROID, 1) to 0.05, + ItemStack(MItems.PILL_ANDROID, 1) to 0.15, ItemStack(MItems.PILL_HEAL, 1) to 0.3, )) @@ -103,7 +148,7 @@ fun addLootModifiers(it: LootModifiers) { it.add("end_city_modifications", PlainLootAppender( arrayOf(LootTableIdCondition(BuiltInLootTables.END_CITY_TREASURE)), - ItemStack(MItems.PILL_ANDROID, 1) to 0.1, + ItemStack(MItems.PILL_ANDROID, 1) to 0.15, ItemStack(MItems.PILL_HUMANE, 1) to 0.3, ItemStack(MItems.PILL_OBLIVION, 1) to 0.5, ItemStack(MItems.ZPM_BATTERY, 1) to 0.005, diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTables.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTables.kt index 6aa71da6f..e7c9a96e5 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTables.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTables.kt @@ -3,12 +3,12 @@ package ru.dbotthepony.mc.otm.datagen.loot -import com.mojang.datafixers.util.Pair import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction import net.minecraft.advancements.critereon.StatePropertiesPredicate import net.minecraft.data.DataGenerator import net.minecraft.data.loot.LootTableProvider +import net.minecraft.data.loot.LootTableSubProvider import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.ItemLike import net.minecraft.world.level.block.Block @@ -24,18 +24,11 @@ import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition -import net.minecraft.world.level.storage.loot.providers.nbt.ContextNbtProvider import net.minecraft.world.level.storage.loot.providers.number.ConstantValue import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import java.util.function.BiConsumer -import java.util.function.Consumer -import java.util.function.Supplier - -private typealias LootTableSaver = BiConsumer -private typealias LootTableCallback = Consumer -private typealias LootTableCallbackProvider = Supplier -private typealias LootTuple = Pair +import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.data.loot.CopyTileNbtFunction data class NbtCopy(val source: String, val destination: String, val strategy: CopyNbtFunction.MergeStrategy = CopyNbtFunction.MergeStrategy.REPLACE) @@ -43,29 +36,7 @@ fun TileNbtCopy(source: String, strategy: CopyNbtFunction.MergeStrategy = CopyNb return NbtCopy(source, "BlockEntityTag.$source", strategy) } -private val basicTags = arrayOf( - TileNbtCopy(MatteryBlockEntity.REDSTONE_SIGNAL_KEY), - TileNbtCopy(MatteryBlockEntity.REDSTONE_SETTING_KEY), -) - -private val poweredTags = arrayOf( - *basicTags, - TileNbtCopy(MatteryBlockEntity.ENERGY_KEY), - TileNbtCopy(MatteryBlockEntity.BATTERY_KEY), -) - -private val workerTags = arrayOf( - *poweredTags, - TileNbtCopy(MatteryWorkerBlockEntity.JOB_KEY), - TileNbtCopy(MatteryWorkerBlockEntity.WORK_TICKS_KEY), -) - -private val poweredMatterWorker = arrayOf( - *workerTags, - TileNbtCopy(MatteryBlockEntity.MATTER_STORAGE_KEY), -) - -class LootTables(generator: DataGenerator) : LootTableProvider(generator) { +class LootTables(generator: DataGenerator) : LootTableProvider(generator.packOutput, setOf() /* because we don't fucking validate you fuck */, listOf() /* because we attach everything after class is constructed duh */) { private val providersTable = Reference2ObjectArrayMap LootTable.Builder>>() fun builder(context: LootContextParamSet, id: ResourceLocation, provider: LootTable.Builder.() -> Unit) { @@ -80,10 +51,10 @@ class LootTables(generator: DataGenerator) : LootTableProvider(generator) { .put(id, provider) == null) { "Duplicate loot pool entry for $id" } } - override fun getTables(): List { + override fun getTables(): List { return providersTable.entries.stream().map { entry -> - Pair.of(LootTableCallbackProvider { - LootTableCallback { + SubProviderEntry({ + LootTableSubProvider { for ((id, callback) in entry.value) { it.accept(id, callback.invoke()) } @@ -163,91 +134,11 @@ class LootTables(generator: DataGenerator) : LootTableProvider(generator) { } } - fun tile(block: Block, f: (CopyNbtFunction.Builder) -> Unit = {}) { + fun tile(block: Block, vararg filterTags: String) { singleLootPool(LootContextParamSets.BLOCK, block.lootTable) { add(LootItem.lootTableItem(block).also { - it.apply(CopyNbtFunction.copyData(ContextNbtProvider.BLOCK_ENTITY).also { - it.copy("Name", "BlockEntityTag.Name") - f(it) - }) + it.apply(CopyTileNbtFunction(filterTags.stream())) }) } } - - fun tile(block: Block, vararg tags: NbtCopy) { - tile(block) { - for ((source, destination, strategy) in tags) { - it.copy(source, destination, strategy) - } - } - } - - fun basicTile(block: Block, vararg tags: NbtCopy) { - tile(block) { - for ((source, destination, strategy) in tags) { - it.copy(source, destination, strategy) - } - - for ((source, destination, strategy) in basicTags) { - it.copy(source, destination, strategy) - } - } - } - - fun poweredTile(block: Block, vararg tags: NbtCopy) { - tile(block) { - for ((source, destination, strategy) in tags) { - it.copy(source, destination, strategy) - } - - for ((source, destination, strategy) in poweredTags) { - it.copy(source, destination, strategy) - } - } - } - - fun workerTile(block: Block, vararg tags: NbtCopy) { - tile(block) { - for ((source, destination, strategy) in tags) { - it.copy(source, destination, strategy) - } - - for ((source, destination, strategy) in workerTags) { - it.copy(source, destination, strategy) - } - } - } - - fun matterWorkerTile(block: Block, vararg tags: NbtCopy) { - tile(block) { - for ((source, destination, strategy) in tags) { - it.copy(source, destination, strategy) - } - - for ((source, destination, strategy) in poweredMatterWorker) { - it.copy(source, destination, strategy) - } - } - } - - fun tile(block: Block, vararg tags: String) { - tile(block, *tags.map { NbtCopy(it, "BlockEntityTag.$it", CopyNbtFunction.MergeStrategy.REPLACE) }.toTypedArray()) - } - - // fix overload resolution by adding extra required argument - fun basicTile(block: Block, f: String, vararg tags: String) { - basicTile(block, *tags.map { TileNbtCopy(it) }.toMutableList().also { it.add(TileNbtCopy(f)) }.toTypedArray()) - } - - fun poweredTile(block: Block, f: String, vararg tags: String) { - poweredTile(block, *tags.map { TileNbtCopy(it) }.toMutableList().also { it.add(TileNbtCopy(f)) }.toTypedArray()) - } - - fun workerTile(block: Block, f: String, vararg tags: String) { - workerTile(block, *tags.map { TileNbtCopy(it) }.toMutableList().also { it.add(TileNbtCopy(f)) }.toTypedArray()) - } - - fun matterWorkerTile(block: Block, f: String, vararg tags: String) { - matterWorkerTile(block, *tags.map { TileNbtCopy(it) }.toMutableList().also { it.add(TileNbtCopy(f)) }.toTypedArray()) - } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt index 1c838327f..afea9c71a 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/loot/LootTablesData.kt @@ -6,15 +6,6 @@ import net.minecraft.world.level.block.state.properties.DoubleBlockHalf import net.minecraft.world.level.storage.loot.entries.LootItem import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition -import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity -import ru.dbotthepony.mc.otm.block.entity.EnergyCounterBlockEntity -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.ENERGY_KEY -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.MATTER_STORAGE_KEY -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.REDSTONE_SETTING_KEY -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.REDSTONE_SIGNAL_KEY -import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport.Companion.FILTER_KEY -import ru.dbotthepony.mc.otm.block.entity.storage.StoragePowerSupplierBlockEntity import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MItems @@ -43,6 +34,7 @@ fun addLootTables(lootTables: LootTables) { lootTables.createSlabItemTable(MRegistry.FLOOR_TILES_SLAB.blocks.values) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.CARBON_FIBRE_BLOCK) { condition(ExplosionCondition.survivesExplosion()) } + lootTables.dropsSelf(MBlocks.METAL_MESH) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.TRITANIUM_RAW_BLOCK) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.TRITANIUM_STRIPED_BLOCK) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.TRITANIUM_STRIPED_WALL) { condition(ExplosionCondition.survivesExplosion()) } @@ -57,6 +49,15 @@ fun addLootTables(lootTables: LootTables) { lootTables.dropsSelf(MBlocks.DANGER_STRIPE_BLOCK) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.METAL_BEAM) { condition(ExplosionCondition.survivesExplosion()) } lootTables.dropsSelf(MBlocks.TRITANIUM_INGOT_BLOCK) { condition(ExplosionCondition.survivesExplosion()) } + lootTables.dropsSelf(MBlocks.TRITANIUM_BARS) { condition(ExplosionCondition.survivesExplosion()) } + lootTables.dropsSelf(MBlocks.ENERGY_CABLES.values) { condition(ExplosionCondition.survivesExplosion()) } + + lootTables.dropsSelf(MBlocks.INFINITE_WATER_SOURCE) { condition(ExplosionCondition.survivesExplosion()) } + + lootTables.dropsSelf(MBlocks.ENGINE) { condition(ExplosionCondition.survivesExplosion()) } + + for (block in MBlocks.TRITANIUM_ANVIL) + lootTables.dropsSelf(block) { condition(ExplosionCondition.survivesExplosion()) } for (door in MBlocks.TRITANIUM_TRAPDOOR.values) lootTables.dropsSelf(door) { condition(ExplosionCondition.survivesExplosion()) } @@ -129,40 +130,42 @@ fun addLootTables(lootTables: LootTables) { lootPool { item(Items.BLACK_DYE) { setCount(64) } } } + lootTables.tile(MBlocks.COBBLESTONE_GENERATOR) + lootTables.tile(MBlocks.ESSENCE_STORAGE) + lootTables.tile(MBlocks.MATTER_RECONSTRUCTOR) + lootTables.tile(MBlocks.FLUID_TANK) + lootTables.tile(MBlocks.PAINTER) + lootTables.tile(MBlocks.MATTER_ENTANGLER) + lootTables.tile(MBlocks.ENERGY_SERVO) + lootTables.tile(MBlocks.ENERGY_COUNTER) + lootTables.tile(MBlocks.CHEMICAL_GENERATOR) + lootTables.tile(MBlocks.HOLO_SIGN, "isLocked") + lootTables.tile(MBlocks.STORAGE_CABLE) + lootTables.tile(MBlocks.ANDROID_STATION) + lootTables.tile(MBlocks.ANDROID_CHARGER) + lootTables.tile(MBlocks.BATTERY_BANK) + lootTables.tile(MBlocks.DRIVE_VIEWER) - lootTables.tile(MBlocks.ENERGY_COUNTER, - EnergyCounterBlockEntity.IO_LIMIT_KEY, EnergyCounterBlockEntity.PASSED_ENERGY_KEY, - EnergyCounterBlockEntity.POWER_HISTORY_KEY, EnergyCounterBlockEntity.POWER_HISTORY_POINTER_KEY) + lootTables.tile(MBlocks.STORAGE_BUS) + lootTables.tile(MBlocks.STORAGE_IMPORTER) + lootTables.tile(MBlocks.STORAGE_EXPORTER) + lootTables.tile(MBlocks.STORAGE_POWER_SUPPLIER) + lootTables.tile(MBlocks.DRIVE_RACK) - lootTables.tile(MBlocks.CHEMICAL_GENERATOR, - ChemicalGeneratorBlockEntity.WORK_TICKS_KEY, - ChemicalGeneratorBlockEntity.WORK_TICKS_TOTAL_KEY, - ENERGY_KEY, - REDSTONE_SIGNAL_KEY, - REDSTONE_SETTING_KEY, - ) + lootTables.tile(MBlocks.MATTER_DECOMPOSER) + lootTables.tile(MBlocks.MATTER_REPLICATOR) + lootTables.tile(MBlocks.MATTER_RECYCLER) + lootTables.tile(MBlocks.MATTER_SCANNER) + lootTables.tile(MBlocks.PLATE_PRESS) + lootTables.tile(MBlocks.TWIN_PLATE_PRESS) - lootTables.dropsSelf(MBlocks.STORAGE_CABLE) - lootTables.poweredTile(MBlocks.ANDROID_STATION) - lootTables.basicTile(MBlocks.BATTERY_BANK) - lootTables.poweredTile(MBlocks.DRIVE_VIEWER) + lootTables.tile(MBlocks.POWERED_FURNACE) + lootTables.tile(MBlocks.POWERED_SMOKER) + lootTables.tile(MBlocks.POWERED_BLAST_FURNACE) - lootTables.poweredTile(MBlocks.STORAGE_BUS, TileNbtCopy(FILTER_KEY)) - lootTables.poweredTile(MBlocks.STORAGE_IMPORTER, TileNbtCopy(FILTER_KEY)) - lootTables.poweredTile(MBlocks.STORAGE_EXPORTER, TileNbtCopy(FILTER_KEY)) - lootTables.poweredTile(MBlocks.STORAGE_POWER_SUPPLIER, TileNbtCopy(StoragePowerSupplierBlockEntity.POWER_PASSED_KEY)) - lootTables.poweredTile(MBlocks.DRIVE_RACK) - - lootTables.matterWorkerTile(MBlocks.MATTER_DECOMPOSER) - lootTables.matterWorkerTile(MBlocks.MATTER_REPLICATOR) - lootTables.matterWorkerTile(MBlocks.MATTER_RECYCLER) - lootTables.workerTile(MBlocks.MATTER_SCANNER) - lootTables.workerTile(MBlocks.PLATE_PRESS) - - lootTables.basicTile(MBlocks.MATTER_PANEL, TileNbtCopy("tasks")) - lootTables.basicTile(MBlocks.PATTERN_STORAGE) - lootTables.basicTile(MBlocks.MATTER_CAPACITOR_BANK) - lootTables.poweredTile(MBlocks.MATTER_BOTTLER, - TileNbtCopy(MATTER_STORAGE_KEY), TileNbtCopy(MatterBottlerBlockEntity.IS_BOTTLING_KEY)) + lootTables.tile(MBlocks.MATTER_PANEL) + lootTables.tile(MBlocks.PATTERN_STORAGE) + lootTables.tile(MBlocks.MATTER_CAPACITOR_BANK) + lootTables.tile(MBlocks.MATTER_BOTTLER) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/BlockModels.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/BlockModels.kt index af8524102..52418774b 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/BlockModels.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/BlockModels.kt @@ -9,30 +9,5 @@ fun addBlockModels(provider: MatteryBlockModelProvider) { resourceCubeAll(MBlocks.TRITANIUM_RAW_BLOCK) resourceCubeAll(MBlocks.DEEPSLATE_TRITANIUM_ORE) resourceCubeAll(MBlocks.TRITANIUM_INGOT_BLOCK) - - exec { - copy("block/battery/battery1", "block/battery/battery0").also { it.offset(-4f, 0f, 0f) } - copy("block/battery/battery2", "block/battery/battery0").also { it.offset(-8f, 0f, 0f) } - - copy("block/battery/battery3", "block/battery/battery0").also { it.offset(0f, 6f, 0f) } - copy("block/battery/battery4", "block/battery/battery0").also { it.offset(-4f, 6f, 0f) } - copy("block/battery/battery5", "block/battery/battery0").also { it.offset(-8f, 6f, 0f) } - - copy("block/battery/battery7", "block/battery/battery6").also { it.offset(4f, 0f, 0f) } - copy("block/battery/battery8", "block/battery/battery6").also { it.offset(8f, 0f, 0f) } - - copy("block/battery/battery9", "block/battery/battery6").also { it.offset(0f, 6f, 0f) } - copy("block/battery/battery10", "block/battery/battery6").also { it.offset(4f, 6f, 0f) } - copy("block/battery/battery11", "block/battery/battery6").also { it.offset(8f, 6f, 0f) } - - for (i in 0 .. 11) { - withExistingParent("block/battery/matter_capacitor$i", ResourceLocation(ru.dbotthepony.mc.otm.datagen.DataGen.MOD_ID, "block/battery/battery$i")) - .texture("1", "block/matterybank_core") - } - - for (i in 1 .. 7) { - copy("block/pattern/model$i", "block/pattern/model0").also { it.offset(-2f * i, 0f, 0f) } - } - } } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelBuilder.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelBuilder.kt deleted file mode 100644 index 7f9d49323..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelBuilder.kt +++ /dev/null @@ -1,163 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen.models - -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.mojang.math.Vector3f -import net.minecraft.client.renderer.block.model.BlockModel -import net.minecraft.core.Direction -import net.minecraft.resources.ResourceLocation -import net.minecraftforge.client.model.generators.ModelBuilder -import net.minecraftforge.common.data.ExistingFileHelper -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.set - -data class TextureSize(val width: Float, val height: Float) { - constructor(arr: JsonArray) : this(arr[0].asFloat, arr[1].asFloat) - - init { - require(width > 0f) { "Invalid width $width" } - require(height > 0f) { "Invalid width $height" } - } -} - -class MatteryModelBuilder(resourceLocation: ResourceLocation, existingFileHelper: ExistingFileHelper) : ModelBuilder(resourceLocation, existingFileHelper) { - var textureSize: TextureSize? = null - - fun fromJson(input: JsonObject) { - input["parent"]?.let { - parent(ExistingModelFile(ResourceLocation(it.asString), existingFileHelper)) - } - - input["ambientocclusion"]?.let { - ambientOcclusion = it.asBoolean - } - - input["gui_light"]?.let { - guiLight = BlockModel.GuiLight.valueOf(it.asString) - } - - (input["texture_size"] as? JsonArray)?.let { - textureSize = TextureSize(it) - } - - (input["textures"] as? JsonObject)?.let { - for ((k, v) in it.entrySet()) { - texture(k, v.asString) - } - } - - (input["elements"] as? JsonArray)?.let { - var i = -1 - - for (value in it) { - i++ - check(value is JsonObject) { "Encountered invalid element at $i" } - element().fromJson(value) - } - } - } - - override fun toJson(): JsonObject { - return super.toJson().also { - val textureSize = textureSize - - if (textureSize != null) { - it["texture_size"] = JsonArray().also { - it.add(textureSize.width) - it.add(textureSize.height) - } - } - } - } - - override fun element(): MatteryModelElement { - check(customLoader == null) { "Can not use custom loaders and elements at the same time" } - return MatteryModelElement().also(elements::add) - } - - override fun element(index: Int): MatteryModelElement { - return super.element(index) as MatteryModelElement - } - - fun offset(x: Float, y: Float, z: Float) { - for (element in elements) { - (element as MatteryModelElement).offset(x, y, z) - } - } - - fun offset(value: Vector3f) { - for (element in elements) { - (element as MatteryModelElement).offset(value) - } - } - - inner class MatteryModelElement : ElementBuilder() { - private var from: Vector3f = Vector3f() - private var to: Vector3f = Vector3f(16f, 16f, 16f) - - fun from(value: Vector3f) = from(value.x(), value.y(), value.z()) - fun to(value: Vector3f) = to(value.x(), value.y(), value.z()) - - override fun from(x: Float, y: Float, z: Float): MatteryModelElement { - from = Vector3f(x, y, z) - super.from(x, y, z) - return this - } - - override fun to(x: Float, y: Float, z: Float): MatteryModelElement { - to = Vector3f(x, y, z) - super.to(x, y, z) - return this - } - - fun offset(x: Float, y: Float, z: Float): MatteryModelElement { - from(x + from.x(), y + from.y(), z + from.z()) - return to(x + to.x(), y + to.y(), z + to.z()) - } - - fun offset(value: Vector3f) = offset(value.x(), value.y(), value.z()) - - fun fromJson(input: JsonObject) { - val from = input["from"] as JsonArray - val to = input["to"] as JsonArray - - from(from[0].asFloat, from[1].asFloat, from[2].asFloat) - to(to[0].asFloat, to[1].asFloat, to[2].asFloat) - - (input["faces"] as? JsonObject)?.let { - for ((k, v) in it.entrySet()) { - with(face(Direction.valueOf(k.uppercase()))) { - check(v is JsonObject) { "Element has invalid face at $k" } - - (v["uv"] as? JsonArray)?.also { uv -> - check(uv.size() == 4) { "Element at $k has invalid number of uvs ${uv.size()}" } - uvs(uv[0].asFloat, uv[1].asFloat, uv[2].asFloat, uv[3].asFloat) - } - - (v["rotation"] as? JsonPrimitive)?.also { rotation -> - when (rotation.asInt) { - 90 -> rotation(FaceRotation.CLOCKWISE_90) - -90, 270 -> rotation(FaceRotation.COUNTERCLOCKWISE_90) - 180, -180 -> rotation(FaceRotation.UPSIDE_DOWN) - else -> rotation(FaceRotation.ZERO) - } - } - - (v["emissivity"] as? JsonPrimitive)?.asInt?.also { emissivity -> - emissivity(emissivity) - } - - (v["ao"] as? JsonPrimitive)?.asBoolean?.also { ao -> - ao(ao) - } - - (v["texture"] as? JsonPrimitive)?.also { - texture(it.asString) - } - } - } - } - } - } -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelProvider.kt index dd0d3d415..f2cdb8a5e 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/models/MatteryModelProvider.kt @@ -1,49 +1,17 @@ package ru.dbotthepony.mc.otm.datagen.models -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.packs.PackType import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.DoorBlock -import net.minecraftforge.client.model.generators.ModelProvider +import net.minecraftforge.client.model.generators.BlockModelProvider import net.minecraftforge.data.event.GatherDataEvent import ru.dbotthepony.mc.otm.datagen.DataGen import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.datagen.modLocation import java.util.LinkedList -private typealias Callback = (MatteryModelProvider) -> Unit +class MatteryBlockModelProvider(event: GatherDataEvent) : BlockModelProvider(event.generator.packOutput, DataGen.MOD_ID, event.existingFileHelper) { + private val callbacks = LinkedList<(MatteryBlockModelProvider) -> Unit>() -sealed class MatteryModelProvider(event: GatherDataEvent, folder: String) : ModelProvider(event.generator, DataGen.MOD_ID, folder, ::MatteryModelBuilder, event.existingFileHelper) { - private fun extendWithFolder(rl: ResourceLocation): ResourceLocation { - return if (rl.path.contains("/")) rl else ResourceLocation(rl.namespace, folder + "/" + rl.path) - } - - fun copy(destination: String, source: String): MatteryModelBuilder { - val destinationLocation = extendWithFolder(if (destination.contains(":")) ResourceLocation(destination) else ResourceLocation(modid, destination)) - val sourceLocation = extendWithFolder(if (source.contains(":")) ResourceLocation("models/$source.json") else ResourceLocation(modid, "models/$source.json")) - - check(!generatedModels.containsKey(destinationLocation)) { "Model provider already contains model $destinationLocation" } - existingFileHelper.trackGenerated(destinationLocation, MODEL) - - return factory.apply(destinationLocation).also { - generatedModels[destinationLocation] = it - val resource = existingFileHelper.getResource(sourceLocation, PackType.CLIENT_RESOURCES) - val stream = resource.open() - val reader = stream.reader() - - try { - it.fromJson(JsonParser.parseReader(reader) as JsonObject) - } finally { - reader.close() - stream.close() - } - } - } - - private val callbacks = LinkedList() - - fun exec(callback: Callback) { + fun exec(callback: (MatteryBlockModelProvider) -> Unit) { callbacks.add(callback) } @@ -52,9 +20,7 @@ sealed class MatteryModelProvider(event: GatherDataEvent, folder: String) : Mode callback(this) } } -} -class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(event, BLOCK_FOLDER) { override fun getName(): String { return "Block Models: $modid" } @@ -62,7 +28,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeGlassAll(blocks: Collection) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${block.registryName!!.path}")).renderType("translucent") + cubeAll(block.registryName!!.path, modLocation("block/decorative/${block.registryName!!.path}")).renderType("translucent") } } } @@ -70,7 +36,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeGlassAll(vararg blocks: Block) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${block.registryName!!.path}")).renderType("translucent") + cubeAll(block.registryName!!.path, modLocation("block/decorative/${block.registryName!!.path}")).renderType("translucent") } } } @@ -78,7 +44,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeCubeAll(vararg blocks: Block) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${block.registryName!!.path}")) + cubeAll(block.registryName!!.path, modLocation("block/decorative/${block.registryName!!.path}")) } } } @@ -86,7 +52,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeCubeAll(subdir: String, vararg blocks: Block) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${subdir}/${block.registryName!!.path}")) + cubeAll(block.registryName!!.path, modLocation("block/decorative/${subdir}/${block.registryName!!.path}")) } } } @@ -94,7 +60,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeCubeAll(subdir: String, suffix: String, vararg blocks: Block) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${subdir}/${block.registryName!!.path}$suffix")) + cubeAll(block.registryName!!.path, modLocation("block/decorative/${subdir}/${block.registryName!!.path}$suffix")) } } } @@ -102,20 +68,20 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun decorativeCubeAll(blocks: Collection) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/${block.registryName!!.path}")) + cubeAll(block.registryName!!.path, modLocation("block/decorative/${block.registryName!!.path}")) } } } fun decorativeCubeAll(block: Block, texture: String) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/decorative/$texture")) + cubeAll(block.registryName!!.path, modLocation("block/decorative/$texture")) } } fun column(block: Block, end: String, side: String) { exec { - cubeColumn(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, end), ResourceLocation(DataGen.MOD_ID, side)) + cubeColumn(block.registryName!!.path, modLocation(end), modLocation(side)) } } @@ -126,7 +92,7 @@ class MatteryBlockModelProvider(event: GatherDataEvent) : MatteryModelProvider(e fun resourceCubeAll(vararg blocks: Block) { for (block in blocks) { exec { - cubeAll(block.registryName!!.path, ResourceLocation(DataGen.MOD_ID, "block/resource/${block.registryName!!.path}")) + cubeAll(block.registryName!!.path, modLocation("block/resource/${block.registryName!!.path}")) } } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/BlastingRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/BlastingRecipes.kt deleted file mode 100644 index fea76cc46..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/BlastingRecipes.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen.recipes - -import net.minecraft.data.recipes.FinishedRecipe -import net.minecraft.data.recipes.SimpleCookingRecipeBuilder -import net.minecraft.world.item.crafting.Ingredient -import ru.dbotthepony.mc.otm.datagen.modLocation -import ru.dbotthepony.mc.otm.registry.MItemTags -import ru.dbotthepony.mc.otm.registry.MItems -import java.util.function.Consumer - -fun addBlastingRecipes(consumer: Consumer) { - SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItems.MIRROR_COMPOUND), MItems.MIRROR, 0.1f, 100).unlockedBy(MItems.MIRROR_COMPOUND).save(consumer) - - SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_PLATES), MItems.TRITANIUM_INGOT, 0f, 100).unlockedBy(MItemTags.TRITANIUM_PLATES).save(consumer, modLocation("tritanium_ingot_from_plates")) - SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_PLATES), MItems.TRITANIUM_INGOT, 0f, 50).unlockedBy(MItemTags.TRITANIUM_PLATES).save(consumer, modLocation("tritanium_ingot_from_plates_blasting")) -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CookingRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CookingRecipes.kt new file mode 100644 index 000000000..b1a73ef25 --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CookingRecipes.kt @@ -0,0 +1,55 @@ +package ru.dbotthepony.mc.otm.datagen.recipes + +import net.minecraft.data.recipes.RecipeCategory +import net.minecraft.data.recipes.SimpleCookingRecipeBuilder +import net.minecraft.util.valueproviders.ConstantFloat +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.level.ItemLike +import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.registry.MItemTags +import ru.dbotthepony.mc.otm.registry.MItems + +private fun RecipeOutput.addRecyclingRecipe(inputs: Collection, result: Item, name: String) { + val inputStacks = inputs.map(::ItemStack) + + SimpleCookingRecipeBuilder.smelting( + Ingredient.of(inputStacks.stream()), + RecipeCategory.MISC, result, 0f, 200 + ).also { r -> inputs.forEach { r.unlockedBy(it) } }.save(this, modLocation("smelting/${name}")) + + SimpleCookingRecipeBuilder.blasting( + Ingredient.of(inputStacks.stream()), + RecipeCategory.MISC, result, 0f, 100 + ).also { r -> inputs.forEach { r.unlockedBy(it) } }.save(this, modLocation("blasting/${name}")) +} + +fun addBlastingRecipes(consumer: RecipeOutput) { + SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItems.MIRROR_COMPOUND), RecipeCategory.MISC, MItems.MIRROR, 0.1f, 100).unlockedBy( + MItems.MIRROR_COMPOUND).save(consumer) + + SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_PLATES), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 0f, 100).unlockedBy( + MItemTags.TRITANIUM_PLATES).save(consumer, modLocation("tritanium_ingot_from_plates")) + SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_PLATES), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 0f, 50).unlockedBy( + MItemTags.TRITANIUM_PLATES).save(consumer, modLocation("tritanium_ingot_from_plates_blasting")) + + consumer.addRecyclingRecipe(MItems.TRITANIUM_TOOLS, MItems.TRITANIUM_NUGGET, "tritanium_nugget_from_tools") + consumer.addRecyclingRecipe(MItems.SIMPLE_TRITANIUM_ARMOR, MItems.TRITANIUM_NUGGET, "tritanium_nugger_from_armor") +} + +fun addOreSmeltingRecipes(consumer: RecipeOutput) { + SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_ORES), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 1f, 200).unlockedBy(MItemTags.TRITANIUM_ORES).save(consumer, modLocation("smelting/tritanium_ingot_from_ore_block")) + SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_ORES), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 1f, 100).unlockedBy(MItemTags.TRITANIUM_ORES).save(consumer, modLocation("blasting/tritanium_ingot_from_ore_block")) + + SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_ORE_CLUMPS), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 1f, 200).unlockedBy(MItemTags.TRITANIUM_ORE_CLUMPS).save(consumer, modLocation("smelting/tritanium_ingot_from_raw_ore")) + SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_ORE_CLUMPS), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 1f, 100).unlockedBy(MItemTags.TRITANIUM_ORE_CLUMPS).save(consumer, modLocation("blasting/tritanium_ingot_from_raw_ore")) + + SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_DUSTS), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 0f, 200).unlockedBy(MItemTags.TRITANIUM_DUSTS).save(consumer, modLocation("smelting/tritanium_ingot_from_dust")) + SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_DUSTS), RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 0f, 100).unlockedBy(MItemTags.TRITANIUM_DUSTS).save(consumer, modLocation("blasting/tritanium_ingot_from_dust")) +} + +fun addMicrowaveRecipes(provider: MatteryRecipeProvider) { + provider.microwave("pattern_drive_normal_erase", Ingredient.of(MItems.PATTERN_DRIVE_NORMAL), Ingredient.of(MItems.PATTERN_DRIVE_NORMAL), workTicks = 30 * 20, experience = ConstantFloat.of(0f)) +} + diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt index 9527f614a..b2561618f 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt @@ -1,51 +1,60 @@ package ru.dbotthepony.mc.otm.datagen.recipes -import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.data.recipes.RecipeCategory import net.minecraft.data.recipes.ShapelessRecipeBuilder import net.minecraft.tags.ItemTags import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient import net.minecraftforge.common.Tags +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.modLocation -import java.util.function.Consumer +import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe +import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe -fun addCraftingTableRecipes(consumer: Consumer) { - MatteryRecipe(MRegistry.CARGO_CRATES.item) - .row(MItemTags.TRITANIUM_PLATES, Tags.Items.CHESTS, MItemTags.TRITANIUM_PLATES) +fun addCraftingTableRecipes(consumer: RecipeOutput) { + val machinesCategory = RecipeCategory.DECORATIONS + + MatteryRecipe(MRegistry.CARGO_CRATES.item, category = RecipeCategory.DECORATIONS) + .row(MItemTags.TRITANIUM_PLATES, multiIngredient(Tags.Items.CHESTS_WOODEN, Tags.Items.BARRELS_WOODEN), MItemTags.TRITANIUM_PLATES) .unlockedBy(MItemTags.TRITANIUM_PLATES) .unlockedBy(Tags.Items.CHESTS) .build(consumer) for ((dye, crate) in MRegistry.CARGO_CRATES.blocks) { - ShapelessRecipeBuilder(crate, 1) + ShapelessRecipeBuilder(RecipeCategory.DECORATIONS, crate, 1) .requires(Ingredient.of(MRegistry.CARGO_CRATES.allItems.entries.stream().filter { it.key != dye }.map { ItemStack(it.value) })) .requires(dye.tag) .unlockedBy(MRegistry.CARGO_CRATES.allItems.entries.stream().filter { it.key != dye }.map { it.value }) .save(consumer, "${crate.registryName}_alt") } - ShapelessRecipeBuilder(MItems.TRITANIUM_INGOT_BLOCK, 1) + ShapelessRecipeBuilder(RecipeCategory.BUILDING_BLOCKS, MItems.TRITANIUM_INGOT_BLOCK, 1) .requires(Ingredient.of(MItemTags.TRITANIUM_INGOTS), 9) .unlockedBy(MItemTags.TRITANIUM_INGOTS) .save(consumer) - ShapelessRecipeBuilder(MItems.TRITANIUM_INGOT, 9) + ShapelessRecipeBuilder(RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 9) .requires(Ingredient.of(MItemTags.TRITANIUM_INGOTS_STORAGE)) .unlockedBy(MItemTags.TRITANIUM_INGOTS_STORAGE) .save(consumer, modLocation("tritanium_ingot_from_storage")) - ShapelessRecipeBuilder(MItems.ENERGY_COUNTER, 1) + ShapelessRecipeBuilder(machinesCategory, MItems.ENERGY_COUNTER, 1) .requires(MItems.ENERGY_COUNTER) .unlockedBy(MItems.ENERGY_COUNTER) .save(consumer, modLocation("energy_counter_reset")) - MatteryRecipe(MBlocks.PLATE_PRESS) + ShapelessRecipeBuilder(machinesCategory, MItems.HOLO_SIGN, 1) + .requires(MItems.HOLO_SIGN) + .unlockedBy(MItems.HOLO_SIGN) + .save(consumer, modLocation("holo_sign_reset")) + + MatteryRecipe(MBlocks.PLATE_PRESS, category = machinesCategory) .row(MItems.ELECTRIC_PARTS, MItems.ENERGY_BUS, MItems.ELECTRIC_PARTS) .row(MItemTags.TRITANIUM_INGOTS, Items.BLAST_FURNACE, MItemTags.TRITANIUM_INGOTS) .row(MItemTags.PISTONS, MItemTags.TRITANIUM_INGOTS, MItemTags.PISTONS) @@ -53,14 +62,23 @@ fun addCraftingTableRecipes(consumer: Consumer) { .unlockedBy(MItems.ELECTRIC_PARTS) .build(consumer) - MatteryRecipe(MBlocks.PLATE_PRESS) + MatteryRecipe(MBlocks.PLATE_PRESS, category = machinesCategory) + .rowB(MItemTags.PISTONS) .rowB(MItems.MACHINE_FRAME) - .rowAC(MItemTags.PISTONS, MItemTags.PISTONS) .unlockedBy(MItemTags.TRITANIUM_INGOTS) .unlockedBy(MItems.ELECTRIC_PARTS) .build(consumer, "advanced") - MatteryRecipe(MItems.PATTERN_DRIVE_NORMAL) + MatteryRecipe(MBlocks.TWIN_PLATE_PRESS, category = machinesCategory) + .setUpgradeSource(MItems.PLATE_PRESS) + .addUpgradeOps(UpgradeRecipe.Direct("BlockEntityTag")) + .rowB(MItemTags.PISTONS) + .rowB(MItems.PLATE_PRESS) + .rowB(MItemTags.TRITANIUM_PLATES) + .unlockedBy(MItems.PLATE_PRESS) + .build(consumer) + + MatteryRecipe(MItems.PATTERN_DRIVE_NORMAL, category = machinesCategory) .rowAC(MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT) .row(MItemTags.ADVANCED_CIRCUIT, MItemTags.TRITANIUM_PLATES, MItemTags.ADVANCED_CIRCUIT) .rowAC(MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT) @@ -68,7 +86,7 @@ fun addCraftingTableRecipes(consumer: Consumer) { .build(consumer) // Машины - MatteryRecipe(MItems.MATTER_RECYCLER) + MatteryRecipe(MItems.MATTER_RECYCLER, category = machinesCategory) .row(MItems.MATTER_CAPACITOR_PARTS, Items.HOPPER, MItemTags.BASIC_CIRCUIT) .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) .row(MItems.MATTER_CABLE, MItems.MATTER_IO_PORT, MItems.MATTER_CABLE) @@ -76,21 +94,21 @@ fun addCraftingTableRecipes(consumer: Consumer) { .build(consumer) // Блоки - MatteryRecipe(MItems.MATTER_CAPACITOR_BANK) + MatteryRecipe(MItems.MATTER_CAPACITOR_BANK, category = machinesCategory) .row(Tags.Items.GLASS, MItemTags.IRON_PLATES, Tags.Items.GLASS) .row(MItemTags.IRON_PLATES, MItems.MACHINE_FRAME, MItemTags.IRON_PLATES) .row(MItems.MATTER_CABLE, MItems.MATTER_IO_PORT, MItems.MATTER_CABLE) .unlockedBy(MItems.MATTER_CABLE) .build(consumer) - MatteryRecipe(MItems.BATTERY_BANK) + MatteryRecipe(MItems.BATTERY_BANK, category = machinesCategory) .row(Tags.Items.GLASS, MItemTags.IRON_PLATES, Tags.Items.GLASS) .row(MItemTags.IRON_PLATES, MItems.MACHINE_FRAME, MItemTags.IRON_PLATES) .row(MItems.ELECTRIC_PARTS, MItems.ENERGY_BUS, MItems.ELECTRIC_PARTS) .unlockedBy(MItems.ENERGY_BUS) .build(consumer) - MatteryRecipe(MItems.GRAVITATION_STABILIZER) + MatteryRecipe(MItems.GRAVITATION_STABILIZER, category = machinesCategory) .row(MItemTags.ADVANCED_CIRCUIT, MItems.GRAVITATION_FIELD_SENSOR, MItemTags.ADVANCED_CIRCUIT) .row(MItems.MATTER_TRANSFORM_MATRIX, MItems.MACHINE_FRAME, MItems.MATTER_TRANSFORM_MATRIX) .row(MItemTags.TRITANIUM_PLATES, MItems.GRAVITATION_FIELD_LIMITER, MItemTags.TRITANIUM_PLATES) @@ -98,7 +116,7 @@ fun addCraftingTableRecipes(consumer: Consumer) { .unlockedBy(MItems.GRAVITATION_FIELD_SENSOR) .build(consumer) - MatteryRecipe(MItems.PORTABLE_GRAVITATION_STABILIZER) + MatteryRecipe(MItems.PORTABLE_GRAVITATION_STABILIZER, category = RecipeCategory.COMBAT) .rowB(MItemTags.TRITANIUM_PLATES) .row(MItemTags.TRITANIUM_PLATES, MItems.GRAVITATION_FIELD_SENSOR, MItemTags.TRITANIUM_PLATES) .row(MItemTags.TRITANIUM_PLATES, MItems.GRAVITATION_FIELD_LIMITER, MItemTags.TRITANIUM_PLATES) @@ -106,150 +124,347 @@ fun addCraftingTableRecipes(consumer: Consumer) { .unlockedBy(MItems.GRAVITATION_FIELD_SENSOR) .build(consumer) - MatteryRecipe(MItems.GRAVITATION_FIELD_SENSOR) + MatteryRecipe(MItems.GRAVITATION_FIELD_SENSOR, category = RecipeCategory.MISC) .rowB(MItemTags.BASIC_CIRCUIT) .row(MItemTags.TRITANIUM_PLATES, MItems.ELECTROMAGNET, MItemTags.TRITANIUM_PLATES) .rowB(MItemTags.IRON_PLATES) .unlockedBy(MItems.ELECTROMAGNET) .build(consumer) - MatteryRecipe(MItems.GRAVITATION_FIELD_LIMITER) + MatteryRecipe(MItems.GRAVITATION_FIELD_LIMITER, category = RecipeCategory.MISC) .row(Tags.Items.ENDER_PEARLS, MItemTags.ADVANCED_CIRCUIT, Tags.Items.ENDER_PEARLS) .row(MItemTags.GOLD_WIRES, MItems.QUANTUM_TRANSCEIVER, MItemTags.GOLD_WIRES) .rowB(MItemTags.TRITANIUM_PLATES) .unlockedBy(MItems.QUANTUM_TRANSCEIVER) .build(consumer) - MatteryRecipe(MItems.BLACK_HOLE_SCANNER) + MatteryRecipe(MItems.BLACK_HOLE_SCANNER, category = RecipeCategory.TOOLS) .row(MItemTags.IRON_PLATES, Tags.Items.GLASS_PANES_COLORLESS, MItemTags.IRON_PLATES) .row(MItemTags.GOLD_WIRES, MItems.GRAVITATION_FIELD_SENSOR, MItemTags.ADVANCED_CIRCUIT) .rowAC(Tags.Items.DUSTS_GLOWSTONE, MItemTags.TRITANIUM_PLATES) .unlockedBy(MItems.GRAVITATION_FIELD_SENSOR) .build(consumer) - MatteryRecipe(MItems.PHANTOM_ATTRACTOR) + MatteryRecipe(MItems.PHANTOM_ATTRACTOR, category = machinesCategory) .row(Tags.Items.DUSTS_REDSTONE, Tags.Items.GLASS_COLORLESS, Tags.Items.DUSTS_REDSTONE) .row(MItemTags.TRITANIUM_PLATES, MItems.QUANTUM_TRANSCEIVER, MItemTags.TRITANIUM_PLATES) .row(MItemTags.TRITANIUM_PLATES, ItemTags.BEDS, MItemTags.TRITANIUM_PLATES) .unlockedBy(MItems.QUANTUM_TRANSCEIVER) .build(consumer) - MatteryRecipe(MItems.QUANTUM_TRANSCEIVER, 2) + MatteryRecipe(MItems.QUANTUM_TRANSCEIVER, 2, category = RecipeCategory.MISC) .rowAC(MItemTags.COPPER_WIRES, MItemTags.COPPER_WIRES) .row(MItemTags.GOLD_WIRES, Tags.Items.ENDER_PEARLS, MItemTags.GOLD_WIRES) .row(MItemTags.GOLD_WIRES, MItems.ELECTROMAGNET, MItemTags.GOLD_WIRES) .unlockedBy(Tags.Items.ENDER_PEARLS) .build(consumer) - MatteryRecipe(MItems.ELECTROMAGNET) + MatteryRecipe(MItems.ELECTROMAGNET, category = RecipeCategory.MISC) .row(MItemTags.COPPER_WIRES, Tags.Items.INGOTS_IRON, MItemTags.COPPER_WIRES) - .unlockedBy(Tags.Items.ENDER_PEARLS) + .unlockedBy(Tags.Items.INGOTS_IRON) .build(consumer) - MatteryRecipe(MItems.ENERGY_SERVO) + MatteryRecipe(MItems.ELECTROMOTOR, category = RecipeCategory.MISC) + .rowB(MItems.ELECTROMAGNET) + .row(MItems.ELECTROMAGNET, Tags.Items.INGOTS_IRON, MItems.ELECTROMAGNET) + .row(MItemTags.COPPER_WIRES, Tags.Items.INGOTS_IRON, MItemTags.COPPER_WIRES) + .unlockedBy(MItems.ELECTROMAGNET) + .build(consumer) + + MatteryRecipe(MItems.ENERGY_SERVO, category = RecipeCategory.MISC) .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) .row(MItemTags.TRITANIUM_PLATES, MItems.ENERGY_BUS, MItemTags.TRITANIUM_PLATES) .unlockedBy(MItems.ENERGY_BUS) .build(consumer) - MatteryRecipe(MItems.MIRROR_COMPOUND, 3) + MatteryRecipe(MItems.MIRROR_COMPOUND, 3, category = RecipeCategory.MISC) .row(Tags.Items.GLASS_PANES_COLORLESS, Tags.Items.GLASS_PANES_COLORLESS, Tags.Items.GLASS_PANES_COLORLESS) .row(MItemTags.IRON_PLATES, MItemTags.IRON_PLATES, MItemTags.IRON_PLATES) .unlockedBy(MItemTags.IRON_PLATES) .unlockedBy(Tags.Items.GLASS_PANES_COLORLESS) .build(consumer) + MatteryRecipe(MItems.REINFORCED_TRITANIUM_PLATE, category = RecipeCategory.MISC) + .rowB(MItemTags.CARBON_PLATES) + .row(MItemTags.CARBON_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.CARBON_PLATES) + .rowB(MItemTags.CARBON_PLATES) + .unlockedBy(MItemTags.TRITANIUM_PLATES) + .build(consumer) + + MatteryRecipe(MItems.CARBON_FIBRE_BLOCK, category = RecipeCategory.BUILDING_BLOCKS) + .rowAB(MItemTags.CARBON_PLATES, MItemTags.CARBON_PLATES) + .rowAB(MItemTags.CARBON_PLATES, MItemTags.CARBON_PLATES) + .unlockedBy(MItemTags.CARBON_PLATES) + .build(consumer) + + ShapelessRecipeBuilder(RecipeCategory.MISC, MItems.CARBON_MESH, 4) + .requires(Ingredient.of(MItems.CARBON_FIBRE_BLOCK)) + .unlockedBy(MItemTags.CARBON_PLATES) + .unlockedBy(MItems.CARBON_FIBRE_BLOCK) + .save(consumer, modLocation("carbon_mesh_from_block")) + // броня - MatteryRecipe(MItems.TRITANIUM_HELMET) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.IRON_PLATES, MItemTags.TRITANIUM_PLATES) - .unlockedBy(MItemTags.TRITANIUM_PLATES) + MatteryRecipe(MItems.TRITANIUM_HELMET, category = RecipeCategory.COMBAT) + .setUpgradeSource(Items.LEATHER_HELMET) + .addUpgradeOps( + UpgradeRecipe.Direct("display"), + UpgradeRecipe.Direct("Enchantments"), + ) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, Items.LEATHER_HELMET, MItemTags.REINFORCED_TRITANIUM_PLATES) + .unlockedBy(MItemTags.REINFORCED_TRITANIUM_PLATES) .build(consumer) - MatteryRecipe(MItems.TRITANIUM_PANTS) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.IRON_PLATES, MItemTags.TRITANIUM_PLATES) - .rowAC(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .unlockedBy(MItemTags.TRITANIUM_PLATES) + MatteryRecipe(MItems.TRITANIUM_PANTS, category = RecipeCategory.COMBAT) + .setUpgradeSource(Items.LEATHER_LEGGINGS) + .addUpgradeOps( + UpgradeRecipe.Direct("display"), + UpgradeRecipe.Direct("Enchantments"), + ) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, Items.LEATHER_LEGGINGS, MItemTags.REINFORCED_TRITANIUM_PLATES) + .rowAC(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .unlockedBy(MItemTags.REINFORCED_TRITANIUM_PLATES) .build(consumer) - MatteryRecipe(MItems.TRITANIUM_CHESTPLATE) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.IRON_PLATES, MItemTags.TRITANIUM_PLATES) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .unlockedBy(MItemTags.TRITANIUM_PLATES) + MatteryRecipe(MItems.TRITANIUM_CHESTPLATE, category = RecipeCategory.COMBAT) + .setUpgradeSource(Items.LEATHER_CHESTPLATE) + .addUpgradeOps( + UpgradeRecipe.Direct("display"), + UpgradeRecipe.Direct("Enchantments"), + ) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, Items.LEATHER_CHESTPLATE, MItemTags.REINFORCED_TRITANIUM_PLATES) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .unlockedBy(MItemTags.REINFORCED_TRITANIUM_PLATES) .build(consumer) - MatteryRecipe(MItems.TRITANIUM_BOOTS) - .rowAC(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) - .row(MItemTags.TRITANIUM_PLATES, MItemTags.IRON_PLATES, MItemTags.TRITANIUM_PLATES) - .unlockedBy(MItemTags.TRITANIUM_PLATES) + MatteryRecipe(MItems.TRITANIUM_BOOTS, category = RecipeCategory.COMBAT) + .setUpgradeSource(Items.LEATHER_BOOTS) + .addUpgradeOps( + UpgradeRecipe.Direct("display"), + UpgradeRecipe.Direct("Enchantments"), + ) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, Items.LEATHER_BOOTS, MItemTags.REINFORCED_TRITANIUM_PLATES) + .rowAC(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .unlockedBy(MItemTags.REINFORCED_TRITANIUM_PLATES) + .build(consumer) + + // простая броня + MatteryRecipe(MItems.SIMPLE_TRITANIUM_HELMET, category = RecipeCategory.COMBAT) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .build(consumer) + + MatteryRecipe(MItems.SIMPLE_TRITANIUM_PANTS, category = RecipeCategory.COMBAT) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .build(consumer) + + MatteryRecipe(MItems.SIMPLE_TRITANIUM_CHESTPLATE, category = RecipeCategory.COMBAT) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .build(consumer) + + MatteryRecipe(MItems.SIMPLE_TRITANIUM_BOOTS, category = RecipeCategory.COMBAT) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .rowAC(MItemTags.TRITANIUM_INGOTS, MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) .build(consumer) // простые батарейки - MatteryRecipe(MItems.BATTERY_CRUDE) + MatteryRecipe(MItems.BATTERY_CRUDE, category = RecipeCategory.MISC) .rowB(Tags.Items.DUSTS_REDSTONE) .rowB(Tags.Items.CROPS_POTATO) .rowB(Tags.Items.INGOTS_IRON) .build(consumer) - MatteryRecipe(MItems.BATTERY_BASIC) + MatteryRecipe(MItems.BATTERY_BASIC, category = RecipeCategory.MISC) .rowAC(Tags.Items.DUSTS_REDSTONE, Tags.Items.DUSTS_REDSTONE) .rowB(MItems.ELECTRIC_PARTS) .rowB(MItemTags.IRON_PLATES) .build(consumer) - MatteryRecipe(MItems.BATTERY_NORMAL) + MatteryRecipe(MItems.BATTERY_NORMAL, category = RecipeCategory.MISC) .rowB(MItems.ELECTRIC_PARTS) .row(MItemTags.COPPER_WIRES, MItemTags.IRON_PLATES, MItemTags.COPPER_WIRES) .row(Tags.Items.DUSTS_REDSTONE, Tags.Items.DUSTS_REDSTONE, Tags.Items.DUSTS_REDSTONE) .build(consumer) - MatteryRecipe(MItems.BATTERY_DENSE) + MatteryRecipe(MItems.BATTERY_DENSE, category = RecipeCategory.MISC) .row(Tags.Items.DUSTS_REDSTONE, MItems.ENERGY_BUS, Tags.Items.DUSTS_REDSTONE) .row(MItemTags.GOLD_WIRES, MItemTags.TRITANIUM_PLATES, MItemTags.GOLD_WIRES) .row(Tags.Items.DUSTS_REDSTONE, Tags.Items.DUSTS_REDSTONE, Tags.Items.DUSTS_REDSTONE) .build(consumer) - MatteryRecipe(MItems.BATTERY_CAPACITOR) + MatteryRecipe(MItems.BATTERY_CAPACITOR, category = RecipeCategory.MISC) .row(Tags.Items.DUSTS_REDSTONE, MItems.ENERGY_BUS, Tags.Items.DUSTS_REDSTONE) .row(MItemTags.GOLD_WIRES, MItemTags.TRITANIUM_PLATES, MItemTags.GOLD_WIRES) .row(MItemTags.GOLD_WIRES, Tags.Items.DUSTS_REDSTONE, MItemTags.GOLD_WIRES) .build(consumer) // накопители материи - MatteryRecipe(MItems.MATTER_CAPACITOR_DENSE) + MatteryRecipe(MItems.MATTER_CAPACITOR_DENSE, category = RecipeCategory.MISC) .row(MItems.MATTER_CAPACITOR_PARTS, Tags.Items.GLASS, MItems.MATTER_CAPACITOR_PARTS) .row(MItemTags.TRITANIUM_PLATES, Tags.Items.ENDER_PEARLS, MItemTags.TRITANIUM_PLATES) .rowAC(Tags.Items.GEMS_DIAMOND, Tags.Items.GEMS_DIAMOND) .build(consumer) // станция андроида - MatteryRecipe(MItems.ANDROID_STATION) + MatteryRecipe(MItems.ANDROID_STATION, category = machinesCategory) .row(MItems.ELECTRIC_PARTS, MItemTags.ADVANCED_CIRCUIT, MItems.ELECTRIC_PARTS) .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) .row(MItemTags.TRITANIUM_PLATES, MItems.ELECTRIC_PARTS, MItemTags.TRITANIUM_PLATES) .build(consumer) + // беспроводной зарядник андроидов + MatteryRecipe(MItems.ANDROID_CHARGER, category = machinesCategory) + .row(MItems.ELECTRIC_PARTS, MItems.QUANTUM_TRANSCEIVER, MItems.ELECTRIC_PARTS) + .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) + .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) + .build(consumer) + // Энерго меч - MatteryRecipe(MItems.ENERGY_SWORD) + MatteryRecipe(MItems.ENERGY_SWORD, category = RecipeCategory.COMBAT) .rowBC(MItemTags.TRITANIUM_PLATES, MItemTags.GOLD_WIRES) .rowBC(MItemTags.TRITANIUM_PLATES, MItemTags.GOLD_WIRES) .row(MItems.BATTERY_CAPACITOR, MItems.TRITANIUM_SWORD, MItemTags.ADVANCED_CIRCUIT) .unlockedBy(MItems.BATTERY_CAPACITOR) .buildEnergetic(consumer) - // лампа - MatteryRecipe(MItems.LABORATORY_LAMP) - .row(MItemTags.IRON_PLATES, MItemTags.HARDENED_GLASS_PANES_COLORLESS, MItemTags.IRON_PLATES) - .row(MItems.MIRROR, Items.GLOWSTONE, MItems.MIRROR) - .row(MItemTags.TRITANIUM_PLATES, Tags.Items.DUSTS_REDSTONE, MItemTags.TRITANIUM_PLATES) - .build(consumer) - // апгрейд на сетку крафта - MatteryRecipe(MItems.ExopackUpgrades.CRAFTING_UPGRADE) + MatteryRecipe(MItems.ExopackUpgrades.CRAFTING_UPGRADE, category = RecipeCategory.TOOLS) .row(MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT) .row(MItemTags.CRAFTING_TABLES, MItems.QUANTUM_TRANSCEIVER, MItemTags.CRAFTING_TABLES) .row(MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES, MItemTags.TRITANIUM_PLATES) .build(consumer) + + // апгрейд на переплавку + MatteryRecipe(MItems.ExopackUpgrades.SMELTING_UPGRADE, category = RecipeCategory.TOOLS) + .row(MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT, MItemTags.ADVANCED_CIRCUIT) + .row(Items.FURNACE, MItems.QUANTUM_TRANSCEIVER, Items.FURNACE) + .row(MItemTags.TRITANIUM_PLATES, Items.FURNACE, MItemTags.TRITANIUM_PLATES) + .build(consumer) + + // апгрейд на эндер сундук + MatteryRecipe(MItems.ExopackUpgrades.ENDER_UPGRADE, category = RecipeCategory.TOOLS) + .row(MItemTags.ADVANCED_CIRCUIT, MItems.ELECTROMAGNET, MItemTags.ADVANCED_CIRCUIT) + .row(MItems.ELECTROMAGNET, Items.ENDER_CHEST, MItems.ELECTROMAGNET) + .row(MItemTags.TRITANIUM_PLATES, MItems.ELECTROMAGNET, MItemTags.TRITANIUM_PLATES) + .build(consumer) + + // генератор коблы + MatteryRecipe(MItems.COBBLESTONE_GENERATOR, category = machinesCategory) + .row(MItemTags.HARDENED_GLASS_COLORLESS, MItems.TRITANIUM_PICKAXE, MItemTags.HARDENED_GLASS_COLORLESS) + .row(Items.LAVA_BUCKET, Items.HOPPER, Items.WATER_BUCKET) + .rowB(Tags.Items.CHESTS) + .build(consumer) + + MatteryRecipe(MItems.TRITANIUM_SHEARS, category = RecipeCategory.TOOLS) + .rowB(MItemTags.TRITANIUM_INGOTS) + .rowA(MItemTags.TRITANIUM_INGOTS) + .build(consumer) + + MatteryRecipe(MItems.TRITANIUM_SHIELD, category = RecipeCategory.COMBAT) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, Items.SHIELD, MItemTags.REINFORCED_TRITANIUM_PLATES) + .row(MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES, MItemTags.REINFORCED_TRITANIUM_PLATES) + .rowB(MItemTags.REINFORCED_TRITANIUM_PLATES) + .build(consumer) + + ShapelessRecipeBuilder(RecipeCategory.MISC, MItems.TRITANIUM_NUGGET, 9) + .requires(MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_NUGGETS) + .save(consumer) + + ShapelessRecipeBuilder(RecipeCategory.MISC, MItems.TRITANIUM_INGOT, 1) + .requires(Ingredient.of(MItemTags.TRITANIUM_NUGGETS), 9) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.TRITANIUM_NUGGETS) + .save(consumer, modLocation("ingot_from_nuggets")) + + MatteryRecipe(MItems.ESSENCE_STORAGE, category = machinesCategory) + .row(MItems.MATTER_CAPACITOR_PARTS, Items.ENDER_EYE, MItemTags.ADVANCED_CIRCUIT) + .row(MItemTags.TRITANIUM_PLATES, MItems.MACHINE_FRAME, MItemTags.TRITANIUM_PLATES) + .row(MItemTags.GOLD_WIRES, MItemTags.HARDENED_GLASS, MItemTags.HARDENED_GLASS) + .build(consumer) + + MatteryRecipe(MItems.ESSENCE_SERVO, category = RecipeCategory.TOOLS) + .row(MItemTags.TRITANIUM_PLATES, MItems.QUANTUM_TRANSCEIVER, MItemTags.TRITANIUM_PLATES) + .rowB(Tags.Items.RODS_WOODEN) + .rowB(Tags.Items.RODS_WOODEN) + .build(consumer) + + MatteryRecipe(MItems.MATTER_RECONSTRUCTOR, category = machinesCategory) + .setUpgradeSource(MItems.MATTER_REPLICATOR) + .addUpgradeOps( + UpgradeRecipe.Indirect("BlockEntityTag.${MatteryBlockEntity.ENERGY_KEY}", "BlockEntityTag.energy"), + UpgradeRecipe.Indirect("BlockEntityTag.${MatteryBlockEntity.MATTER_STORAGE_KEY}", "BlockEntityTag.matter"), + ) + .row(MItemTags.ADVANCED_CIRCUIT, Tags.Items.GEMS_EMERALD, MItemTags.ADVANCED_CIRCUIT) + .row(MItems.ELECTRIC_PARTS, MItems.MATTER_REPLICATOR, MItems.ELECTRIC_PARTS) + .row(MItems.ELECTROMAGNET, MItems.ELECTROMAGNET, MItems.ELECTROMAGNET) + .build(consumer) + + MatteryRecipe(MItems.FLUID_CAPSULE, category = RecipeCategory.TOOLS, count = 8) + .row(MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS) + .rowB(MItemTags.HARDENED_GLASS_PANES) + .row(MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS, MItemTags.TRITANIUM_NUGGETS) + .unlockedBy(MItemTags.HARDENED_GLASS_PANES) + .unlockedBy(MItemTags.TRITANIUM_NUGGETS) + .build(consumer) + + MatteryRecipe(MItems.FLUID_TANK, category = RecipeCategory.DECORATIONS) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.HARDENED_GLASS_PANES, MItemTags.TRITANIUM_INGOTS) + .rowAC(MItemTags.HARDENED_GLASS_PANES, MItemTags.HARDENED_GLASS_PANES) + .row(MItemTags.TRITANIUM_INGOTS, MItemTags.HARDENED_GLASS_PANES, MItemTags.TRITANIUM_INGOTS) + .unlockedBy(MItemTags.HARDENED_GLASS_PANES) + .unlockedBy(MItemTags.TRITANIUM_INGOTS) + .build(consumer) + + consumer.accept(ExplosiveHammerPrimingRecipe(Ingredient.of(Tags.Items.NUGGETS_IRON), modLocation("hammer_priming")).toFinished()) + + MatteryRecipe(MItems.EXPLOSIVE_HAMMER, category = RecipeCategory.COMBAT) + .rowB(Tags.Items.INGOTS_IRON) + .rowAB(Tags.Items.INGOTS_IRON, Tags.Items.RODS_WOODEN) + .rowB(Tags.Items.RODS_WOODEN) + .unlockedBy(Items.FLINT_AND_STEEL) + .build(consumer) + + MatteryRecipe(MItems.POWERED_FURNACE, category = machinesCategory) + .row(Items.FURNACE, MItems.MACHINE_FRAME, Items.FURNACE) + .unlockedBy(MItems.MACHINE_FRAME) + .build(consumer) + + MatteryRecipe(MItems.POWERED_SMOKER, category = machinesCategory) + .rowAC(Items.FURNACE, Items.FURNACE) + .row(MItems.ELECTROMAGNET, MItems.MACHINE_FRAME, MItems.ELECTROMAGNET) + .unlockedBy(MItems.MACHINE_FRAME) + .build(consumer) + + MatteryRecipe(MItems.POWERED_BLAST_FURNACE, category = machinesCategory) + .row(MItems.ELECTROMAGNET, Items.FURNACE, MItems.ELECTROMAGNET) + .row(MItems.ELECTROMAGNET, MItems.MACHINE_FRAME, MItems.ELECTROMAGNET) + .row(MItems.ELECTROMAGNET, Items.FURNACE, MItems.ELECTROMAGNET) + .unlockedBy(MItems.MACHINE_FRAME) + .build(consumer) + + MatteryRecipe(MItems.INFINITE_WATER_SOURCE, category = machinesCategory) + .row(MItemTags.IRON_PLATES, MItemTags.IRON_PLATES, MItemTags.IRON_PLATES) + .rowAC(Items.WATER_BUCKET, Items.WATER_BUCKET) + .row(MItemTags.IRON_PLATES, MItemTags.IRON_PLATES, MItemTags.IRON_PLATES) + .unlockedBy(Items.WATER_BUCKET) + .build(consumer) + + MatteryRecipe(MItems.PAINTER, category = machinesCategory) + .row(Tags.Items.RODS_WOODEN, Items.BUCKET, Items.BUCKET) + .row(MItemTags.IRON_PLATES, Items.BUCKET, MItemTags.IRON_PLATES) + .row(MItemTags.IRON_PLATES, MItemTags.CRAFTING_TABLES, MItemTags.IRON_PLATES) + .unlockedBy(Tags.Items.DYES) + .build(consumer) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/DecorativesRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/DecorativesRecipes.kt index d3f4b6067..ef69bdd76 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/DecorativesRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/DecorativesRecipes.kt @@ -1,70 +1,67 @@ package ru.dbotthepony.mc.otm.datagen.recipes import net.minecraft.data.recipes.* -import net.minecraft.resources.ResourceLocation -import net.minecraft.tags.ItemTags import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.level.ItemLike import net.minecraftforge.common.Tags -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.datagen.DataGen +import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.function.Consumer -private fun stairs(base: ItemLike, result: ItemLike, consumer: Consumer) { - MatteryRecipe(result, 4) +private fun stairs(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { + MatteryRecipe(result, 4, category = RecipeCategory.BUILDING_BLOCKS) .rowA(base) .rowAB(base, base) .row(base, base, base) .unlockedBy(base) - .build(consumer) + .build(consumer, modLocation("decorative/stairs/${base.asItem().registryName!!.path}")) } -private fun slab(base: ItemLike, result: ItemLike, consumer: Consumer) { - MatteryRecipe(result, 6) +private fun slab(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { + MatteryRecipe(result, 6, category = RecipeCategory.BUILDING_BLOCKS) .row(base, base, base) .unlockedBy(base) - .build(consumer) + .build(consumer, modLocation("decorative/slabs/${base.asItem().registryName!!.path}")) } -private fun wall(base: ItemLike, result: ItemLike, consumer: Consumer) { - MatteryRecipe(result, 6) +private fun wall(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { + MatteryRecipe(result, 6, category = RecipeCategory.BUILDING_BLOCKS) .row(base, base, base) .row(base, base, base) .unlockedBy(base) - .build(consumer) + .build(consumer, modLocation("decorative/walls/${base.asItem().registryName!!.path}")) } -private fun cut(base: ItemLike, result: ItemLike, amount: Int, consumer: Consumer) { +private fun cut(base: ItemLike, result: ItemLike, amount: Int, consumer: RecipeOutput) { SingleItemRecipeBuilder - .stonecutting(Ingredient.of(base), result, amount) + .stonecutting(Ingredient.of(base), RecipeCategory.BUILDING_BLOCKS, result, amount) .unlockedBy(base) - .save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "stonecutting/${result.asItem().registryName!!.path}_from_${base.asItem().registryName!!.path}")) + .save(consumer, modLocation("stonecutting/${result.asItem().registryName!!.path}_from_${base.asItem().registryName!!.path}")) } -private fun stairsWithCut(base: ItemLike, result: ItemLike, consumer: Consumer) { +private fun stairsWithCut(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { stairs(base, result, consumer) cut(base, result, 1, consumer) } -private fun slabWithCut(base: ItemLike, result: ItemLike, consumer: Consumer) { +private fun slabWithCut(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { slab(base, result, consumer) cut(base, result, 2, consumer) } -private fun wallWithCut(base: ItemLike, result: ItemLike, consumer: Consumer) { +private fun wallWithCut(base: ItemLike, result: ItemLike, consumer: RecipeOutput) { wall(base, result, consumer) cut(base, result, 1, consumer) } -fun addDecorativesRecipes(provider: MatteryRecipeProvider, consumer: Consumer) { +fun addDecorativesRecipes(provider: MatteryRecipeProvider, consumer: RecipeOutput) { // Напольная плитка for ((color, unrefinedItem) in MRegistry.UNREFINED_FLOOR_TILES.items) { MatteryRecipe(unrefinedItem, 24) @@ -72,16 +69,32 @@ fun addDecorativesRecipes(provider: MatteryRecipeProvider, consumer: Consumer FinishedRecipe): RecipeOutput { + return object : RecipeOutput { + override fun accept(recipe: FinishedRecipe) { + this@map.accept(mapper(recipe)) + } + } +} + /** * [ShapedRecipeBuilder] that doesn't suck */ @Suppress("unused") -class MatteryRecipe(val result: ItemLike, val count: Int = 1) { +class MatteryRecipe(val result: ItemLike, val count: Int = 1, val category: RecipeCategory = RecipeCategory.MISC) { private val rows = arrayOfNulls(3) private var index = 0 @@ -115,12 +128,12 @@ class MatteryRecipe(val result: ItemLike, val count: Int = 1) { } } - fun build(consumer: Consumer, name: String? = null) { + private fun buildRegular(): ShapedRecipeBuilder { if (index == 0) { throw NoSuchElementException("No recipe rows were defined") } - val builder = ShapedRecipeBuilder(result, count) + val builder = ShapedRecipeBuilder(category, result, count) val pairs = ArrayList>() val iterator = charlist.iterator() @@ -139,25 +152,75 @@ class MatteryRecipe(val result: ItemLike, val count: Int = 1) { builder.unlockedBy(a, b) } + return builder + } + + private var upgradeSource: ResourceLocation? = null + private val copyPaths = ArrayList() + + fun setUpgradeSource(source: ResourceLocation): MatteryRecipe { + this.upgradeSource = source + return this + } + + fun setUpgradeSource(source: ItemLike): MatteryRecipe { + this.upgradeSource = source.asItem().registryName!! + return this + } + + fun addUpgradeOps(vararg operations: UpgradeRecipe.Op): MatteryRecipe { + for (op in operations) copyPaths.add(op) + return this + } + + private fun filter(): (FinishedRecipe) -> FinishedRecipe { + if (upgradeSource != null) { + check(copyPaths.isNotEmpty()) { "Defined upgrade recipe without nbt migration operations" } + + return { + object : FinishedRecipe by it { + override fun serializeRecipeData(pJson: JsonObject) { + pJson["parent"] = it.serializeRecipe() + pJson["copyPaths"] = UpgradeRecipe.COPY_PATHS_CODEC.toJsonStrict(copyPaths) + pJson["source"] = upgradeSource!!.toString() + } + + override fun getType(): RecipeSerializer<*> { + return UpgradeRecipe.CODEC + } + } + } + } + + return { it } + } + + fun build(consumer: RecipeOutput, name: String? = null) { + val builder = buildRegular() + if (name != null) { - builder.save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, + builder.save(consumer.map(filter()), modLocation( if (result.asItem().registryName!!.namespace == OverdriveThatMatters.MOD_ID) "${result.asItem().registryName!!.path}_$name" else "${result.asItem().registryName!!.namespace}_${result.asItem().registryName!!.path}_$name" )) } else { - builder.save(consumer) + builder.save(consumer.map(filter())) } } - fun buildEnergetic(consumer: Consumer, name: String? = null) { - build({ - consumer.accept(object : FinishedRecipe by it { + fun build(consumer: RecipeOutput, name: ResourceLocation) { + buildRegular().save(consumer.map(filter()), name) + } + + fun buildEnergetic(consumer: RecipeOutput, name: String? = null) { + build(consumer.map { + object : FinishedRecipe by it { override fun getType(): RecipeSerializer<*> { return EnergyContainerRecipe.Companion } - }) + } }, name) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/MatteryRecipeProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/MatteryRecipeProvider.kt index f985ee946..510ce2bd2 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/MatteryRecipeProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/MatteryRecipeProvider.kt @@ -1,40 +1,44 @@ package ru.dbotthepony.mc.otm.datagen.recipes +import net.minecraft.advancements.CriterionTriggerInstance import net.minecraft.advancements.critereon.EntityPredicate import net.minecraft.advancements.critereon.InventoryChangeTrigger import net.minecraft.advancements.critereon.ItemPredicate import net.minecraft.advancements.critereon.MinMaxBounds import net.minecraft.data.DataGenerator -import net.minecraft.data.recipes.FinishedRecipe import net.minecraft.data.recipes.RecipeBuilder import net.minecraft.data.recipes.RecipeProvider import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.ItemTags import net.minecraft.tags.TagKey +import net.minecraft.util.valueproviders.ConstantFloat +import net.minecraft.util.valueproviders.FloatProvider import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.level.ItemLike import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.datagen.DataGen +import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe -import java.util.LinkedList -import java.util.function.Consumer +import java.util.* import java.util.stream.Stream -private typealias RecipeBuilderCallback = (MatteryRecipeProvider, consumer: Consumer) -> Unit +private typealias RecipeBuilderCallback = (MatteryRecipeProvider, consumer: RecipeOutput) -> Unit -fun has(p_176521_: MinMaxBounds.Ints, p_176522_: ItemLike): InventoryChangeTrigger.TriggerInstance { +fun has(p_176521_: MinMaxBounds.Ints, p_176522_: ItemLike): CriterionTriggerInstance { return inventoryTrigger(ItemPredicate.Builder.item().of(p_176522_).withCount(p_176521_).build()) } -fun has(p_125978_: ItemLike): InventoryChangeTrigger.TriggerInstance { +fun has(p_125978_: ItemLike): CriterionTriggerInstance { return inventoryTrigger(ItemPredicate.Builder.item().of(p_125978_).build()) } -fun has(p_125976_: TagKey): InventoryChangeTrigger.TriggerInstance { +fun has(p_125976_: TagKey): CriterionTriggerInstance { return inventoryTrigger(ItemPredicate.Builder.item().of(p_125976_).build()) } -fun inventoryTrigger(vararg p_126012_: ItemPredicate): InventoryChangeTrigger.TriggerInstance { +fun inventoryTrigger(vararg p_126012_: ItemPredicate): CriterionTriggerInstance { return InventoryChangeTrigger.TriggerInstance( EntityPredicate.Composite.ANY, MinMaxBounds.Ints.ANY, @@ -44,6 +48,20 @@ fun inventoryTrigger(vararg p_126012_: ItemPredicate): InventoryChangeTrigger.Tr ) } +fun multiIngredient(vararg items: Any) : Ingredient { + val values = arrayListOf() + + for (item in items) { + if (item is ItemStack) { + values.add(Ingredient.ItemValue(item)) + } else if (item is TagKey<*>) { + values.add(Ingredient.TagValue(item as TagKey)) + } + } + + return Ingredient.fromValues(values.stream()) +} + fun T.unlockedBy(item: ItemLike): T { val location = item.asItem().registryName!! unlockedBy("has_${location.namespace}_${location.path}", has(item)) @@ -63,7 +81,7 @@ fun T.unlockedBy(item: TagKey): T { return this } -class MatteryRecipeProvider(generatorIn: DataGenerator) : RecipeProvider(generatorIn) { +class MatteryRecipeProvider(generatorIn: DataGenerator) : RecipeProvider(generatorIn.packOutput) { private val callbacks = LinkedList() fun exec(callback: RecipeBuilderCallback): MatteryRecipeProvider { @@ -71,27 +89,34 @@ class MatteryRecipeProvider(generatorIn: DataGenerator) : RecipeProvider(generat return this } - override fun buildCraftingRecipes(callback: Consumer) { + override fun buildRecipes(callback: RecipeOutput) { for (lambda in callbacks) { lambda(this, callback) } } - fun plate(id: String, count: Int = 1, workTicks: Int = 200) { + fun plate(id: String, count: Int = 1, workTicks: Int = 200, experience: FloatProvider = ConstantFloat.ZERO) { exec { _, consumer -> - consumer.accept(PlatePressShallowFinishedRecipe( - ResourceLocation(DataGen.MOD_ID, "plates/$id"), - ResourceLocation("forge", "ingots/$id"), - ResourceLocation("forge", "plates/$id"), + consumer.accept(PlatePressRecipe( + modLocation("plates/$id"), + Ingredient.of(ItemTags.create(ResourceLocation("forge", "ingots/$id"))), + Ingredient.of(ItemTags.create(ResourceLocation("forge", "plates/$id"))), count, - workTicks - )) + workTicks, + experience = experience + ).toFinished()) } } - fun plate(id: String, ingredient: Ingredient, result: Ingredient, count: Int = 1, workTicks: Int = 200) { + fun plate(id: String, ingredient: Ingredient, result: Ingredient, count: Int = 1, workTicks: Int = 200, experience: FloatProvider = ConstantFloat.ZERO) { exec { it, callback -> - callback.accept(PlatePressFinishedRecipe(PlatePressRecipe(ResourceLocation(DataGen.MOD_ID, "plate_$id"), ingredient, result, count, workTicks))) + callback.accept(PlatePressRecipe(modLocation("plates/$id"), ingredient, result, count, workTicks, experience = experience).toFinished()) + } + } + + fun microwave(id: String, ingredient: Ingredient, result: Ingredient, count: Int = 1, workTicks: Int = 200, experience: FloatProvider = ConstantFloat.ZERO) { + exec { it, callback -> + callback.accept(MicrowaveRecipe(modLocation("microwave/$id"), ingredient, result, count, workTicks, experience = experience).toFinished()) } } } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/OreRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/OreRecipes.kt deleted file mode 100644 index c9939a3da..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/OreRecipes.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen.recipes - -import net.minecraft.data.recipes.FinishedRecipe -import net.minecraft.data.recipes.SimpleCookingRecipeBuilder -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.crafting.Ingredient -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.registry.MItemTags -import ru.dbotthepony.mc.otm.registry.MItems -import java.util.function.Consumer - -fun addOreSmeltingRecipes(consumer: Consumer) { - SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_ORES), MItems.TRITANIUM_INGOT, 1f, 100).unlockedBy(MItemTags.TRITANIUM_ORES).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "smelting/tritanium_ingot_from_ore_block")) - SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_ORES), MItems.TRITANIUM_INGOT, 1f, 200).unlockedBy(MItemTags.TRITANIUM_ORES).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "blasting/tritanium_ingot_from_ore_block")) - - SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_ORE_CLUMPS), MItems.TRITANIUM_INGOT, 1f, 100).unlockedBy(MItemTags.TRITANIUM_ORE_CLUMPS).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "smelting/tritanium_ingot_from_raw_ore")) - SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_ORE_CLUMPS), MItems.TRITANIUM_INGOT, 1f, 200).unlockedBy(MItemTags.TRITANIUM_ORE_CLUMPS).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "blasting/tritanium_ingot_from_raw_ore")) - - SimpleCookingRecipeBuilder.blasting(Ingredient.of(MItemTags.TRITANIUM_DUSTS), MItems.TRITANIUM_INGOT, 0f, 100).unlockedBy(MItemTags.TRITANIUM_DUSTS).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "smelting/tritanium_ingot_from_dust")) - SimpleCookingRecipeBuilder.smelting(Ingredient.of(MItemTags.TRITANIUM_DUSTS), MItems.TRITANIUM_INGOT, 0f, 200).unlockedBy(MItemTags.TRITANIUM_DUSTS).save(consumer, ResourceLocation(OverdriveThatMatters.MOD_ID, "blasting/tritanium_ingot_from_dust")) -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt new file mode 100644 index 000000000..ca1dbba7d --- /dev/null +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt @@ -0,0 +1,292 @@ +package ru.dbotthepony.mc.otm.datagen.recipes + +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.recipe.PainterArmorDyeRecipe +import ru.dbotthepony.mc.otm.recipe.PainterRecipe +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MRegistry + +private val Item.recipeName get() = registryName!!.namespace + "/" + registryName!!.path + +private fun generate(consumer: RecipeOutput, items: Map, amount: Int = 1) { + for ((targetColor, targetItem) in items) { + if (targetColor == null) continue + + consumer.accept(PainterRecipe( + modLocation("painter/" + targetItem.recipeName), + Ingredient.of(items.entries.stream().filter { it.key != null && it.key != targetColor }.map { ItemStack(it.value) }), + ItemStack(targetItem), + mapOf(targetColor to amount) + ).toFinished()) + } +} + +private fun generate(consumer: RecipeOutput, default: Item, items: Map, amount: Int = 1, cleaning: Boolean = true) { + generate(consumer, items) + + if (cleaning) + cleaning(consumer, default, items) + + for ((k1, v1) in items) { + if (k1 == null) continue + + consumer.accept(PainterRecipe( + modLocation("painter/" + default.recipeName + "/" + v1.recipeName), + Ingredient.of(default), + ItemStack(v1), + mapOf(k1 to amount) + ).toFinished()) + } +} + +private fun cleaning(consumer: RecipeOutput, to: Item, from: Map) { + consumer.accept(PainterRecipe( + modLocation("painter/cleaning/" + to.recipeName), + Ingredient.of(from.entries.stream().filter { it.key != null }.map { ItemStack(it.value) }), + ItemStack(to), + mapOf(null to 15) + ).toFinished()) +} + +private fun striped(consumer: RecipeOutput, name: String, items: List>>, base: Map) { + for ((stripeItem, colors) in items) { + val (baseColor, stripe) = colors + + consumer.accept(PainterRecipe( + modLocation("painter/stripes_$name/${baseColor.getName()}/${stripe.getName()}"), + Ingredient.of(base[baseColor]), + ItemStack(stripeItem), + setOf(stripe) + ).toFinished()) + } +} + +fun addPainterRecipes(consumer: RecipeOutput) { + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_WOOL, + DyeColor.ORANGE to Items.ORANGE_WOOL, + DyeColor.MAGENTA to Items.MAGENTA_WOOL, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_WOOL, + DyeColor.YELLOW to Items.YELLOW_WOOL, + DyeColor.LIME to Items.LIME_WOOL, + DyeColor.PINK to Items.PINK_WOOL, + DyeColor.GRAY to Items.GRAY_WOOL, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_WOOL, + DyeColor.CYAN to Items.CYAN_WOOL, + DyeColor.PURPLE to Items.PURPLE_WOOL, + DyeColor.BLUE to Items.BLUE_WOOL, + DyeColor.BROWN to Items.BROWN_WOOL, + DyeColor.GREEN to Items.GREEN_WOOL, + DyeColor.RED to Items.RED_WOOL, + DyeColor.BLACK to Items.BLACK_WOOL, + )) + + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_CARPET, + DyeColor.ORANGE to Items.ORANGE_CARPET, + DyeColor.MAGENTA to Items.MAGENTA_CARPET, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_CARPET, + DyeColor.YELLOW to Items.YELLOW_CARPET, + DyeColor.LIME to Items.LIME_CARPET, + DyeColor.PINK to Items.PINK_CARPET, + DyeColor.GRAY to Items.GRAY_CARPET, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_CARPET, + DyeColor.CYAN to Items.CYAN_CARPET, + DyeColor.PURPLE to Items.PURPLE_CARPET, + DyeColor.BLUE to Items.BLUE_CARPET, + DyeColor.BROWN to Items.BROWN_CARPET, + DyeColor.GREEN to Items.GREEN_CARPET, + DyeColor.RED to Items.RED_CARPET, + DyeColor.BLACK to Items.BLACK_CARPET, + )) + + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_BED, + DyeColor.ORANGE to Items.ORANGE_BED, + DyeColor.MAGENTA to Items.MAGENTA_BED, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_BED, + DyeColor.YELLOW to Items.YELLOW_BED, + DyeColor.LIME to Items.LIME_BED, + DyeColor.PINK to Items.PINK_BED, + DyeColor.GRAY to Items.GRAY_BED, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_BED, + DyeColor.CYAN to Items.CYAN_BED, + DyeColor.PURPLE to Items.PURPLE_BED, + DyeColor.BLUE to Items.BLUE_BED, + DyeColor.BROWN to Items.BROWN_BED, + DyeColor.GREEN to Items.GREEN_BED, + DyeColor.RED to Items.RED_BED, + DyeColor.BLACK to Items.BLACK_BED, + ), 3) + + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_CANDLE, + DyeColor.ORANGE to Items.ORANGE_CANDLE, + DyeColor.MAGENTA to Items.MAGENTA_CANDLE, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_CANDLE, + DyeColor.YELLOW to Items.YELLOW_CANDLE, + DyeColor.LIME to Items.LIME_CANDLE, + DyeColor.PINK to Items.PINK_CANDLE, + DyeColor.GRAY to Items.GRAY_CANDLE, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_CANDLE, + DyeColor.CYAN to Items.CYAN_CANDLE, + DyeColor.PURPLE to Items.PURPLE_CANDLE, + DyeColor.BLUE to Items.BLUE_CANDLE, + DyeColor.BROWN to Items.BROWN_CANDLE, + DyeColor.GREEN to Items.GREEN_CANDLE, + DyeColor.RED to Items.RED_CANDLE, + DyeColor.BLACK to Items.BLACK_CANDLE, + )) + + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_CONCRETE, + DyeColor.ORANGE to Items.ORANGE_CONCRETE, + DyeColor.MAGENTA to Items.MAGENTA_CONCRETE, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_CONCRETE, + DyeColor.YELLOW to Items.YELLOW_CONCRETE, + DyeColor.LIME to Items.LIME_CONCRETE, + DyeColor.PINK to Items.PINK_CONCRETE, + DyeColor.GRAY to Items.GRAY_CONCRETE, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_CONCRETE, + DyeColor.CYAN to Items.CYAN_CONCRETE, + DyeColor.PURPLE to Items.PURPLE_CONCRETE, + DyeColor.BLUE to Items.BLUE_CONCRETE, + DyeColor.BROWN to Items.BROWN_CONCRETE, + DyeColor.GREEN to Items.GREEN_CONCRETE, + DyeColor.RED to Items.RED_CONCRETE, + DyeColor.BLACK to Items.BLACK_CONCRETE, + )) + + generate(consumer, mapOf( + DyeColor.WHITE to Items.WHITE_CONCRETE_POWDER, + DyeColor.ORANGE to Items.ORANGE_CONCRETE_POWDER, + DyeColor.MAGENTA to Items.MAGENTA_CONCRETE_POWDER, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_CONCRETE_POWDER, + DyeColor.YELLOW to Items.YELLOW_CONCRETE_POWDER, + DyeColor.LIME to Items.LIME_CONCRETE_POWDER, + DyeColor.PINK to Items.PINK_CONCRETE_POWDER, + DyeColor.GRAY to Items.GRAY_CONCRETE_POWDER, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_CONCRETE_POWDER, + DyeColor.CYAN to Items.CYAN_CONCRETE_POWDER, + DyeColor.PURPLE to Items.PURPLE_CONCRETE_POWDER, + DyeColor.BLUE to Items.BLUE_CONCRETE_POWDER, + DyeColor.BROWN to Items.BROWN_CONCRETE_POWDER, + DyeColor.GREEN to Items.GREEN_CONCRETE_POWDER, + DyeColor.RED to Items.RED_CONCRETE_POWDER, + DyeColor.BLACK to Items.BLACK_CONCRETE_POWDER, + )) + + generate(consumer, MRegistry.CARGO_CRATES.item, MRegistry.CARGO_CRATES.items) + generate(consumer, MRegistry.TRITANIUM_BLOCK.item, MRegistry.TRITANIUM_BLOCK.items) + generate(consumer, MRegistry.TRITANIUM_STAIRS.item, MRegistry.TRITANIUM_STAIRS.items) + generate(consumer, MRegistry.TRITANIUM_SLAB.item, MRegistry.TRITANIUM_SLAB.items) + generate(consumer, MRegistry.TRITANIUM_WALL.item, MRegistry.TRITANIUM_WALL.items) + + generate(consumer, Items.TERRACOTTA, mapOf( + DyeColor.WHITE to Items.WHITE_TERRACOTTA, + DyeColor.ORANGE to Items.ORANGE_TERRACOTTA, + DyeColor.MAGENTA to Items.MAGENTA_TERRACOTTA, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_TERRACOTTA, + DyeColor.YELLOW to Items.YELLOW_TERRACOTTA, + DyeColor.LIME to Items.LIME_TERRACOTTA, + DyeColor.PINK to Items.PINK_TERRACOTTA, + DyeColor.GRAY to Items.GRAY_TERRACOTTA, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_TERRACOTTA, + DyeColor.CYAN to Items.CYAN_TERRACOTTA, + DyeColor.PURPLE to Items.PURPLE_TERRACOTTA, + DyeColor.BLUE to Items.BLUE_TERRACOTTA, + DyeColor.BROWN to Items.BROWN_TERRACOTTA, + DyeColor.GREEN to Items.GREEN_TERRACOTTA, + DyeColor.RED to Items.RED_TERRACOTTA, + DyeColor.BLACK to Items.BLACK_TERRACOTTA, + ), cleaning = false) + + generate(consumer, Items.SHULKER_BOX, mapOf( + DyeColor.WHITE to Items.WHITE_SHULKER_BOX, + DyeColor.ORANGE to Items.ORANGE_SHULKER_BOX, + DyeColor.MAGENTA to Items.MAGENTA_SHULKER_BOX, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_SHULKER_BOX, + DyeColor.YELLOW to Items.YELLOW_SHULKER_BOX, + DyeColor.LIME to Items.LIME_SHULKER_BOX, + DyeColor.PINK to Items.PINK_SHULKER_BOX, + DyeColor.GRAY to Items.GRAY_SHULKER_BOX, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_SHULKER_BOX, + DyeColor.CYAN to Items.CYAN_SHULKER_BOX, + DyeColor.PURPLE to Items.PURPLE_SHULKER_BOX, + DyeColor.BLUE to Items.BLUE_SHULKER_BOX, + DyeColor.BROWN to Items.BROWN_SHULKER_BOX, + DyeColor.GREEN to Items.GREEN_SHULKER_BOX, + DyeColor.RED to Items.RED_SHULKER_BOX, + DyeColor.BLACK to Items.BLACK_SHULKER_BOX, + )) + + generate(consumer, Items.GLASS, mapOf( + DyeColor.WHITE to Items.WHITE_STAINED_GLASS, + DyeColor.ORANGE to Items.ORANGE_STAINED_GLASS, + DyeColor.MAGENTA to Items.MAGENTA_STAINED_GLASS, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_STAINED_GLASS, + DyeColor.YELLOW to Items.YELLOW_STAINED_GLASS, + DyeColor.LIME to Items.LIME_STAINED_GLASS, + DyeColor.PINK to Items.PINK_STAINED_GLASS, + DyeColor.GRAY to Items.GRAY_STAINED_GLASS, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_STAINED_GLASS, + DyeColor.CYAN to Items.CYAN_STAINED_GLASS, + DyeColor.PURPLE to Items.PURPLE_STAINED_GLASS, + DyeColor.BLUE to Items.BLUE_STAINED_GLASS, + DyeColor.BROWN to Items.BROWN_STAINED_GLASS, + DyeColor.GREEN to Items.GREEN_STAINED_GLASS, + DyeColor.RED to Items.RED_STAINED_GLASS, + DyeColor.BLACK to Items.BLACK_STAINED_GLASS, + )) + + generate(consumer, Items.GLASS_PANE, mapOf( + DyeColor.WHITE to Items.WHITE_STAINED_GLASS_PANE, + DyeColor.ORANGE to Items.ORANGE_STAINED_GLASS_PANE, + DyeColor.MAGENTA to Items.MAGENTA_STAINED_GLASS_PANE, + DyeColor.LIGHT_BLUE to Items.LIGHT_BLUE_STAINED_GLASS_PANE, + DyeColor.YELLOW to Items.YELLOW_STAINED_GLASS_PANE, + DyeColor.LIME to Items.LIME_STAINED_GLASS_PANE, + DyeColor.PINK to Items.PINK_STAINED_GLASS_PANE, + DyeColor.GRAY to Items.GRAY_STAINED_GLASS_PANE, + DyeColor.LIGHT_GRAY to Items.LIGHT_GRAY_STAINED_GLASS_PANE, + DyeColor.CYAN to Items.CYAN_STAINED_GLASS_PANE, + DyeColor.PURPLE to Items.PURPLE_STAINED_GLASS_PANE, + DyeColor.BLUE to Items.BLUE_STAINED_GLASS_PANE, + DyeColor.BROWN to Items.BROWN_STAINED_GLASS_PANE, + DyeColor.GREEN to Items.GREEN_STAINED_GLASS_PANE, + DyeColor.RED to Items.RED_STAINED_GLASS_PANE, + DyeColor.BLACK to Items.BLACK_STAINED_GLASS_PANE, + )) + + generate(consumer, MRegistry.INDUSTRIAL_GLASS.item, MRegistry.INDUSTRIAL_GLASS.items) + generate(consumer, MRegistry.INDUSTRIAL_GLASS_PANE.item, MRegistry.INDUSTRIAL_GLASS_PANE.items) + generate(consumer, MRegistry.DECORATIVE_CRATE.item, MRegistry.DECORATIVE_CRATE.items) + generate(consumer, MRegistry.TRITANIUM_PRESSURE_PLATE.item, MRegistry.TRITANIUM_PRESSURE_PLATE.items) + generate(consumer, MItems.TRITANIUM_DOOR[null]!!, MItems.TRITANIUM_DOOR) + generate(consumer, MItems.TRITANIUM_TRAPDOOR[null]!!, MItems.TRITANIUM_TRAPDOOR) + + generate(consumer, MRegistry.VENT.item, MRegistry.VENT.items) + generate(consumer, MRegistry.VENT_ALTERNATIVE.item, MRegistry.VENT_ALTERNATIVE.items) + generate(consumer, MItems.CARGO_CRATE_MINECARTS[null]!!, MItems.CARGO_CRATE_MINECARTS) + + generate(consumer, MRegistry.UNREFINED_FLOOR_TILES.items) + generate(consumer, MRegistry.FLOOR_TILES.items) + generate(consumer, MRegistry.FLOOR_TILES_SLAB.items) + generate(consumer, MRegistry.FLOOR_TILES_STAIRS.items) + + striped(consumer, "full", MRegistry.TRITANIUM_STRIPED_BLOCK.itemsWithColor, MRegistry.TRITANIUM_BLOCK.items) + striped(consumer, "stairs", MRegistry.TRITANIUM_STRIPED_STAIRS.itemsWithColor, MRegistry.TRITANIUM_STAIRS.items) + striped(consumer, "walls", MRegistry.TRITANIUM_STRIPED_WALL.itemsWithColor, MRegistry.TRITANIUM_WALL.items) + striped(consumer, "slabs", MRegistry.TRITANIUM_STRIPED_SLAB.itemsWithColor, MRegistry.TRITANIUM_SLAB.items) + + for (color in DyeColor.entries) { + consumer.accept(PainterArmorDyeRecipe(modLocation("painter/armor_dye_" + color.getName().lowercase()), mapOf(color to 1)).toFinished()) + } + consumer.accept(PainterArmorDyeRecipe(modLocation("painter/armor_clear_dye"), mapOf(null to 15)).toFinished()) +} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressFinishedRecipe.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressFinishedRecipe.kt deleted file mode 100644 index 948b8ac53..000000000 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressFinishedRecipe.kt +++ /dev/null @@ -1,79 +0,0 @@ -package ru.dbotthepony.mc.otm.datagen.recipes - -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.data.recipes.FinishedRecipe -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.crafting.RecipeSerializer -import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe -import ru.dbotthepony.mc.otm.recipe.PlatePressRecipeFactory -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.set - -class PlatePressFinishedRecipe(private val recipe: PlatePressRecipe) : FinishedRecipe { - override fun serializeRecipeData(it: JsonObject) { - it["input"] = recipe.input.toJson() - - it["result"] = recipe.output.toJson().also { - if (it is JsonObject && recipe.count != 1) - it["count"] = JsonPrimitive(recipe.count) - } - - it["work_time"] = JsonPrimitive(recipe.workTime) - } - - override fun getId(): ResourceLocation { - return recipe.id - } - - override fun getType(): RecipeSerializer<*> { - return PlatePressRecipeFactory - } - - override fun serializeAdvancement(): JsonObject? { - return null - } - - override fun getAdvancementId(): ResourceLocation? { - return null - } -} - -class PlatePressShallowFinishedRecipe( - private val id: ResourceLocation, - private val input: ResourceLocation, - private val output: ResourceLocation, - private val count: Int = 1, - private val workTime: Int = 200 -) : FinishedRecipe { - override fun serializeRecipeData(it: JsonObject) { - it["input"] = JsonObject().also { - it["tag"] = JsonPrimitive(input.toString()) - } - - it["result"] = JsonObject().also { - it["tag"] = JsonPrimitive(output.toString()) - - if (count != 1) - it["count"] = JsonPrimitive(count) - } - - it["work_time"] = JsonPrimitive(workTime) - } - - override fun getId(): ResourceLocation { - return id - } - - override fun getType(): RecipeSerializer<*> { - return PlatePressRecipeFactory - } - - override fun serializeAdvancement(): JsonObject? { - return null - } - - override fun getAdvancementId(): ResourceLocation? { - return null - } -} diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressRecipes.kt index 2131d5383..c8c9bfffe 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PlatePressRecipes.kt @@ -1,19 +1,29 @@ package ru.dbotthepony.mc.otm.datagen.recipes +import net.minecraft.util.valueproviders.ConstantFloat +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.Ingredient +import net.minecraftforge.common.Tags +import ru.dbotthepony.mc.otm.registry.MItemTags +import ru.dbotthepony.mc.otm.registry.MItems + fun addPlatePressRecipes(provider: MatteryRecipeProvider) { - val baselineMetals = arrayOf("iron", "silver", "bronze", "lead", "constantan", "brass") - val softMetals = arrayOf("gold", "aluminum", "aluminium", "copper", "electrum", "zinc") - val hardMetals = arrayOf("tritanium", "steel", "tungsten", "uranium") + val baselineMetals = arrayOf("iron" to 0.2f, "silver" to 0.3f, "bronze" to 0.3f, "lead" to 0.3f, "constantan" to 0.4f, "brass" to 0.3f) + val softMetals = arrayOf("gold" to 0.4f, "aluminum" to 0.3f, "aluminium" to 0.3f, "copper" to 0.2f, "electrum" to 0.4f, "zinc" to 0.3f) + val hardMetals = arrayOf("tritanium" to 0.5f, "steel" to 0.5f, "tungsten" to 0.55f, "uranium" to 0.5f) - for (thing in baselineMetals) { - provider.plate(thing) + for ((thing, exp) in baselineMetals) { + provider.plate(thing, experience = ConstantFloat.of(exp), workTicks = 160) } - for (thing in softMetals) { - provider.plate(thing, workTicks = 140) + for ((thing, exp) in softMetals) { + provider.plate(thing, workTicks = 120, experience = ConstantFloat.of(exp)) } - for (thing in hardMetals) { - provider.plate(thing, workTicks = 300) + for ((thing, exp) in hardMetals) { + provider.plate(thing, workTicks = 240, experience = ConstantFloat.of(exp)) } + + provider.plate("carbon", result = Ingredient.of(MItemTags.CARBON_PLATES), ingredient = Ingredient.of(Items.COAL), workTicks = 140, experience = ConstantFloat.of(0.3f)) + provider.plate("circuit_plating", result = Ingredient.of(MItems.CIRCUIT_PLATING), ingredient = Ingredient.of(Tags.Items.SAND), workTicks = 120, experience = ConstantFloat.of(0.2f)) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/ShapelessRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/ShapelessRecipes.kt index 72fb7f8b0..366979b0c 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/ShapelessRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/ShapelessRecipes.kt @@ -1,11 +1,16 @@ package ru.dbotthepony.mc.otm.datagen.recipes import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.data.recipes.RecipeCategory import net.minecraft.data.recipes.ShapelessRecipeBuilder +import net.minecraft.tags.TagKey import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.level.ItemLike +import net.minecraftforge.common.Tags import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.datagen.modLocation import ru.dbotthepony.mc.otm.registry.MItemTags @@ -13,26 +18,50 @@ import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.function.Consumer -fun addShapelessRecipes(consumer: Consumer) { - for (color in DyeColor.values()) { - ShapelessRecipeBuilder(MItems.CARGO_CRATE_MINECARTS[color]!!, 1) +typealias RecipeOutput = Consumer + +fun hammerRecipe(output: ItemLike, input: ItemLike, consumer: RecipeOutput) { + ShapelessRecipeBuilder(RecipeCategory.MISC, output, 1) + .requires(MItemTags.TOOLS_HAMMERS) + .requires(input) + .unlockedBy(MItemTags.TOOLS_HAMMERS) + .unlockedBy(input) + .save(consumer) +} + +fun hammerRecipe(output: ItemLike, input: TagKey, consumer: RecipeOutput) { + ShapelessRecipeBuilder(RecipeCategory.MISC, output, 1) + .requires(MItemTags.TOOLS_HAMMERS) + .requires(input) + .unlockedBy(MItemTags.TOOLS_HAMMERS) + .unlockedBy(input) + .save(consumer) +} + +fun addShapelessRecipes(consumer: RecipeOutput) { + for (color in DyeColor.entries) { + ShapelessRecipeBuilder(RecipeCategory.TRANSPORTATION, MItems.CARGO_CRATE_MINECARTS[color]!!, 1) .requires(Items.MINECART) .requires(MRegistry.CARGO_CRATES.items[color]!!) .unlockedBy(Items.MINECART) .unlockedBy(MRegistry.CARGO_CRATES.items[color]!!) .save(consumer) - ShapelessRecipeBuilder(MItems.CARGO_CRATE_MINECARTS[color]!!, 1) + ShapelessRecipeBuilder(RecipeCategory.TRANSPORTATION, MItems.CARGO_CRATE_MINECARTS[color]!!, 1) .requires(Ingredient.of(MItems.CARGO_CRATE_MINECARTS.entries.stream().filter { it.key != color }.map { ItemStack(it.value) })) .requires(color.tag) .unlockedBy(MItems.CARGO_CRATE_MINECARTS.entries.stream().filter { it.key != color }.map { it.value }) .save(consumer, modLocation(MItems.CARGO_CRATE_MINECARTS[color]!!.registryName!!.path + "_alt")) } - ShapelessRecipeBuilder(MItems.CARGO_CRATE_MINECARTS[null]!!, 1) + ShapelessRecipeBuilder(RecipeCategory.TRANSPORTATION, MItems.CARGO_CRATE_MINECARTS[null]!!, 1) .requires(Items.MINECART) .requires(MRegistry.CARGO_CRATES.item) .unlockedBy(Items.MINECART) .unlockedBy(MRegistry.CARGO_CRATES.item) .save(consumer) + + hammerRecipe(MItems.TRITANIUM_PLATE, MItemTags.TRITANIUM_INGOTS, consumer) + hammerRecipe(MItems.IRON_PLATE, Tags.Items.INGOTS_IRON, consumer) + hammerRecipe(MItems.GOLD_PLATE, Tags.Items.INGOTS_GOLD, consumer) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt index d94a3bad2..61702d244 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt @@ -1,12 +1,13 @@ package ru.dbotthepony.mc.otm.datagen.tags +import net.minecraft.resources.ResourceLocation import net.minecraft.tags.BlockTags import net.minecraft.tags.ItemTags import net.minecraft.world.effect.MobEffects -import net.minecraft.world.item.BlockItem import net.minecraft.world.item.Items import net.minecraft.world.item.Tiers import net.minecraft.world.level.block.Blocks +import net.minecraftforge.common.Tags import ru.dbotthepony.mc.otm.registry.MBlockTags import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.registry.MItemTags @@ -20,16 +21,26 @@ fun addTags(tagsProvider: TagsProvider) { tagsProvider.dusts.add("tritanium", MItems.TRITANIUM_DUST) tagsProvider.ingots.add("tritanium", MItems.TRITANIUM_INGOT) + tagsProvider.items.Appender(ItemTags.BEACON_PAYMENT_ITEMS).add(MItems.TRITANIUM_INGOT) tagsProvider.plates.add("tritanium", MItems.TRITANIUM_PLATE) tagsProvider.plates.add("iron", MItems.IRON_PLATE) tagsProvider.plates.add("gold", MItems.GOLD_PLATE) + tagsProvider.plates.add("carbon", MItems.CARBON_MESH) + + tagsProvider.items.forge("reinforced_tritanium").add(MItems.REINFORCED_TRITANIUM_PLATE) tagsProvider.storageBlocksAsItem.add("tritanium", MItems.TRITANIUM_INGOT_BLOCK) tagsProvider.storageBlocksAsBlock.add("tritanium", MBlocks.TRITANIUM_INGOT_BLOCK) + tagsProvider.blocks.Appender(BlockTags.BEACON_BASE_BLOCKS).add(MBlocks.TRITANIUM_INGOT_BLOCK) + + tagsProvider.stoneOre("tritanium", MBlocks.TRITANIUM_ORE) + tagsProvider.deepslateOre("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE) + tagsProvider.singleDropOre( + MBlocks.TRITANIUM_ORE, + MBlocks.DEEPSLATE_TRITANIUM_ORE + ) - tagsProvider.ore("tritanium", MBlocks.TRITANIUM_ORE) - tagsProvider.ore("tritanium", MBlocks.DEEPSLATE_TRITANIUM_ORE) tagsProvider.clump("tritanium", MItems.TRITANIUM_ORE_CLUMP, MBlocks.TRITANIUM_RAW_BLOCK) tagsProvider.wires.add("copper", MItems.COPPER_WIRING) @@ -40,10 +51,21 @@ fun addTags(tagsProvider: TagsProvider) { MBlocks.STORAGE_CABLE ) - tagsProvider.items.appender(MItemTags.CRAFTING_TABLES).add(Items.CRAFTING_TABLE) - tagsProvider.blocks.appender(MBlockTags.CRAFTING_TABLES).add(Blocks.CRAFTING_TABLE) + tagsProvider.items.Appender(MItemTags.CRAFTING_TABLES).add(Items.CRAFTING_TABLE) + tagsProvider.items.Appender(MItemTags.WORKBENCHES).add(Items.CRAFTING_TABLE) + tagsProvider.items.Appender(MItemTags.WORKBENCH).add(Items.CRAFTING_TABLE) + tagsProvider.blocks.Appender(MBlockTags.CRAFTING_TABLES).add(Blocks.CRAFTING_TABLE) + tagsProvider.blocks.Appender(MBlockTags.WORKBENCHES).add(Blocks.CRAFTING_TABLE) + tagsProvider.blocks.Appender(MBlockTags.WORKBENCH).add(Blocks.CRAFTING_TABLE) - tagsProvider.items.appender(MItemTags.MINECART_CARGO_CRATES).add(MItems.CARGO_CRATE_MINECARTS.values) + tagsProvider.items.Appender(MItemTags.CRAFTING_TABLES) + .add(MItemTags.WORKBENCHES) + .add(MItemTags.WORKBENCH) + tagsProvider.blocks.Appender(MBlockTags.CRAFTING_TABLES) + .add(MBlockTags.WORKBENCHES) + .add(MBlockTags.WORKBENCH) + + tagsProvider.items.Appender(MItemTags.MINECART_CARGO_CRATES).add(MItems.CARGO_CRATE_MINECARTS.values) tagsProvider.items.forge("hardened_glass").add(MRegistry.INDUSTRIAL_GLASS.allItems.values) tagsProvider.items.forge("hardened_glass/colorless").add(MRegistry.INDUSTRIAL_GLASS.item) @@ -67,51 +89,88 @@ fun addTags(tagsProvider: TagsProvider) { MRegistry.INDUSTRIAL_GLASS.forEachBlock { s, _, block -> tagsProvider.blocks.forge("hardened_glass/$s").add(block) } MRegistry.INDUSTRIAL_GLASS_PANE.forEachBlock { s, _, block -> tagsProvider.blocks.forge("hardened_glass_panes/$s").add(block) } - tagsProvider.items.appender(MItemTags.INDUSTRIAL_GLASS).add(MRegistry.INDUSTRIAL_GLASS.allItems.values) - tagsProvider.blocks.appender(MBlockTags.INDUSTRIAL_GLASS).add(MRegistry.INDUSTRIAL_GLASS.allBlocks.values) - tagsProvider.blocks.appender(MBlockTags.CARGO_CRATES).add(MRegistry.CARGO_CRATES.allBlocks.values) + tagsProvider.items.Appender(MItemTags.INDUSTRIAL_GLASS).add(MRegistry.INDUSTRIAL_GLASS.allItems.values) + tagsProvider.blocks.Appender(MBlockTags.INDUSTRIAL_GLASS).add(MRegistry.INDUSTRIAL_GLASS.allBlocks.values) + tagsProvider.blocks.Appender(MBlockTags.CARGO_CRATES).add(MRegistry.CARGO_CRATES.allBlocks.values) - tagsProvider.items.appender(ItemTags.DOORS).add(MItems.TRITANIUM_DOOR.values) - tagsProvider.blocks.appender(BlockTags.DOORS).add(MBlocks.TRITANIUM_DOOR.values) + tagsProvider.items.Appender(ItemTags.DOORS).add(MItems.TRITANIUM_DOOR.values) + tagsProvider.blocks.Appender(BlockTags.DOORS).add(MBlocks.TRITANIUM_DOOR.values) - tagsProvider.items.appender(ItemTags.TRAPDOORS).add(MItems.TRITANIUM_TRAPDOOR.values) - tagsProvider.blocks.appender(BlockTags.TRAPDOORS).add(MBlocks.TRITANIUM_TRAPDOOR.values) + tagsProvider.items.Appender(ItemTags.TRAPDOORS).add(MItems.TRITANIUM_TRAPDOOR.values) + tagsProvider.blocks.Appender(BlockTags.TRAPDOORS).add(MBlocks.TRITANIUM_TRAPDOOR.values) - tagsProvider.blocks.appender(BlockTags.PRESSURE_PLATES).add(MRegistry.TRITANIUM_PRESSURE_PLATE.allBlocks.values) + tagsProvider.blocks.Appender(BlockTags.PRESSURE_PLATES).add(MRegistry.TRITANIUM_PRESSURE_PLATE.allBlocks.values) - tagsProvider.items.appender(MItemTags.MACHINES).add(MItems.MACHINES) - tagsProvider.blocks.appender(MBlockTags.MACHINES).add(MItems.MACHINES.stream().map { it as? BlockItem }.filter { it != null }.map { it!!.block }) + tagsProvider.items.Appender(MItemTags.MACHINES).add(MItems.MACHINES) + tagsProvider.blocks.Appender(MBlockTags.MACHINES).add(MItems.MACHINES.stream().map { it!!.block }) - tagsProvider.blocks.appender(BlockTags.STAIRS) + tagsProvider.blocks.Appender(BlockTags.ANVIL).add(MBlocks.TRITANIUM_ANVIL) + tagsProvider.items.Appender(ItemTags.ANVIL).add(MItems.TRITANIUM_ANVIL) + + tagsProvider.items.Appender(MItemTags.TRITANIUM_NUGGETS).add(MItems.TRITANIUM_NUGGET) + tagsProvider.items.Appender(MItemTags.NUGGETS).add(MItems.TRITANIUM_NUGGET) + + tagsProvider.items.Appender(Tags.Items.ARMORS_HELMETS) + .add(MItems.TRITANIUM_HELMET, MItems.SIMPLE_TRITANIUM_HELMET) + tagsProvider.items.Appender(Tags.Items.ARMORS_CHESTPLATES) + .add(MItems.TRITANIUM_CHESTPLATE, MItems.SIMPLE_TRITANIUM_CHESTPLATE, MItems.PORTABLE_GRAVITATION_STABILIZER) + tagsProvider.items.Appender(Tags.Items.ARMORS_LEGGINGS) + .add(MItems.TRITANIUM_PANTS, MItems.SIMPLE_TRITANIUM_PANTS) + tagsProvider.items.Appender(Tags.Items.ARMORS_BOOTS) + .add(MItems.TRITANIUM_BOOTS, MItems.SIMPLE_TRITANIUM_BOOTS) + + tagsProvider.items.Appender(ItemTags.FREEZE_IMMUNE_WEARABLES).add(MItems.TRITANIUM_ARMOR) + + tagsProvider.items.Appender(Tags.Items.SHEARS).add(MItems.TRITANIUM_SHEARS) + tagsProvider.items.Appender(Tags.Items.TOOLS_SHIELDS).add(MItems.TRITANIUM_SHIELD) + + tagsProvider.items.Appender(MItemTags.TOOLS_HAMMERS).add(MItems.EXPLOSIVE_HAMMER) + tagsProvider.items.forge("tools").add(MItemTags.TOOLS_HAMMERS) + + tagsProvider.items.Appender(MItemTags.UPGRADES) + .add(MItems.MachineUpgrades.Basic.LIST) + .add(MItems.MachineUpgrades.Normal.LIST) + .add(MItems.MachineUpgrades.Advanced.LIST) + .add(MItems.MachineUpgrades.Creative.LIST) + + tagsProvider.blocks.Appender(BlockTags.STAIRS) .add(MRegistry.FLOOR_TILES_STAIRS.blocks.values) .add(MRegistry.TRITANIUM_STAIRS.allBlocks.values) .add(MRegistry.TRITANIUM_STRIPED_STAIRS.flatBlocks) .add(MBlocks.TRITANIUM_STRIPED_STAIRS) - tagsProvider.blocks.appender(BlockTags.SLABS) + tagsProvider.blocks.Appender(BlockTags.SLABS) .add(MRegistry.TRITANIUM_SLAB.allBlocks.values) .add(MRegistry.TRITANIUM_STRIPED_SLAB.flatBlocks) .add(MRegistry.FLOOR_TILES_SLAB.blocks.values) .add(MBlocks.TRITANIUM_STRIPED_SLAB) - tagsProvider.blocks.appender(BlockTags.WALLS) + tagsProvider.blocks.Appender(BlockTags.WALLS) .add(MRegistry.TRITANIUM_WALL.allBlocks.values) .add(MRegistry.TRITANIUM_STRIPED_WALL.flatBlocks) .add(MBlocks.TRITANIUM_STRIPED_WALL) - tagsProvider.items.appender(ItemTags.SLABS) + tagsProvider.items.Appender(ItemTags.STAIRS) + .add(MRegistry.FLOOR_TILES_STAIRS.items.values) + .add(MRegistry.TRITANIUM_STAIRS.allItems.values) + .add(MRegistry.TRITANIUM_STRIPED_STAIRS.flatItems) + .add(MItems.TRITANIUM_STRIPED_STAIRS) + + tagsProvider.items.Appender(ItemTags.SLABS) .add(MRegistry.TRITANIUM_SLAB.allItems.values) .add(MRegistry.TRITANIUM_STRIPED_SLAB.flatItems) .add(MRegistry.FLOOR_TILES_SLAB.items.values) .add(MItems.TRITANIUM_STRIPED_SLAB) - tagsProvider.items.appender(ItemTags.WALLS) + tagsProvider.items.Appender(ItemTags.WALLS) .add(MRegistry.TRITANIUM_WALL.allItems.values) .add(MRegistry.TRITANIUM_STRIPED_WALL.flatItems) .add(MItems.TRITANIUM_STRIPED_WALL) tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_DOOR.values, Tiers.IRON) tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_TRAPDOOR.values, Tiers.IRON) + tagsProvider.requiresPickaxe(MBlocks.PAINTER, Tiers.STONE) + tagsProvider.requiresPickaxe(MBlocks.ENERGY_CABLES.values, Tiers.STONE) tagsProvider.requiresPickaxe(listOf( MBlocks.ANDROID_STATION, @@ -126,7 +185,13 @@ fun addTags(tagsProvider: TagsProvider) { MBlocks.ENERGY_COUNTER, MBlocks.CHEMICAL_GENERATOR, MBlocks.PLATE_PRESS, + MBlocks.TWIN_PLATE_PRESS, MBlocks.MATTER_RECYCLER, + MBlocks.MATTER_ENTANGLER, + + MBlocks.POWERED_FURNACE, + MBlocks.POWERED_SMOKER, + MBlocks.POWERED_BLAST_FURNACE, MBlocks.STORAGE_BUS, MBlocks.STORAGE_IMPORTER, @@ -141,8 +206,21 @@ fun addTags(tagsProvider: TagsProvider) { MBlocks.ENERGY_SERVO, MBlocks.TRITANIUM_INGOT_BLOCK, + MBlocks.METAL_JUNK, + MBlocks.METAL_MESH, + MBlocks.TRITANIUM_BARS, + + MBlocks.ENGINE, + MBlocks.HOLO_SIGN, + MBlocks.COBBLESTONE_GENERATOR, + MBlocks.ESSENCE_STORAGE, + MBlocks.MATTER_RECONSTRUCTOR, + MBlocks.FLUID_TANK, + MBlocks.ANDROID_CHARGER, ), Tiers.IRON) + tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_ANVIL, Tiers.IRON) + tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_ORE, Tiers.IRON) tagsProvider.requiresPickaxe(MBlocks.DEEPSLATE_TRITANIUM_ORE, Tiers.IRON) tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_RAW_BLOCK, Tiers.IRON) @@ -153,6 +231,8 @@ fun addTags(tagsProvider: TagsProvider) { tagsProvider.requiresPickaxe(MBlocks.TRITANIUM_STRIPED_SLAB, Tiers.IRON) tagsProvider.requiresPickaxe(MBlocks.CARBON_FIBRE_BLOCK, Tiers.IRON) + tagsProvider.requiresPickaxe(MBlocks.INFINITE_WATER_SOURCE, Tiers.STONE) + tagsProvider.requiresPickaxe(MRegistry.CARGO_CRATES.allBlocks.values, Tiers.IRON) tagsProvider.requiresPickaxe(MRegistry.VENT.allBlocks.values, Tiers.IRON) @@ -196,6 +276,12 @@ fun addTags(tagsProvider: TagsProvider) { MBlocks.BLACK_HOLE, ) + tagsProvider.guardedByPiglins.add( + MBlockTags.CARGO_CRATES, + ) + + tagsProvider.impermeable.add(MRegistry.INDUSTRIAL_GLASS.allBlocks.values) + tagsProvider.androidImmuneEffects.add( MobEffects.CONDUIT_POWER, MobEffects.HEAL, @@ -212,4 +298,9 @@ fun addTags(tagsProvider: TagsProvider) { MobEffects.DOLPHINS_GRACE, MobEffects.CONFUSION, ) + + tagsProvider.androidImmuneEffects.add( + ResourceLocation("rats", "plague"), + ResourceLocation("rats", "synesthesia"), + ) } diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt index 7df9e7f57..295439ac2 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/TagsProvider.kt @@ -1,7 +1,11 @@ package ru.dbotthepony.mc.otm.datagen.tags import it.unimi.dsi.fastutil.objects.ObjectArraySet +import net.minecraft.core.HolderLookup import net.minecraft.core.Registry +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.core.registries.Registries +import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.tags.BlockTags import net.minecraft.tags.GameEventTags @@ -10,191 +14,212 @@ import net.minecraft.world.item.Item import net.minecraft.world.item.Tier import net.minecraft.world.item.Tiers import net.minecraft.world.level.block.Block +import net.minecraftforge.common.Tags import net.minecraftforge.data.event.GatherDataEvent +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.IForgeRegistry import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.datagen.DataGen import java.util.stream.Stream import net.minecraft.data.tags.TagsProvider as MinecraftTagsProvider -interface MTagAppender { - fun add(value: T): MTagAppender - fun addSafe(value: T): Boolean - fun add(vararg values: T): MTagAppender - fun addSafe(vararg values: T): Boolean { - var any = false - for (value in values) any = addSafe(value) - return any - } - - fun add(values: Collection): MTagAppender - fun add(values: Stream): MTagAppender = add(values.toList()) - - fun addSafe(values: Collection): Boolean { - var any = false - for (value in values) any = addSafe(value) - return any - } +private fun vanillaLookup(key: ResourceKey>): (T) -> ResourceLocation { + val registry by lazy { (BuiltInRegistries.REGISTRY.get(key.location()) ?: throw NoSuchElementException("No such registry $key")) as Registry } + return { registry.getKey(it) ?: throw NoSuchElementException("Registry $key does not contain $it") } } -interface ForgeTagAppender : MTagAppender { - fun add(key: String, value: T): MTagAppender - fun addSafe(key: String, value: T): Boolean - fun add(key: String, vararg values: T): MTagAppender - fun addSafe(key: String, vararg values: T): Boolean { - var any = false - for (value in values) any = addSafe(key, value) - return any - } +class TagsProvider(private val event: GatherDataEvent) { + inner class Delegate private constructor(key: ResourceKey>, val lookup: (T) -> ResourceLocation) : MinecraftTagsProvider(event.generator.packOutput, key, event.lookupProvider, DataGen.MOD_ID, event.existingFileHelper) { + constructor(registry: IForgeRegistry) : this(registry.registryKey, { registry.getKey(it) ?: throw NoSuchElementException("Registry $registry does not contain $it") }) + constructor(key: ResourceKey>) : this(key, vanillaLookup(key)) - fun add(key: String, values: Collection): MTagAppender - - fun addSafe(key: String, values: Collection): Boolean { - var any = false - for (value in values) any = addSafe(key, value) - return any - } -} - -class TagsProvider( - private val event: GatherDataEvent -) { - inner class Delegate(registry: Registry) : MinecraftTagsProvider(event.generator, registry, DataGen.MOD_ID, event.existingFileHelper) { init { - event.generator.addProvider(true, this) + if (isRegistered) + event.generator.addProvider(event.includeServer(), this) + else + delegates.add(this) } - private val tags = HashMap, ObjectArraySet>() + private val tags = HashMap, ObjectArraySet>() + private val rigidTags = HashMap, ObjectArraySet>>() + private val tagInTag = HashMap, ObjectArraySet>>() - override fun addTags() { - if (tags.isEmpty()) { - return + inner class Appender(val tag: TagKey) { + constructor(tag: ResourceLocation) : this(TagKey.create(registryKey, tag)) + + init { + require(tag.registry == registryKey) { "Trying to create appender for $tag inside registry $registryKey" } } + private val locations by lazy { tags.computeIfAbsent(tag) { ObjectArraySet() } } + private val rigidLocations by lazy { rigidTags.computeIfAbsent(tag) { ObjectArraySet() } } + private val tagsInTags by lazy { tagInTag.computeIfAbsent(tag) { ObjectArraySet() } } + + fun add(value: ResourceLocation): Appender { + check(locations.add(value)) { "Tag ${tag.location} of registry ${registryKey.location()} already contains $value" } + return this + } + + fun add(value: ResourceKey): Appender { + require(value.registry() == registryKey.location()) { "Invalid registry in provided ResourceKey: ${value.registry()} (this tag appender is for ${registryKey.location()})" } + // check(rigidLocations.add(value)) { "Tag ${tag.location} of registry ${registryKey.location()} already contains $value" } + // return this + return add(value.location()) + } + + fun add(value: TagKey): Appender { + require(value.registry() == registryKey) { "Invalid registry in provided ResourceKey: ${value.registry().location()} (this tag appender is for ${registryKey.location()})" } + check(tagsInTags.add(value)) { "Tag ${tag.location} of registry ${registryKey.location()} already contains $value" } + return this + } + + fun add(value: T): Appender { + return add(ResourceKey.create(registryKey, lookup.invoke(value))) + } + + fun add(values: Collection): Appender { + for (value in values) add(value) + return this + } + + fun add(vararg values: T): Appender { + for (value in values) add(value) + return this + } + + fun add(vararg values: ResourceKey): Appender { + for (value in values) add(value) + return this + } + + fun add(vararg values: TagKey): Appender { + for (value in values) add(value) + return this + } + + fun add(vararg values: ResourceLocation): Appender { + for (value in values) add(value) + return this + } + + fun add(values: Stream): Appender { + values.forEach { add(it) } + return this + } + + fun leaf(name: String) = Appender(TagKey.create(tag.registry, ResourceLocation(tag.location.namespace, tag.location.path + "/$name"))) + + fun add(leaf: String, value: T) = also { leaf(leaf).add(value) } + fun add(leaf: String, value: TagKey) = also { leaf(leaf).add(value) } + fun add(leaf: String, vararg value: T) = also { leaf(leaf).add(*value) } + fun add(leaf: String, vararg value: TagKey) = also { leaf(leaf).add(*value) } + fun add(leaf: String, value: Stream) = also { leaf(leaf).add(value) } + fun add(leaf: String, value: ResourceKey) = also { leaf(leaf).add(value) } + fun add(leaf: String, vararg value: ResourceKey) = also { leaf(leaf).add(*value) } + fun add(leaf: String, value: ResourceLocation) = also { leaf(leaf).add(value) } + fun add(leaf: String, vararg value: ResourceLocation) = also { leaf(leaf).add(*value) } + } + + fun forge(path: String) = Appender(ResourceLocation("forge", path)) + fun minecraft(path: String) = Appender(ResourceLocation("minecraft", path)) + + override fun addTags(provider: HolderLookup.Provider) { for ((tag, values) in tags) { - tag(tag).also { - for (value in values) { - it.add(value) - } + val appender = tag(tag) + + for (value in values) { + appender.addOptional(value) } } - } - fun getSet(tag: TagKey): MutableSet { - return tags.computeIfAbsent(tag) { ObjectArraySet() } - } + for ((tag, values) in rigidTags) { + val appender = tag(tag) - fun getSet(location: ResourceLocation) = getSet(TagKey.create(registry.key(), location)) - - fun appender(tag: TagKey, message: ((T) -> Any) = { "$it is already in $tag" }): MTagAppender { - val list = getSet(tag) - - return object : MTagAppender { - override fun add(value: T): MTagAppender { - if (!list.add(value)) { - throw IllegalStateException(message.invoke(value).toString()) - } - - return this - } - - override fun add(values: Collection): MTagAppender { - for (value in values) add(value) - return this - } - - override fun addSafe(value: T): Boolean { - return list.add(value) - } - - override fun add(vararg values: T): MTagAppender { - values.forEach(this::add) - return this + for (value in values) { + appender.add(value) } } - } - fun forge(path: String): ForgeTagAppender { - val parent by lazy { appender(ResourceLocation("forge", path)) } + for ((tag, values) in tagInTag) { + val appender = tag(tag) - return object : ForgeTagAppender { - override fun add(key: String, value: T): MTagAppender { - val tag = TagKey.create(registry.key(), ResourceLocation("forge", "$path/$key")) - - if (!getSet(tag).add(value)) { - throw IllegalStateException("$value is already in $tag") - } - - return this - } - - override fun addSafe(key: String, value: T): Boolean { - val tag = TagKey.create(registry.key(), ResourceLocation("forge", "$path/$key")) - return getSet(tag).add(value) - } - - override fun add(key: String, vararg values: T): MTagAppender { - for (value in values) add(key, value) - return this - } - - override fun add(key: String, values: Collection): MTagAppender { - for (value in values) add(key, value) - return this - } - - override fun add(value: T): MTagAppender { - return parent.add(value) - } - - override fun addSafe(value: T): Boolean { - return parent.addSafe(value) - } - - override fun add(vararg values: T): MTagAppender { - return parent.add(*values) - } - - override fun add(values: Collection): MTagAppender { - return parent.add(values) + for (value in values) { + appender.addTag(value) } } } - - fun appender(location: ResourceLocation): MTagAppender { - return appender(TagKey.create(registry.key(), location)) - } - - fun appender(location: ResourceLocation, message: (T) -> Any): MTagAppender { - return appender(TagKey.create(registry.key(), location), message) - } } - val blocks = Delegate(Registry.BLOCK) - val items = Delegate(Registry.ITEM) - val mobEffects = Delegate(Registry.MOB_EFFECT) + private val delegates = ArrayList>() + private var isRegistered = false - val androidImmuneEffects = mobEffects.appender(MatteryPlayerCapability.ANDROID_IMMUNE_EFFECTS) + fun register() { + if (!isRegistered) { + isRegistered = true - val requiresShovel = blocks.appender(BlockTags.MINEABLE_WITH_SHOVEL) - val requiresAxe = blocks.appender(BlockTags.MINEABLE_WITH_AXE) - val requiresHoe = blocks.appender(BlockTags.MINEABLE_WITH_HOE) - val requiresPickaxe = blocks.appender(BlockTags.MINEABLE_WITH_PICKAXE) + for (value in delegates) { + event.generator.addProvider(event.includeServer(), value) + } - val requiresStoneTool = blocks.appender(BlockTags.NEEDS_STONE_TOOL) - val requiresIronTool = blocks.appender(BlockTags.NEEDS_IRON_TOOL) - val requiresDiamondTool = blocks.appender(BlockTags.NEEDS_DIAMOND_TOOL) + delegates.clear() + } + } - val witherImmune = blocks.appender(BlockTags.WITHER_IMMUNE) - val dragonImmune = blocks.appender(BlockTags.DRAGON_IMMUNE) + val blocks = Delegate(ForgeRegistries.BLOCKS) + val items = Delegate(ForgeRegistries.ITEMS) + val mobEffects = Delegate(ForgeRegistries.MOB_EFFECTS) + + val androidImmuneEffects = mobEffects.Appender(MatteryPlayerCapability.ANDROID_IMMUNE_EFFECTS) + + val requiresShovel = blocks.Appender(BlockTags.MINEABLE_WITH_SHOVEL) + val requiresAxe = blocks.Appender(BlockTags.MINEABLE_WITH_AXE) + val requiresHoe = blocks.Appender(BlockTags.MINEABLE_WITH_HOE) + val requiresPickaxe = blocks.Appender(BlockTags.MINEABLE_WITH_PICKAXE) + + val requiresStoneTool = blocks.Appender(BlockTags.NEEDS_STONE_TOOL) + val requiresIronTool = blocks.Appender(BlockTags.NEEDS_IRON_TOOL) + val requiresDiamondTool = blocks.Appender(BlockTags.NEEDS_DIAMOND_TOOL) + + val witherImmune = blocks.Appender(BlockTags.WITHER_IMMUNE) + val dragonImmune = blocks.Appender(BlockTags.DRAGON_IMMUNE) + val guardedByPiglins = blocks.Appender(BlockTags.GUARDED_BY_PIGLINS) + val impermeable = blocks.Appender(BlockTags.IMPERMEABLE) + + fun stoneOre(key: String, block: Block): TagsProvider { + ore(key, block) + + itemGroundOresStone.add(block.asItem()) + blockGroundOresStone.add(block) + + return this + } + + fun deepslateOre(key: String, block: Block): TagsProvider { + ore(key, block) + + itemGroundOresDeepslate.add(block.asItem()) + blockGroundOresDeepslate.add(block) + + return this + } + + fun singleDropOre(vararg blocks: Block): TagsProvider { + for (block in blocks) { + itemOreRatesSingular.add(block.asItem()) + blockOreRatesSingular.add(block) + } + + return this + } fun ore(key: String, block: Block): TagsProvider { val forgeKey = ResourceLocation("forge", "ores/$key") - val b = TagKey.create(Registry.BLOCK_REGISTRY, forgeKey) - val i = TagKey.create(Registry.ITEM_REGISTRY, forgeKey) + val b = TagKey.create(Registries.BLOCK, forgeKey) + val i = TagKey.create(Registries.ITEM, forgeKey) - items.getSet(i).add(block.asItem()) + items.Appender(i).add(block.asItem()) itemOres.add(block.asItem()) - blocks.getSet(b).add(block) + blocks.Appender(b).add(block) blockOres.add(block) return this @@ -220,18 +245,32 @@ class TagsProvider( } val circuits = items.forge("circuits") - val dusts = items.forge("dusts") - val ingots = items.forge("ingots") - val itemOres = items.forge("ores") - val blockOres = blocks.forge("ores") + val dusts = items.Appender(Tags.Items.DUSTS) + val ingots = items.Appender(Tags.Items.INGOTS) + val itemOres = items.Appender(Tags.Items.ORES) + val blockOres = blocks.Appender(Tags.Blocks.ORES) val plates = items.forge("plates") - val storageBlocksAsItem = items.forge("storage_blocks") - val storageBlocksAsBlock = blocks.forge("storage_blocks") - val rawMaterials = items.forge("raw_materials") + val storageBlocksAsItem = items.Appender(Tags.Items.STORAGE_BLOCKS) + val storageBlocksAsBlock = blocks.Appender(Tags.Blocks.STORAGE_BLOCKS) + val rawMaterials = items.Appender(Tags.Items.RAW_MATERIALS) val wires = items.forge("wires") - val gameEvents = Delegate(Registry.GAME_EVENT) - val vibrations = gameEvents.appender(GameEventTags.VIBRATIONS) + val blockGroundOresStone = blocks.Appender(Tags.Blocks.ORES_IN_GROUND_STONE) + val blockGroundOresDeepslate = blocks.Appender(Tags.Blocks.ORES_IN_GROUND_DEEPSLATE) +// val blockGroundOresNetherrack = blocks.forge("ores_in_ground/netherrack") + val itemGroundOresStone = items.Appender(Tags.Items.ORES_IN_GROUND_STONE) + val itemGroundOresDeepslate = items.Appender(Tags.Items.ORES_IN_GROUND_DEEPSLATE) +// val itemGroundOresNetherrack = items.forge("ores_in_ground/netherrack") + +// val blockOreRatesSparse = blocks.forge("ore_rates/sparse") + val blockOreRatesSingular = blocks.Appender(Tags.Blocks.ORE_RATES_SINGULAR) +// val blockOreRatesDense = blocks.forge("ore_rates/dense") +// val itemOreRatesSparse = items.forge("ore_rates/sparse") + val itemOreRatesSingular = items.Appender(Tags.Items.ORE_RATES_SINGULAR) +// val itemOreRatesDense = items.forge("ore_rates/dense") + + val gameEvents = Delegate(Registries.GAME_EVENT) + val vibrations = gameEvents.Appender(GameEventTags.VIBRATIONS) fun requiresPickaxe(block: Block, tier: Tier? = null): TagsProvider { requiresPickaxe.add(block) diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 61fb44f29..424e15699 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -4,8 +4,7 @@ import kotlin.KotlinVersion; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.eventbus.api.EventPriority; @@ -15,12 +14,14 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.IdMappingEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ru.dbotthepony.mc.otm.android.AndroidResearchManager; import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature; -import ru.dbotthepony.mc.otm.block.entity.SynchronizedBlockEntity; +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity; import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue; +import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity; import ru.dbotthepony.mc.otm.capability.MatteryCapability; import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; import ru.dbotthepony.mc.otm.capability.drive.DrivePool; @@ -33,23 +34,35 @@ import ru.dbotthepony.mc.otm.client.model.ExosuitModel; import ru.dbotthepony.mc.otm.client.model.GravitationStabilizerModel; import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel; import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer; -import ru.dbotthepony.mc.otm.client.render.WidgetAtlasHolder; -import ru.dbotthepony.mc.otm.compat.mekanism.QIOKt; -import ru.dbotthepony.mc.otm.compat.mekanism.TooltipsKt; -import ru.dbotthepony.mc.otm.core.Decimal; -import ru.dbotthepony.mc.otm.item.ItemTritaniumArmor; +import ru.dbotthepony.mc.otm.client.render.blockentity.BatteryBankRenderer; +import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer; +import ru.dbotthepony.mc.otm.compat.adastra.AdAstraCompatKt; +import ru.dbotthepony.mc.otm.compat.curios.CuriosCompatKt; +import ru.dbotthepony.mc.otm.config.AndroidConfig; +import ru.dbotthepony.mc.otm.config.CablesConfig; +import ru.dbotthepony.mc.otm.config.ClientConfig; +import ru.dbotthepony.mc.otm.config.ExopackConfig; +import ru.dbotthepony.mc.otm.config.ItemsConfig; +import ru.dbotthepony.mc.otm.config.MachinesConfig; +import ru.dbotthepony.mc.otm.config.ServerCompatConfig; +import ru.dbotthepony.mc.otm.config.ServerConfig; +import ru.dbotthepony.mc.otm.config.ToolsConfig; +import ru.dbotthepony.mc.otm.item.ChestUpgraderItem; +import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem; +import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem; import ru.dbotthepony.mc.otm.item.QuantumBatteryItem; import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem; import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem; import ru.dbotthepony.mc.otm.matter.MatterManager; import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.registry.*; -import ru.dbotthepony.mc.otm.storage.*; -import ru.dbotthepony.mc.otm.worldgen.OreGen; +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger; +import top.theillusivec4.curios.api.CuriosApi; import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; import javax.annotation.ParametersAreNonnullByDefault; +import java.util.concurrent.atomic.AtomicInteger; // The value here should match an entry in the META-INF/mods.toml file @Mod(OverdriveThatMatters.MOD_ID) @@ -59,35 +72,26 @@ public final class OverdriveThatMatters { // Directly reference a log4j logger. public static final String MOD_ID = "overdrive_that_matters"; private static final Logger LOGGER = LogManager.getLogger(); + public static final AtomicInteger INGREDIENT_CACHE_INVALIDATION_COUNTER; - public static OverdriveThatMatters INSTANCE; - private StorageStackType ITEM_STORAGE; - - public StorageStackType ITEM_STORAGE() { - return ITEM_STORAGE; + static { + try { + var f = Ingredient.class.getDeclaredField("INVALIDATION_COUNTER"); + f.setAccessible(true); + INGREDIENT_CACHE_INVALIDATION_COUNTER = (AtomicInteger) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } } + public static OverdriveThatMatters INSTANCE; public static ResourceLocation loc(String path) { return new ResourceLocation(MOD_ID, path); } - public final CreativeModeTab CREATIVE_TAB = new CreativeModeTab("otm") { - @Override - public ItemStack makeIcon() { - return new ItemStack(MItems.INSTANCE.getBATTERY_CREATIVE(), 1); - } - }; - - public final CreativeModeTab CREATIVE_TAB_DECORATIVE = new CreativeModeTab("otm_decorative") { - @Override - public ItemStack makeIcon() { - return new ItemStack(MRegistry.INSTANCE.getVENT().getItem(), 1); - } - }; - private static void checkIfKotlinIsInstalled() { - if (!KotlinVersion.CURRENT.isAtLeast(1, 6, 10)) { - throw new UnsupportedClassVersionError("Installed kotlin version is " + KotlinVersion.CURRENT + ", when at least 1.6.10 is required."); + if (!KotlinVersion.CURRENT.isAtLeast(1, 9, 0)) { + throw new UnsupportedClassVersionError("Installed kotlin version is " + KotlinVersion.CURRENT + ", when at least 1.9.0 is required."); } } @@ -131,35 +135,43 @@ public final class OverdriveThatMatters { modBus.addListener(EventPriority.NORMAL, AndroidAbilityKeyMapping.INSTANCE::register); modBus.addListener(EventPriority.NORMAL, TritaniumArmorModel::register); modBus.addListener(EventPriority.NORMAL, GravitationStabilizerModel::register); - modBus.addListener(EventPriority.NORMAL, WidgetAtlasHolder::register); + modBus.addListener(EventPriority.NORMAL, MCreativeTabs.INSTANCE::register); + + modBus.addListener(EventPriority.NORMAL, BatteryBankRenderer.Companion::onRegisterAdditionalModels); + modBus.addListener(EventPriority.NORMAL, MatterBatteryBankRenderer.Companion::onRegisterAdditionalModels); }); ClientConfig.INSTANCE.register(); ServerConfig.INSTANCE.register(); + CablesConfig.INSTANCE.register(); + ServerCompatConfig.INSTANCE.register(); + AndroidConfig.INSTANCE.register(); + ExopackConfig.INSTANCE.register(); + ItemsConfig.INSTANCE.register(); + MachinesConfig.INSTANCE.register(); + ToolsConfig.INSTANCE.register(); } private void setup(final FMLCommonSetupEvent event) { - EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::onServerPostTick); - EVENT_BUS.addListener(EventPriority.HIGHEST, DrivePool.INSTANCE::serverStopEvent); - EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::serverStartEvent); EVENT_BUS.addListener(EventPriority.NORMAL, DrivePool.INSTANCE::onWorldSave); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopped); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopping); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStarting); - EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onWorldTick); + EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onLevelTick); EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::isMobEffectApplicable); EVENT_BUS.addListener(EventPriority.LOW, MatteryPlayerCapability.Companion::onHurtEvent); + EVENT_BUS.addListener(EventPriority.HIGH, MatteryPlayerCapability.Companion::onAttackEvent); EVENT_BUS.addGenericListener(Entity.class, EventPriority.NORMAL, MatteryPlayerCapability.Companion::onAttachCapabilityEvent); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerChangeDimensionEvent); EVENT_BUS.addListener(EventPriority.LOWEST, MatteryPlayerCapability.Companion::onPlayerDeath); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerCloneEvent); - EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::canEntitySpawn); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStartTracking); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onStopTracking); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::addCommands); EVENT_BUS.addListener(EventPriority.NORMAL, ExplosionQueue.Companion::onWorldTick); EVENT_BUS.addListener(EventPriority.NORMAL, AbstractWeaponItem.Companion::tick); @@ -174,30 +186,36 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::onDataPackSync); EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::addCommands); - EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping); - EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onLevelUnload); - EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onWatch); - EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onForget); - EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerDisconnected); - EVENT_BUS.addListener(EventPriority.LOWEST, SynchronizedBlockEntity.Companion::postLevelTick); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onServerStopping); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onLevelUnload); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onWatch); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onForget); + EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected); + + EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill); EVENT_BUS.addListener(EventPriority.NORMAL, EnderTeleporterFeature.Companion::onEntityDeath); - EVENT_BUS.addListener(EventPriority.HIGH, ItemTritaniumArmor.Companion::onHurt); + EVENT_BUS.addListener(EventPriority.HIGH, TritaniumArmorItem.Companion::onHurt); + + EVENT_BUS.addListener(EventPriority.NORMAL, ExplosiveHammerItem.Companion::onLeftClickBlock); + + EVENT_BUS.addListener(EventPriority.NORMAL, ChestUpgraderItem.Companion::onEntityInteract); + + EVENT_BUS.addListener(EventPriority.NORMAL, DevChestBlockEntity.Companion::mappingsChanged); MatteryPlayerNetworkChannel.INSTANCE.register(); MenuNetworkChannel.INSTANCE.register(); WeaponNetworkChannel.INSTANCE.register(); - RegistryNetworkChannel.INSTANCE.register(); - WorldNetworkChannel.INSTANCE.register(); GenericNetworkChannel.INSTANCE.register(); - ITEM_STORAGE = StorageRegistry.register(ItemStackWrapper.class, ItemStackWrapper.EMPTY, new Decimal("3.125")); - - if (ModList.get().isLoaded("mekanism")) { - EVENT_BUS.addGenericListener(BlockEntity.class, EventPriority.NORMAL, QIOKt::attachCapabilities); + if (ModList.get().isLoaded(CuriosApi.MODID)) { + EVENT_BUS.addListener(EventPriority.NORMAL, CuriosCompatKt::onCuriosSlotModifiersUpdated); } - OreGen.INSTANCE.register(); + if (AdAstraCompatKt.isAdAstraLoaded()) { + EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onDamageEvent); + EVENT_BUS.addListener(EventPriority.NORMAL, AdAstraCompatKt::onMatteryTick); + } } private void setupClient(final FMLClientSetupEvent event) { @@ -223,13 +241,10 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.LOWEST, ClientTickHandlerKt::onClientTick); EVENT_BUS.addListener(EventPriority.HIGHEST, ClientTickHandlerKt::onClientConnected); EVENT_BUS.addListener(EventPriority.HIGHEST, ClientTickHandlerKt::onClientDisconnected); + EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::tooltipEvent); EVENT_BUS.addListener(EventPriority.NORMAL, QuantumBatteryItem.Companion::clientDisconnect); - if (ModList.get().isLoaded("mekanism")) { - EVENT_BUS.addListener(EventPriority.NORMAL, TooltipsKt::tooltipEvent); - } - EVENT_BUS.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::onRenderGuiEvent); EVENT_BUS.addListener(EventPriority.NORMAL, AndroidMenuKeyMapping.INSTANCE::onMouseClick); diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index fbea8708e..90c7f3aa5 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -1,14 +1,18 @@ package ru.dbotthepony.mc.otm.capability; -import mekanism.api.energy.IStrictEnergyHandler; -import net.minecraftforge.common.capabilities.*; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityToken; +import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import org.jetbrains.annotations.NotNull; +import ru.dbotthepony.mc.otm.block.entity.cable.EnergyCableBlockEntity; import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive; -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler; +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage; +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage; import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider; import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage; -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode; -import ru.dbotthepony.mc.otm.graph.storage.IStorageGraphNode; +import ru.dbotthepony.mc.otm.graph.matter.MatterNode; +import ru.dbotthepony.mc.otm.graph.storage.StorageNode; import top.theillusivec4.curios.api.type.capability.ICurio; import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; @@ -25,11 +29,11 @@ public class MatteryCapability { @Nonnull @NotNull - public static final Capability MATTER = CapabilityManager.get(new CapabilityToken<>() {}); + public static final Capability MATTER = CapabilityManager.get(new CapabilityToken<>() {}); @Nonnull @NotNull - public static final Capability MATTER_NODE = CapabilityManager.get(new CapabilityToken<>() {}); + public static final Capability MATTER_NODE = CapabilityManager.get(new CapabilityToken<>() {}); @Nonnull @NotNull @@ -41,15 +45,15 @@ public class MatteryCapability { @Nonnull @NotNull - public static final Capability DRIVE = CapabilityManager.get(new CapabilityToken<>() {}); + public static final Capability> DRIVE = CapabilityManager.get(new CapabilityToken<>() {}); @Nonnull @NotNull - public static final Capability STORAGE_NODE = CapabilityManager.get(new CapabilityToken<>() {}); + public static final Capability STORAGE_NODE = CapabilityManager.get(new CapabilityToken<>() {}); @Nonnull @NotNull - public static final Capability MEKANISM_ENERGY = CapabilityManager.get(new CapabilityToken<>() {}); + public static final Capability ENERGY_CABLE_NODE = CapabilityManager.get(new CapabilityToken<>() {}); @Nonnull @NotNull @@ -59,14 +63,19 @@ public class MatteryCapability { @NotNull public static final Capability CURIOS_ITEM = CapabilityManager.get(new CapabilityToken<>() {}); + @Nonnull + @NotNull + public static final Capability UPGRADE = CapabilityManager.get(new CapabilityToken<>() {}); + public static void register(RegisterCapabilitiesEvent event) { event.register(IMatteryEnergyStorage.class); event.register(MatteryPlayerCapability.class); - event.register(IMatterHandler.class); - event.register(IMatterGraphNode.class); + event.register(IMatterStorage.class); + event.register(MatterNode.class); event.register(IPatternStorage.class); event.register(IReplicationTaskProvider.class); event.register(IMatteryDrive.class); - event.register(IStorageGraphNode.class); + event.register(StorageNode.class); + event.register(IMatteryUpgrade.class); } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java b/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java index 12c4f44e7..3fef0651e 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java +++ b/src/main/java/ru/dbotthepony/mc/otm/client/model/ExosuitModel.java @@ -1,7 +1,9 @@ package ru.dbotthepony.mc.otm.client.model; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.client.Minecraft; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.PlayerModel; import net.minecraft.client.model.geom.PartPose; @@ -19,8 +21,11 @@ import net.minecraft.client.renderer.entity.layers.RenderLayer; import net.minecraft.client.renderer.entity.player.PlayerRenderer; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.event.RenderPlayerEvent; +import org.joml.Vector3f; +import org.joml.Vector4f; import ru.dbotthepony.mc.otm.OverdriveThatMatters; import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.network.SmokeParticlesPacket; import javax.annotation.Nonnull; import java.util.Set; @@ -32,6 +37,7 @@ public final class ExosuitModel { public static final HumanoidModel modelGlow; public static final ResourceLocation texture = new ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/exosuit.png"); + public static final ResourceLocation textureColor = new ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/models/armor/exosuit_color.png"); static { MeshDefinition meshdefinition = new MeshDefinition(); @@ -116,13 +122,18 @@ public final class ExosuitModel { float p_117357_, float p_117358_ ) { + if (player.isInvisible()) + return; + var cap = player.getCapability(MatteryCapability.MATTERY_PLAYER); if (!cap.isPresent()) { return; } - if (cap.resolve().get().getHasExoPack() && cap.resolve().get().getDisplayExoPack()) { + var mattery = cap.resolve().get(); + + if (mattery.getHasExopack() && mattery.isExopackVisible()) { var model = getParentModel(); model.copyPropertiesTo(modelNormal); model.copyPropertiesTo(modelGlow); @@ -138,14 +149,40 @@ public final class ExosuitModel { 1f, 1f, 1f, 1f ); - modelGlow.renderToBuffer( - poseStack, - bufferSource.getBuffer(RenderType.entityTranslucentEmissive(texture)), - packedLight, - overlayCoords, - // rgba - 1f, 1f, 1f, 1f - ); + var color = cap.resolve().get().getExopackColor(); + + if (color != null) { + modelNormal.renderToBuffer( + poseStack, + bufferSource.getBuffer(RenderType.entityCutoutNoCull(textureColor)), + packedLight, + overlayCoords, + // rgba + color.getRed(), color.getGreen(), color.getBlue(), 1f + ); + } + + if (mattery.getExopackGlows()) { + modelGlow.renderToBuffer( + poseStack, + bufferSource.getBuffer(RenderType.entityTranslucentEmissive(texture)), + packedLight, + overlayCoords, + // rgba + 1f, 1f, 1f, 1f + ); + } else { + modelGlow.renderToBuffer( + poseStack, + bufferSource.getBuffer(RenderType.entityCutoutNoCull(texture)), + packedLight, + overlayCoords, + // rgba + 1f, 1f, 1f, 1f + ); + } + + mattery.makeSmokeParticles(poseStack, model); } } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/model/TritaniumArmorModel.java b/src/main/java/ru/dbotthepony/mc/otm/client/model/TritaniumArmorModel.java index af48f51ed..028b769e0 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/client/model/TritaniumArmorModel.java +++ b/src/main/java/ru/dbotthepony/mc/otm/client/model/TritaniumArmorModel.java @@ -69,9 +69,6 @@ public class TritaniumArmorModel { PartDefinition chestplateslope_r1 = body.addOrReplaceChild("chestplateslope_r1", CubeListBuilder.create().texOffs(44, 41).addBox(-4.0F, 0.5F, 0.4F, 8.0F, 4.0F, 2.0F, new CubeDeformation(0.39F)), PartPose.offsetAndRotation(0.0F, 6.4F, -6.2F, 1.2217F, 0.0F, 0.0F)); - PartDefinition thruster_r1 = body.addOrReplaceChild("thruster_r1", CubeListBuilder.create().texOffs(24, 0).addBox(-5.0F, 1.0F, 3.6F, 2.0F, 5.0F, 3.0F, new CubeDeformation(0.4F)) - .texOffs(24, 0).addBox(3.0F, 1.0F, 3.6F, 2.0F, 5.0F, 3.0F, new CubeDeformation(0.4F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.3927F, 0.0F, 0.0F)); - PartDefinition right_arm = partdefinition.addOrReplaceChild("right_arm", CubeListBuilder.create().texOffs(24, 32).addBox(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.3F)) .texOffs(42, 56).addBox(-4.0F, -3.0F, -2.0F, 4.0F, 4.0F, 4.0F, new CubeDeformation(0.4F)), PartPose.offset(-5.0F, 2.0F, 0.0F)); diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/screen/panels/FlexGridPanel.java b/src/main/java/ru/dbotthepony/mc/otm/client/screen/panels/FlexGridPanel.java deleted file mode 100644 index a2a960fe3..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/client/screen/panels/FlexGridPanel.java +++ /dev/null @@ -1,191 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels; - -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class FlexGridPanel extends EditablePanel { - public enum FlexAlign { - TOP_LEFT, - TOP_CENTER, - TOP_RIGHT, - - MIDDLE_LEFT, - MIDDLE_CENTER, - MIDDLE_RIGHT, - - BOTTOM_LEFT, - BOTTOM_CENTER, - BOTTOM_RIGHT - } - - protected FlexAlign align = FlexAlign.MIDDLE_CENTER; - public int panels_per_row = 1; - - public FlexGridPanel(@Nonnull S screen, @Nullable EditablePanel parent, float x, float y, float width, float height) { - super(screen, parent, x, y, width, height); - } - - public FlexGridPanel(@Nonnull S screen, @Nullable EditablePanel parent, float x, float y) { - super(screen, parent, x, y); - } - - public FlexGridPanel(@Nonnull S screen, @Nullable EditablePanel parent) { - super(screen, parent); - } - - public FlexAlign getAlign() { - return align; - } - - public FlexGridPanel setAlign(FlexAlign align) { - this.align = align; - return this; - } - - @Override - public void performLayout() { - if (align == FlexAlign.MIDDLE_CENTER) { - // список потомков - var children = getUndockedVisibleChildren(); - - if (children.size() == 0) { - return; - } - - // хранит общую ширину всех потомков в ряд - // а так же ограничитель ширины ряда после - // определения количества рядов - float desired_width = 0; - - // минимально допустимая ширина одного ряда - float min_width = getWidth(); - - // "финальная" ширина этой панели - float this_width = getWidth() - getDockPadding().left() - getDockPadding().right(); - - if (this_width <= 0) { - return; - } - - for (var child : children) { - min_width = Math.min(min_width, child.getWidth() + child.getDockMargin().left() + child.getDockMargin().right()); - desired_width += child.getWidth() + child.getDockMargin().left() + child.getDockMargin().right(); - } - - int rows = 1; - - // если общая ширина больше чем ширина панели, делим пополам пока не найдем нужную ширину и количество рядов - while (desired_width > this_width && desired_width > min_width) { - desired_width /= 2; - rows++; - } - - if (desired_width < min_width) { - desired_width = min_width; - } - - int index; - - // определение высоты всех рядов вместе - float total_height = 0; - - // утютю никаких goto - // зато код чище некуда! - while (desired_width <= this_width) { - index = 0; - total_height = 0; - - panels_per_row = 0; - boolean calculate_row_width = true; - - for (int row = 0; row < rows; row++) { - float max_height = 0; - float total_width = 0; - - for (int i = index; i < children.size(); i++) { - var child = children.get(i); - var gain_width = child.getWidth() + child.getDockMargin().left() + child.getDockMargin().right(); - - index = i; - - if (gain_width + total_width > desired_width) { - if (calculate_row_width) { - panels_per_row = i + 1; - calculate_row_width = false; - } - - break; - } - - max_height = Math.max(max_height, child.getHeight() + child.getDockMargin().top() + child.getDockMargin().bottom()); - total_width += gain_width; - } - - total_height += max_height; - } - - if (index + 1 < children.size() && desired_width != this_width) { - // не все панели уместились. ну чтож - desired_width = Math.min(desired_width + min_width, this_width); - } else { - break; - } - } - - index = 0; - - // ширину на середину для позиционирования по центру - this_width /= 2; - - // определение точки по середине по высоте - float h_middle = (getHeight() - getDockPadding().bottom() - getDockPadding().top() - total_height) / 2; - - // OverdriveThatMatters.LOGGER.info("Общая высота {}, рядов {}, середина {}", total_height, rows, h_middle); - - for (int row = 0; row < rows; row++) { - float max_height = 0; - float total_width = 0; - - int until = index; - - for (int i = index; i < children.size(); i++) { - var child = children.get(i); - var gain_width = child.getWidth() + child.getDockMargin().left() + child.getDockMargin().right(); - - until = i; - - if (gain_width + total_width > desired_width) { - break; - } - - max_height = Math.max(max_height, child.getHeight() + child.getDockMargin().top() + child.getDockMargin().bottom()); - total_width += gain_width; - } - - total_width /= 2; - max_height /= 2; - - // OverdriveThatMatters.LOGGER.info("Ряд {}, общая ширина {}, максимальная высота {}, позиция {}, c {} до {}", row, total_width * 2, max_height * 2, h_middle, index, until); - - float accumulate_width = 0; - - for (int i = index; i <= until; i++) { - var child = children.get(i); - - child.setPos((int) (this_width - total_width + accumulate_width + child.getDockMargin().left()), (int) (h_middle + max_height - child.getHeight() / 2)); - accumulate_width += child.getWidth() + child.getDockMargin().left() + child.getDockMargin().right(); - } - - h_middle += max_height * 2; - - index = until; - } - } - - super.performLayout(); - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/DispenserBlockEntityMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/DispenserBlockEntityMixin.java new file mode 100644 index 000000000..7cba3efe4 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/DispenserBlockEntityMixin.java @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.entity.DispenserBlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu; + +@Mixin(DispenserBlockEntity.class) +public abstract class DispenserBlockEntityMixin { + @Overwrite + public AbstractContainerMenu createMenu(int p_59312_, Inventory p_59313_) { + return MatteryChestMenu.c3x3(p_59312_, p_59313_, (DispenserBlockEntity) (Object) this); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/EnchantmentHelperMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/EnchantmentHelperMixin.java new file mode 100644 index 000000000..86f576290 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/EnchantmentHelperMixin.java @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +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.CallbackInfoReturnable; +import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem; + +@Mixin(EnchantmentHelper.class) +public class EnchantmentHelperMixin { + @Inject( + method = "getSweepingDamageRatio(Lnet/minecraft/world/entity/LivingEntity;)F", + at = @At("HEAD"), + cancellable = true) + private static void getSweepingDamageRatio(LivingEntity p_44822_, CallbackInfoReturnable info) { + var result = EnergySwordItem.getSweepingDamageRatioHook(p_44822_); + + if (result != null) { + info.setReturnValue(result); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/FoodDataMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/FoodDataMixin.java new file mode 100644 index 000000000..95b445540 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/FoodDataMixin.java @@ -0,0 +1,49 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.Difficulty; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.food.FoodData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; + +@Mixin(FoodData.class) +public class FoodDataMixin { + @Shadow + private int lastFoodLevel; + @Shadow + private int tickTimer; + @Shadow + private int foodLevel; + @Shadow + private float exhaustionLevel; + + @Inject( + method = "tick(Lnet/minecraft/world/entity/player/Player;)V", + at = @At("HEAD"), + cancellable = true + ) + private void tick(Player player, CallbackInfo info) { + player.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + if (it.isAndroid()) { + info.cancel(); + + // полностью подменяем логику если андроид + lastFoodLevel = foodLevel; + + if (player.level.getDifficulty() == Difficulty.PEACEFUL) { + exhaustionLevel = 0f; + } else { + tickTimer = 0; + } + + // не обновляем уровень истощения ибо он обнуляется логикой внутри MatteryPlayerCapability + // а так же не регенерируем + // ну и не получаем урон от "голодания" + } + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/HopperBlockEntityMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/HopperBlockEntityMixin.java new file mode 100644 index 000000000..9c1af9bfd --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/HopperBlockEntityMixin.java @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.entity.HopperBlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu; + +@Mixin(HopperBlockEntity.class) +public abstract class HopperBlockEntityMixin { + @Overwrite + public AbstractContainerMenu createMenu(int p_59312_, Inventory p_59313_) { + return MatteryChestMenu.hopper(p_59312_, p_59313_, (HopperBlockEntity) (Object) this); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/InventoryChangeTriggerMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/InventoryChangeTriggerMixin.java new file mode 100644 index 000000000..6aea160bd --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/InventoryChangeTriggerMixin.java @@ -0,0 +1,22 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.advancements.critereon.InventoryChangeTrigger; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import ru.dbotthepony.mc.otm.triggers.MatteryInventoryChangeTrigger; + +@Mixin(InventoryChangeTrigger.class) +public abstract class InventoryChangeTriggerMixin { + @Overwrite + public void trigger(ServerPlayer p_43150_, Inventory p_43151_, ItemStack p_43152_) { + MatteryInventoryChangeTrigger.INSTANCE.trigger(p_43150_, p_43151_, p_43152_); + } + + @Overwrite + private void trigger(ServerPlayer p_43154_, Inventory p_43155_, ItemStack p_43156_, int p_43157_, int p_43158_, int p_43159_) { + MatteryInventoryChangeTrigger.INSTANCE.trigger(p_43154_, p_43155_, p_43156_, p_43157_, p_43158_, p_43159_); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAbstractHurtingProjectile.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAbstractHurtingProjectile.java new file mode 100644 index 000000000..aa61faf9a --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAbstractHurtingProjectile.java @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; +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.CallbackInfoReturnable; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.registry.MSoundEvents; + +@Mixin(AbstractHurtingProjectile.class) +public class MixinAbstractHurtingProjectile { + @Inject( + method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/projectile/AbstractHurtingProjectile;markHurt()V", + ordinal = 0 + ) + ) + public void onProjectileHit(DamageSource pSource, float pAmount, CallbackInfoReturnable cir) { + Entity entity = pSource.getEntity(); + if (entity == null) return; + + entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(cap -> { + AbstractHurtingProjectile proj = (AbstractHurtingProjectile)(Object)this; + + if (cap.isAndroid() && proj.getOwner() != entity) { + entity.level.playSound(entity, proj.blockPosition(), MSoundEvents.INSTANCE.getANDROID_PROJ_PARRY(), SoundSource.PLAYERS, 1.0f, 0.95f + entity.level.random.nextFloat() * 0.1f); + } + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAnvilBlock.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAnvilBlock.java new file mode 100644 index 000000000..6569a8cd6 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinAnvilBlock.java @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.level.block.AnvilBlock; +import net.minecraft.world.level.block.state.BlockState; +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.CallbackInfoReturnable; +import ru.dbotthepony.mc.otm.registry.MBlocks; + +@Mixin(AnvilBlock.class) +@SuppressWarnings("unused") +public class MixinAnvilBlock { + @Inject( + method = "damage(Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/world/level/block/state/BlockState;", + at = @At("HEAD"), + cancellable = true) + private static void damage(BlockState pState, CallbackInfoReturnable info) { + var list = MBlocks.INSTANCE.getTRITANIUM_ANVIL(); + + for (int i = 0; i < list.size(); i++) { + if (pState.is(list.get(i))) { + if (i == list.size() - 1) { + info.setReturnValue(null); + } else { + info.setReturnValue(list.get(i + 1).defaultBlockState().setValue(AnvilBlock.FACING, pState.getValue(AnvilBlock.FACING))); + } + + return; + } + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinGameRenderer.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinGameRenderer.java new file mode 100644 index 000000000..53c0c3d30 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinGameRenderer.java @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.server.packs.resources.ResourceProvider; +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; +import ru.dbotthepony.mc.otm.client.render.GlitchRenderer; +import ru.dbotthepony.mc.otm.client.render.RenderHelperKt; + +@Mixin(GameRenderer.class) +public class MixinGameRenderer { + @Inject( + method = "render(FJZ)V", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/blaze3d/pipeline/RenderTarget;bindWrite(Z)V" + ) + ) + private void render(float p_109094_, long p_109095_, boolean p_109096_, CallbackInfo ci) { + GlitchRenderer.render(); + } + + @Inject( + method = "reloadShaders(Lnet/minecraft/server/packs/resources/ResourceProvider;)V", + at = @At("HEAD") + ) + private void reloadShaders(ResourceProvider p_250719_, CallbackInfo ci) { + RenderHelperKt.reloadShaders(p_250719_); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java new file mode 100644 index 000000000..0b9a709b3 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinInventory.java @@ -0,0 +1,90 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.registries.ForgeRegistries; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; + +import java.util.function.Predicate; + +@Mixin(Inventory.class) +public class MixinInventory { + @Final + @Shadow + public Player player; + + @Inject( + method = "add(ILnet/minecraft/world/item/ItemStack;)Z", + at = @At("HEAD"), + cancellable = true + ) + private void add(int pSlot, ItemStack pStack, CallbackInfoReturnable hook) { + if (pStack.isEmpty()) { + return; + } + + if (pSlot == -1) { + this.player.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + try { + hook.setReturnValue(it.inventoryAddImpl(pStack)); + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Adding item to inventory (Overdrive That Matters detour)"); + CrashReportCategory crashreportcategory = crashreport.addCategory("Item being added"); + crashreportcategory.setDetail("Registry Name", () -> String.valueOf(ForgeRegistries.ITEMS.getKey(pStack.getItem()))); + crashreportcategory.setDetail("Item Class", () -> pStack.getItem().getClass().getName()); + crashreportcategory.setDetail("Item ID", Item.getId(pStack.getItem())); + crashreportcategory.setDetail("Item data", pStack.getDamageValue()); + crashreportcategory.setDetail("Item name", () -> pStack.getHoverName().getString()); + throw new ReportedException(crashreport); + } + }); + } + } + + @Inject( + method = "dropAll()V", + at = @At("TAIL") + ) + private void dropAll(CallbackInfo ci) { + MatteryPlayerCapability.inventoryDropAll((Inventory)(Object)this); + } + + @Inject( + method = "clearContent()V", + at = @At("TAIL") + ) + private void clearContent(CallbackInfo ci) { + MatteryPlayerCapability.inventoryClearContent((Inventory)(Object)this); + } + + @Inject( + method = "clearOrCountMatchingItems(Ljava/util/function/Predicate;ILnet/minecraft/world/Container;)I", + at = @At("RETURN"), + cancellable = true + ) + private void clearOrCountMatchingItems(Predicate predicate, int count, Container container, CallbackInfoReturnable cir) { + player.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + if (!it.getHasExopack()) return; + + int i = cir.getReturnValue(); + i += ContainerHelper.clearOrCountMatchingItems(it.getExopackContainer(), predicate, count - i, count == 0); + + cir.setReturnValue(i); + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinLivingEntity.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinLivingEntity.java new file mode 100644 index 000000000..3cb4441d6 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinLivingEntity.java @@ -0,0 +1,68 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.ForgeEventFactory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.config.ServerConfig; +import ru.dbotthepony.mc.otm.core.util.ExperienceUtilsKt; +import ru.dbotthepony.mc.otm.registry.MItems; + +@SuppressWarnings("ConstantConditions") +@Mixin(LivingEntity.class) +public class MixinLivingEntity { + @Shadow + protected Player lastHurtByPlayer; + + @Inject( + method = "dropExperience()V", + at = @At("HEAD"), + cancellable = true) + public void dropExperience(CallbackInfo hook) { + if (((Object) this) instanceof Player player && ServerConfig.INSTANCE.getDROP_EXPERIENCE_CAPSULES()) { + player.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + hook.cancel(); + + long totalExperience = ExperienceUtilsKt.getTotalXpRequiredForLevel(player.experienceLevel); + totalExperience += (long) (player.experienceProgress * player.getXpNeededForNextLevel()); + + double min = ServerConfig.INSTANCE.getMIN_EXPERIENCE_DROPPED(); + double max = ServerConfig.INSTANCE.getMAX_EXPERIENCE_DROPPED(); + + if (min == max) { + totalExperience *= min; + } else { + if (min > max) { + min = 0.4; + max = 0.8; + } + + totalExperience *= min + player.getRandom().nextDouble() * (max - min); + } + + if (totalExperience >= Integer.MAX_VALUE) { + int hooked = ForgeEventFactory.getExperienceDrop(player, lastHurtByPlayer, Integer.MAX_VALUE); + + if (hooked != Integer.MAX_VALUE) { + totalExperience = hooked; + } + } else { + totalExperience = ForgeEventFactory.getExperienceDrop(player, lastHurtByPlayer, (int) totalExperience); + } + + if (totalExperience > 0L) { + if (it.isAndroid()) { + player.drop(MItems.INSTANCE.getESSENCE_DRIVE().make(totalExperience), true, false); + } else { + player.drop(MItems.INSTANCE.getESSENCE_CAPSULE().make(totalExperience), true, false); + } + } + }); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java new file mode 100644 index 000000000..bbf7892bd --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinMinecraft.java @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +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; +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; + +@Mixin(Minecraft.class) +public class MixinMinecraft { + @Redirect( + method = "pickBlock()V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/player/Inventory;findSlotMatchingItem(Lnet/minecraft/world/item/ItemStack;)I" + ) + ) + private int pickBlock(Inventory inventory, ItemStack itemStack) { + int i = inventory.findSlotMatchingItem(itemStack); + + MatteryPlayerCapability.pickBlockHook(i, itemStack); + + return i; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java index c49676bdc..4dd8325cb 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPatchProjectileFinder.java @@ -16,7 +16,8 @@ public class MixinPatchProjectileFinder { value = "INVOKE", target = "net.minecraftforge.common.ForgeHooks.getProjectile(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;", ordinal = 2, - shift = At.Shift.BEFORE + shift = At.Shift.BEFORE, + remap = false ), cancellable = true) private void exosuitGetProjectileHook(ItemStack weaponItem, CallbackInfoReturnable hook) { diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java new file mode 100644 index 000000000..1e112bcde --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/MixinPlayer.java @@ -0,0 +1,19 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.player.Player; +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; +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; + +@Mixin(Player.class) +public class MixinPlayer { + @Inject( + method = "destroyVanishingCursedItems()V", + at = @At("TAIL") + ) + private void destroyVanishingCursedItems(CallbackInfo ci) { + MatteryPlayerCapability.playerDestroyVanishingCursedItems((Player)(Object)this); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/SimpleCriterionTriggerMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/SimpleCriterionTriggerMixin.java new file mode 100644 index 000000000..d5d3e701f --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/SimpleCriterionTriggerMixin.java @@ -0,0 +1,54 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.advancements.CriterionTrigger; +import net.minecraft.advancements.CriterionTriggerInstance; +import net.minecraft.advancements.critereon.InventoryChangeTrigger; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.server.PlayerAdvancements; +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; +import ru.dbotthepony.mc.otm.triggers.MatteryInventoryChangeTrigger; + +// i tried to mixin into InventoryChangeTrigger with extends SimpleCriterionTrigger and @Overwrite+@Override +// while also defining SimpleCriterionTrigger methods non final in accesstransfoer +// it didn't work. +@Mixin(SimpleCriterionTrigger.class) +public abstract class SimpleCriterionTriggerMixin implements CriterionTrigger { + @Inject( + method = "removePlayerListener(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V", + at = @At("HEAD"), + cancellable = true + ) + public void removePlayerListener(PlayerAdvancements p_66254_, CriterionTrigger.Listener p_66255_, CallbackInfo info) { + if (((Object) this) instanceof InventoryChangeTrigger) { + MatteryInventoryChangeTrigger.INSTANCE.removePlayerListener(p_66254_, p_66255_); + info.cancel(); + } + } + + @Inject( + method = "addPlayerListener(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V", + at = @At("HEAD"), + cancellable = true + ) + public void addPlayerListener(PlayerAdvancements p_66254_, CriterionTrigger.Listener p_66255_, CallbackInfo info) { + if (((Object) this) instanceof InventoryChangeTrigger) { + MatteryInventoryChangeTrigger.INSTANCE.addPlayerListener(p_66254_, p_66255_); + info.cancel(); + } + } + + @Inject( + method = "removePlayerListeners(Lnet/minecraft/server/PlayerAdvancements;)V", + at = @At("HEAD"), + cancellable = true + ) + public void removePlayerListeners(PlayerAdvancements p_66254_, CallbackInfo info) { + if (((Object) this) instanceof InventoryChangeTrigger) { + MatteryInventoryChangeTrigger.INSTANCE.removePlayerListeners(p_66254_); + info.cancel(); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java new file mode 100644 index 000000000..6201620d0 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityOxygenSystemMixin.java @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.mixin.compat.ad_astra; + +import earth.terrarium.ad_astra.common.entity.system.EntityOxygenSystem; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +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; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.config.ServerCompatConfig; + +@Mixin(EntityOxygenSystem.class) +public class EntityOxygenSystemMixin { + @Inject( + method = "oxygenTick(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/server/level/ServerLevel;)V", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private static void oxygenTick(LivingEntity entity, ServerLevel level, CallbackInfo hook) { + if (entity instanceof Player && ServerCompatConfig.AdAstra.INSTANCE.getANDROIDS_DO_NOT_NEED_OXYGEN()) { + entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + if (it.isAndroid()) { + hook.cancel(); + } + }); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java new file mode 100644 index 000000000..421069a9c --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/EntityTemperatureSystemMixin.java @@ -0,0 +1,27 @@ +package ru.dbotthepony.mc.otm.mixin.compat.ad_astra; + +import earth.terrarium.ad_astra.common.entity.system.EntityTemperatureSystem; +import earth.terrarium.ad_astra.common.util.ModUtils; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.LivingEntity; +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; +import ru.dbotthepony.mc.otm.config.ServerCompatConfig; + +// STAHP! +@Mixin(EntityTemperatureSystem.class) +public class EntityTemperatureSystemMixin { + @Inject( + method = "temperatureTick(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/server/level/ServerLevel;)V", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private static void temperatureTick(LivingEntity entity, ServerLevel level, CallbackInfo hook) { + if (ServerCompatConfig.AdAstra.INSTANCE.getWHATS_UP_WITH_TEMPERATURE() && !ModUtils.planetHasAtmosphere(level)) { + hook.cancel(); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java new file mode 100644 index 000000000..e8a1a7d57 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/compat/ad_astra/OxygenUtilsMixin.java @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.mixin.compat.ad_astra; + +import earth.terrarium.ad_astra.common.util.OxygenUtils; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +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.CallbackInfoReturnable; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.config.ServerCompatConfig; + +@Mixin(OxygenUtils.class) +public class OxygenUtilsMixin { + @Inject( + method = "entityHasOxygen(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;)Z", + at = @At("HEAD"), + cancellable = true, + remap = false + ) + private static void entityHasOxygen(Level level, LivingEntity entity, CallbackInfoReturnable hook) { + if (entity instanceof Player && ServerCompatConfig.AdAstra.INSTANCE.getANDROIDS_DO_NOT_NEED_OXYGEN()) { + entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresent(it -> { + if (it.isAndroid()) { + hook.setReturnValue(true); + } + }); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java b/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java deleted file mode 100644 index 1f4d002cb..000000000 --- a/src/main/java/ru/dbotthepony/mc/otm/registry/MRecipes.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.dbotthepony.mc.otm.registry; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.ForgeRegistries; -import ru.dbotthepony.mc.otm.OverdriveThatMatters; -import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe; -import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe; -import ru.dbotthepony.mc.otm.recipe.PlatePressRecipeFactory; - -public class MRecipes { - public static class MatteryRecipeType> implements RecipeType { - public final ResourceLocation name; - - private MatteryRecipeType(ResourceLocation name) { - this.name = name; - } - - @Override - public String toString() { - return name.toString(); - } - } - - public static final MatteryRecipeType PLATE_PRESS = new MatteryRecipeType<>(OverdriveThatMatters.loc(MNames.PLATE_PRESS)); - public static final MatteryRecipeType ENERGY_CONTAINER = new MatteryRecipeType<>(OverdriveThatMatters.loc("energy_container")); - - private static final DeferredRegister> serializerRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID); - private static final DeferredRegister> typeRegistry = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID); - - static { - serializerRegistry.register(MNames.PLATE_PRESS, () -> PlatePressRecipeFactory.INSTANCE); - serializerRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> EnergyContainerRecipe.Companion); - typeRegistry.register(MNames.PLATE_PRESS, () -> PLATE_PRESS); - typeRegistry.register(ENERGY_CONTAINER.name.getPath(), () -> ENERGY_CONTAINER); - } - - public static void register(IEventBus bus) { - serializerRegistry.register(bus); - typeRegistry.register(bus); - } -} diff --git a/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShape.java b/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShape.java index a414961ad..daadec952 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShape.java +++ b/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShape.java @@ -5,6 +5,7 @@ import net.minecraft.core.Direction; import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; +import ru.dbotthepony.mc.otm.core.math.BlockRotation; import javax.annotation.ParametersAreNonnullByDefault; @@ -47,7 +48,7 @@ public record BlockShape(SimpleCuboid ...shapes) { return new BlockShape(list); } - public BlockShape rotate(Direction dir) { + public BlockShape rotateFromSouth(Direction dir) { if (dir == Direction.SOUTH) return this; @@ -69,7 +70,7 @@ public record BlockShape(SimpleCuboid ...shapes) { return this; } - public BlockShape rotateInv(Direction dir) { + public BlockShape rotateFromNorth(Direction dir) { if (dir == Direction.SOUTH) return rotateAroundY(Math.PI); @@ -91,11 +92,29 @@ public record BlockShape(SimpleCuboid ...shapes) { return this; } + public BlockShape rotateFromNorth(BlockRotation dir) { + var result = rotateFromNorth(dir.getFront()); + + if (dir.getTop() != Direction.DOWN && dir.getTop() != Direction.UP) + result = result.rotateFromNorth(dir.getTop()); + + return result; + } + + public BlockShape rotateFromSouth(BlockRotation dir) { + var result = rotateFromSouth(dir.getFront()); + + if (dir.getTop() != Direction.DOWN && dir.getTop() != Direction.UP) + result = result.rotateFromSouth(dir.getTop()); + + return result; + } + public VoxelShape computeShape() { VoxelShape final_shape = shapes[0].getShape(); for (int i = 1; i < shapes.length; i++) - final_shape = Shapes.joinUnoptimized(final_shape, shapes[i].getShape(), BooleanOp.OR); + final_shape = Shapes.join(final_shape, shapes[i].getShape(), BooleanOp.OR); return final_shape; } diff --git a/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShapes.java b/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShapes.java index 9417abf28..dc64f03ec 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShapes.java +++ b/src/main/java/ru/dbotthepony/mc/otm/shapes/BlockShapes.java @@ -23,11 +23,13 @@ public class BlockShapes { new SimpleCuboid(0.0625d, 0.0625d, 0.4375d, 0.9375d, 0.9375d, 0.5625d), new SimpleCuboid(0.125d, 0.03125d, 0.125d, 0.875d, 0.09375d, 0.875d), new SimpleCuboid(0.125d, 0.90625d, 0.125d, 0.875d, 0.96875d, 0.875d), - new SimpleCuboid(0.1875d, 0.1875d, 0d, 0.8125d, 0.8125d, 0.1875d), + new SimpleCuboid(0.3125d, 0.3125d, 0d, 0.6875d, 0.6875d, 0.1875d), new SimpleCuboid(0.0625d, 0.125d, 0.125d, 0.125d, 0.875d, 0.25d), new SimpleCuboid(0.0625d, 0.125d, 0.75d, 0.125d, 0.875d, 0.875d), new SimpleCuboid(0.875d, 0.125d, 0.75d, 0.9375d, 0.875d, 0.875d), new SimpleCuboid(0.875d, 0.125d, 0.125d, 0.9375d, 0.875d, 0.25d), + new SimpleCuboid(0.125d, 0.625d, 0.15625d, 0.875d, 0.75d, 0.21875d), + new SimpleCuboid(0.125d, 0.25d, 0.15625d, 0.875d, 0.375d, 0.21875d), new SimpleCuboid(0d, 0.125d, 0.375d, 0.125d, 0.875d, 0.625d), new SimpleCuboid(0.875d, 0.125d, 0.375d, 1d, 0.875d, 0.625d) ); @@ -378,21 +380,52 @@ public class BlockShapes { ); public static final BlockShape PLATE_PRESS_IDLE = new BlockShape( - new SimpleCuboid(0d, 0d, 0d, 1d, 0.5d, 1d), - new SimpleCuboid(0d, 0.5d, 0.4375d, 1d, 0.75d, 1d), - new SimpleCuboid(0.75d, 0.5d, 0.0625d, 1d, 0.75d, 0.4375d), - new SimpleCuboid(0.125d, 0.5d, 0.25d, 0.6875d, 1d, 0.8125d), - new SimpleCuboid(0.8125d, 0.5d, 0d, 0.9375d, 0.75d, 0.0625d), - new SimpleCuboid(0.9375d, 0.75d, 0.9375d, 1d, 0.875d, 1d), - new SimpleCuboid(0d, 0.75d, 0.9375d, 0.0625d, 0.875d, 1d), - new SimpleCuboid(0.01875d, 0.8125d, 0.0625d, 0.05d, 0.875d, 0.9375d), - new SimpleCuboid(0d, 0.5d, 0d, 0.0625d, 0.875d, 0.0625d), - new SimpleCuboid(0.0625d, 0.8125d, 0.95d, 0.9375d, 0.875d, 0.98125d), - new SimpleCuboid(0.6875d, 0.5d, 0.5625d, 0.75d, 0.875d, 0.75d), - new SimpleCuboid(0.6875d, 0.5d, 0.3125d, 0.75d, 0.875d, 0.5d), - new SimpleCuboid(0.0625d, 0.5d, 0.3125d, 0.125d, 0.875d, 0.5d), - new SimpleCuboid(0.0625d, 0.5d, 0.5625d, 0.125d, 0.875d, 0.75d), - new SimpleCuboid(0.775d, 0.75d, 0.125d, 0.9625d, 0.9375d, 0.875d) + new SimpleCuboid(0.625d, 0d, 0d, 1d, 0.375d, 1d), + new SimpleCuboid(0.625d, 0.625d, 0d, 1d, 1d, 1d), + new SimpleCuboid(0.625d, 0.375d, 0.125d, 1d, 0.625d, 1d), + new SimpleCuboid(0.6875d, 0.375d, 0.0625d, 0.9375d, 0.625d, 0.125d), + new SimpleCuboid(0.0625d, 0.5d, 0.1875d, 0.5625d, 0.9375d, 0.9375d), + new SimpleCuboid(0.0625d, 0d, 0d, 0.5625d, 0.4375d, 1d), + new SimpleCuboid(0.5625d, 0d, 0.0625d, 0.625d, 1d, 0.1875d), + new SimpleCuboid(0.5625d, 0d, 0.8125d, 0.625d, 1d, 0.9375d), + new SimpleCuboid(0d, 0d, 0.0625d, 0.0625d, 1d, 0.1875d), + new SimpleCuboid(0d, 0d, 0.8125d, 0.0625d, 1d, 0.9375d), + new SimpleCuboid(0.5625d, 0.8125d, 0.1875d, 0.625d, 0.9375d, 0.8125d), + new SimpleCuboid(0.5625d, 0.0625d, 0.1875d, 0.625d, 0.1875d, 0.8125d), + new SimpleCuboid(0d, 0.8125d, 0.1875d, 0.0625d, 0.9375d, 0.8125d), + new SimpleCuboid(0d, 0.0625d, 0.1875d, 0.0625d, 0.1875d, 0.8125d), + new SimpleCuboid(0.125d, 0.5625d, -0.0625d, 0.5d, 0.5625d, 0.0625d), + new SimpleCuboid(0.0625d, 0.5d, 0.0625d, 0.5625d, 0.5625d, 0.1875d), + new SimpleCuboid(0.0625d, 0.875d, 0.0625d, 0.5625d, 0.9375d, 0.1875d), + new SimpleCuboid(0.5d, 0.5625d, 0.0625d, 0.5625d, 0.875d, 0.1875d), + new SimpleCuboid(0.0625d, 0.5625d, 0.0625d, 0.125d, 0.875d, 0.1875d) + ); + + public static final BlockShape TWIN_PLATE_PRESS_IDLE = new BlockShape( + new SimpleCuboid(0.625d, 0d, 0d, 1d, 0.375d, 1d), + new SimpleCuboid(0.625d, 0.625d, 0d, 1d, 1d, 1d), + new SimpleCuboid(0.625d, 0.375d, 0.125d, 1d, 0.625d, 1d), + new SimpleCuboid(0.6875d, 0.375d, 0.0625d, 0.9375d, 0.625d, 0.125d), + new SimpleCuboid(0.0625d, 0.5d, 0.1875d, 0.5625d, 0.9375d, 0.9375d), + new SimpleCuboid(0.5625d, 0d, 0.0625d, 0.625d, 1d, 0.1875d), + new SimpleCuboid(0.5625d, 0d, 0.8125d, 0.625d, 1d, 0.9375d), + new SimpleCuboid(0d, 0d, 0.0625d, 0.0625d, 1d, 0.1875d), + new SimpleCuboid(0d, 0d, 0.8125d, 0.0625d, 1d, 0.9375d), + new SimpleCuboid(0.5625d, 0.8125d, 0.1875d, 0.625d, 0.9375d, 0.8125d), + new SimpleCuboid(0.5625d, 0.0625d, 0.1875d, 0.625d, 0.1875d, 0.8125d), + new SimpleCuboid(0d, 0.8125d, 0.1875d, 0.0625d, 0.9375d, 0.8125d), + new SimpleCuboid(0d, 0.0625d, 0.1875d, 0.0625d, 0.1875d, 0.8125d), + new SimpleCuboid(0.125d, 0.5625d, -0.0625d, 0.5d, 0.5625d, 0.0625d), + new SimpleCuboid(0.0625d, 0.5d, 0.0625d, 0.5625d, 0.5625d, 0.1875d), + new SimpleCuboid(0.0625d, 0.875d, 0.0625d, 0.5625d, 0.9375d, 0.1875d), + new SimpleCuboid(0.5d, 0.5625d, 0.0625d, 0.5625d, 0.875d, 0.1875d), + new SimpleCuboid(0.0625d, 0.5625d, 0.0625d, 0.125d, 0.875d, 0.1875d), + new SimpleCuboid(0.0625d, 0d, 0.1875d, 0.5625d, 0.4375d, 0.9375d), + new SimpleCuboid(0.5d, 0.0625d, 0.0625d, 0.5625d, 0.375d, 0.1875d), + new SimpleCuboid(0.0625d, 0.375d, 0.0625d, 0.5625d, 0.4375d, 0.1875d), + new SimpleCuboid(0.0625d, 0.0625d, 0.0625d, 0.125d, 0.375d, 0.1875d), + new SimpleCuboid(0.0625d, 0d, 0.0625d, 0.5625d, 0.0625d, 0.1875d), + new SimpleCuboid(0.125d, 0.0625d, -0.0625d, 0.5d, 0.0625d, 0.0625d) ); public static final BlockShape GRAVITATION_STABILIZER = new BlockShape( @@ -569,4 +602,187 @@ public class BlockShapes { new SimpleCuboid(0.03125d, 0.125d, 0.65625d, 0.34375d, 0.625d, 0.96875d), new SimpleCuboid(0.65625d, 0.125d, 0.65625d, 0.96875d, 0.625d, 0.96875d) ); + + public static final BlockShape ENGINE = new BlockShape( + new SimpleCuboid(0d, 0d, 0.875d, 1d, 1d, 1d), + new SimpleCuboid(0.0625d, 0.0625d, 0.625d, 0.9375d, 0.9375d, 0.875d), + new SimpleCuboid(0.25d, 0.25d, 0.4375d, 0.75d, 0.75d, 0.625d), + new SimpleCuboid(0.1875d, 0.1875d, 0.25d, 0.8125d, 0.8125d, 0.4375d), + new SimpleCuboid(0.125d, 0.125d, 0d, 0.875d, 0.875d, 0.25d), + new SimpleCuboid(0d, 0.875d, 0.5625d, 0.125d, 1d, 0.875d), + new SimpleCuboid(0.0625d, 0.875d, 0.25d, 0.125d, 0.9375d, 0.5625d), + new SimpleCuboid(0.0625d, 0.0625d, 0.25d, 0.125d, 0.125d, 0.5625d), + new SimpleCuboid(0.875d, 0.0625d, 0.25d, 0.9375d, 0.125d, 0.5625d), + new SimpleCuboid(0.875d, 0.875d, 0.25d, 0.9375d, 0.9375d, 0.5625d), + new SimpleCuboid(0.875d, 0.125d, 0.4375d, 0.9375d, 0.875d, 0.5625d), + new SimpleCuboid(0.0625d, 0.125d, 0.4375d, 0.125d, 0.875d, 0.5625d), + new SimpleCuboid(0.125d, 0.875d, 0.4375d, 0.875d, 0.9375d, 0.5625d), + new SimpleCuboid(0.125d, 0.0625d, 0.4375d, 0.875d, 0.125d, 0.5625d), + new SimpleCuboid(0.875d, 0.875d, 0.5625d, 1d, 1d, 0.875d), + new SimpleCuboid(0.875d, 0d, 0.5625d, 1d, 0.125d, 0.875d), + new SimpleCuboid(0d, 0d, 0.5625d, 0.125d, 0.125d, 0.875d) + ); + + public static final BlockShape CARGO_CRATE_OPEN = new BlockShape( + new SimpleCuboid(0d, 0d, 0d, 1d, 0.8125d, 1d), + new SimpleCuboid(0.125d, 0.8125d, 0.125d, 0.875d, 0.9375d, 0.875d), + new SimpleCuboid(0d, 0.9375d, 0d, 1d, 1.125d, 1d), + new SimpleCuboid(0d, 0.8125d, 0.4375d, 0d, 0.9375d, 0.5625d), + new SimpleCuboid(1d, 0.8125d, 0.4375d, 1d, 0.9375d, 0.5625d), + new SimpleCuboid(0.4375d, 0.8125d, 1d, 0.5625d, 0.9375d, 1d), + new SimpleCuboid(0.4375d, 0.8125d, 0d, 0.5625d, 0.9375d, 0d) + ); + + public static final BlockShape HOLO_SIGN = new BlockShape( + new SimpleCuboid(0d, 0d, 0.875d, 1d, 1d, 1d), + new SimpleCuboid(0.0625d, 0.0625d, 0.8125d, 0.9375d, 0.9375d, 0.875d), + new SimpleCuboid(0d, 0d, 0.625d, 1d, 1d, 0.8125d), + new SimpleCuboid(0.875d, 0.3125d, 0.5625d, 1d, 0.6875d, 0.625d), + new SimpleCuboid(0d, 0.3125d, 0.5625d, 0.125d, 0.6875d, 0.625d), + new SimpleCuboid(0.0625d, 0.875d, 0.625d, 0.125d, 0.9375d, 0.625d), + new SimpleCuboid(0.0625d, 0.0625d, 0.625d, 0.125d, 0.125d, 0.625d), + new SimpleCuboid(0.875d, 0.0625d, 0.625d, 0.9375d, 0.125d, 0.625d), + new SimpleCuboid(0.875d, 0.875d, 0.625d, 0.9375d, 0.9375d, 0.625d), + new SimpleCuboid(0.875d, 0.375d, 0.5625d, 0.875d, 0.625d, 0.625d), + new SimpleCuboid(0.125d, 0.375d, 0.5625d, 0.125d, 0.625d, 0.625d) + ); + + public static final BlockShape COBBLESTONE_GENERATOR = new BlockShape( + new SimpleCuboid(0d, 0d, 0d, 1d, 0.375d, 1d), + new SimpleCuboid(0.25d, 0.375d, 0d, 0.75d, 1d, 1d), + new SimpleCuboid(0.0625d, 0.375d, 0.0625d, 0.25d, 0.9375d, 0.9375d), + new SimpleCuboid(0.75d, 0.375d, 0.0625d, 0.9375d, 0.9375d, 0.9375d) + ); + + public static final BlockShape ESSENCE_STORAGE = new BlockShape( + new SimpleCuboid(0d, 0d, 0d, 1d, 0.125d, 1d), + new SimpleCuboid(0d, 0.1875d, 0d, 1d, 0.3125d, 1d), + new SimpleCuboid(0.5625d, 0.3125d, 0d, 1d, 1d, 0.875d), + new SimpleCuboid(0d, 0.625d, 0.875d, 1d, 1d, 1d), + new SimpleCuboid(0d, 0.3125d, 0.5625d, 0.5625d, 1d, 0.875d), + new SimpleCuboid(0.0625d, 0.125d, 0.0625d, 0.9375d, 0.1875d, 0.9375d), + new SimpleCuboid(0.0625d, 0.3125d, 0.875d, 0.9375d, 0.625d, 0.9375d), + new SimpleCuboid(0.0625d, 0.3125d, 0.0625d, 0.5625d, 0.9375d, 0.5625d) + ); + + public static final BlockShape MATTER_RECONSTRUCTOR = new BlockShape( + new SimpleCuboid(0d, 0.25d, 0d, 1d, 0.5d, 1d), + new SimpleCuboid(0d, 0d, 0d, 1d, 0.25d, 0.6875d), + new SimpleCuboid(0.8125d, 0.5d, 0d, 1d, 1d, 1d), + new SimpleCuboid(0d, 0.5d, 0d, 0.1875d, 1d, 1d), + new SimpleCuboid(0.1875d, 0.5d, 0.0625d, 0.8125d, 0.9375d, 0.9375d), + new SimpleCuboid(-0.0625d, 0.5d, 0.0625d, 0d, 0.9375d, 0.9375d), + new SimpleCuboid(1d, 0.5d, 0.0625d, 1.0625d, 0.9375d, 0.9375d), + new SimpleCuboid(0.25d, 0.5d, 0.25d, 0.75d, 0.5625d, 0.75d), + new SimpleCuboid(0.875d, 0d, 0.6875d, 0.9375d, 0.25d, 0.9375d), + new SimpleCuboid(0.0625d, 0d, 0.6875d, 0.1875d, 0.25d, 0.9375d), + new SimpleCuboid(0.1875d, 0d, 0.6875d, 0.875d, 0.25d, 0.9375d), + new SimpleCuboid(0.36875d, 0.65625d, 0.5875d, 0.44375d, 0.78125d, 0.9d), + new SimpleCuboid(0.4375d, 0.5d, 0.1875d, 0.5625d, 0.625d, 0.3125d) + ); + + public static final BlockShape FLUID_TANK = new BlockShape( + new SimpleCuboid(0d, 0d, 0d, 1d, 0.125d, 1d), + new SimpleCuboid(0d, 0.875d, 0d, 1d, 1d, 1d), + new SimpleCuboid(0.03125d, 0.125d, 0.03125d, 0.96875d, 0.1875d, 0.96875d), + new SimpleCuboid(0.03125d, 0.8125d, 0.03125d, 0.96875d, 0.875d, 0.96875d), + new SimpleCuboid(0d, 0.1875d, 0d, 1d, 0.8125d, 1d) + ); + + public static final BlockShape ANDROID_CHARGER_BASE = new BlockShape( + new SimpleCuboid(0d, 0d, 0d, 1d, 0.5d, 1d), + new SimpleCuboid(0.6875d, 0.5d, 0.0625d, 0.9375d, 0.75d, 0.9375d), + new SimpleCuboid(0.3125d, 0.5d, 0.0625d, 0.0625d, 0.75d, 0.9375d), + new SimpleCuboid(0.125d, 0.5d, 0.125d, 0.875d, 0.6875d, 0.875d), + new SimpleCuboid(0.0625d, 0.5d, 0.0625d, 0.3125d, 0.75d, 0.9375d), + new SimpleCuboid(0.9375d, 0.5d, 0.0625d, 0.6875d, 0.75d, 0.9375d), + new SimpleCuboid(0.1875d, 0.6875d, 0.1875d, 0.3125d, 1d, 0.3125d), + new SimpleCuboid(0.25d, 0.6875d, 0.25d, 0.75d, 1d, 0.75d), + new SimpleCuboid(0.6875d, 0.6875d, 0.1875d, 0.8125d, 1d, 0.3125d), + new SimpleCuboid(0.6875d, 0.6875d, 0.6875d, 0.8125d, 1d, 0.8125d), + new SimpleCuboid(0.1875d, 0.6875d, 0.6875d, 0.3125d, 1d, 0.8125d), + new SimpleCuboid(0.3125d, 0.5d, 0.75d, 0.6875d, 1d, 1d) + ); + + public static final BlockShape ANDROID_CHARGER_MIDDLE = new BlockShape( + new SimpleCuboid(0.25d, 0d, 0.25d, 0.75d, 1d, 0.75d), + new SimpleCuboid(0.1875d, 0d, 0.6875d, 0.3125d, 1d, 0.8125d), + new SimpleCuboid(0.6875d, 0d, 0.1875d, 0.8125d, 1d, 0.3125d), + new SimpleCuboid(0.1875d, 0d, 0.1875d, 0.3125d, 1d, 0.3125d), + new SimpleCuboid(0.6875d, 0d, 0.6875d, 0.8125d, 1d, 0.8125d), + new SimpleCuboid(0.3125d, 0d, 0.75d, 0.6875d, 1d, 1d), + new SimpleCuboid(0.125d, -0.0625d, 0.125d, 0.875d, 0.3125d, 0.875d), + new SimpleCuboid(0.125d, -0.0625d, 0.875d, 0.875d, 0.3125d, 0.125d), + new SimpleCuboid(0.125d, 0.6875d, 0.875d, 0.875d, 1.0625d, 0.125d), + new SimpleCuboid(0.125d, 0.6875d, 0.125d, 0.875d, 1.0625d, 0.875d) + ); + + public static final BlockShape ANDROID_CHARGER_TOP = new BlockShape( + new SimpleCuboid(0.1875d, 0.6875d, 0d, 0.8125d, 0.875d, 0d), + new SimpleCuboid(0.1875d, 0d, 0.6875d, 0.3125d, 0.3125d, 0.8125d), + new SimpleCuboid(0.6875d, 0d, 0.1875d, 0.8125d, 0.3125d, 0.3125d), + new SimpleCuboid(0.1875d, 0d, 0.1875d, 0.3125d, 0.3125d, 0.3125d), + new SimpleCuboid(0.6875d, 0d, 0.6875d, 0.8125d, 0.3125d, 0.8125d), + new SimpleCuboid(0.25d, 0d, 0.25d, 0.75d, 0.3125d, 0.75d), + new SimpleCuboid(0.3125d, 0d, 0.75d, 0.6875d, 0.5d, 1d), + new SimpleCuboid(0.0625d, 0.25d, 0.0625d, 0.3125d, 0.5d, 0.9375d), + new SimpleCuboid(0.9375d, 0.25d, 0.0625d, 0.6875d, 0.5d, 0.9375d), + new SimpleCuboid(0.6875d, 0.25d, 0.0625d, 0.9375d, 0.5d, 0.9375d), + new SimpleCuboid(0.3125d, 0.25d, 0.0625d, 0.0625d, 0.5d, 0.9375d), + new SimpleCuboid(0.125d, 0.3125d, 0.125d, 0.875d, 0.5d, 0.875d), + new SimpleCuboid(0d, 0.5d, 0d, 1d, 1d, 1d) + ); + + public static final BlockShape POWERED_FURNACE = new BlockShape( + new SimpleCuboid(0.9375d, 0.5d, 0d, 1d, 1d, 0.0625d), + new SimpleCuboid(0d, 0.5d, 0d, 0.0625d, 1d, 0.0625d), + new SimpleCuboid(0d, 0.5d, 0.0625d, 1d, 1d, 0.6875d), + new SimpleCuboid(0.6875d, 0.5d, 0.6875d, 1d, 1d, 0.9375d), + new SimpleCuboid(0.6875d, 0.3125d, 0.9375d, 0.75d, 1d, 1d), + new SimpleCuboid(0.9375d, 0.3125d, 0.9375d, 1d, 1d, 1d), + new SimpleCuboid(0.75d, 0.9375d, 0.9375d, 0.9375d, 1d, 1d), + new SimpleCuboid(0.6875d, 0d, 0.9375d, 1d, 0.3125d, 1d), + new SimpleCuboid(0d, 0.375d, 0.9375d, 0.6875d, 0.5d, 1d), + new SimpleCuboid(0.0625d, 0.5d, 0d, 0.9375d, 0.5625d, 0.0625d), + new SimpleCuboid(0.0625d, 0.9375d, 0d, 0.9375d, 1d, 0.0625d), + new SimpleCuboid(0d, 0.0625d, 0.0625d, 1d, 0.4375d, 0.4375d), + new SimpleCuboid(0.8125d, 0d, 0d, 0.9375d, 0.5d, 0.5d), + new SimpleCuboid(0.0625d, 0d, 0d, 0.1875d, 0.5d, 0.5d), + new SimpleCuboid(0.0625d, 0.5d, 0.75d, 0.3125d, 1d, 1d), + new SimpleCuboid(0.375d, 0.5d, 0.75d, 0.625d, 1d, 1d), + new SimpleCuboid(0d, 0d, 0.5d, 1d, 0.5d, 0.9375d), + new SimpleCuboid(0d, 0.5d, 0.6875d, 0.6875d, 0.875d, 0.9375d), + new SimpleCuboid(-0.03125d, 1.03125d, 0.0625d, 1.03125d, 1.03125d, 0.125d), + new SimpleCuboid(-0.03125d, 0.71875d, 0.0625d, -0.03125d, 1.03125d, 0.125d), + new SimpleCuboid(-0.03125d, 0.71875d, 0.125d, -0.03125d, 0.78125d, 0.625d), + new SimpleCuboid(1.03125d, 0.71875d, 0.125d, 1.03125d, 0.78125d, 0.625d), + new SimpleCuboid(1.03125d, 0.71875d, 0.0625d, 1.03125d, 1.03125d, 0.125d), + new SimpleCuboid(-0.0625d, 0.625d, 0.375d, 0d, 0.875d, 0.625d), + new SimpleCuboid(1d, 0.625d, 0.375d, 1.0625d, 0.875d, 0.625d), + new SimpleCuboid(0.0625d, 0.5625d, 0.061875d, 0.9375d, 0.9375d, 0.061875d) + ); + + public static final BlockShape POWERED_BLAST_FURNACE = new BlockShape( + new SimpleCuboid(0.0625d, 0.625d, 0.6875d, 0.9375d, 0.6875d, 0.9375d), + new SimpleCuboid(0.0625d, 0.5d, 0.6875d, 0.9375d, 0.5625d, 0.9375d), + new SimpleCuboid(0.0625d, 0.375d, 0.6875d, 0.9375d, 0.4375d, 0.9375d), + new SimpleCuboid(0d, 0d, 0d, 1d, 0.3125d, 1d), + new SimpleCuboid(0d, 0.3125d, 0.375d, 1d, 1d, 0.6875d), + new SimpleCuboid(0d, 0.75d, 0.6875d, 1d, 1d, 1d), + new SimpleCuboid(0.125d, 0.3125d, 0.6875d, 0.875d, 0.75d, 0.875d), + new SimpleCuboid(0.1875d, 0.3125d, 0d, 0.8125d, 1d, 0.375d), + new SimpleCuboid(0.25d, 1d, 0.25d, 0.75d, 1.0625d, 0.75d), + new SimpleCuboid(0.03125d, 0.3125d, 0.65625d, 0.96875d, 0.5d, 0.96875d), + new SimpleCuboid(0.96875d, 0.3125d, 0.65625d, 0.03125d, 0.75d, 0.96875d), + new SimpleCuboid(0.8125d, 0.3125d, 0.0625d, 0.9375d, 0.9375d, 0.375d), + new SimpleCuboid(0.0625d, 0.3125d, 0.0625d, 0.1875d, 0.9375d, 0.375d), + new SimpleCuboid(0.061875d, 0.5625d, 0.125d, 0.061875d, 0.625d, 0.3125d), + new SimpleCuboid(0.061875d, 0.6875d, 0.125d, 0.061875d, 0.75d, 0.3125d), + new SimpleCuboid(0.061875d, 0.8125d, 0.125d, 0.061875d, 0.875d, 0.3125d), + new SimpleCuboid(0.938125d, 0.8125d, 0.125d, 0.938125d, 0.875d, 0.3125d), + new SimpleCuboid(0.938125d, 0.5625d, 0.125d, 0.938125d, 0.625d, 0.3125d), + new SimpleCuboid(0.938125d, 0.6875d, 0.125d, 0.938125d, 0.75d, 0.3125d), + new SimpleCuboid(0.3125d, 1.000625d, 0.125d, 0.6875d, 1.000625d, 0.1875d) + ); + } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ClientConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ClientConfig.kt deleted file mode 100644 index 5f1638064..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ClientConfig.kt +++ /dev/null @@ -1,34 +0,0 @@ -package ru.dbotthepony.mc.otm - -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.fml.ModLoadingContext -import net.minecraftforge.fml.config.ModConfig - -object ClientConfig { - private val specBuilder = ForgeConfigSpec.Builder() - @Suppress("JoinDeclarationAndAssignment") - private val spec: ForgeConfigSpec - private var registered = false - - var EXOPACK_INVENTORY_ROWS: Int by specBuilder - .comment("Amount of inventory rows to show when wearing Exosuit") - .defineInRange("exosuitInventoryRows", 3, 3, 6) - - var JUMP_BOOST_LOOK_ANGLE: Double by specBuilder - .comment("If looking below this angle (actually, looking 'above' as you see in game, but not as you expect it, check with debug screen), Crouch + Jump will trigger jump boost android ability") - .defineInRange("jumpBoostTriggerAngle", 30.0, -180.0, 180.0) - - var EXOPACK_FREE_SCROLL: Boolean by specBuilder - .comment("Allow to scroll Exopack inventory in non OTM inventories when hovering just over inventory slots, not only scrollbar") - .define("exopackFreeScroll", true) - - init { - spec = specBuilder.build() - } - - fun register() { - check(!registered) { "Already registered config" } - registered = true - ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, spec) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index 2a0ca3784..a8f8c9096 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -15,44 +15,73 @@ import net.minecraftforge.event.server.ServerStoppedEvent import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.fml.loading.FMLLoader import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.IConditionalTickable -import ru.dbotthepony.mc.otm.core.ITickable -import ru.dbotthepony.mc.otm.core.TickList -import ru.dbotthepony.mc.otm.core.TimerQueue +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import ru.dbotthepony.mc.otm.core.util.AtomicallyInvalidatedLazy +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable +import ru.dbotthepony.mc.otm.core.util.TickList +import ru.dbotthepony.mc.otm.graph.GraphNodeList +import ru.dbotthepony.mc.otm.network.MatteryNetworkChannel import java.util.* +import java.util.concurrent.atomic.AtomicInteger private val preServerTick = TickList() private val postServerTick = TickList() private val preWorldTick = WeakHashMap() private val postWorldTick = WeakHashMap() -private val preServerTickTimers = TimerQueue() -private val postServerTickTimers = TimerQueue() +private val clientThreads = WeakHashSet() +private val serverThreads = WeakHashSet() -fun onceServerPre(inTicks: Int, callback: Runnable): TimerQueue.Timer? { +private val serverCounter = AtomicInteger() +private var _server: MinecraftServer? = null +val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT } + +val UNIVERSE_TICKS get() = postServerTick.ticks +val Level.ticksPassed get() = postWorldTick.computeIfAbsent(this) { TickList() }.ticks + +fun lazyPerServer(fn: (MinecraftServer) -> V): Lazy { + return AtomicallyInvalidatedLazy(serverCounter) { + if (!SERVER_IS_LIVE) + throw IllegalStateException("No server is running") + + fn.invoke(_server!!) + } +} + +fun onceServerPre(inTicks: Int, callback: Runnable): TickList.Timer? { if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add timer $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping")) return null } - return preServerTickTimers.Timer(inTicks, callback) + return preServerTick.Timer(inTicks, callback) } -fun onceServer(inTicks: Int, callback: Runnable): TimerQueue.Timer? { +fun onceServer(inTicks: Int, callback: Runnable): TickList.Timer? { if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add ticker $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping")) return null } - return postServerTickTimers.Timer(inTicks, callback) + return postServerTick.Timer(inTicks, callback) } -private var _server: MinecraftServer? = null -private var _serverThread: Thread? = null -private var _clientThread: Thread? = null +/** + * schedules execution of Runnable somewhere in the future, + * at discretion of tick list + */ +fun sometimeServer(callback: Runnable): TickList.Timer? { + if (!SERVER_IS_LIVE) { + LOGGER.error("Refusing to add ticker $callback while server is dying", IllegalStateException("Server is stopping")) + return null + } -val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT } + return postServerTick.sometime(callback) +} private val isPausedImpl: Boolean get() { val server = _server @@ -65,7 +94,7 @@ private val isPausedImpl: Boolean get() { } val isPaused: Boolean get() { - if (_clientThread === null) { + if (clientThreads.isEmpty()) { return false } @@ -73,11 +102,7 @@ val isPaused: Boolean get() { } fun recordClientThread() { - if (_clientThread != null) { - throw IllegalStateException("Already have client channel") - } - - _clientThread = Thread.currentThread() + clientThreads.add(Thread.currentThread()) } fun runIfClient(lambda: () -> Unit) { @@ -120,11 +145,11 @@ fun runOnClient(value: V, lambda: () -> V): V { } fun isServerThread(): Boolean { - return Thread.currentThread() === _serverThread + return Thread.currentThread() in serverThreads } fun isClientThread(): Boolean { - return Thread.currentThread() === _clientThread + return Thread.currentThread() in clientThreads } val MINECRAFT_SERVER: MinecraftServer @@ -146,28 +171,38 @@ private val LOGGER = LogManager.getLogger() fun onServerTick(event: ServerTickEvent) { if (event.phase === TickEvent.Phase.START) { - preServerTickTimers.tick() preServerTick.tick() + serverThreads.add(Thread.currentThread()) } else { - postServerTickTimers.tick() postServerTick.tick() + // чтоб не плодить кучу подписчиков, вызовем напрямую отсюда + GraphNodeList.tick() + AbstractProfiledStorage.onServerPostTick() + MatteryNetworkChannel.onServerPostTick() } } -fun onWorldTick(event: LevelTickEvent) { +fun onLevelTick(event: LevelTickEvent) { if (event.phase === TickEvent.Phase.START) { preWorldTick[event.level]?.tick() + + if (event.side.isClient) { + clientThreads.add(Thread.currentThread()) + } else if (event.side.isServer) { + serverThreads.add(Thread.currentThread()) + } } else { postWorldTick[event.level]?.tick() + MatteryBlockEntity.postLevelTick(event) } } fun onceServerPre(ticker: ITickable) { - preServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") + preServerTick.once(ticker, SERVER_IS_LIVE, "Server is stopping") } fun onceServer(ticker: ITickable) { - postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") + postServerTick.once(ticker, SERVER_IS_LIVE, "Server is stopping") } fun tickServerPre(ticker: IConditionalTickable) { @@ -178,41 +213,31 @@ fun tickServer(ticker: IConditionalTickable) { postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") } -fun tickUntilServerPre(ticker: () -> Boolean) { - preServerTick.until(ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickUntilServer(ticker: () -> Boolean) { - postServerTick.until(ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickWhileServerPre(condition: () -> Boolean, ticker: () -> Unit) { - preServerTick.`while`(condition, ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickWhileServer(condition: () -> Boolean, ticker: () -> Unit) { - postServerTick.`while`(condition, ticker, SERVER_IS_LIVE, "Server is stopping") -} - fun Level.once(ticker: ITickable) { + if (this.isClientSide) return + if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return } - postWorldTick.computeIfAbsent(this) { TickList() }.add(ticker) + postWorldTick.computeIfAbsent(this) { TickList() }.once(ticker) } fun Level.oncePre(ticker: ITickable) { + if (this.isClientSide) return + if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return } - preWorldTick.computeIfAbsent(this) { TickList() }.add(ticker) + preWorldTick.computeIfAbsent(this) { TickList() }.once(ticker) } fun Level.addTicker(ticker: IConditionalTickable) { + if (this.isClientSide) return + if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return @@ -222,6 +247,8 @@ fun Level.addTicker(ticker: IConditionalTickable) { } fun Level.addTickerPre(ticker: IConditionalTickable) { + if (this.isClientSide) return + if (!SERVER_IS_LIVE) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return @@ -231,24 +258,26 @@ fun Level.addTickerPre(ticker: IConditionalTickable) { } fun Level.until(ticker: () -> Boolean) { + if (this.isClientSide) return addTicker(IConditionalTickable.wrap(ticker)) } fun Level.untilPre(ticker: () -> Boolean) { + if (this.isClientSide) return addTickerPre(IConditionalTickable.wrap(ticker)) } fun Level.`while`(condition: () -> Boolean, ticker: () -> Unit) { + if (this.isClientSide) return addTicker(IConditionalTickable.wrap(condition, ticker)) } fun Level.whilePre(condition: () -> Boolean, ticker: () -> Unit) { + if (this.isClientSide) return addTickerPre(IConditionalTickable.wrap(condition, ticker)) } private fun clear() { - preServerTickTimers.clear() - postServerTickTimers.clear() preServerTick.clear() postServerTick.clear() preWorldTick.clear() @@ -259,22 +288,27 @@ fun onServerStarting(event: ServerAboutToStartEvent) { clear() SERVER_IS_LIVE = true _server = event.server - _serverThread = Thread.currentThread() + serverThreads.add(Thread.currentThread()) + serverCounter.incrementAndGet() + MatteryNetworkChannel.onServerStarting() } fun onServerStopping(event: ServerStoppingEvent) { clear() SERVER_IS_LIVE = false + serverCounter.incrementAndGet() + MatteryNetworkChannel.onServerStopping() } fun onServerStopped(event: ServerStoppedEvent) { if (SERVER_IS_LIVE) { - LOGGER.fatal("ServerStoppingEvent did not fire. If server has crashed this is normal. However, if server finished it's work 'gracefully' this is a bug.") + LOGGER.fatal("ServerStoppingEvent did not fire. If server has crashed this is normal. However, if server finished it's work 'gracefully' this is a bug!") clear() SERVER_IS_LIVE = false } _server = null - _serverThread = null + serverCounter.incrementAndGet() + MatteryNetworkChannel.onServerStopped() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt index 0c2c192d0..7a9493536 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigList.kt @@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList import net.minecraft.resources.ResourceLocation import net.minecraftforge.common.ForgeConfigSpec import net.minecraftforge.registries.IForgeRegistry +import ru.dbotthepony.mc.otm.config.getValue import java.util.LinkedList abstract class ObservedConfigList(val parent: ForgeConfigSpec.ConfigValue>, private val allowNulls: Boolean = false) : AbstractMutableList(), RandomAccess { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt deleted file mode 100644 index 3bac5a3ce..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ServerConfig.kt +++ /dev/null @@ -1,270 +0,0 @@ -package ru.dbotthepony.mc.otm - -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.fml.ModLoadingContext -import net.minecraftforge.fml.config.ModConfig -import ru.dbotthepony.mc.otm.block.entity.AndroidStationBlockEntity -import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity -import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.matter.MatterDecomposerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.matter.MatterRecyclerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity -import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.defineDecimal -import ru.dbotthepony.mc.otm.item.EnergySwordItem -import ru.dbotthepony.mc.otm.registry.MNames - -interface VerboseBalanceValues { - val capacity: Decimal - val receive: Decimal - val extract: Decimal -} - -interface BatteryBalanceValues : VerboseBalanceValues { - val initialBatteryLevel: Decimal -} - -interface ConciseBalanceValues { - val capacity: Decimal - val throughput: Decimal -} - -object ServerConfig { - private val specBuilder = ForgeConfigSpec.Builder() - @Suppress("JoinDeclarationAndAssignment") - private val spec: ForgeConfigSpec - private var registered = false - - private fun verboseValues(name: String, storage: Decimal, receive: Decimal, extract: Decimal = receive): VerboseBalanceValues { - specBuilder.push(name) - - val obj = object : VerboseBalanceValues { - override val capacity: Decimal by specBuilder.defineDecimal("capacity", storage, minimum = Decimal.ONE) - override val receive: Decimal by specBuilder.defineDecimal("receive", receive, minimum = Decimal.ONE) - override val extract: Decimal by specBuilder.defineDecimal("extract", extract, minimum = Decimal.ONE) - } - - specBuilder.pop() - - return obj - } - - private fun batteryValues(name: String, storage: Decimal, receive: Decimal, extract: Decimal = receive, initialBatteryLevel: Decimal = Decimal.ZERO): BatteryBalanceValues { - specBuilder.push(name) - - val obj = object : BatteryBalanceValues { - override val capacity: Decimal by specBuilder.defineDecimal("capacity", storage, minimum = Decimal.ONE) - override val receive: Decimal by specBuilder.defineDecimal("receive", receive, minimum = Decimal.ONE) - override val extract: Decimal by specBuilder.defineDecimal("extract", extract, minimum = Decimal.ONE) - override val initialBatteryLevel: Decimal by specBuilder.defineDecimal("initialBatteryLevel", initialBatteryLevel, minimum = Decimal.ZERO) - } - - specBuilder.pop() - - return obj - } - - private fun conciseValues(name: String, storage: Decimal, throughput: Decimal): ConciseBalanceValues { - specBuilder.push(name) - - val obj = object : ConciseBalanceValues { - override val capacity: Decimal by specBuilder.defineDecimal("capacity", storage, minimum = Decimal.ONE) - override val throughput: Decimal by specBuilder.defineDecimal("throughput", throughput, minimum = Decimal.ONE) - } - - specBuilder.pop() - - return obj - } - - val LABORATORY_LAMP_LIGHT_LENGTH: Int by specBuilder.comment("In blocks").defineInRange("laboratoryLampLightLength", 6, 1, 128) - - init { - specBuilder.comment("Energy batteries balance values").push("energyBatteries") - } - - val BATTERY_CRUDE = batteryValues(MNames.BATTERY_CRUDE, Decimal(100_000), Decimal(160), Decimal(40), Decimal(80_000)) - val BATTERY_BASIC = batteryValues(MNames.BATTERY_BASIC, Decimal(400_000), Decimal(600)) - val BATTERY_NORMAL = batteryValues(MNames.BATTERY_NORMAL, Decimal(2_000_000), Decimal(1_000)) - val BATTERY_DENSE = batteryValues(MNames.BATTERY_DENSE, Decimal(10_000_000), Decimal(2_000)) - val BATTERY_CAPACITOR = batteryValues(MNames.BATTERY_CAPACITOR, Decimal(500_000), Decimal(50_000)) - - val QUANTUM_BATTERY = conciseValues(MNames.QUANTUM_BATTERY, Decimal(40_000_000), Decimal(10_000)) - val QUANTUM_CAPACITOR = conciseValues(MNames.QUANTUM_CAPACITOR, Decimal(1_000_000), Decimal(200_000)) - - val ZPM_BATTERY = conciseValues(MNames.ZPM_BATTERY, Decimal(200_000_000_000_000L), Decimal(200_000_000L)) - - init { - specBuilder.pop() - - specBuilder.comment("Matter capacitors and pattern drives balance values").push("matterCapacitorsAndDrives") - } - - val MATTER_CAPACITOR_BASIC by specBuilder.defineDecimal(MNames.MATTER_CAPACITOR_BASIC, Decimal(2_000), minimum = Decimal.ONE_TENTH) - val MATTER_CAPACITOR_NORMAL by specBuilder.defineDecimal(MNames.MATTER_CAPACITOR_NORMAL, Decimal(40_000), minimum = Decimal.ONE_TENTH) - val MATTER_CAPACITOR_DENSE by specBuilder.defineDecimal(MNames.MATTER_CAPACITOR_DENSE, Decimal(400_000), minimum = Decimal.ONE_TENTH) - - val MATTER_DUST_CAPACITY by specBuilder.comment("Maximal matter value one matter dust item can have").defineDecimal("matterDustCapacity", Decimal(2_000), minimum = Decimal.ONE_TENTH) - - val PATTERN_DRIVE_NORMAL: Int by specBuilder.defineInRange(MNames.PATTERN_DRIVE_NORMAL, 4, 1, Int.MAX_VALUE) - - init { - specBuilder.pop() - - specBuilder.comment("Balance values of machinery").push("machines") - - AndroidStationBlockEntity.registerConfig(specBuilder) - ChemicalGeneratorBlockEntity.registerConfig(specBuilder) - MatterRecyclerBlockEntity.registerConfig(specBuilder) - MatterBottlerBlockEntity.registerConfig(specBuilder) - MatterReplicatorBlockEntity.registerConfig(specBuilder) - MatterScannerBlockEntity.registerConfig(specBuilder) - MatterDecomposerBlockEntity.registerConfig(specBuilder) - } - - val PLATE_PRESS = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, MNames.PLATE_PRESS) - val STORAGE_POWER_SUPPLIER = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, MNames.STORAGE_POWER_SUPPLIER, capacity = Decimal(100_000), throughput = Decimal(320)) - val STORAGE_INTERFACES = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, "storage_interfaces", capacity = Decimal(10_000)) - val ITEM_MONITOR = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, MNames.ITEM_MONITOR) - val DRIVE_VIEWER = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, MNames.DRIVE_VIEWER) - val DRIVE_RACK = BlockEnergyStorageImpl.makeConfigEntry(specBuilder, MNames.DRIVE_RACK, capacity = Decimal(80_000)) - - init { - specBuilder.pop() - - specBuilder.comment("Tweaking of android players").push("androidPlayer") - } - - val REGENERATE_ENERGY: Boolean by specBuilder - .comment("If (technically) hunger is above threshold, it turns into energy") - .comment("This setting controls whenever to regenerate small amount of energy while eating as Android") - .comment("And also whenever to regenerate energy while in peaceful") - .comment("If this is disabled, any (technically) excess hunger will be nullified, unless playing on peaceful difficulty.") - .define("regenerateEnergy", true) - - object NanobotsRegeneration { - val COOLDOWN: List by specBuilder - .comment("In ticks, time between heal ticks") - .comment("One heal tick restores 1 heart (2 health points) at most") - .comment("If not getting hurt in specified period of ticks, heal tick takes place, tick timer resets to zero and THIS array' index advances by 1") - .comment("Index inside this array can not exceed of one of ability's") - .comment("") - .comment("Wording in pseudocode:") - .comment("if (ticksSinceTakingDamage >= cooldownConfigOption[healTicks /* or config's biggest index, whichever is smaller */]) {") - .comment(" healTicks = min(healTicks + 1, this.level /* ability level */)") - .comment(" ticksSinceTakingDamage = 0") - .comment(" this.ply.heal(...)") - .comment("}") - .defineList("cooldown", { mutableListOf(80, 60, 40, 20) }) { it is Int } - - val ENERGY_PER_HITPOINT by specBuilder - .comment("Energy required to regenerate 1 health point (half a heart)") - .defineDecimal("energyPerHitpoint", Decimal(800)) - } - - val ANDROID_ENERGY_PER_HUNGER_POINT by specBuilder.defineDecimal("energyPerHunger", Decimal(2000), Decimal.ZERO) - val ANDROID_MAX_ENERGY by specBuilder.comment("Internal battery of every android has this much storage").defineDecimal("capacity", Decimal(80_000), Decimal.ZERO) - val NIGHT_VISION_POWER_DRAW by specBuilder.defineDecimal("nightVisionPowerDraw", Decimal(8), Decimal.ZERO) - val FALL_DAMAGE_REDUCTION_PER_LEVEL: Double by specBuilder.comment("In percent. Level of feature is multiplied by this").defineInRange("fallDamageReductionPerDampenerLevel", 0.25, 0.01, 1.0) - - object EnderTeleporter { - init { - specBuilder.comment("Ender Teleporter ability").push("ender_teleporter") - } - - val ENERGY_COST by specBuilder.defineDecimal("energyCost", Decimal(4096), Decimal.ZERO) - val COOLDOWN: Int by specBuilder.comment("In ticks").defineInRange("cooldown", 40, 0, Int.MAX_VALUE) - val MAX_PHASE_DISTANCE: Int by specBuilder.comment("Determines how much blocks can we 'phase' through to teleport on solid surface").defineInRange("maxPhaseDistance", 6, 0, Int.MAX_VALUE) - val MAX_DISTANCE: Double by specBuilder.comment("In blocks, euclidean distance").defineInRange("maxDistance", 12.0, 2.0, Int.MAX_VALUE.toDouble()) - - init { - specBuilder.pop() - } - } - - object AndroidJumpBoost { - init { - specBuilder.comment("Jump boost ability").push("jump_boost") - } - - val ENERGY_COST by specBuilder.defineDecimal("energyCost", Decimal(1024), Decimal.ZERO) - val POWER: Double by specBuilder.comment("The jump height on jump boost, as (level + 1) of feature, in meters per second").defineInRange("power", 6.0, 0.0, Double.MAX_VALUE) - val BASE_COOLDOWN: Int by specBuilder.comment("In ticks").defineInRange("baseCooldown", 40, 0, Int.MAX_VALUE) - val COOLDOWN_REDUCTION: Int by specBuilder.comment("In ticks, per level of feature").defineInRange("cooldownReduction", 20, 0, Int.MAX_VALUE) - - init { - specBuilder.pop() - } - } - - object AndroidItemMagnet { - init { - specBuilder.comment("Item magnet ability").push("item_magnet") - } - - val POWER_DRAW by specBuilder.comment("Per tick per stack").defineDecimal("powerDraw", Decimal(8), Decimal.ZERO) - val RADIUS_HORIZONTAL: Double by specBuilder.defineInRange("radiusHorizontal", 6.0, 0.0, Double.MAX_VALUE / 4.0) - val RADIUS_VERTICAL: Double by specBuilder.defineInRange("radiusVertical", 3.0, 0.0, Double.MAX_VALUE / 4.0) - - init { - specBuilder.pop() - } - } - - object Shockwave { - init { - specBuilder.comment("Shockwave ability").push("shockwave") - } - - val TERMINAL_VELOCITY: Double by specBuilder.comment("In meters per second vertically").defineInRange("terminalVelocity", 5.6, 0.0) - val ACCELERATION: Double by specBuilder.comment("In meters per second vertically").defineInRange("acceleration", 4.0, 0.0) - val COOLDOWN: Int by specBuilder.comment("In ticks").defineInRange("cooldown", 30, 1) - val RADIUS_HORIZONTAL: Double by specBuilder.comment("In meters").defineInRange("radiusHorizontal", 4.0, 0.0) - val RADIUS_VERTICAL: Double by specBuilder.comment("In meters").defineInRange("radiusVertical", 1.0, 0.0) - - val RADIUS_HORIZONTAL_WARDEN: Double by specBuilder.comment("In meters, when searching for Warden").defineInRange("radiusHorizontalWarden", 16.0, 0.0) - val RADIUS_VERTICAL_WARDEN: Double by specBuilder.comment("In meters, when searching for Warden").defineInRange("radiusVerticalWarden", 6.0, 0.0) - - val BREAK_BLOCKS: Boolean by specBuilder.comment("Break blocks without any blast resistance").define("breakBlocks", true) - val DAMAGE: Double by specBuilder.comment("Max potential damage done by shockwave").defineInRange("damage", 12.0, 0.0, Float.MAX_VALUE.toDouble()) - val WARDEN_DAMAGE_MULT: Double by specBuilder.defineInRange("wardenDamageMultiplier", 4.0, 0.0, Float.MAX_VALUE.toDouble()) - val ENERGY_COST by specBuilder.defineDecimal("energyCost", Decimal(2048), Decimal.ZERO) - - init { - specBuilder.pop() - } - } - - init { - // access instances so spec is built - NanobotsRegeneration - EnderTeleporter - AndroidJumpBoost - AndroidItemMagnet - Shockwave - - specBuilder.pop() - - specBuilder.comment("Tweaking of exosuits").push("exosuitPlayer") - } - - val INFINITE_EXOSUIT_UPGRADES: Boolean by specBuilder.comment("Allows to apply the same upgrade over and over again.", "Obviously completely breaks balance.").define("infinite_upgrades", false) - - init { - specBuilder.pop() - - EnergySwordItem.registerConfig(specBuilder) - } - - init { - spec = specBuilder.build() - } - - fun register() { - check(!registered) { "Already registered config" } - registered = true - ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, spec) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/SystemTime.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/SystemTime.kt index bd8a5b7ee..2f831c00b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/SystemTime.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/SystemTime.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.mc.otm import net.minecraft.util.TimeSource -import ru.dbotthepony.mc.otm.core.formatTickDuration +import ru.dbotthepony.mc.otm.core.util.formatTickDuration import java.util.function.LongSupplier /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt index 80decd149..a681d2a20 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidFeature.kt @@ -3,11 +3,11 @@ package ru.dbotthepony.mc.otm.android import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import net.minecraft.nbt.CompoundTag import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.event.entity.living.LivingAttackEvent import net.minecraftforge.event.entity.living.LivingHurtEvent import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import java.io.DataInputStream +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer import java.io.InputStream abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayerCapability) : INBTSerializable { @@ -35,13 +35,14 @@ abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: Matt open fun removeModifiers() {} open fun onHurt(event: LivingHurtEvent) {} + open fun onAttack(event: LivingAttackEvent) {} open fun collectNetworkPayload(): FastByteArrayOutputStream? { return synchronizer.collectNetworkPayload() } open fun applyNetworkPayload(stream: InputStream) { - synchronizer.applyNetworkPayload(stream) + synchronizer.read(stream) } override fun serializeNBT(): CompoundTag { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index 0b1fc9484..563f0e293 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -1,44 +1,64 @@ package ru.dbotthepony.mc.otm.android import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import net.minecraft.ChatFormatting import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.ListTag import net.minecraft.network.chat.Component -import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.eventbus.api.Cancelable +import net.minecraftforge.eventbus.api.Event +import net.minecraftforge.eventbus.api.Event.HasResult import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.awareItemsStream -import ru.dbotthepony.mc.otm.capability.itemsStream -import ru.dbotthepony.mc.otm.client.render.SkinElement -import ru.dbotthepony.mc.otm.container.iterator import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.addAll -import ru.dbotthepony.mc.otm.core.getCompoundList -import ru.dbotthepony.mc.otm.core.nonEmpty import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.milliTime -import ru.dbotthepony.mc.otm.nanoTime -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger -import java.io.DataInputStream import java.io.InputStream import kotlin.math.absoluteValue class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlayerCapability) : INBTSerializable { + /** + * Fired on main event bus [MinecraftForge.EVENT_BUS] + */ + data class OnResearched(val research: AndroidResearch) : Event() + + /** + * Fired on main event bus [MinecraftForge.EVENT_BUS] + */ + data class OnUnResearched(val research: AndroidResearch) : Event() + + /** + * Fired on main event bus [MinecraftForge.EVENT_BUS] + */ + data class OnRefunded(val research: AndroidResearch) : Event() + + /** + * Fired on main event bus [MinecraftForge.EVENT_BUS] + */ + data class GatherTooltipsEvent(val research: AndroidResearch, val tooltips: MutableList) : Event() + + /** + * Fired on main event bus [MinecraftForge.EVENT_BUS] + */ + @HasResult + data class ConsumeResearchCost(val research: AndroidResearch, val isSimulating: Boolean) : Event() + val ply: Player get() = capability.ply val synchronizer = FieldSynchronizer() - var isResearched by synchronizer.bool() + var isResearched by synchronizer.bool().property + private set + + var tag = CompoundTag() private set /** @@ -53,63 +73,24 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay return } - onUnResearch() + onUnResearched() isResearched = false } - private data class RememberResearchLevel(val level: Int?) - - private val oldResearchLevel = Object2ObjectArrayMap, RememberResearchLevel>() - - fun onUnResearch() { - for (feature in type.resolvedFeatures) { - val level = oldResearchLevel[feature.feature] - val get = capability.getFeature(feature.feature) - - if (level != null && get != null) { - if (get.level == feature.level) { - if (level.level == null) { - capability.removeFeature(feature.feature) - } else { - get.level = level.level - - for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersDown) { - transformer.apply(this to get) - } - } - } - } + fun onUnResearched() { + for (result in type.results) { + result.onUnResearched(this) } - oldResearchLevel.clear() + MinecraftForge.EVENT_BUS.post(OnUnResearched(this)) } fun onResearched() { - oldResearchLevel.clear() - - try { - for (feature in type.resolvedFeatures) { - var get = capability.getFeature(feature.feature) - - if (get == null) { - get = capability.addFeature(feature.feature) - get.level = feature.level - oldResearchLevel[feature.feature] = RememberResearchLevel(null) - } else { - if (get.level < feature.level) { - oldResearchLevel[feature.feature] = RememberResearchLevel(feature.level) - get.level = feature.level - } - } - - for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersUp) { - transformer.apply(this to get) - } - } - } catch(err: Throwable) { - oldResearchLevel.clear() - throw err + for (result in type.results) { + result.onResearched(this) } + + MinecraftForge.EVENT_BUS.post(OnResearched(this)) } /** @@ -125,6 +106,15 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay return true } + val event = ConsumeResearchCost(this, simulate) + MinecraftForge.EVENT_BUS.post(event) + + if (event.result == Event.Result.ALLOW) { + return true + } else if (event.result == Event.Result.DENY) { + return false + } + if (!simulate && !consumeResearchCost(true)) { return false } @@ -166,11 +156,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay * * Returns true whenever player accepted all resources refunded, false otherwise. */ - fun refund(simulate: Boolean): Boolean { - if (simulate) { - return true - } - + fun refund(): Boolean { if (type.experienceLevels > 0) { var experiencePoints = 0 @@ -195,6 +181,8 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay } } + MinecraftForge.EVENT_BUS.post(OnRefunded(this)) + return true } @@ -203,7 +191,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay } fun applyNetworkPayload(stream: InputStream) { - synchronizer.applyNetworkPayload(stream) + synchronizer.read(stream) } val canResearch: Boolean get() { @@ -304,7 +292,12 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay val tooltipLines: List get() { val lines = ArrayList() lines.add(type.displayName) - lines.addAll(type.description.iterator()) + + for (line in type.description) { + line.addLines(this, lines) + } + + MinecraftForge.EVENT_BUS.post(GatherTooltipsEvent(this, lines)) return lines } @@ -389,38 +382,12 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay override fun serializeNBT(): CompoundTag { return CompoundTag().also { it["researched"] = isResearched - - it["oldResearchLevel"] = ListTag().also { - for ((k, v) in oldResearchLevel) { - it.add(CompoundTag().also { - it["key"] = k.registryName!!.toString() - it["value"] = CompoundTag().also { - it["isPresent"] = v.level != null - - if (v.level != null) { - it["value"] = v.level - } - } - }) - } - } + it["tag"] = tag } } override fun deserializeNBT(nbt: CompoundTag) { isResearched = nbt.getBoolean("researched") - - oldResearchLevel.clear() - - for (tag in nbt.getCompoundList("oldResearchLevel")) { - val key = tag.getString("key") - val type = MRegistry.ANDROID_FEATURES.getValue(ResourceLocation(key)) ?: continue - val value = tag.getCompound("value") - - val isPresent = value.getBoolean("isPresent") - val int = value.getInt("value") - - oldResearchLevel[type] = RememberResearchLevel(if (isPresent) int else null) - } + tag = nbt.getCompound("tag") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt index 550c2a973..98d175bfc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDataProvider.kt @@ -1,17 +1,34 @@ package ru.dbotthepony.mc.otm.android +import com.google.gson.JsonObject import it.unimi.dsi.fastutil.objects.ObjectArraySet import net.minecraft.data.CachedOutput -import net.minecraft.data.DataGenerator import net.minecraft.data.DataProvider +import net.minecraft.data.PackOutput import net.minecraft.resources.ResourceLocation +import net.minecraftforge.data.event.GatherDataEvent +import ru.dbotthepony.mc.otm.core.toJson +import ru.dbotthepony.mc.otm.core.toJsonStrict +import ru.dbotthepony.mc.otm.core.util.WriteOnce import java.util.Collections import java.util.LinkedList +import java.util.concurrent.CompletableFuture import java.util.function.Consumer @Suppress("unused") -open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerator) : DataProvider { - protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, AndroidResearchManager.DIRECTORY) +open class AndroidResearchDataProvider() : DataProvider { + var pathProvider: PackOutput.PathProvider by WriteOnce("You need to call bindPackOutput before registering this data provider") + private set + + constructor(output: PackOutput) : this() { + bindPackOutput(output) + } + + constructor(event: GatherDataEvent) : this(event.generator.packOutput) + + fun bindPackOutput(output: PackOutput) { + pathProvider = output.createPathProvider(PackOutput.Target.DATA_PACK, AndroidResearchManager.DIRECTORY) + } protected val callbacks = LinkedList<(Consumer) -> Unit>() @@ -35,15 +52,14 @@ open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerato return this } - final override fun run(output: CachedOutput) { - AndroidResearchManager.fireRegistrationEvent() - + final override fun run(output: CachedOutput): CompletableFuture<*> { val set = ObjectArraySet() val added = LinkedList() + val futures = ArrayList>() addEverything { if (set.add(it.id)) { - DataProvider.saveStable(output, it.toJson(), pathProvider.json(it.id)) + futures.add(DataProvider.saveStable(output, AndroidResearchType.CODEC.toJsonStrict(it).also { (it as JsonObject).remove("id") }, pathProvider.json(it.id))) AndroidResearchManager.put(it) added.add(it) } else { @@ -55,6 +71,8 @@ open class AndroidResearchDataProvider(protected val dataGenerator: DataGenerato value.validate() generated.add(value) } + + return CompletableFuture.allOf(*futures.toTypedArray()) } override fun getName(): String { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt new file mode 100644 index 000000000..5c071f633 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchDescription.kt @@ -0,0 +1,147 @@ +package ru.dbotthepony.mc.otm.android + +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.data.ComponentCodec +import ru.dbotthepony.mc.otm.data.SingletonCodec +import ru.dbotthepony.mc.otm.data.simpleCodec +import ru.dbotthepony.mc.otm.registry.RegistryDelegate + +object AndroidResearchDescriptions { + private val registrar = DeferredRegister.create(AndroidResearchDescription.registryKey, OverdriveThatMatters.MOD_ID) + + init { + registrar.register("plain") { PlainAndroidResearchDescription } + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + } + + val ENDER_TELEPORTER: AndroidResearchDescription.Singleton by registrar.register("ender_teleporter") { + AndroidResearchDescription.singleton { + TranslatableComponent("otm.gui.power_cost_per_use", AndroidConfig.EnderTeleporter.ENERGY_COST.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.YELLOW)) } + } + + val FALL_DAMPENERS: AndroidResearchDescription.Leveled by registrar.register("fall_dampeners") { + AndroidResearchDescription.Leveled { _, list, level -> + list.add(TranslatableComponent("otm.fall_dampeners.description", + TextComponent("%.1f".format((AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_P * level).toFloat().coerceIn(0f, 1f) * 100f)).withStyle(ChatFormatting.YELLOW), + TextComponent("%.1f".format((AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_F * level).toFloat())).withStyle(ChatFormatting.YELLOW), + )) } + } + + val SWIM_BOOSTERS: AndroidResearchDescription.Leveled by registrar.register("swim_boosters") { + AndroidResearchDescription.Leveled { _, list, level -> + list.add(TranslatableComponent( + "android_research.overdrive_that_matters.swim_boosters.description", + TextComponent("%.1f".format(AndroidConfig.SWIM_BOOSTERS * (1 + level) * 100.0)).withStyle(ChatFormatting.YELLOW) + )) } + } + + val ITEM_MAGNET: AndroidResearchDescription.Singleton by registrar.register("item_magnet") { + AndroidResearchDescription.singleton { TranslatableComponent("otm.gui.power_cost_per_tick", AndroidConfig.Magnet.POWER_DRAW.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.YELLOW)) } + } + + val JUMP_BOOST: AndroidResearchDescription.Singleton by registrar.register("jump_boost") { + AndroidResearchDescription.singleton { TranslatableComponent("otm.gui.power_cost_per_use", AndroidConfig.JumpBoost.ENERGY_COST.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.YELLOW)) } + } + + val SHOCKWAVE: AndroidResearchDescription.Singleton by registrar.register("shockwave") { + AndroidResearchDescription.singleton { TranslatableComponent("otm.gui.power_cost_per_use", AndroidConfig.Shockwave.ENERGY_COST.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.YELLOW)) } + } +} + +/** + * Instance, representing ready to use description populator + */ +interface AndroidResearchDescription { + /** + * Type, representing raw description populator in registry + */ + interface Type { + val codec: Codec + } + + abstract class Singleton : AndroidResearchDescription, Type { + override val codec = SingletonCodec(this) + override val type: Type<*> + get() = this + } + + class Leveled(val callback: (research: AndroidResearch, lines: MutableList, level: Int) -> Unit) : Type { + inner class Instance(val level: Int) : AndroidResearchDescription { + override fun addLines(research: AndroidResearch, lines: MutableList) { + callback.invoke(research, lines, level) + } + + override val type: Type<*> + get() = this@Leveled + } + + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group(Codec.INT.fieldOf("level").forGetter(Instance::level)).apply(it, ::Instance) + } + } + } + + fun addLines(research: AndroidResearch, lines: MutableList) + val type: Type<*> + + companion object { + private val delegate = RegistryDelegate>("android_research_description") { + disableSaving() + } + + val registry by delegate + val registryKey get() = delegate.key + + val CODEC: Codec by lazy { + registry.codec.dispatch({ it.type }, { it.codec }) + } + + internal fun register(bus: IEventBus) { + bus.addListener(delegate::build) + } + + fun singleton(callback: (research: AndroidResearch) -> Component): Singleton { + return object : Singleton() { + override fun addLines(research: AndroidResearch, lines: MutableList) { + lines.add(callback.invoke(research)) + } + } + } + } +} + +object PlainAndroidResearchDescription : AndroidResearchDescription.Type { + data class Instance(val line: Component) : AndroidResearchDescription { + override fun addLines(research: AndroidResearch, lines: MutableList) { + lines.add(line.copy()) + } + + override val type: PlainAndroidResearchDescription + get() = PlainAndroidResearchDescription + } + + fun make(line: Component) = Instance(line) + + override val codec: Codec by lazy { + Codec + .either(ComponentCodec, simpleCodec(::Instance, Instance::line, ComponentCodec)) + .xmap({ c -> c.map({ Instance(it) }, { it }) }, { c -> Either.left(c.line) }) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt index bddc41cd8..86eaa2846 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchManager.kt @@ -4,18 +4,15 @@ import com.google.common.collect.ImmutableMap import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonObject +import com.google.gson.JsonSyntaxException import net.minecraft.client.server.IntegratedServer import net.minecraft.network.FriendlyByteBuf -import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.server.packs.resources.ResourceManager import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.util.profiling.ProfilerFiller -import net.minecraftforge.common.MinecraftForge import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.OnDatapackSyncEvent -import net.minecraftforge.eventbus.api.Event -import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.MINECRAFT_SERVER @@ -23,57 +20,17 @@ import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry +import ru.dbotthepony.mc.otm.core.fromJsonStrict +import ru.dbotthepony.mc.otm.core.fromNetwork +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.toNetwork +import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel -import ru.dbotthepony.mc.otm.network.enqueueWork -import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.onceServer import java.util.LinkedList -import java.util.function.Supplier - -typealias AndroidResultTransformer = SerializedFunctionRegistry.BoundFunction, Unit> -typealias ComponentSupplier = SerializedFunctionRegistry.BoundFunction object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable { - /** - * Feel free to register functions inside this thing from anywhere in your code - * (registration and querying is completely thread safe). - * - * Just make sure client and server has the same set of functions defined. - */ - val featureResultTransformers = SerializedFunctionRegistry, Unit>() - - /** - * Feel free to register functions inside this thing from anywhere in your code - * (registration and querying is completely thread safe). - * - * Just make sure client and server has the same set of functions defined. - */ - val descriptionFuncs = SerializedFunctionRegistry() - - fun descriptionFunc(name: ResourceLocation, base: String, vararg argument: Supplier): ComponentSupplier { - return descriptionFuncs.register(name) {-> - return@register TranslatableComponent(base, *argument.map { it.get() }.toTypedArray()) - }.bind() - } - - private var firedRegistrationEvent = false - - /** - * Event-style registration of serializable functions, for those who prefer/need it - * - * Fired *once* on [MinecraftForge.EVENT_BUS] before loading android research - */ - object RegisterFuncsEvent : Event() { - val manager get() = AndroidResearchManager - val featureResults by ::featureResultTransformers - val descriptionFunctions by ::descriptionFuncs - - fun descriptionFunc(name: ResourceLocation, base: String, vararg argument: Supplier): ComponentSupplier = AndroidResearchManager.descriptionFunc(name, base, *argument) - } - const val DIRECTORY = "otm_android_research" private val LOGGER = LogManager.getLogger() @@ -97,23 +54,11 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s var researchMap: Map = mapOf() private set - internal fun fireRegistrationEvent() { - if (!firedRegistrationEvent) { - try { - MinecraftForge.EVENT_BUS.post(RegisterFuncsEvent) - } finally { - firedRegistrationEvent = true - } - } - } - override fun apply( jsonElementMap: Map, manager: ResourceManager, profiler: ProfilerFiller ) { - fireRegistrationEvent() - val builder = ImmutableMap.builder() for ((k, v) in jsonElementMap) { @@ -122,7 +67,13 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s continue } - builder.put(k, AndroidResearchType.fromJson(v, k)) + v["id"] = k.toString() + + try { + builder.put(k, AndroidResearchType.CODEC.fromJsonStrict(v)) + } catch(err: RuntimeException) { + throw JsonSyntaxException("Caught an exception while decoding android research $k", err) + } } researchMap = builder.build() @@ -148,23 +99,23 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s val packet = SyncPacket(researchMap.values) if (event.player != null) { - RegistryNetworkChannel.send(event.player!!, packet) + GenericNetworkChannel.send(event.player!!, packet) } else { - RegistryNetworkChannel.send(PacketDistributor.ALL.noArg(), packet) + GenericNetworkChannel.send(PacketDistributor.ALL.noArg(), packet) } } class SyncPacket(val collection: Collection) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { - buff.writeCollection(collection) { a, b -> b.toNetwork(a) } + buff.writeCollection(collection) { a, b -> AndroidResearchType.CODEC.toNetwork(a, b) } + LOGGER.debug("Constructed android research registry packet, ${buff.writerIndex()} bytes in size") } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { context.packetHandled = true - if (NULLABLE_MINECRAFT_SERVER is IntegratedServer) { + if (NULLABLE_MINECRAFT_SERVER is IntegratedServer) return - } val builder = ImmutableMap.builder() @@ -185,6 +136,7 @@ object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().s } fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket { - return SyncPacket(buff.readCollection({ LinkedList() }, AndroidResearchType.Companion::fromNetwork)) + LOGGER.info("Received android research registry packet, ${buff.readableBytes()} bytes in size") + return SyncPacket(buff.readCollection({ LinkedList() }, { AndroidResearchType.CODEC.fromNetwork(it) })) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt new file mode 100644 index 000000000..06f430dd2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchResult.kt @@ -0,0 +1,182 @@ +package ru.dbotthepony.mc.otm.android + +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.data.SingletonCodec +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.registry.RegistryDelegate + +object AndroidResearchResults { + private val registrar = DeferredRegister.create(AndroidResearchResult.registryKey, OverdriveThatMatters.MOD_ID) + + init { + registrar.register("feature") { AndroidResearchResult.Feature.Companion } + registrar.register("feature_level") { AndroidResearchResult.FeatureLevel.Companion } + } + + private object NanobotsArmorStrength : AndroidResearchResult.Singleton { + override val codec: Codec = SingletonCodec(this) + override val type: AndroidResearchResult.Type<*> + get() = this + + override fun onResearched(research: AndroidResearch) { + val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return + feature.strength++ + } + + override fun onUnResearched(research: AndroidResearch) { + val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return + feature.strength-- + } + } + + private object NanobotsArmorSpeed : AndroidResearchResult.Singleton { + override val codec: Codec = SingletonCodec(this) + override val type: AndroidResearchResult.Type<*> + get() = this + + override fun onResearched(research: AndroidResearch) { + val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return + feature.speed++ + } + + override fun onUnResearched(research: AndroidResearch) { + val feature = research.capability.getFeature(AndroidFeatures.NANOBOTS_ARMOR) ?: return + feature.speed-- + } + } + + val NANOBOTS_ARMOR_STRENGTH: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_strength") { NanobotsArmorStrength } + val NANOBOTS_ARMOR_SPEED: AndroidResearchResult.Singleton<*> by registrar.register("nanobots_armor_speed") { NanobotsArmorSpeed } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + } +} + +interface AndroidResearchResult { + interface Type { + val codec: Codec + } + + interface Singleton> : Type, AndroidResearchResult + + /** + * Adds specific android feature [id] to target, does nothing if target already has specified feature + */ + class Feature(val id: ResourceLocation, val optional: Boolean = false) : AndroidResearchResult { + val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id") + + override val type: Type<*> + get() = Companion + + override fun onResearched(research: AndroidResearch) { + research.capability.addFeature(feature ?: return) + } + + override fun onUnResearched(research: AndroidResearch) { + research.capability.removeFeature(feature ?: return) + } + + companion object : Type { + override val codec: Codec by lazy { + Codec + .either(ResourceLocation.CODEC, RecordCodecBuilder.create { // KT-52757 + it.group( + ResourceLocation.CODEC.fieldOf("id").forGetter(Feature::id), + Codec.BOOL.optionalFieldOf("optional", false).forGetter(Feature::optional) + ).apply(it, ::Feature) + }) + .xmap( + { c -> c.map({ Feature(it) }, { it }) }, + { c -> if (c.optional) Either.left(c.id) else Either.right(c) } + ) + } + } + } + + /** + * Increases level of specific android feature [id] by specified amount [levels] + */ + class FeatureLevel(val id: ResourceLocation, val optional: Boolean = false, val levels: Int = 1) : AndroidResearchResult { + val feature = MRegistry.ANDROID_FEATURES.getValue(id) ?: if (optional) null else throw NoSuchElementException("Unknown android feature $id") + + override val type: Type<*> + get() = Companion + + override fun onResearched(research: AndroidResearch) { + val get = research.capability.getFeature(feature ?: return) + + if (get == null) { + LOGGER.warn("Unable to advance level of android feature $id for ${research.ply} because they have no such android feature.") + } else { + get.level += levels + } + } + + override fun onUnResearched(research: AndroidResearch) { + val get = research.capability.getFeature(feature ?: return) + + if (get == null) { + LOGGER.warn("Unable to decrease level of android feature $id for ${research.ply} because they have no such android feature.") + } else { + get.level += levels + } + } + + companion object : Type { + override val codec: Codec by lazy { + Codec + .either(ResourceLocation.CODEC, RecordCodecBuilder.create { // KT-52757 + it.group( + ResourceLocation.CODEC.fieldOf("id").forGetter(FeatureLevel::id), + Codec.BOOL.optionalFieldOf("optional", false).forGetter(FeatureLevel::optional), + Codec.INT.optionalFieldOf("levels", 1).forGetter(FeatureLevel::levels), + ).apply(it, ::FeatureLevel) + }) + .xmap( + { c -> c.map({ FeatureLevel(it) }, { it }) }, + { c -> if (c.optional && c.levels == 1) Either.left(c.id) else Either.right(c) } + ) + } + } + } + + val type: Type<*> + + /** + * Called when research is applied + */ + fun onResearched(research: AndroidResearch) {} + + /** + * Called when research is refunded + */ + fun onUnResearched(research: AndroidResearch) {} + + companion object { + private val LOGGER = LogManager.getLogger() + private val delegate = RegistryDelegate>("android_research_result") { + disableSaving() + } + + val registry by delegate + val registryKey get() = delegate.key + + val CODEC: Codec by lazy { + registry.codec.dispatch({ it.type }, { it.codec }) + } + + internal fun register(bus: IEventBus) { + bus.addListener(delegate::build) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt index 7b5b67288..931d545c0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt @@ -1,14 +1,13 @@ package ru.dbotthepony.mc.otm.android import com.google.common.collect.ImmutableList -import com.google.common.collect.Streams -import com.google.gson.JsonArray -import com.google.gson.JsonElement import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException -import com.google.gson.internal.bind.TypeAdapters -import net.minecraft.network.FriendlyByteBuf +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.ListCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import net.minecraft.network.chat.Component import net.minecraft.network.chat.ComponentContents import net.minecraft.network.chat.MutableComponent @@ -17,30 +16,28 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.level.ItemLike import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.SkinElement -import ru.dbotthepony.mc.otm.client.render.SkinElementType -import ru.dbotthepony.mc.otm.core.ListSet +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.client.render.sprites.SpriteType +import ru.dbotthepony.mc.otm.core.collect.ListSet import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.isActuallyEmpty -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.toImmutableList -import ru.dbotthepony.mc.otm.data.stream +import ru.dbotthepony.mc.otm.data.ComponentCodec +import ru.dbotthepony.mc.otm.data.IngredientCodec +import ru.dbotthepony.mc.otm.data.JsonElementCodec import ru.dbotthepony.mc.otm.isClient -import ru.dbotthepony.mc.otm.registry.MRegistry -import java.util.LinkedList +import java.util.Optional +import java.util.function.Function import java.util.stream.Stream import kotlin.collections.ArrayList import kotlin.collections.HashSet +import kotlin.jvm.optionals.getOrNull private fun findPrerequisites( initial: Collection, - add: MutableSet = HashSet(), + add: MutableSet = ObjectOpenHashSet(), top: Boolean = true ): Set { for (value in initial) { @@ -56,7 +53,7 @@ private fun findPrerequisites( private fun findAllPrerequisites( initial: Collection, - add: MutableSet = HashSet(), + add: MutableSet = ObjectOpenHashSet(), ): Set { for (value in initial) { add.add(value) @@ -68,7 +65,7 @@ private fun findAllPrerequisites( private fun findAllChildren( initial: Collection, - add: MutableSet = HashSet(), + add: MutableSet = ObjectOpenHashSet(), ): Set { for (value in initial) { add.add(value) @@ -84,10 +81,9 @@ class AndroidResearchType( blockedBy: Collection, items: Collection>, - features: Collection, + results: Collection, - descriptionLines: Collection, - descriptionSuppliers: Collection = listOf(), + description: Collection, val experienceLevels: Int = 0, private val customName: Component? = null, @@ -97,123 +93,57 @@ class AndroidResearchType( val itemIcon: Item? = null, iconText: Component? = null, ) { + private constructor( + id: ResourceLocation, + prerequisites: Collection, + blockedBy: Collection, + + items: Collection>, + results: Collection, + + description: Collection, + + experienceLevels: Int, + customName: Optional, + + // ok + skinIcon: Optional, + itemIcon: Optional, + iconText: Optional, + ) : this(id, prerequisites, blockedBy, items, results, description, experienceLevels, customName.getOrNull(), skinIcon.getOrNull(), itemIcon.getOrNull(), iconText.getOrNull()) + private val iconTextValue = iconText?.copy() val iconText get() = iconTextValue?.copy() val resolvedSkinIcon by lazy { check(isClient) { "Invalid realm" } if (skinIcon != null) - SkinElementType.fromJson(skinIcon) + SpriteType.fromJson(skinIcon) else null } data class Reference( val id: ResourceLocation, - val isRigid: Boolean + val optional: Boolean = false ) { - fun toJson(): JsonObject { - return JsonObject().also { - it["id"] = JsonPrimitive(id.toString()) - it["is_rigid"] = JsonPrimitive(isRigid) - } - } - - fun toNetwork(buff: FriendlyByteBuf) { - buff.writeUtf(id.toString()) - buff.writeBoolean(isRigid) - } - companion object { - fun fromNetwork(buff: FriendlyByteBuf): Reference { - return Reference( - ResourceLocation(buff.readUtf()), - buff.readBoolean() - ) - } - - fun fromJson(value: JsonElement): Reference { - if (value is JsonPrimitive) { - return Reference(ResourceLocation(value.asString), true) - } else if (value is JsonObject) { - return Reference( - ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), - (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true) - } else { - throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") - } - } - } - } - - data class FeatureReference( - val id: ResourceLocation, - val level: Int = 0, - val isRigid: Boolean, - val transformersUp: Collection = listOf(), - val transformersDown: Collection = listOf(), - ) { - fun toJson(): JsonObject { - return JsonObject().also { - it["id"] = JsonPrimitive(id.toString()) - it["level"] = JsonPrimitive(level) - it["is_rigid"] = JsonPrimitive(isRigid) - it["functions_up"] = JsonArray().also { - for (transformer in transformersUp) { - it.add(transformer.toJson()) - } - } - - it["functions_down"] = JsonArray().also { - for (transformer in transformersDown) { - it.add(transformer.toJson()) - } - } - } - } - - fun toNetwork(buff: FriendlyByteBuf) { - buff.writeUtf(id.toString()) - buff.writeVarInt(level) - buff.writeBoolean(isRigid) - buff.writeCollection(transformersUp) { a, b -> b.toNetwork(a) } - buff.writeCollection(transformersDown) { a, b -> b.toNetwork(a) } - } - - companion object { - fun fromNetwork(buff: FriendlyByteBuf): FeatureReference { - return FeatureReference( - ResourceLocation(buff.readUtf()), - buff.readVarInt(), - buff.readBoolean(), - buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList(), - buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList() - ) - } - - fun fromJson(value: JsonElement): FeatureReference { - if (value is JsonPrimitive) { - return FeatureReference(ResourceLocation(value.asString), 0, true) - } else if (value is JsonObject) { - return FeatureReference( - ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), - (value["level"] as JsonPrimitive?)?.asInt ?: 0, - (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true, - (value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList()), - (value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList()), + val CODEC: Codec by lazy { + Codec + .either(ResourceLocation.CODEC, RecordCodecBuilder.create { + it.group( + ResourceLocation.CODEC.fieldOf("id").forGetter(Reference::id), + Codec.BOOL.optionalFieldOf("optional", false).forGetter(Reference::optional) + ).apply(it, ::Reference) + }) + .xmap( + { c -> c.map(::Reference, Function.identity()) }, + { c -> if (c.optional) Either.right(c) else Either.left(c.id) } ) - } else { - throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") - } } } } - data class ResolvedFeature( - val feature: AndroidFeatureType<*>, - val level: Int, - ) - val researchTreeDepth: Int by lazy { if (flatPrerequisites.isEmpty()) { return@lazy 0 @@ -242,38 +172,18 @@ class AndroidResearchType( val blockedBy: List = ImmutableList.copyOf(blockedBy) val resolvedPrerequisites: List by lazy { - ImmutableList.copyOf(this.prerequisites.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } }) + ImmutableList.copyOf(this.prerequisites.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.optional) throw NoSuchElementException("Unable to find research ${it.id}") } }) } val resolvedBlockedBy: List by lazy { - ImmutableList.copyOf(this.blockedBy.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.isRigid) throw NoSuchElementException("Unable to find research ${it.id}") } }) - } - - val features: List = ImmutableList.copyOf(features) - - val resolvedFeatures: List by lazy { - ImmutableList.copyOf(features.mapNotNull { - MRegistry.ANDROID_FEATURES.getValue(it.id) - .let { e -> if (e == null && it.isRigid) - throw NoSuchElementException("Unable to find research ${it.id}") - else if (e != null) - ResolvedFeature(e, it.level) - else - null } }) + ImmutableList.copyOf(this.blockedBy.mapNotNull { AndroidResearchManager[it.id].also { e -> if (e == null && it.optional) throw NoSuchElementException("Unable to find research ${it.id}") } }) } private val definedItems: List> = ImmutableList.copyOf(items) + val results: ImmutableList = ImmutableList.copyOf(results) val items: Stream> get() = definedItems.stream().filter { !it.first.isActuallyEmpty } - - private val descriptionLines: List = ImmutableList.copyOf(descriptionLines.map { it.copy() }) - - private val descriptionSuppliers: List = ImmutableList.copyOf(descriptionSuppliers) - - /** - * Stream containing copies of original [Component]s in list - */ - val description: Stream get() = Streams.concat(descriptionLines.stream().map { it.copy() }, descriptionSuppliers.stream().map { it.apply(Unit) }) + val description: ImmutableList = ImmutableList.copyOf(description) /** * Flat list of research preceding this research. @@ -468,152 +378,35 @@ class AndroidResearchType( return customName?.copy() ?: MutableComponent.create(displayContents) } - fun toJson(): JsonElement { - return JsonObject().also { - // it["id"] = JsonPrimitive(id.toString()) - - it["prerequisites"] = JsonArray().also { for (value in prerequisites) it.add(value.toJson()) } - it["blocked_by"] = JsonArray().also { for (value in blockedBy) it.add(value.toJson()) } - it["required_items"] = JsonArray().also { for (item in definedItems) it.add(JsonObject().also { it["count"] = JsonPrimitive(item.second); it["ingredient"] = item.first.toJson() }) } - it["feature_result"] = JsonArray().also { for (feature in features) it.add(feature.toJson()) } - it["description"] = JsonArray().also { for (line in descriptionLines) it.add(Component.Serializer.toJsonTree(line)) } - it["description_funcs"] = JsonArray().also { for (line in descriptionSuppliers) it.add(line.toJson()) } - it["experience"] = JsonPrimitive(experienceLevels) - - if (skinIcon != null) { - it["skin_icon"] = skinIcon - } - - if (itemIcon != null) { - it["item_icon"] = JsonPrimitive(itemIcon.registryName!!.toString()) - } - - if (iconTextValue != null) { - it["icon_text"] = Component.Serializer.toJsonTree(iconTextValue) - } - - if (customName != null) { - it["custom_name"] = Component.Serializer.toJsonTree(customName) - } - } - } - fun validate() { - resolvedFeatures resolvedBlockedBy resolvedPrerequisites } - fun toNetwork(buff: FriendlyByteBuf) { - buff.writeUtf(id.toString()) - buff.writeCollection(prerequisites) { a, b -> b.toNetwork(a) } - buff.writeCollection(blockedBy) { a, b -> b.toNetwork(a) } - buff.writeCollection(definedItems) { a, b -> b.first.toNetwork(a); a.writeVarInt(b.second) } - buff.writeCollection(features) { a, b -> b.toNetwork(a) } - buff.writeCollection(descriptionLines) { a, b -> a.writeComponent(b) } - buff.writeCollection(descriptionSuppliers) { a, b -> b.toNetwork(a) } - buff.writeVarInt(experienceLevels) - - buff.writeBoolean(customName != null) - if (customName != null) buff.writeComponent(customName) - - buff.writeBoolean(iconTextValue != null) - if (iconTextValue != null) buff.writeComponent(iconTextValue) - - buff.writeBoolean(skinIcon != null) - if (skinIcon != null) buff.writeUtf(skinIcon.toString()) - - buff.writeBoolean(itemIcon != null) - if (itemIcon != null) buff.writeUtf(itemIcon.registryName!!.toString()) - } - companion object { - fun fromNetwork(buff: FriendlyByteBuf): AndroidResearchType { - val id = ResourceLocation(buff.readUtf()) - val prerequisites = buff.readCollection({ LinkedList() }, Reference::fromNetwork) - val blockedBy = buff.readCollection({ LinkedList() }, Reference::fromNetwork) - val items = buff.readCollection({ LinkedList() }, { Ingredient.fromNetwork(it) to it.readVarInt() }) - val features = buff.readCollection({ LinkedList() }, FeatureReference::fromNetwork) - val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent) - val descriptionSuppliers = buff.readCollection({ LinkedList() }, { AndroidResearchManager.descriptionFuncs.fromNetwork(it) }) - val experienceLevels = buff.readVarInt() - - val customName = if (buff.readBoolean()) { - buff.readComponent() - } else { - null + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + ResourceLocation.CODEC.fieldOf("id").forGetter(AndroidResearchType::id), + ListCodec(Reference.CODEC).fieldOf("prerequisites").forGetter(AndroidResearchType::prerequisites), + ListCodec(Reference.CODEC).fieldOf("blockedBy").forGetter(AndroidResearchType::blockedBy), + ListCodec( + RecordCodecBuilder.create> { + it.group( + IngredientCodec.fieldOf("item").forGetter { it.first }, + Codec.intRange(1, Int.MAX_VALUE).optionalFieldOf("count", 1).forGetter { it.second } + ).apply(it, ::Pair) + } + ).fieldOf("items").forGetter { it.definedItems }, + ListCodec(AndroidResearchResult.CODEC).fieldOf("results").forGetter(AndroidResearchType::results), + ListCodec(AndroidResearchDescription.CODEC).fieldOf("description").forGetter(AndroidResearchType::description), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("experienceLevels").forGetter(AndroidResearchType::experienceLevels), + ComponentCodec.optionalFieldOf("customName").forGetter { Optional.ofNullable(it.customName) }, + JsonElementCodec.xmap({ it as? JsonObject ?: throw JsonSyntaxException("Not a json object: $it") }, { it }).optionalFieldOf("skinIcon").forGetter { Optional.ofNullable(it.skinIcon) }, + ForgeRegistries.ITEMS.codec.optionalFieldOf("itemIcon").forGetter { Optional.ofNullable(it.itemIcon) }, + ComponentCodec.optionalFieldOf("iconTextValue").forGetter { Optional.ofNullable(it.iconTextValue) }, + ).apply(it, ::AndroidResearchType) } - - val iconTextValue = if (buff.readBoolean()) { - buff.readComponent() - } else { - null - } - - val skinIcon = if (buff.readBoolean()) { - TypeAdapters.JSON_ELEMENT.fromJson(buff.readUtf()) as JsonObject - } else { - null - } - - val itemIcon = if (buff.readBoolean()) { - ForgeRegistries.ITEMS.getValue(ResourceLocation(buff.readUtf()))?.let { if (it == Items.AIR) null else it } - } else { - null - } - - return AndroidResearchType( - id = id, - prerequisites = prerequisites, - blockedBy = blockedBy, - items = items, - features = features, - descriptionLines = descriptionLines, - descriptionSuppliers = descriptionSuppliers.filterNotNull(), - experienceLevels = experienceLevels, - customName = customName, - iconText = iconTextValue, - skinIcon = skinIcon, - itemIcon = itemIcon, - ) - } - - fun fromJson(value: JsonElement, id: ResourceLocation): AndroidResearchType { - if (value !is JsonObject) { - throw JsonSyntaxException("Android research type must be of Json Object") - } - - val prerequisites = value["prerequisites"] as JsonArray? ?: JsonArray() - val blocked_by = value["blocked_by"] as JsonArray? ?: JsonArray() - val items = value["required_items"] as JsonArray? ?: JsonArray() - val features = value["feature_result"] as JsonArray? ?: JsonArray() - val description = value["description"] as JsonArray? ?: JsonArray() - val description_funcs = value["description_funcs"] as JsonArray? ?: JsonArray() - val experience = value["experience"]?.asInt ?: 0 - val customName = value["custom_name"]?.let(Component.Serializer::fromJson) - val iconText = value["icon_text"]?.let(Component.Serializer::fromJson) - val skinIcon = value["skin_icon"] as JsonObject? - val itemIcon = value["item_icon"]?.let { ForgeRegistries.ITEMS.getValue(ResourceLocation(it.asString)).let { if (it == Items.AIR) null else it } } - - return AndroidResearchType( - id = id, - prerequisites = prerequisites.stream().map { Reference.fromJson(it) }.toList(), - blockedBy = blocked_by.stream().map { Reference.fromJson(it) }.toList(), - features = features.stream().map { FeatureReference.fromJson(it) }.toList(), - items = items.stream() - .map { it as? JsonObject ?: throw JsonSyntaxException("One of items is not an JsonObject") } - .map { Ingredient.fromJson(it["ingredient"] ?: throw JsonSyntaxException("Missing ingredient key")) to (it["count"]?.asInt ?: throw JsonSyntaxException("Missing count key")) } - .toList(), - descriptionLines = description.stream().map { Component.Serializer.fromJson(it) }.toList() as List, - descriptionSuppliers = description_funcs.stream() - .map { AndroidResearchManager.descriptionFuncs.fromJson(it) ?: throw NullPointerException("$id is missing description supplier function or it is invalid! JSON: $it") } - .toList() as List, - experienceLevels = experience, - customName = customName, - iconText = iconText, - skinIcon = skinIcon, - itemIcon = itemIcon, - ) } } @@ -622,24 +415,24 @@ class AndroidResearchType( val id: ResourceLocation, var experience: Int = 0, var customName: Component? = null, - var description: MutableList? = null, - var descriptionSuppliers: MutableList? = null, + description: MutableList? = null, var itemIcon: Item? = null, - var skinIcon: AbstractSkinElement? = null, + var skinIcon: AbstractMatterySprite? = null, var iconText: Component? = null, ) { + val description = ArrayList(description ?: listOf()) + val results = ArrayList() + private val items = ArrayList>() private val prerequisites = ArrayList() private val blockers = ArrayList() - private val features = ArrayList() - fun withIconText(icon: Component? = null): Builder { this.iconText = icon return this } - fun withIcon(icon: AbstractSkinElement? = null): Builder { + fun withIcon(icon: AbstractMatterySprite? = null): Builder { this.skinIcon = icon this.itemIcon = null return this @@ -662,19 +455,20 @@ class AndroidResearchType( return this } - fun withDescription(): Builder { - this.description = mutableListOf(TranslatableComponent("android_research.${id.namespace}.${id.path}.description")) + fun clearDescription(): Builder { + this.description.clear() return this } - fun withDescription(range: IntRange): Builder { - val result = ArrayList() + fun withDescription(): Builder { + return withDescription(TranslatableComponent("android_research.${id.namespace}.${id.path}.description")) + } + fun withDescription(range: IntRange): Builder { for (i in range) { - result.add(TranslatableComponent("android_research.${id.namespace}.${id.path}.description$i")) + withDescription(TranslatableComponent("android_research.${id.namespace}.${id.path}.description$i")) } - this.description = result return this } @@ -684,73 +478,21 @@ class AndroidResearchType( } fun withDescription(vararg description: Component): Builder { - this.description = description.toMutableList() + for (component in description) + withDescription(PlainAndroidResearchDescription.Instance(component)) + return this } fun withDescription(description: Collection): Builder { - this.description = ArrayList(description.size).also { it.addAll(description) } + for (component in description) + withDescription(PlainAndroidResearchDescription.Instance(component)) + return this } - fun appendDescription(range: IntRange): Builder { - val result = this.description ?: ArrayList() - - for (i in range) { - result.add(TranslatableComponent("android_research.${id.namespace}.${id.path}.description$i")) - } - - this.description = result - return this - } - - fun appendDescription(description: Component): Builder { - this.description = (this.description ?: mutableListOf()).also { it.add(description) } - return this - } - - fun appendDescription(vararg description: Component): Builder { - this.description = (this.description ?: mutableListOf()).also { it.addAll(description) } - return this - } - - fun appendDescription(description: Collection): Builder { - this.description = (this.description ?: mutableListOf()).also { it.addAll(description) } - return this - } - - fun withDescription(vararg description: ComponentSupplier): Builder { - this.descriptionSuppliers = description.toMutableList() - return this - } - - fun withDescriptionSupplier(description: Collection): Builder { - this.descriptionSuppliers = ArrayList(description.size).also { it.addAll(description) } - return this - } - - fun appendDescription(description: ComponentSupplier): Builder { - this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.add(description) } - return this - } - - fun appendDescriptionSupplier(description: ComponentSupplier): Builder { - this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.add(description) } - return this - } - - fun appendDescription(vararg description: ComponentSupplier): Builder { - this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.addAll(description) } - return this - } - - fun appendDescriptionSupplier(vararg description: ComponentSupplier): Builder { - this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.addAll(description) } - return this - } - - fun appendDescriptionSupplier(description: Collection): Builder { - this.descriptionSuppliers = (this.descriptionSuppliers ?: mutableListOf()).also { it.addAll(description) } + fun withDescription(vararg description: AndroidResearchDescription): Builder { + this.description.addAll(description) return this } @@ -759,14 +501,14 @@ class AndroidResearchType( * research tree render logic (yet). */ @JvmOverloads - fun addPrerequisite(id: ResourceLocation, rigid: Boolean = false): Builder { - prerequisites.add(Reference(id, rigid)) + fun addPrerequisite(id: ResourceLocation, optional: Boolean = true): Builder { + prerequisites.add(Reference(id, optional)) return this } @JvmOverloads - fun addBlocker(id: ResourceLocation, rigid: Boolean = false): Builder { - blockers.add(Reference(id, rigid)) + fun addBlocker(id: ResourceLocation, optional: Boolean = true): Builder { + blockers.add(Reference(id, optional)) return this } @@ -774,46 +516,28 @@ class AndroidResearchType( * Please avoid having multiple prerequisites as case with more than 1 prerequisite does not have proper * research tree render logic (yet). */ - fun addPrerequisite(type: AndroidResearchType, rigid: Boolean = true) = addPrerequisite(type.id, rigid) - fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid) + fun addPrerequisite(type: AndroidResearchType, optional: Boolean = false) = addPrerequisite(type.id, optional) + fun addBlocker(type: AndroidResearchType, optional: Boolean = false) = addBlocker(type.id, optional) - @JvmOverloads - fun addFeatureResult( - id: ResourceLocation, - level: Int = 0, - rigid: Boolean = false, - transformersUp: Collection = listOf(), - transformersDown: Collection = listOf(), - ): Builder { - features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown)) + fun addResult(result: AndroidResearchResult): Builder { + results.add(result) return this } - @JvmOverloads - fun addFeatureResult( - feature: AndroidFeatureType<*>, - level: Int = 0, - rigid: Boolean = true, - transformersUp: Collection = listOf(), - transformersDown: Collection = listOf(), - ): Builder { - features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown)) - return this + fun addFeatureResult(id: ResourceLocation, optional: Boolean = false): Builder { + return addResult(AndroidResearchResult.Feature(id, optional)) } - fun addFeatureResult( - id: ResourceLocation, - rigid: Boolean = false, - transformersUp: Collection = listOf(), - transformersDown: Collection = listOf(), - ): Builder { - features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown)) - return this + fun addFeatureResult(feature: AndroidFeatureType<*>, optional: Boolean = false): Builder { + return addFeatureResult(feature.registryName!!, optional) } - fun addFeatureResult(ref: FeatureReference): Builder { - features.add(ref) - return this + fun addFeatureLevel(id: ResourceLocation, optional: Boolean = false, levels: Int = 1): Builder { + return addResult(AndroidResearchResult.FeatureLevel(id, optional, levels)) + } + + fun addFeatureLevel(feature: AndroidFeatureType<*>, optional: Boolean = false, levels: Int = 1): Builder { + return addFeatureLevel(feature.registryName!!, optional, levels) } fun addItem(cost: ItemStack): Builder { @@ -845,9 +569,8 @@ class AndroidResearchType( prerequisites = prerequisites, blockedBy = blockers, items = items, - features = features, - descriptionLines = description ?: listOf(), - descriptionSuppliers = descriptionSuppliers ?: listOf(), + results = results, + description = description, experienceLevels = experience, customName = customName, skinIcon = skinIcon?.toJson(), diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt index 10611e63c..82238ae29 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidSwitchableFeature.kt @@ -1,13 +1,16 @@ package ru.dbotthepony.mc.otm.android -import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.multiplayer.ClientLevel import net.minecraft.nbt.CompoundTag import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.nbt.set abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: MatteryPlayerCapability) : AndroidFeature(type, android) { var isActive by synchronizer.bool(setter = setter@{ value, access, setByRemote -> - if (value != access.read()) { + if (value != access.readBoolean()) { access.write(value) if (!setByRemote) { @@ -18,18 +21,23 @@ abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: Ma } } } - }) + }).property open val allowToSwitchByPlayer: Boolean get() = true open val allowToSwitchByPlayerWhileSpectator: Boolean get() = true open val maxCooldown: Int get() = 0 - open var cooldown by synchronizer.int() + open var cooldown by synchronizer.int().property val isOnCooldown: Boolean get() = maxCooldown > 0 && cooldown > 0 val cooldownPercent: Float get() { if (maxCooldown <= 0) return 0.0f + + if (ply.level is ClientLevel) { + return ((cooldown.toFloat() - minecraft.partialTick) / maxCooldown.toFloat()).coerceIn(0.0f, 1.0f) + } + return (cooldown.toFloat() / maxCooldown.toFloat()).coerceIn(0.0f, 1.0f) } @@ -37,9 +45,9 @@ abstract class AndroidSwitchableFeature(type: AndroidFeatureType<*>, android: Ma cooldown = maxCooldown } - // TODO: PoseStack is stripped from server dist + // TODO: GuiGraphics is stripped from server dist // but it doesn't seem to cause issues? - abstract fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) + abstract fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor = RGBAColor.WHITE) override fun serializeNBT(): CompoundTag { return super.serializeNBT().also { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt index 6f1df894e..b04db591f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/AttackBoostFeature.kt @@ -13,7 +13,7 @@ class AttackBoostFeature(android: MatteryPlayerCapability) : AndroidFeature(Andr if (modifier != null) { modifier.removePermanentModifier(MODIFIER_ID) - modifier.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.06, AttributeModifier.Operation.MULTIPLY_TOTAL)) + modifier.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * 0.15, AttributeModifier.Operation.MULTIPLY_TOTAL)) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt index 55af601f2..6fb7cd0b6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/EnderTeleporterFeature.kt @@ -2,8 +2,6 @@ package ru.dbotthepony.mc.otm.android.feature import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack -import com.mojang.math.Vector3f -import net.minecraft.ChatFormatting import net.minecraft.client.Camera import net.minecraft.client.renderer.LevelRenderer import net.minecraft.core.BlockPos @@ -27,29 +25,31 @@ import net.minecraftforge.event.ForgeEventFactory import net.minecraftforge.event.entity.living.LivingDeathEvent import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidActiveFeature -import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource import ru.dbotthepony.mc.otm.client.render.ResearchIcons -import ru.dbotthepony.mc.otm.client.render.element import ru.dbotthepony.mc.otm.client.render.linesIgnoreZRenderType -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.core.asVector -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 -import ru.dbotthepony.mc.otm.core.formatPower +import ru.dbotthepony.mc.otm.client.render.sprites.sprite +import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.core.genericPositions -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.shortestDistanceBetween -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.holder +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.asVector +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.rotateXDegrees +import ru.dbotthepony.mc.otm.core.math.rotateYDegrees +import ru.dbotthepony.mc.otm.core.math.shortestDistanceBetween +import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.milliTime import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger import java.util.* import kotlin.math.sin @@ -59,10 +59,10 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv private set override val maxCooldown: Int - get() = ServerConfig.EnderTeleporter.COOLDOWN + get() = AndroidConfig.EnderTeleporter.COOLDOWN private fun canUse(): Boolean { - return !isOnCooldown && android.androidEnergy.extractEnergyInnerExact(ServerConfig.EnderTeleporter.ENERGY_COST, true).isPositive + return !isOnCooldown && android.androidEnergy.extractEnergyExact(AndroidConfig.EnderTeleporter.ENERGY_COST, true) } private fun isValidGround(blockPos: BlockPos): Boolean { @@ -111,14 +111,14 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv } private fun tryToPhaseThroughWall(blockPos: BlockPos, normal: Vec3i): TraceResult? { - val phasedBlocks = ArrayList(ServerConfig.EnderTeleporter.MAX_PHASE_DISTANCE) + val phasedBlocks = ArrayList(AndroidConfig.EnderTeleporter.MAX_PHASE_DISTANCE) phasedBlocks.add(blockPos) - for (extend in 1 .. ServerConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { + for (extend in 1 .. AndroidConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { val pos = blockPos + normal * extend if (isAirGap(pos)) { - for (y in 0 .. ServerConfig.EnderTeleporter.MAX_PHASE_DISTANCE - extend) { + for (y in 0 .. AndroidConfig.EnderTeleporter.MAX_PHASE_DISTANCE - extend) { val newPos = BlockPos(pos.x, pos.y - y, pos.z) if (isValidPosition(newPos)) { @@ -141,7 +141,7 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv val result = ply.level.clip(ClipContext( headPosition, - headPosition + aimVector * (ServerConfig.EnderTeleporter.MAX_DISTANCE * 2.0), + headPosition + aimVector * (AndroidConfig.EnderTeleporter.MAX_DISTANCE * 2.0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, ply @@ -157,7 +157,7 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv !ply.isShiftKeyDown && result.direction == Direction.UP && isValidPosition(result.blockPos.above()) && - shortestDistanceBetween(testPositions, result.blockPos.above().asVector()) <= ServerConfig.EnderTeleporter.MAX_DISTANCE + shortestDistanceBetween(testPositions, result.blockPos.above().asVector()) <= AndroidConfig.EnderTeleporter.MAX_DISTANCE ) { return TraceResult(result.blockPos.above()) } @@ -215,14 +215,14 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv if (!isAirGap(pos)) { phasedBlocks++ - if (phasedBlocks >= ServerConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { + if (phasedBlocks >= AndroidConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { break } phasedBlocksList.add(pos) } - if (shortestDistanceBetween(testPositions, pos.asVector()) > ServerConfig.EnderTeleporter.MAX_DISTANCE) { + if (shortestDistanceBetween(testPositions, pos.asVector()) > AndroidConfig.EnderTeleporter.MAX_DISTANCE) { break } @@ -269,14 +269,14 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv if (!isAirGap(pos)) { phasedBlocks++ - if (phasedBlocks >= ServerConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { + if (phasedBlocks >= AndroidConfig.EnderTeleporter.MAX_PHASE_DISTANCE) { break } phasedBlocksList.add(pos) } - if (shortestDistanceBetween(testPositions, pos.asVector()) > ServerConfig.EnderTeleporter.MAX_DISTANCE) { + if (shortestDistanceBetween(testPositions, pos.asVector()) > AndroidConfig.EnderTeleporter.MAX_DISTANCE) { break } @@ -301,13 +301,13 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv val event = ForgeEventFactory.onEnderTeleport(ply, blockPos.x + 0.5, blockPos.y.toDouble(), blockPos.z + 0.5) if (event.isCanceled) { - (ply as ServerPlayer).connection.send(ClientboundSoundEntityPacket(SoundEvents.ITEM_BREAK, SoundSource.PLAYERS, ply, 0.3f, 0.5f, ply.level.random.nextLong())) + (ply as ServerPlayer).connection.send(ClientboundSoundEntityPacket(SoundEvents.ITEM_BREAK.holder, SoundSource.PLAYERS, ply, 0.3f, 0.5f, ply.level.random.nextLong())) return false } putOnCooldown() lastTeleport = ply.server!!.tickCount - android.androidEnergy.extractEnergyInner(ServerConfig.EnderTeleporter.ENERGY_COST, false) + android.androidEnergy.extractEnergy(AndroidConfig.EnderTeleporter.ENERGY_COST, false) ply.level.playSound(null, ply, SoundEvents.ENDERMAN_TELEPORT, SoundSource.PLAYERS, 0.3f, 0.8f + ply.level.random.nextFloat() * 0.4f) ply.teleportTo(event.targetX, event.targetY, event.targetZ) @@ -333,8 +333,8 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv poseStack.pushPose() poseStack.translate(x - vx, y - vy, z - vz) - poseStack.mulPose(Vector3f.YP.rotationDegrees(-camera.yRot)) - poseStack.mulPose(Vector3f.XP.rotationDegrees(camera.xRot)) + poseStack.rotateYDegrees(-camera.yRot) + poseStack.rotateXDegrees(camera.xRot) val size = 1f + sin(milliTime / 250.0).toFloat() * 0.2f val half = size / -2f @@ -372,12 +372,12 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv } } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { if (cooldown > 0) { RenderSystem.setShaderColor(1f, 0.4f, 0.4f, 1f) } - ResearchIcons.ICON_ENDER_TELEPORT.render(stack, x, y, width, height) + ResearchIcons.ICON_ENDER_TELEPORT.render(graphics, x, y, width, height, color = color) if (cooldown > 0) { RenderSystem.setShaderColor(1f, 1f, 1f, 1f) @@ -392,20 +392,14 @@ class EnderTeleporterFeature(capability: MatteryPlayerCapability) : AndroidActiv private val SHAPE_CHECK_NOT_FENCE = Block.box(6.0, 17.0, 6.0, 10.0, 31.0, 10.0) private val SHAPE_CHECK_SUPPORT_PLAYER = Block.box(6.0, 0.0, 6.0, 10.0, 16.0, 10.0) - val SPRITE = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/item/black_hole.png").element(0f, 0f, 16f, 16f, 16f, 16f) - - val POWER_COST_DESCRIPTION = - AndroidResearchManager.descriptionFunc( - ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.ENDER_TELEPORTER), - "otm.gui.power_cost_per_use", - { ServerConfig.EnderTeleporter.ENERGY_COST.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + val SPRITE = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/item/black_hole.png").sprite(0f, 0f, 16f, 16f, 16f, 16f) fun onEntityDeath(event: LivingDeathEvent) { val android = event.entity.matteryPlayer ?: return val server = NULLABLE_MINECRAFT_SERVER ?: return if (android.isAndroid && event.source.isFall) { - val feature = android.getFeature(AndroidFeatures.ENDER_TELEPORTER) as EnderTeleporterFeature? ?: return + val feature = android.getFeature(AndroidFeatures.ENDER_TELEPORTER) ?: return if (server.tickCount - feature.lastTeleport <= 120) { EnderTeleporterFallDeathTrigger.trigger(event.entity as ServerPlayer) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt index c942434be..ea63587bb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/FallDampenersFeature.kt @@ -1,32 +1,25 @@ package ru.dbotthepony.mc.otm.android.feature -import net.minecraft.ChatFormatting -import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer +import net.minecraftforge.event.entity.living.LivingAttackEvent import net.minecraftforge.event.entity.living.LivingHurtEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidFeature -import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger class FallDampenersFeature(capability: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.FALL_DAMPENERS, capability) { override fun onHurt(event: LivingHurtEvent) { if (event.source.isFall) { - val reduction = (ServerConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL * (level + 1)).toFloat() + val reduction = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_P * (level + 1)).toFloat().coerceIn(0f, 1f) + val flat = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_F * (level + 1)).toFloat().coerceIn(0f, Float.MAX_VALUE) val old = event.amount + event.amount = ((event.amount - flat) * (1f - reduction)).coerceAtLeast(0f) - if (reduction >= 1f) { + if (event.amount == 0f) { event.isCanceled = true - event.amount = 0f - } else { - event.amount *= (1f - reduction) } if (ply is ServerPlayer && ply.health > event.amount && ply.health <= old) { @@ -35,9 +28,14 @@ class FallDampenersFeature(capability: MatteryPlayerCapability) : AndroidFeature } } - companion object { - val DESCRIPTION = AndroidResearchManager.descriptionFuncs.register(ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.FALL_DAMPENERS)) { level: Int -> - TranslatableComponent("otm.fall_dampeners.description", TextComponent("%.1f".format((ServerConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL * level).toFloat().coerceAtLeast(0f).coerceAtMost(1f) * 100f)).withStyle(ChatFormatting.YELLOW)) + override fun onAttack(event: LivingAttackEvent) { + if (event.source.isFall) { + val reduction = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_P * (level + 1)).toFloat().coerceIn(0f, 1f) + val flat = (AndroidConfig.FALL_DAMAGE_REDUCTION_PER_LEVEL_F * (level + 1)).toFloat().coerceIn(0f, Float.MAX_VALUE) + + if (reduction >= 1f || event.amount <= flat) { + event.isCanceled = true + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt index 55f579594..ad4d00cc5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ItemMagnetFeature.kt @@ -1,43 +1,30 @@ package ru.dbotthepony.mc.otm.android.feature -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.ChatFormatting import net.minecraft.client.multiplayer.ClientLevel import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerLevel -import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.Entity import net.minecraft.world.entity.item.ItemEntity -import net.minecraftforge.event.ForgeEventFactory -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.android.AndroidResearchManager +import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInner -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.ResearchIcons -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.core.formatSi +import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid -import ru.dbotthepony.mc.otm.core.minus -import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.position -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.math.times +import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.WorldNetworkChannel -import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.MNames import java.util.UUID import java.util.WeakHashMap import java.util.function.Predicate -import java.util.function.Supplier private data class SharedItemEntityData(val owner: UUID? = null, val age: Int = 0, val lifespan: Int = 0, val hasPickupDelay: Boolean = true) { companion object { @@ -57,8 +44,7 @@ class ItemEntityDataPacket(val itemUUID: Int, val owner: UUID? = null, val age: buff.writeBoolean(hasPickupDelay) } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { val level = minecraft.player?.level as ClientLevel? ?: return val entity = level.getEntity(itemUUID) as ItemEntity? ?: return datatable[entity] = SharedItemEntityData(owner, age, lifespan, hasPickupDelay) @@ -79,13 +65,13 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable private val clientPredicate = Predicate { it is ItemEntity && (datatable[it] ?: SharedItemEntityData.EMPTY).let { !it.hasPickupDelay && (it.owner == null || it.owner != ply.uuid || it.lifespan - it.age <= 200) } } private fun doTick(server: Boolean) { - if (ply.isSpectator || server && !android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, true).isPositive) { + if (ply.isSpectator || server && !android.androidEnergy.extractEnergyExact(AndroidConfig.Magnet.POWER_DRAW, true)) { return } val entities = ply.level.getEntitiesInEllipsoid( ply.position, - Vector(ServerConfig.AndroidItemMagnet.RADIUS_HORIZONTAL, ServerConfig.AndroidItemMagnet.RADIUS_VERTICAL, ServerConfig.AndroidItemMagnet.RADIUS_HORIZONTAL), + Vector(AndroidConfig.Magnet.RADIUS_HORIZONTAL, AndroidConfig.Magnet.RADIUS_VERTICAL, AndroidConfig.Magnet.RADIUS_HORIZONTAL), if (server) Predicate { it is ItemEntity } else clientPredicate ) @@ -93,7 +79,7 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable ent as ItemEntity if (server) { - WorldNetworkChannel.send(ply, ItemEntityDataPacket(ent.id, ent.owner, ent.age, ent.lifespan, ent.hasPickUpDelay())) + GenericNetworkChannel.send(ply, ItemEntityDataPacket(ent.id, ent.owner, ent.age, ent.lifespan, ent.hasPickUpDelay())) if (!serverPredicate.test(ent)) { continue @@ -104,7 +90,7 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable if (data.position.distanceToSqr(ent.position) < 1.0) { data.ticksSinceActivity++ } else { - if (!android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, false).isPositive) { + if (!android.androidEnergy.extractEnergyExact(AndroidConfig.Magnet.POWER_DRAW, false)) { return } @@ -120,7 +106,7 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable override fun tickClient() { super.tickClient() - if (!ply.isSpectator && isActive && android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidItemMagnet.POWER_DRAW, true).isPositive) { + if (!ply.isSpectator && isActive && android.androidEnergy.extractEnergyExact(AndroidConfig.Magnet.POWER_DRAW, true)) { doTick(false) } } @@ -133,15 +119,7 @@ class ItemMagnetFeature(capability: MatteryPlayerCapability) : AndroidSwitchable } } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - ResearchIcons.ICON_ITEM_MAGNET.render(stack, x, y, width, height) - } - - companion object { - val POWER_COST_DESCRIPTION = - AndroidResearchManager.descriptionFunc(ResourceLocation( - OverdriveThatMatters.MOD_ID, MNames.ITEM_MAGNET), - "otm.gui.power_cost_per_tick", - { ServerConfig.AndroidItemMagnet.POWER_DRAW.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_ITEM_MAGNET.render(graphics, x, y, width, height, color = color) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt index 6490a1816..9d800d816 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/JumpBoostFeature.kt @@ -1,67 +1,50 @@ package ru.dbotthepony.mc.otm.android.feature -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.ChatFormatting -import net.minecraft.nbt.CompoundTag import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.sounds.SoundSource -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.ClientConfig -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.ResearchIcons -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.config.ClientConfig +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel -import ru.dbotthepony.mc.otm.network.enqueueWork -import ru.dbotthepony.mc.otm.network.packetHandled -import ru.dbotthepony.mc.otm.network.sender +import ru.dbotthepony.mc.otm.network.SmokeParticlesPacket import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.registry.MSoundEvents -import java.util.function.Supplier object TriggerJumpBoostPacket : MatteryPacket { override fun write(buff: FriendlyByteBuf) { // no op } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { + val mattery = context.sender?.matteryPlayer ?: return - context.enqueueWork { - val mattery = context.sender?.matteryPlayer ?: return@enqueueWork + if (!mattery.isAndroid) + return - if (!mattery.isAndroid) - return@enqueueWork + val feature = mattery.getFeature(AndroidFeatures.JUMP_BOOST) ?: return - val feature = mattery.getFeature(AndroidFeatures.JUMP_BOOST) as JumpBoostFeature? ?: return@enqueueWork + if (feature.isActive && feature.cooldown <= 4 && mattery.androidEnergy.extractEnergyExact(AndroidConfig.JumpBoost.ENERGY_COST, false)) { + feature.putOnCooldown() - if (feature.isActive && feature.cooldown <= 4 && mattery.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidJumpBoost.ENERGY_COST, false).isPositive) { - feature.putOnCooldown() + context.sender.level.playSound( + context.sender, context.sender, + MSoundEvents.ANDROID_JUMP_BOOST, SoundSource.PLAYERS, + 1f, 1f + ) - context.sender?.let { - it.level.playSound( - it, - it, - MSoundEvents.ANDROID_JUMP_BOOST, - SoundSource.PLAYERS, - 1f, - 1f - ) - } - } + GenericNetworkChannel.makeSmoke(context.sender, context.sender.x, context.sender.y, context.sender.z) } } } @@ -70,7 +53,7 @@ class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF private var tickCooldownClient = false override val maxCooldown: Int - get() = (ServerConfig.AndroidJumpBoost.BASE_COOLDOWN - ServerConfig.AndroidJumpBoost.COOLDOWN_REDUCTION * level).coerceAtLeast(0) + get() = (AndroidConfig.JumpBoost.BASE_COOLDOWN - AndroidConfig.JumpBoost.COOLDOWN_REDUCTION * level).coerceAtLeast(0) override var cooldown by synchronizer.int(setter = setter@{ value, access, setByRemote -> access.write(value) @@ -78,7 +61,7 @@ class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF if (setByRemote) { tickCooldownClient = false } - }) + }).property private var lastGround = false @@ -92,19 +75,18 @@ class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF val old = lastGround lastGround = ply.isOnGround - if (isActive && cooldown <= 0 && old != lastGround && !lastGround && isJumping && isShifting && ply.xRot <= ClientConfig.JUMP_BOOST_LOOK_ANGLE && android.androidEnergy.extractEnergyInnerExact(ServerConfig.AndroidJumpBoost.ENERGY_COST, true).isPositive) { - ply.deltaMovement += Vector(0.0, ServerConfig.AndroidJumpBoost.POWER * (level + 1) / 20.0, 0.0) + if (isActive && cooldown <= 0 && old != lastGround && !lastGround && isJumping && isShifting && ply.xRot <= ClientConfig.JUMP_BOOST_LOOK_ANGLE && android.androidEnergy.extractEnergyExact(AndroidConfig.JumpBoost.ENERGY_COST, true)) { + ply.deltaMovement += Vector(0.0, AndroidConfig.JumpBoost.POWER * (level + 1) / 20.0, 0.0) putOnCooldown() MatteryPlayerNetworkChannel.sendToServer(TriggerJumpBoostPacket) ply.level.playSound( - ply, - ply, - MSoundEvents.ANDROID_JUMP_BOOST, - SoundSource.PLAYERS, - 1f, - 1f + ply, ply, + MSoundEvents.ANDROID_JUMP_BOOST, SoundSource.PLAYERS, + 1f, 1f ) + + SmokeParticlesPacket.makeSmoke(ply.x, ply.y, ply.z, ply.random, ply.level) } } @@ -114,24 +96,7 @@ class JumpBoostFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF } } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - if (cooldown > 0) { - RenderSystem.setShaderColor(1f, 0.4f, 0.4f, 1f) - } - - ResearchIcons.ICON_JUMP_BOOST.render(stack, x, y, width, height) - - if (cooldown > 0) { - RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - } - } - - - companion object { - val POWER_COST_DESCRIPTION = - AndroidResearchManager.descriptionFunc( - ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.JUMP_BOOST), - "otm.gui.power_cost_per_use", - { ServerConfig.AndroidJumpBoost.ENERGY_COST.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_JUMP_BOOST.render(graphics, x, y, width, height, color = if (cooldown > 0) color * RGBAColor.REDDISH else color) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt index a8653abf3..8cc4a134a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature.kt @@ -1,9 +1,12 @@ package ru.dbotthepony.mc.otm.android.feature +import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.ai.attributes.AttributeModifier import net.minecraft.world.entity.ai.attributes.Attributes +import net.minecraft.world.entity.player.Player import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.registry.AndroidFeatures import java.util.* @@ -31,5 +34,34 @@ class LimbOverclockingFeature(android: MatteryPlayerCapability) : AndroidFeature companion object { private val MODIFIER_ID = UUID.fromString("4a3fae46-e57b-4e20-857d-f5c2b2c8f2f2") + + @JvmStatic + fun getBrushCooldown(entity: LivingEntity): Int { + if (entity !is Player) return 10 + val matteryPlayer = entity.matteryPlayer ?: return 10 + if (!matteryPlayer.isAndroid || !matteryPlayer.hasFeature(AndroidFeatures.LIMB_OVERCLOCKING)) return 10 + + val level = matteryPlayer.getFeature(AndroidFeatures.LIMB_OVERCLOCKING)!!.level + 1 + return (10 - level * 2).coerceAtLeast(2) + } + + @JvmStatic + fun getBrushTick(entity: LivingEntity): Int { + if (entity !is Player) return 5 + val matteryPlayer = entity.matteryPlayer ?: return 5 + if (!matteryPlayer.isAndroid || !matteryPlayer.hasFeature(AndroidFeatures.LIMB_OVERCLOCKING)) return 5 + + val level = matteryPlayer.getFeature(AndroidFeatures.LIMB_OVERCLOCKING)!!.level + 1 + return (5 - level).coerceAtLeast(1) + } + + @JvmStatic + fun getBrushableBlockCooldown(player: Player): Long { + val matteryPlayer = player.matteryPlayer ?: return 10L + if (!matteryPlayer.isAndroid || !matteryPlayer.hasFeature(AndroidFeatures.LIMB_OVERCLOCKING)) return 10L + + val level = matteryPlayer.getFeature(AndroidFeatures.LIMB_OVERCLOCKING)!!.level + 1 + return (10L - level * 2L).coerceAtLeast(2L) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt index f1f9723d9..66a16c3ce 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsArmorFeature.kt @@ -4,36 +4,38 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraftforge.event.entity.living.LivingHurtEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidFeature -import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.StatNames -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger import kotlin.math.roundToInt class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) { - var strength: Int = 0 - set(value) { field = value.coerceIn(0 .. 3) } + var strength by synchronizer.int( + setter = setter@{ + value, access, _ -> access.write(value.coerceIn(0 .. 3)) + } + ).property var speed: Int = 0 set(value) { field = value.coerceIn(0 .. 3) } private var ticksPassed = 0 - private var layers = 0 + var layers by synchronizer.int().property override fun tickServer() { - if (layers < strength + 1 && android.androidEnergy.extractEnergyInnerExact(ENERGY_PER_LAYER, true).isPositive) { + if (layers < strength + 1 && android.androidEnergy.extractEnergyExact(ENERGY_PER_LAYER, true)) { ticksPassed++ if (ticksPassed >= TICKS[speed]) { layers++ - android.androidEnergy.extractEnergyInner(ENERGY_PER_LAYER, false) + android.androidEnergy.extractEnergy(ENERGY_PER_LAYER, false) + ticksPassed = 0 } } else { ticksPassed = 0 @@ -48,7 +50,7 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An if (absorbed > 0.1f) { val powerRequired = ENERGY_PER_HITPOINT * absorbed - val powerExtracted = android.androidEnergy.extractEnergyInner(powerRequired, false) + val powerExtracted = android.androidEnergy.extractEnergy(powerRequired, false) val realAbsorbed = (powerExtracted / ENERGY_PER_HITPOINT).toFloat() val ply = ply @@ -101,33 +103,5 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An 0.45f, 0.6f, ) - - val STRENGTH_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_up")) - { level: Int -> - if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level - 1) { - (second as NanobotsArmorFeature).strength = level - } - } - - val STRENGTH_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_down")) - { level: Int -> - if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level) { - (second as NanobotsArmorFeature).strength = level - 1 - } - } - - val SPEED_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_up")) - { level: Int -> - if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level - 1) { - (second as NanobotsArmorFeature).speed = level - } - } - - val SPEED_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_down")) - { level: Int -> - if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level) { - (second as NanobotsArmorFeature).speed = level - 1 - } - } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt index e2e2b25c1..ab19f08ee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NanobotsRegenerationFeature.kt @@ -2,13 +2,14 @@ package ru.dbotthepony.mc.otm.android.feature import net.minecraft.nbt.CompoundTag import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.GameRules import net.minecraftforge.event.entity.living.LivingHurtEvent -import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.StatNames -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.nbt.set import kotlin.math.roundToInt class NanobotsRegenerationFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_REGENERATION, android) { @@ -16,19 +17,19 @@ class NanobotsRegenerationFeature(android: MatteryPlayerCapability) : AndroidFea private var healTicks = 0 override fun tickServer() { - if (ply.health > 0f && ply.health < ply.maxHealth) { + if (ply.isHurt && ply.level.gameRules.getBoolean(GameRules.RULE_NATURAL_REGENERATION)) { ticksPassed++ - val waitTime = ServerConfig.NanobotsRegeneration.COOLDOWN.getOrElse(healTicks) { ServerConfig.NanobotsRegeneration.COOLDOWN.last() } + val waitTime = AndroidConfig.NanobotsRegeneration.COOLDOWN.getOrElse(healTicks) { AndroidConfig.NanobotsRegeneration.COOLDOWN.last() } if (ticksPassed > waitTime) { val missingHealth = (ply.maxHealth - ply.health).coerceAtMost(2f) - val power = ServerConfig.NanobotsRegeneration.ENERGY_PER_HITPOINT * missingHealth - val extracted = android.androidEnergy.extractEnergyInner(power, false) + val power = AndroidConfig.NanobotsRegeneration.ENERGY_PER_HITPOINT * missingHealth + val extracted = android.androidEnergy.extractEnergy(power, false) if (extracted.isPositive) { healTicks = (healTicks + 1).coerceAtMost(level) - val healed = (extracted / ServerConfig.NanobotsRegeneration.ENERGY_PER_HITPOINT).toFloat() + val healed = (extracted / AndroidConfig.NanobotsRegeneration.ENERGY_PER_HITPOINT).toFloat() ply.heal(healed) (ply as ServerPlayer?)?.awardStat(StatNames.HEALTH_REGENERATED, (healed * 10f).roundToInt()) ticksPassed = 0 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt index 4eeed3849..ad3ead63a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/NightVisionFeature.kt @@ -1,13 +1,14 @@ package ru.dbotthepony.mc.otm.android.feature -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.effect.MobEffects -import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.ResearchIcons +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.registry.AndroidFeatures class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.NIGHT_VISION, android) { @@ -18,12 +19,8 @@ class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFe if (isActive) { val effect = android.ply.activeEffectsMap[MobEffects.NIGHT_VISION] - if ((effect == null || effect.duration < 220) && (ply.isSpectator || android.androidEnergy.extractEnergyInnerExact(ServerConfig.NIGHT_VISION_POWER_DRAW, true).isPositive)) { + if ((effect == null || effect.duration < 220) && (ply.isSpectator || android.androidEnergy.extractEnergyExact(AndroidConfig.NIGHT_VISION_POWER_DRAW, false))) { android.ply.addEffect(MobEffectInstance(MobEffects.NIGHT_VISION, 220, 0, false, false)) - - if (!ply.isSpectator) { - android.androidEnergy.extractEnergyInner(ServerConfig.NIGHT_VISION_POWER_DRAW, false) - } } } } @@ -36,7 +33,7 @@ class NightVisionFeature(android: MatteryPlayerCapability) : AndroidSwitchableFe } } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - ResearchIcons.ICON_NIGHT_VISION.render(stack, x, y, width, height) + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_NIGHT_VISION.render(graphics, x, y, width, height, color = color) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/PhantomAttractorFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/PhantomAttractorFeature.kt deleted file mode 100644 index 14f8580d3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/PhantomAttractorFeature.kt +++ /dev/null @@ -1,13 +0,0 @@ -package ru.dbotthepony.mc.otm.android.feature - -import com.mojang.blaze3d.vertex.PoseStack -import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature -import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.client.render.ResearchIcons -import ru.dbotthepony.mc.otm.registry.AndroidFeatures - -class PhantomAttractorFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.PHANTOM_ATTRACTOR, android) { - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - ResearchIcons.ICON_PHANTOM_ATTRACTOR.render(stack, x, y, width, height) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt index 1a6d43de2..a6c8437a2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/ShockwaveFeature.kt @@ -1,49 +1,40 @@ package ru.dbotthepony.mc.otm.android.feature -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import it.unimi.dsi.fastutil.objects.ReferenceArraySet -import net.minecraft.ChatFormatting import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.sounds.SoundSource import net.minecraft.world.entity.Entity import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.monster.warden.Warden -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.ResearchIcons -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.core.getEllipsoidBlockPositions +import ru.dbotthepony.mc.otm.config.AndroidConfig import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid import ru.dbotthepony.mc.otm.core.getExplosionResistance -import ru.dbotthepony.mc.otm.core.minus -import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.getEllipsoidBlockPositions +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.roundToIntVector +import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.core.position -import ru.dbotthepony.mc.otm.core.roundToIntVector -import ru.dbotthepony.mc.otm.core.times -import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel -import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket +import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import ru.dbotthepony.mc.otm.network.ShockwaveEffectPacket -import ru.dbotthepony.mc.otm.network.enqueueWork -import ru.dbotthepony.mc.otm.network.packetHandled -import ru.dbotthepony.mc.otm.network.sender import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.registry.AndroidFeatures -import ru.dbotthepony.mc.otm.registry.MNames +import ru.dbotthepony.mc.otm.registry.MDamageTypes import ru.dbotthepony.mc.otm.registry.MSoundEvents import ru.dbotthepony.mc.otm.registry.ShockwaveDamageSource import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger -import java.util.function.Supplier import kotlin.math.pow import kotlin.math.roundToInt @@ -52,15 +43,12 @@ object TriggerShockwavePacket : MatteryPacket { // no op } - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val shockwave = context.sender?.matteryPlayer?.getFeature(AndroidFeatures.SHOCKWAVE) as ShockwaveFeature? ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val shockwave = context.sender?.matteryPlayer?.getFeature(AndroidFeatures.SHOCKWAVE) ?: return - if (!shockwave.isOnCooldown && shockwave.isActive && shockwave.airTicks > 0) { - onceServer { // delay by one tick so player update its position as well - shockwave.shockwave() - } + if (!shockwave.isOnCooldown && shockwave.isActive && shockwave.airTicks > 0) { + onceServer { // delay by one tick so player update its position as well + shockwave.shockwave() } } } @@ -68,7 +56,7 @@ object TriggerShockwavePacket : MatteryPacket { class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.SHOCKWAVE, capability) { override val maxCooldown: Int - get() = ServerConfig.Shockwave.COOLDOWN + get() = AndroidConfig.Shockwave.COOLDOWN private var wasMidair = false private var highestSpeed = 0.0 @@ -83,18 +71,18 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF isActive && ply.isShiftKeyDown && !isOnCooldown && - android.androidEnergy.extractEnergyInnerExact(ServerConfig.Shockwave.ENERGY_COST, true).isPositive && + android.androidEnergy.extractEnergyExact(AndroidConfig.Shockwave.ENERGY_COST, true) && ply.deltaMovement.y < -0.01 && creativeFlightTicks == 0 ) { - ply.deltaMovement += Vector(0.0, -ServerConfig.Shockwave.ACCELERATION / 20.0, 0.0) + ply.deltaMovement += Vector(0.0, -AndroidConfig.Shockwave.ACCELERATION / 20.0, 0.0) } ticker(true) } fun shockwave() { - if (ply.isSpectator || isOnCooldown || !android.androidEnergy.extractEnergyInnerExact(ServerConfig.Shockwave.ENERGY_COST, false).isPositive) { + if (ply.isSpectator || isOnCooldown || !android.androidEnergy.extractEnergyExact(AndroidConfig.Shockwave.ENERGY_COST, false)) { return } @@ -117,14 +105,14 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF // TODO: raycasting val entities = ply.level.getEntitiesInEllipsoid( ply.position, - Vector(ServerConfig.Shockwave.RADIUS_HORIZONTAL, ServerConfig.Shockwave.RADIUS_VERTICAL, ServerConfig.Shockwave.RADIUS_HORIZONTAL), + Vector(AndroidConfig.Shockwave.RADIUS_HORIZONTAL, AndroidConfig.Shockwave.RADIUS_VERTICAL, AndroidConfig.Shockwave.RADIUS_HORIZONTAL), except = ply, ) { (it !is LivingEntity || !it.isSpectator && it.isAlive) } val wardens = ply.level.getEntitiesInEllipsoid( Warden::class.java, ply.position, - Vector(ServerConfig.Shockwave.RADIUS_HORIZONTAL_WARDEN, ServerConfig.Shockwave.RADIUS_VERTICAL_WARDEN, ServerConfig.Shockwave.RADIUS_HORIZONTAL_WARDEN), + Vector(AndroidConfig.Shockwave.RADIUS_HORIZONTAL_WARDEN, AndroidConfig.Shockwave.RADIUS_VERTICAL_WARDEN, AndroidConfig.Shockwave.RADIUS_HORIZONTAL_WARDEN), ) { true } val seen = ReferenceArraySet() @@ -134,12 +122,12 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF val multiplier = (1.0 - distanceMultiplier).pow(0.5) val source = ShockwaveDamageSource(ply) - val damage = multiplier.toFloat() * ServerConfig.Shockwave.DAMAGE.toFloat() * ServerConfig.Shockwave.WARDEN_DAMAGE_MULT.toFloat() + val damage = multiplier.toFloat() * AndroidConfig.Shockwave.DAMAGE.toFloat() * AndroidConfig.Shockwave.WARDEN_DAMAGE_MULT.toFloat() entity.hurt(source, damage) entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 3.0) if (ply is ServerPlayer) { - ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, source, damage) + ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, damage, source) } } @@ -150,12 +138,12 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF // don't hurt items, arrows, etc etc if (entity is LivingEntity) { val source = ShockwaveDamageSource(ply) - val damage = multiplier.toFloat() * ServerConfig.Shockwave.DAMAGE.toFloat() + val damage = multiplier.toFloat() * AndroidConfig.Shockwave.DAMAGE.toFloat() entity.hurt(source, damage) entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 3.0) if (ply is ServerPlayer) { - ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, source, damage) + ShockwaveDamageMobTrigger.trigger(ply as ServerPlayer, entity, damage, source) } } else { entity.deltaMovement += (entity.position - ply.position).normalize() * (multiplier * 6.0) @@ -163,10 +151,10 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF } } - if (ServerConfig.Shockwave.BREAK_BLOCKS) { + if (AndroidConfig.Shockwave.BREAK_BLOCKS) { val rounded = ply.position.roundToIntVector() - for (blockPos in getEllipsoidBlockPositions(ServerConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt(), ServerConfig.Shockwave.RADIUS_VERTICAL.roundToInt(), ServerConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt())) { + for (blockPos in getEllipsoidBlockPositions(AndroidConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt(), AndroidConfig.Shockwave.RADIUS_VERTICAL.roundToInt(), AndroidConfig.Shockwave.RADIUS_HORIZONTAL.roundToInt())) { val newBlockPos = blockPos + rounded val blockState = ply.level.getBlockState(newBlockPos) @@ -199,7 +187,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF !ply.isSpectator && isActive && !isOnCooldown && - android.androidEnergy.extractEnergyInnerExact(ServerConfig.Shockwave.ENERGY_COST, true).isPositive && + android.androidEnergy.extractEnergyExact(AndroidConfig.Shockwave.ENERGY_COST, true) && creativeFlightTicks == 0 ) { val old = wasMidair @@ -209,7 +197,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF highestSpeed = (-ply.deltaMovement.y).coerceAtLeast(highestSpeed) } - if (old != wasMidair && !wasMidair && ServerConfig.Shockwave.TERMINAL_VELOCITY <= (highestSpeed * 20.0) && ply.isShiftKeyDown) { + if (old != wasMidair && !wasMidair && AndroidConfig.Shockwave.TERMINAL_VELOCITY <= (highestSpeed * 20.0) && ply.isShiftKeyDown) { if (isClient) { // I HATE SELF-UPDATING PLAYERS // I HATE SELF-UPDATING PLAYERS @@ -236,23 +224,7 @@ class ShockwaveFeature(capability: MatteryPlayerCapability) : AndroidSwitchableF ticker(false) } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - if (isOnCooldown) { - RenderSystem.setShaderColor(1f, 0.4f, 0.4f, 1f) - } - - ResearchIcons.ICON_SHOCKWAVE.render(stack, x, y, width, height) - - if (isOnCooldown) { - RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - } - } - - companion object { - val POWER_COST_DESCRIPTION = - AndroidResearchManager.descriptionFunc( - ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.SHOCKWAVE), - "otm.gui.power_cost_per_use", - { ServerConfig.Shockwave.ENERGY_COST.formatPower().copy().withStyle(ChatFormatting.YELLOW) }) + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_SHOCKWAVE.render(graphics, x, y, width, height, color = if (isOnCooldown) color * RGBAColor.REDDISH else color) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt index 321d70369..74a4f7a3b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/StepAssistFeature.kt @@ -1,11 +1,12 @@ package ru.dbotthepony.mc.otm.android.feature -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.world.entity.ai.attributes.AttributeModifier import net.minecraftforge.common.ForgeMod import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.ResearchIcons +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.registry.AndroidFeatures import java.util.* @@ -53,8 +54,8 @@ class StepAssistFeature(android: MatteryPlayerCapability) : AndroidSwitchableFea sharedTick() } - override fun renderIcon(stack: PoseStack, x: Float, y: Float, width: Float, height: Float) { - ResearchIcons.ICON_STEP_ASSIST.render(stack, x, y, width, height) + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_STEP_ASSIST.render(graphics, x, y, width, height, color = color) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt new file mode 100644 index 000000000..3129d4a24 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/feature/SwimBoostersFeature.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.mc.otm.android.feature + +import net.minecraft.world.entity.ai.attributes.AttributeModifier +import net.minecraftforge.common.ForgeMod +import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.ResearchIcons +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import java.util.* + +class SwimBoostersFeature(android: MatteryPlayerCapability) : AndroidSwitchableFeature(AndroidFeatures.SWIM_BOOSTERS, android) { + override fun applyModifiers() { + if (!ForgeMod.SWIM_SPEED.isPresent || !isActive) + return + + val attr = ply.getAttribute(ForgeMod.SWIM_SPEED.get()) ?: return + + attr.removeModifier(MODIFIER_ID) + attr.addPermanentModifier(AttributeModifier(MODIFIER_ID, type.displayName.toString(), (level + 1) * AndroidConfig.SWIM_BOOSTERS, AttributeModifier.Operation.ADDITION)) + } + + override fun removeModifiers() { + if (!ForgeMod.SWIM_SPEED.isPresent) + return + + ply.getAttribute(ForgeMod.SWIM_SPEED.get())?.removeModifier(MODIFIER_ID) + } + + override fun renderIcon(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, color: RGBAColor) { + ResearchIcons.ICON_LIMB_OVERCLOCKING.render(graphics, x, y, width, height, color = color) + } + + companion object { + private val MODIFIER_ID = UUID.fromString("4a3ffa46-47a8-a03f-857d-f5c2b2c8f2f6") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt deleted file mode 100644 index bb2a49852..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt +++ /dev/null @@ -1,114 +0,0 @@ -package ru.dbotthepony.mc.otm.block - -import net.minecraft.MethodsReturnNonnullByDefault -import javax.annotation.ParametersAreNonnullByDefault -import net.minecraft.world.level.block.EntityBlock -import net.minecraft.world.item.context.BlockPlaceContext -import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.block.state.properties.BooleanProperty -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityType -import net.minecraft.world.level.block.entity.BlockEntityTicker -import net.minecraft.world.level.block.state.StateDefinition -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import ru.dbotthepony.mc.otm.block.entity.BatteryBankBlockEntity -import net.minecraft.world.level.BlockGetter -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.Block -import net.minecraft.world.phys.shapes.CollisionContext -import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.once -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.shapes.BlockShapes - -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -class BatteryBankBlock : RotatableMatteryBlock(), EntityBlock { - override fun getStateForPlacement(context: BlockPlaceContext): BlockState { - var state = super.getStateForPlacement(context)!! - - for (prop in BATTERY_SLOTS_PROPS) - state = state.setValue(prop, false) - - return state - } - - override fun getTicker( - level: Level, - p_153213_: BlockState, - type: BlockEntityType - ): BlockEntityTicker? { - if (level.isClientSide || type !== MBlockEntities.BATTERY_BANK) - return null - - return BlockEntityTicker { _, _, _, tile -> if (tile is BatteryBankBlockEntity) tile.tick() } - } - - override fun createBlockStateDefinition(builder: StateDefinition.Builder) { - builder.add(*BATTERY_SLOTS_PROPS) - super.createBlockStateDefinition(builder) - } - - override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { - return BatteryBankBlockEntity(blockPos, blockState) - } - - override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext - ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - override fun faceToPlayer(context: BlockPlaceContext): Boolean { - return false - } - - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - val blockEntity = level.getBlockEntity(pos) as? BatteryBankBlockEntity ?: return - level.once { blockEntity.checkSurroundings(level) } - } - - companion object { - val BATTERY_SLOTS_PROPS = arrayOf( - BooleanProperty.create("battery_0") as BooleanProperty, - BooleanProperty.create("battery_1") as BooleanProperty, - BooleanProperty.create("battery_2") as BooleanProperty, - BooleanProperty.create("battery_3") as BooleanProperty, - BooleanProperty.create("battery_4") as BooleanProperty, - BooleanProperty.create("battery_5") as BooleanProperty, - BooleanProperty.create("battery_6") as BooleanProperty, - BooleanProperty.create("battery_7") as BooleanProperty, - BooleanProperty.create("battery_8") as BooleanProperty, - BooleanProperty.create("battery_9") as BooleanProperty, - BooleanProperty.create("battery_10") as BooleanProperty, - BooleanProperty.create("battery_11") as BooleanProperty, - ) - - private val SHAPES: List - - init { - val def = BlockShapes.BATTERY_BANK.computeShape() - - SHAPES = java.util.List.of( - def, - def, - def, - BlockShapes.BATTERY_BANK.rotate(Direction.NORTH).computeShape(), - BlockShapes.BATTERY_BANK.rotate(Direction.WEST).computeShape(), - BlockShapes.BATTERY_BANK.rotate(Direction.EAST).computeShape() - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlackHoleBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlackHoleBlock.kt index 41f64a70a..9b7e6ab92 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlackHoleBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BlackHoleBlock.kt @@ -1,15 +1,18 @@ package ru.dbotthepony.mc.otm.block import net.minecraft.core.BlockPos +import net.minecraft.world.item.DyeColor import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityTicker import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity @@ -17,7 +20,7 @@ import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes class BlackHoleBlock : - Block(Properties.of(Material.STONE).strength(-1f, 7200000.0f)), EntityBlock { + Block(Properties.of(Material.AIR, DyeColor.BLACK).noCollission().sound(SoundType.STONE).strength(-1f, 7200000.0f)), EntityBlock { override fun getShape( p_60555_: BlockState, p_60556_: BlockGetter, @@ -27,6 +30,10 @@ class BlackHoleBlock : return SHAPE } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return BlackHoleBlockEntity(blockPos, blockState) } @@ -48,4 +55,4 @@ class BlackHoleBlock : companion object { private val SHAPE = BlockShapes.BLACK_HOLE.computeShape() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt index d48726a54..02ba02eed 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/Cables.kt @@ -3,24 +3,34 @@ package ru.dbotthepony.mc.otm.block +import com.google.common.collect.ImmutableMap import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult +import net.minecraft.world.entity.player.Player import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.block.state.properties.BooleanProperty import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor +import net.minecraft.world.level.material.PushReaction +import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.shapes.BooleanOp import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.Shapes import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.entity.MatterCableBlockEntity import ru.dbotthepony.mc.otm.block.entity.StorageCableBlockEntity +import java.util.Collections +import java.util.EnumMap -abstract class CableBlock(properties: Properties) : Block(properties) { +abstract class CableBlock(properties: Properties) : MatteryBlock(properties) { init { registerDefaultState(defaultBlockState() .setValue(CONNECTION_SOUTH, false) @@ -33,6 +43,10 @@ abstract class CableBlock(properties: Properties) : Block(properties) { override fun hasDynamicShape() = true + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.DESTROY + } + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { builder.add( CONNECTION_SOUTH, @@ -44,7 +58,45 @@ abstract class CableBlock(properties: Properties) : Block(properties) { ) } + protected fun generateShapes(halfCoreSize: Double): ImmutableMap { + return getShapeForEachState { getShapeFor(it, halfCoreSize) } + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult { + return InteractionResult.PASS + } + companion object { + fun getShapeFor(it: BlockState, halfCoreSize: Double): VoxelShape { + val shapes = ArrayList() + + if (it.getValue(CONNECTION_SOUTH)) + shapes.add(Shapes.box(0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 + halfCoreSize, 0.5 + halfCoreSize, 0.5 + halfCoreSize, 1.0)) + + if (it.getValue(CONNECTION_NORTH)) + shapes.add(Shapes.box(0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.0, 0.5 + halfCoreSize, 0.5 + halfCoreSize, 0.5 - halfCoreSize)) + + if (it.getValue(CONNECTION_DOWN)) + shapes.add(Shapes.box(0.5 - halfCoreSize, 0.0, 0.5 - halfCoreSize, 0.5 + halfCoreSize, 0.5 - halfCoreSize, 0.5 + halfCoreSize)) + + if (it.getValue(CONNECTION_UP)) + shapes.add(Shapes.box(0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 + halfCoreSize, 1.0, 0.5 + halfCoreSize)) + + if (it.getValue(CONNECTION_EAST)) + shapes.add(Shapes.box(0.5 + halfCoreSize, 0.5 - halfCoreSize, 0.5 - halfCoreSize, 1.0, 0.5 + halfCoreSize, 0.5 + halfCoreSize)) + + if (it.getValue(CONNECTION_WEST)) + shapes.add(Shapes.box(0.0, 0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 + halfCoreSize, 0.5 + halfCoreSize)) + + var finalShape = Shapes.box(0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 - halfCoreSize, 0.5 + halfCoreSize, 0.5 + halfCoreSize, 0.5 + halfCoreSize) + + for (shape in shapes) + finalShape = Shapes.join(finalShape, shape, BooleanOp.OR) + + return finalShape + } + val CONNECTION_SOUTH: BooleanProperty = BooleanProperty.create("connect_south") val CONNECTION_WEST: BooleanProperty = BooleanProperty.create("connect_west") val CONNECTION_EAST: BooleanProperty = BooleanProperty.create("connect_east") @@ -52,128 +104,25 @@ abstract class CableBlock(properties: Properties) : Block(properties) { val CONNECTION_UP: BooleanProperty = BooleanProperty.create("connect_up") val CONNECTION_DOWN: BooleanProperty = BooleanProperty.create("connect_down") - val MAPPING_CONNECTION_PROP = listOf( - CONNECTION_DOWN, - CONNECTION_UP, - CONNECTION_NORTH, - CONNECTION_SOUTH, - CONNECTION_WEST, - CONNECTION_EAST - ) + val MAPPING_CONNECTION_PROP: Map = Collections.unmodifiableMap(EnumMap(Direction::class.java) + .let { + it[Direction.DOWN] = CONNECTION_DOWN + it[Direction.UP] = CONNECTION_UP + it[Direction.NORTH] = CONNECTION_NORTH + it[Direction.SOUTH] = CONNECTION_SOUTH + it[Direction.WEST] = CONNECTION_WEST + it[Direction.EAST] = CONNECTION_EAST + Collections.unmodifiableMap(it) + }) } } -class MatterCableBlock : CableBlock( - Properties.of(Material.STONE, MaterialColor.METAL).requiresCorrectToolForDrops().strength(1.0f, 6.0f)), - EntityBlock { - private val CORE_SHAPE: VoxelShape = Shapes.box( - 0.5 - 0.15, - 0.5 - 0.15, - 0.5 - 0.15, - 0.5 + 0.15, - 0.5 + 0.15, - 0.5 + 0.15 - ) - - private val shapes = getShapeForEachState { - val shapes = ArrayList() - val width = 0.15 - - if (it.getValue(CONNECTION_SOUTH)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.5 + width, - 0.5 + width, - 0.5 + width, - 1.0 - ) - ) - } - - if (it.getValue(CONNECTION_NORTH)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.0, - 0.5 + width, - 0.5 + width, - 0.5 - width - ) - ) - } - - if (it.getValue(CONNECTION_DOWN)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.0, - 0.5 - width, - 0.5 + width, - 0.5 - width, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_UP)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.5 - width, - 0.5 + width, - 1.0, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_EAST)) { - shapes.add( - Shapes.box( - 0.5 + width, - 0.5 - width, - 0.5 - width, - 1.0, - 0.5 + width, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_WEST)) { - shapes.add( - Shapes.box( - 0.0, - 0.5 - width, - 0.5 - width, - 0.5 - width, - 0.5 + width, - 0.5 + width - ) - ) - } - - var finalShape = CORE_SHAPE - - for (add_shape in shapes) { - finalShape = Shapes.joinUnoptimized(finalShape, add_shape, BooleanOp.OR) - } - - return@getShapeForEachState finalShape - } +class MatterCableBlock : CableBlock(Properties.of(Material.METAL).requiresCorrectToolForDrops().sound(SoundType.METAL).strength(1.0f, 6.0f)), EntityBlock { + private val shapes = generateShapes(0.15) @Suppress("OVERRIDE_DEPRECATION") - override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext - ): VoxelShape { - return shapes[p_60555_] ?: CORE_SHAPE + override fun getShape(blockState: BlockState, accessor: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape { + return shapes[blockState] ?: Shapes.block() } override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { @@ -181,130 +130,28 @@ class MatterCableBlock : CableBlock( } } -class StorageCableBlock : CableBlock( - Properties.of(Material.STONE, MaterialColor.METAL).requiresCorrectToolForDrops().strength(1.0f, 6.0f)), - EntityBlock { - - companion object { - val CORE_SHAPE: VoxelShape = Shapes.box( - 0.5 - 0.185, - 0.5 - 0.185, - 0.5 - 0.185, - 0.5 + 0.185, - 0.5 + 0.185, - 0.5 + 0.185 - ) - - private const val width = 0.185 - - fun getShapeFor(it: BlockState): MutableList { - val shapes = ArrayList() - - if (it.getValue(CONNECTION_SOUTH)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.5 + width, - 0.5 + width, - 0.5 + width, - 1.0 - ) - ) - } - - if (it.getValue(CONNECTION_NORTH)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.0, - 0.5 + width, - 0.5 + width, - 0.5 - width - ) - ) - } - - if (it.getValue(CONNECTION_DOWN)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.0, - 0.5 - width, - 0.5 + width, - 0.5 - width, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_UP)) { - shapes.add( - Shapes.box( - 0.5 - width, - 0.5 - width, - 0.5 - width, - 0.5 + width, - 1.0, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_EAST)) { - shapes.add( - Shapes.box( - 0.5 + width, - 0.5 - width, - 0.5 - width, - 1.0, - 0.5 + width, - 0.5 + width - ) - ) - } - - if (it.getValue(CONNECTION_WEST)) { - shapes.add( - Shapes.box( - 0.0, - 0.5 - width, - 0.5 - width, - 0.5 - width, - 0.5 + width, - 0.5 + width - ) - ) - } - - shapes.add(CORE_SHAPE) - return shapes - } - } - - private val shapes = getShapeForEachState { - val shapes = getShapeFor(it) - var finalShape = shapes[0] - - for (i in 1 until shapes.size) { - finalShape = Shapes.joinUnoptimized(finalShape, shapes[i], BooleanOp.OR) - } - - return@getShapeForEachState finalShape - } +class StorageCableBlock : CableBlock(Properties.of(Material.METAL).requiresCorrectToolForDrops().sound(SoundType.METAL).strength(1.0f, 6.0f)), EntityBlock { + private val shapes = generateShapes(0.185) @Suppress("OVERRIDE_DEPRECATION") - override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext - ): VoxelShape { - return shapes[p_60555_] ?: CORE_SHAPE + override fun getShape(blockState: BlockState, accessor: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape { + return shapes[blockState] ?: Shapes.block() } override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return StorageCableBlockEntity(blockPos, blockState) } } + +class EnergyCableBlock(val factory: (blockPos: BlockPos, blockState: BlockState) -> BlockEntity) : CableBlock(Properties.of(Material.METAL).requiresCorrectToolForDrops().sound(SoundType.METAL).strength(1.0f, 6.0f)), EntityBlock { + private val shapes = generateShapes(0.185) + + @Suppress("OVERRIDE_DEPRECATION") + override fun getShape(blockState: BlockState, accessor: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape { + return shapes[blockState] ?: Shapes.block() + } + + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { + return factory(blockPos, blockState) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/ExplosionDebuggerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/ExplosionDebuggerBlock.kt index 82e58bbe2..c2709a0dd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/ExplosionDebuggerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/ExplosionDebuggerBlock.kt @@ -4,20 +4,26 @@ import net.minecraft.core.BlockPos import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityTicker import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityExplosionDebugger import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntitySphereDebugger import ru.dbotthepony.mc.otm.registry.MBlockEntities -class BlockExplosionDebugger : Block(Properties.of(Material.STONE)), EntityBlock { +class BlockExplosionDebugger : Block(Properties.of(Material.METAL).sound(SoundType.STONE)), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return BlockEntityExplosionDebugger(p_153215_, p_153216_) } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun getTicker( p_153212_: Level, p_153213_: BlockState, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt index 6d1a6700c..ac7a3628d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt @@ -1,10 +1,13 @@ package ru.dbotthepony.mc.otm.block +import com.google.common.collect.ImmutableMap +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.core.particles.DustParticleOptions +import net.minecraft.network.chat.Component import net.minecraft.util.RandomSource -import net.minecraft.world.Container import net.minecraft.world.Containers import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionResult @@ -12,41 +15,62 @@ import net.minecraft.world.MenuProvider import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock -import net.minecraft.world.level.block.Rotation import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.block.state.StateDefinition -import net.minecraft.world.level.block.state.properties.EnumProperty +import net.minecraft.world.level.block.state.properties.Property import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor import net.minecraft.world.phys.BlockHitResult +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.entity.IRedstoneControlled import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.once -interface IDroppableContainer { - val droppableContainer: Container +fun Block.getShapeForEachState(properties: List>, fn: (BlockState) -> VoxelShape): Map { + val builder = ImmutableMap.Builder() - fun beforeDroppingItems( - oldBlockState: BlockState, - level: Level, - blockPos: BlockPos, - newBlockState: BlockState, - movedByPiston: Boolean - ) {} + if (properties.isEmpty()) { + val shape = fn(stateDefinition.possibleStates.first()) - fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {} + for (state in stateDefinition.possibleStates) { + builder.put(state, shape) + } + } else { + val cache = Object2ObjectArrayMap, VoxelShape>() + + for (state in stateDefinition.possibleStates) { + builder.put(state, cache.computeIfAbsent(properties.map { state[it] }, Object2ObjectFunction { fn(state) })) + } + } + + return builder.build() } -abstract class MatteryBlock @JvmOverloads constructor( - properties: Properties = DEFAULT_PROPERTIES -) : Block(properties) { +fun Block.getShapeForEachState(property: Property<*>, fn: (BlockState) -> VoxelShape): Map { + return getShapeForEachState(listOf(property), fn) +} + +fun interface INeighbourChangeListener { + fun neighborChanged( + state: BlockState, + level: Level, + pos: BlockPos, + neighbour: Block, + neighbourPos: BlockPos, + movedByPiston: Boolean + ) +} + +abstract class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(properties), INeighbourChangeListener { override fun setPlacedBy( level: Level, blockPos: BlockPos, @@ -57,8 +81,13 @@ abstract class MatteryBlock @JvmOverloads constructor( if (this is EntityBlock && itemStack.hasCustomHoverName() && !level.isClientSide) { val tile = level.getBlockEntity(blockPos) - if (tile is MatteryBlockEntity) - tile.customDisplayName = itemStack.displayName + if (tile is MatteryDeviceBlockEntity) { + try { + tile.customDisplayName = Component.Serializer.fromJson(itemStack.tagNotNull.getCompound("display").getString("Name")) + } catch(_: Exception) { + + } + } } super.setPlacedBy(level, blockPos, blockState, entity, itemStack) @@ -85,6 +114,7 @@ abstract class MatteryBlock @JvmOverloads constructor( if (this is EntityBlock && level.isClientSide) return InteractionResult.SUCCESS + @Suppress("DEPRECATION") return super.use(blockState, level, blockPos, ply, hand, blockHitResult) } @@ -93,7 +123,9 @@ abstract class MatteryBlock @JvmOverloads constructor( val state = blockState.getOptionalValue(WorkerState.WORKER_STATE).or { blockState.getOptionalValue(WorkerState.SEMI_WORKER_STATE) } if (state.isPresent && state.get() == WorkerState.WORKING) { - val state2 = blockState.getOptionalValue(RotatableMatteryBlock.FACING).or { blockState.getOptionalValue(RotatableMatteryBlock.FACING_FULL) } + val state2 = blockState.getOptionalValue(BlockRotationFreedom.HORIZONTAL.property) + .or { blockState.getOptionalValue(BlockRotationFreedom.DIRECTIONAL.property) } + .or { blockState.getOptionalValue(BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION.property) } if (state2.isPresent) { val direction = state2.get() @@ -108,7 +140,7 @@ abstract class MatteryBlock @JvmOverloads constructor( yd += ny * 0.5 zd += nz * 0.5 - when (direction) { + when (direction.front) { Direction.DOWN, Direction.UP -> { xd += random.nextDouble() - 0.5 zd += random.nextDouble() - 0.5 @@ -144,7 +176,6 @@ abstract class MatteryBlock @JvmOverloads constructor( return null } - @Suppress("OVERRIDE_DEPRECATION") override fun neighborChanged( state: BlockState, level: Level, @@ -153,13 +184,21 @@ abstract class MatteryBlock @JvmOverloads constructor( neighbourPos: BlockPos, movedByPiston: Boolean ) { + @Suppress("DEPRECATION") super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) if (this is EntityBlock && !level.isClientSide) { - val tile = level.getBlockEntity(pos) + val tile = level.getBlockEntity(pos) ?: return - if (tile is MatteryBlockEntity) - tile.redstoneSignal = level.getBestNeighborSignal(pos) + level.once { + if (!tile.isRemoved) { + if (tile is IRedstoneControlled) + tile.redstoneControl.redstoneSignal = level.getBestNeighborSignal(pos) + + if (tile is MatteryBlockEntity) + tile.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) + } + } } } @@ -171,12 +210,15 @@ abstract class MatteryBlock @JvmOverloads constructor( newBlockState: BlockState, movedByPiston: Boolean ) { - if (!oldBlockState.`is`(newBlockState.block)) { + if (!oldBlockState.`is`(newBlockState.block) && !level.isClientSide) { val blockentity = level.getBlockEntity(blockPos) - if (blockentity is IDroppableContainer) { + if (blockentity is MatteryBlockEntity) { blockentity.beforeDroppingItems(oldBlockState, level, blockPos, newBlockState, movedByPiston) - Containers.dropContents(level, blockPos, blockentity.droppableContainer) + + for (container in blockentity.droppableContainers) + Containers.dropContents(level, blockPos, container) + level.updateNeighbourForOutputSignal(blockPos, this) } } @@ -190,63 +232,14 @@ abstract class MatteryBlock @JvmOverloads constructor( val blockentity = level.getBlockEntity(blockPos) - if (blockentity is IDroppableContainer) { + if (blockentity is MatteryBlockEntity) { blockentity.beforeDestroyedByPlayer(level, blockPos, blockState, player) } } companion object { - val DEFAULT_PROPERTIES: Properties = Properties.of(Material.STONE, MaterialColor.METAL).requiresCorrectToolForDrops().strength(1.5f, 25.0f) + val DEFAULT_PROPERTIES: Properties = Properties.of(Material.METAL).requiresCorrectToolForDrops().destroyTime(1.5f).explosionResistance(25.0f) + val DEFAULT_MACHINE_PROPERTIES: Properties = Properties.of(Material.HEAVY_METAL).requiresCorrectToolForDrops().destroyTime(1.5f).explosionResistance(25.0f) } } -abstract class RotatableMatteryBlock @JvmOverloads constructor(properties: Properties = DEFAULT_PROPERTIES) : MatteryBlock(properties) { - init { - @Suppress("LeakingThis") - registerDefaultState(getStateDefinition().any().setValue(facingProperty, Direction.SOUTH)) - } - - override fun createBlockStateDefinition(builder: StateDefinition.Builder) { - builder.add(facingProperty) - } - - override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - if (hasFreeRotation) { - return defaultBlockState().setValue( - FACING_FULL, - if (faceToPlayer(context)) context.nearestLookingDirection.opposite else context.nearestLookingDirection - ) - } else { - return defaultBlockState().setValue( - FACING, - if (faceToPlayer(context)) context.horizontalDirection.opposite else context.horizontalDirection - ) - } - } - - @Suppress("OVERRIDE_DEPRECATION") - override fun rotate(blockState: BlockState, rotation: Rotation): BlockState { - @Suppress("DEPRECATION") - return super.rotate(blockState, rotation).setValue(facingProperty, rotation.rotate(blockState[facingProperty])) - } - - val facingProperty get() = if (hasFreeRotation) FACING_FULL else FACING - open val hasFreeRotation get() = false - - open fun faceToPlayer(context: BlockPlaceContext): Boolean { - return true - } - - companion object { - val FACING: EnumProperty = EnumProperty.create( - "facing", - Direction::class.java, - Direction.SOUTH, - Direction.WEST, - Direction.NORTH, - Direction.EAST - ) - - val FACING_FULL: EnumProperty = EnumProperty.create("facing", Direction::class.java) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/RotatableMatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/RotatableMatteryBlock.kt new file mode 100644 index 000000000..602ec1b99 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/RotatableMatteryBlock.kt @@ -0,0 +1,68 @@ +package ru.dbotthepony.mc.otm.block + +import net.minecraft.core.Direction +import net.minecraft.world.item.context.BlockPlaceContext +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Rotation +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.StateDefinition +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom + +abstract class RotatableMatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : MatteryBlock(properties) { + init { + @Suppress("LeakingThis") + registerDefaultState(getStateDefinition().any().setValue(rotationProperty, BlockRotation.SOUTH)) + } + + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { + builder.add(rotationProperty) + } + + override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { + return when (val freedom = rotationFreedom()) { + BlockRotationFreedom.HORIZONTAL -> defaultBlockState().setValue( + freedom.property, + freedom.of(if (faceToPlayer(context)) context.horizontalDirection.opposite else context.horizontalDirection) + ) + + BlockRotationFreedom.DIRECTIONAL -> defaultBlockState().setValue( + freedom.property, + freedom.of(if (faceToPlayer(context)) context.nearestLookingDirection.opposite else context.nearestLookingDirection) + ) + + BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION -> { + val primary = if (faceToPlayer(context)) context.nearestLookingDirection.opposite else context.nearestLookingDirection + var secondary = if (faceToPlayer(context)) context.horizontalDirection else context.horizontalDirection.opposite + + if (primary == Direction.DOWN) { + secondary = secondary.opposite + } + + defaultBlockState().setValue( + freedom.property, + freedom.of(primary, secondary) + ) + } + + BlockRotationFreedom.FULL -> TODO("Can't rotate with four rotation freedom yet") + } + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun rotate(blockState: BlockState, rotation: Rotation): BlockState { + @Suppress("DEPRECATION") + return super.rotate(blockState, rotation).setValue(rotationProperty, blockState[rotationProperty].rotate(rotation)) + } + + val rotationProperty get() = rotationFreedom().property + + open fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.HORIZONTAL + } + + open fun faceToPlayer(context: BlockPlaceContext): Boolean { + return true + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/CargoCrateBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/CargoCrateBlock.kt similarity index 60% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/CargoCrateBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/CargoCrateBlock.kt index 7f2364c4b..3074a4033 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/CargoCrateBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/CargoCrateBlock.kt @@ -1,23 +1,30 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.decorative import net.minecraft.core.BlockPos -import net.minecraft.world.Containers import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.DyeColor import net.minecraft.world.item.context.BlockPlaceContext +import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.block.state.properties.BooleanProperty import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor -import ru.dbotthepony.mc.otm.block.entity.CargoCrateBlockEntity +import net.minecraft.world.level.material.PushReaction +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.Shapes +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.shapes.BlockShapes class CargoCrateBlock(val color: DyeColor?) : RotatableMatteryBlock( - Properties.of(Material.STONE, color?.materialColor ?: MaterialColor.COLOR_BLUE).requiresCorrectToolForDrops().strength(1.5f, 30.0f) + Properties.of(Material.METAL, color ?: DyeColor.BLUE).requiresCorrectToolForDrops().sound(SoundType.METAL).strength(1.5f, 30.0f) ), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return CargoCrateBlockEntity(blockPos, blockState) @@ -28,6 +35,10 @@ class CargoCrateBlock(val color: DyeColor?) : RotatableMatteryBlock( builder.add(IS_OPEN) } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { return super.getStateForPlacement(context)?.setValue(IS_OPEN, false) } @@ -43,8 +54,22 @@ class CargoCrateBlock(val color: DyeColor?) : RotatableMatteryBlock( return AbstractContainerMenu.getRedstoneSignalFromContainer(tile.container) } + override fun getShape( + p_60555_: BlockState, + p_60556_: BlockGetter, + p_60557_: BlockPos, + p_60558_: CollisionContext + ): VoxelShape { + if (p_60555_[IS_OPEN]) { + return CARGO_CRATE_OPEN + } else { + return Shapes.block() + } + } + companion object { @JvmField val IS_OPEN: BooleanProperty = BooleanProperty.create("open") + val CARGO_CRATE_OPEN: VoxelShape = BlockShapes.CARGO_CRATE_OPEN.computeShape() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/DevChestBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/DevChestBlock.kt new file mode 100644 index 000000000..af30a777e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/DevChestBlock.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity + +class DevChestBlock : RotatableMatteryBlock(Properties.of(Material.METAL).destroyTime(-1f).explosionResistance(360000f)), EntityBlock { + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { + return DevChestBlockEntity(p_153215_, p_153216_) + } + + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.NORMAL + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/EngineBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/EngineBlock.kt new file mode 100644 index 000000000..3c26d807c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/EngineBlock.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.ChatFormatting +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.block.SoundType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class EngineBlock : RotatableMatteryBlock(Properties.of(Material.METAL, DyeColor.ORANGE).sound(SoundType.METAL).explosionResistance(14f).destroyTime(2.5f).requiresCorrectToolForDrops()) { + override fun appendHoverText( + p_49816_: ItemStack, + p_49817_: BlockGetter?, + p_49818_: MutableList, + p_49819_: TooltipFlag + ) { + super.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) + p_49818_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY)) + } + + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.NORMAL + } + + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } + + private val shapes = getShapeForEachState { BlockShapes.ENGINE.rotateFromNorth(it[rotationProperty]).computeShape() } + + override fun getShape( + p_60555_: BlockState, + p_60556_: BlockGetter, + p_60557_: BlockPos, + p_60558_: CollisionContext + ): VoxelShape { + return shapes[p_60555_]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt new file mode 100644 index 000000000..ea0b5a975 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/FluidTankBlock.kt @@ -0,0 +1,75 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult +import net.minecraft.world.entity.player.Player +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.BlockHitResult +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import net.minecraftforge.fluids.FluidUtil +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class FluidTankBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity { + return FluidTankBlockEntity(pPos, pState) + } + + override fun getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType): BlockEntityTicker? { + if (pLevel.isClientSide) + return null + + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is FluidTankBlockEntity) pBlockEntity.tick() } + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult { + if (FluidUtil.interactWithFluidHandler(ply, hand, level, blockPos, blockHitResult.direction)) { + return InteractionResult.sidedSuccess(level.isClientSide) + } + + return super.use(blockState, level, blockPos, ply, hand, blockHitResult) + } + + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.FLUID_TANK.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } + + override fun getLightEmission(state: BlockState?, level: BlockGetter?, pos: BlockPos?): Int { + if (pos == BlockPos.ZERO) return 15 + + val lightLevel = super.getLightEmission(state, level, pos) + + val tile = level?.getExistingBlockEntity(pos) ?: lightLevel + + if (tile is FluidTankBlockEntity) { + val fluid = tile.synchronizedFluid + + if (!fluid.isEmpty) { + val newLevel = fluid.fluid.fluidType.getLightLevel(fluid) + return newLevel.coerceAtLeast(lightLevel) + } + } + + return lightLevel + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/HoloSignBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/HoloSignBlock.kt new file mode 100644 index 000000000..d54faef8e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/HoloSignBlock.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class HoloSignBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION + } + + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { + return HoloSignBlockEntity(p_153215_, p_153216_) + } + + private val shapes = getShapeForEachState { BlockShapes.HOLO_SIGN.rotateFromNorth(it[rotationProperty]).computeShape() } + + override fun getShape( + pState: BlockState, + pLevel: BlockGetter, + pPos: BlockPos, + pContext: CollisionContext + ): VoxelShape { + return shapes[pState]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt new file mode 100644 index 000000000..29f7645e0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/InfiniteWaterSourceBlock.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.MaterialColor +import net.minecraft.world.level.material.PushReaction +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.InfiniteWaterSourceBlockEntity + +class InfiniteWaterSourceBlock : RotatableMatteryBlock(Properties.of(Material.METAL, MaterialColor.WATER).destroyTime(1.5f).explosionResistance(10f).requiresCorrectToolForDrops()), EntityBlock { + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { + return InfiniteWaterSourceBlockEntity(p_153215_, p_153216_) + } + + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.NORMAL + } + + override fun getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType): BlockEntityTicker? { + if (p_153212_.isClientSide) + return null + + return BlockEntityTicker { _, _, _, p_155256_ -> if (p_155256_ is InfiniteWaterSourceBlockEntity) p_155256_.tick() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/LaboratoryLamp.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt similarity index 79% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/LaboratoryLamp.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt index 63e4ad9ee..090ee9ef2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/LaboratoryLamp.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/LaboratoryLamp.kt @@ -1,38 +1,43 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.decorative import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component +import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.item.TooltipFlag import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.Blocks -import net.minecraft.world.level.block.RenderShape -import net.minecraft.world.level.block.Rotation +import net.minecraft.world.level.block.* import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.block.state.properties.BlockStateProperties import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.Shapes import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.get -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.times -import ru.dbotthepony.mc.otm.core.unaryMinus +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.blockRotation +import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.once import ru.dbotthepony.mc.otm.registry.MBlocks -class LaboratoryLampLight : Block(Properties.of(Material.AIR).strength(-1.0F, 3600000.8F).noLootTable().noOcclusion().lightLevel { 15 }) { +private val FACING_FULL = BlockRotationFreedom.DIRECTIONAL.property + +class LaboratoryLampLight : Block(Properties.of(Material.AIR).strength(-1.0F, 3600000.8F).noCollission().noLootTable().lightLevel { 15 }) { override fun createBlockStateDefinition(builder: StateDefinition.Builder) { super.createBlockStateDefinition(builder) - builder.add(RotatableMatteryBlock.FACING_FULL) + builder.add(FACING_FULL) + } + + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.DESTROY } override fun hasDynamicShape(): Boolean { @@ -52,7 +57,7 @@ class LaboratoryLampLight : Block(Properties.of(Material.AIR).strength(-1.0F, 36 @Suppress("OVERRIDE_DEPRECATION") override fun rotate(blockState: BlockState, rotation: Rotation): BlockState { @Suppress("DEPRECATION") - return super.rotate(blockState, rotation).setValue(RotatableMatteryBlock.FACING_FULL, rotation.rotate(blockState[RotatableMatteryBlock.FACING_FULL])) + return super.rotate(blockState, rotation).setValue(FACING_FULL, blockState[FACING_FULL].rotate(rotation)) } @Suppress("OVERRIDE_DEPRECATION") @@ -79,7 +84,7 @@ class LaboratoryLampLight : Block(Properties.of(Material.AIR).strength(-1.0F, 36 if (level.getBlockState(pos) != state) return@once - val facing = state[RotatableMatteryBlock.FACING_FULL] + val facing = state[FACING_FULL] var hit = false @@ -103,21 +108,25 @@ class LaboratoryLampLight : Block(Properties.of(Material.AIR).strength(-1.0F, 36 } } -class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of(Material.METAL).explosionResistance(12f).destroyTime(2f).requiresCorrectToolForDrops()) { +class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of(Material.METAL, DyeColor.WHITE).sound(SoundType.METAL).explosionResistance(12f).destroyTime(2f).requiresCorrectToolForDrops()) { init { registerDefaultState(stateDefinition.any().setValue(BlockStateProperties.LIT, !invertRedstone)) } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.NORMAL + } + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { super.createBlockStateDefinition(builder) - builder.add(RotatableMatteryBlock.FACING_FULL) + builder.add(FACING_FULL) builder.add(BlockStateProperties.LIT) } override fun getStateForPlacement(context: BlockPlaceContext): BlockState { return super.getStateForPlacement(context)!! .setValue(BlockStateProperties.LIT, !invertRedstone) - .setValue(RotatableMatteryBlock.FACING_FULL, -context.nearestLookingDirection) + .setValue(FACING_FULL, context.nearestLookingDirection.blockRotation.oppositeFront) } override fun appendHoverText( @@ -146,7 +155,7 @@ class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of(Material @Suppress("OVERRIDE_DEPRECATION") override fun rotate(blockState: BlockState, rotation: Rotation): BlockState { @Suppress("DEPRECATION") - return super.rotate(blockState, rotation).setValue(RotatableMatteryBlock.FACING_FULL, rotation.rotate(blockState[RotatableMatteryBlock.FACING_FULL])) + return super.rotate(blockState, rotation).setValue(FACING_FULL, blockState[FACING_FULL].rotate(rotation)) } fun doTick( @@ -171,7 +180,7 @@ class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of(Material level.setBlockAndUpdate(pos, state.setValue(BlockStateProperties.LIT, shouldBeLit)) } - val facing = state[RotatableMatteryBlock.FACING_FULL] + val facing = state[FACING_FULL] for (i in 1 .. ServerConfig.LABORATORY_LAMP_LIGHT_LENGTH) { val target = pos + facing * i @@ -183,7 +192,9 @@ class LaboratoryLamp(val invertRedstone: Boolean) : Block(Properties.of(Material if (shouldBeLit) { if (targetState.isAir && targetState.block != MBlocks.LABORATORY_LAMP_LIGHT) { - level.setBlockAndUpdate(target, MBlocks.LABORATORY_LAMP_LIGHT.defaultBlockState().setValue(RotatableMatteryBlock.FACING_FULL, -facing)) + level.setBlockAndUpdate(target, MBlocks.LABORATORY_LAMP_LIGHT.defaultBlockState() + .setValue(FACING_FULL, facing.oppositeFront) + ) } } else { if (targetState.block == MBlocks.LABORATORY_LAMP_LIGHT) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt new file mode 100644 index 000000000..c13cce68a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/PainterBlock.kt @@ -0,0 +1,22 @@ +package ru.dbotthepony.mc.otm.block.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity + +class PainterBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { + return PainterBlockEntity(p_153215_, p_153216_) + } + + override fun getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType): BlockEntityTicker? { + if (p_153212_.isClientSide) return null + return BlockEntityTicker { p_155253_, p_155254_, p_155255_, p_155256_ -> if (p_155256_ is PainterBlockEntity) p_155256_.tick() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/TritaniumPressurePlate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt similarity index 92% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/TritaniumPressurePlate.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt index abc9fd9ac..2fecb3d4c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/TritaniumPressurePlate.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/decorative/TritaniumPressurePlate.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.decorative import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos @@ -14,6 +14,7 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.block.BasePressurePlateBlock import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.block.state.properties.BlockStateProperties @@ -21,7 +22,7 @@ import net.minecraft.world.level.material.Material import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.get -class TritaniumPressurePlate(color: DyeColor?) : BasePressurePlateBlock(Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE).explosionResistance(80f).noOcclusion().destroyTime(3f).requiresCorrectToolForDrops()) { +class TritaniumPressurePlate(color: DyeColor?) : BasePressurePlateBlock(Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE).sound(SoundType.METAL).explosionResistance(80f).noOcclusion().destroyTime(3f).requiresCorrectToolForDrops()) { override fun appendHoverText( p_49816_: ItemStack, p_49817_: BlockGetter?, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/AndroidStationBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/AndroidStationBlockEntity.kt deleted file mode 100644 index ec79fb785..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/AndroidStationBlockEntity.kt +++ /dev/null @@ -1,121 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.MenuProvider -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.phys.AABB -import net.minecraftforge.common.ForgeConfigSpec -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.defineDecimal -import ru.dbotthepony.mc.otm.core.ifPresentK -import ru.dbotthepony.mc.otm.menu.AndroidStationMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce - -@Suppress("ObjectPropertyName") -class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return AndroidStationMenu(containerID, inventory, this) - } - - override val defaultDisplayName: Component - get() = MBlocks.ANDROID_STATION.name - - override val energy = object : WorkerEnergyStorage(this@AndroidStationBlockEntity::setChangedLight, ::CAPACITY, ::MAX_IO, { null }) { - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return super.extractEnergyInner(howMuch, simulate).also { - if (!simulate && this.batteryLevel.isZero) { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) - } - } - } - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return super.receiveEnergyInner(howMuch, simulate).also { - if (!simulate && it.isPositive) { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) - } - } - } - } - } - - private var tickedOnve = false - - fun tick() { - if (!tickedOnve) { - tickedOnve = true - - if (energy.batteryLevel.isPositive && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) - } else if (energy.batteryLevel.isZero && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) - } - } - - batteryChargeLoop() - - if (isBlockedByRedstone) return - val level = level ?: return - val x = blockPos.x.toDouble() - val y = blockPos.y.toDouble() - val z = blockPos.z.toDouble() - - for (ent in level.getEntitiesOfClass(ServerPlayer::class.java, AABB(x, y, z, x + 1.0, y + 2.0, z + 1.0))) { - ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { - if (!it.isAndroid) - return@ifPresentK - - val missing = it.androidEnergy.missingPower - - if (missing > Decimal.ZERO) { - val extract = energy.extractEnergyInner(missing, true) - - if (extract > Decimal.ZERO) { - val received = it.androidEnergy.receiveEnergyOuter(extract, false) - energy.extractEnergyInner(received, false) - } - } - } - } - } - - companion object { - private var _CAPACITY: DecimalConfigValue by WriteOnce() - private var _MAX_IO: DecimalConfigValue by WriteOnce() - private var _ENERGY_PER_OPERATION: DecimalConfigValue by WriteOnce() - private var _ENERGY_PER_RESEARCH: DecimalConfigValue by WriteOnce() - - val CAPACITY get() = _CAPACITY.get() - val MAX_IO get() = _MAX_IO.get() - val ENERGY_PER_OPERATION get() = _ENERGY_PER_OPERATION.get() - val ENERGY_PER_RESEARCH get() = _ENERGY_PER_RESEARCH.get() - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.ANDROID_STATION) - - _CAPACITY = builder.defineDecimal("capacity", Decimal(100_000), Decimal.ONE) - _MAX_IO = builder.defineDecimal("throughput", Decimal.valueOf(2048), Decimal.ONE) - _ENERGY_PER_OPERATION = builder.comment("Swapping parts, etc").defineDecimal("energyPerOperation", Decimal.valueOf(2048), Decimal.ONE) - _ENERGY_PER_RESEARCH = builder.defineDecimal("energyPerResearch", Decimal.valueOf(16384), Decimal.ONE) - - builder.pop() - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt deleted file mode 100644 index 0247fdf29..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt +++ /dev/null @@ -1,360 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.energy.IEnergyStorage -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.* -import ru.dbotthepony.mc.otm.block.BatteryBankBlock -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.capability.* -import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.menu.BatteryBankMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities - -class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.BATTERY_BANK, p_155229_, p_155230_), IDroppableContainer { - var gaugeLevel by synchronizer.float() - private set - - // 6 на 2 - val container: MatteryContainer = object : MatteryContainer(this::setChanged, CAPACITY) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - - val level = level - if (level != null) { - var state = blockState - - for (i in BatteryBankBlock.BATTERY_SLOTS_PROPS.indices) { - state = state.setValue(BatteryBankBlock.BATTERY_SLOTS_PROPS[i], getItem(i).getCapability(ForgeCapabilities.ENERGY).isPresent) - } - - if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) - } - } - - gaugeLevel = (energy.batteryLevel / energy.maxBatteryLevel).toFloat() - } - } - - override val droppableContainer: Container - get() = container - private val itemHandler = container.handler( - object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(ForgeCapabilities.ENERGY).isPresent - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return true - } - } - ) - - private data class BatteryBankDistribution(val distribution: Array, val maxThroughput: Decimal) - private enum class BankMode { RECEIVE, EXTRACT, BIDIRECTIONAL } - - private inner class BatteryBankEnergy(private val mode: BankMode) : IMatteryEnergyStorage { - override fun canExtract() = mode != BankMode.RECEIVE - override fun canReceive() = mode != BankMode.EXTRACT - - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (mode == BankMode.RECEIVE) - return Decimal.ZERO - - return extractEnergyInner(howMuch, simulate) - } - - fun getDistribution(isReceiving: Boolean): BatteryBankDistribution { - val distribution = Array(container.containerSize) { Decimal.ZERO } - var summ = Decimal.ZERO - - for (i in 0 until container.containerSize) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - val diff: Decimal - - if (isReceiving) { - diff = it.receiveEnergy(it.maxEnergyStoredMattery, true) - } else { - diff = it.extractEnergy(it.energyStoredMattery, true) - } - - distribution[i] = diff - summ += distribution[i] - } - } - } - - if (!summ.isZero) { - for (i in 0 until container.containerSize) { - distribution[i] = distribution[i] / summ - } - } - - return BatteryBankDistribution(distribution, summ) - } - - private fun distributeEnergy(isReceiving: Boolean, howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive) - return Decimal.ZERO - - val distribution = getDistribution(isReceiving) - - if (distribution.maxThroughput.isZero) - return Decimal.ZERO - - val distList = distribution.distribution - var summ = Decimal.ZERO - - for (i in 0 until container.containerSize) { - if (!distList[i].isZero) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - val diff: Decimal - - if (isReceiving) { - diff = it.receiveEnergy(howMuch * distList[i], simulate) - } else { - diff = it.extractEnergy(howMuch * distList[i], simulate) - } - - summ += diff - } - } - } - } - - if (!simulate && !summ.isZero) { - setChangedLight() - gaugeLevel = (batteryLevel / maxBatteryLevel).toFloat() - } - - return summ - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return distributeEnergy(isReceiving = false, howMuch, simulate) - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (mode == BankMode.EXTRACT) - return Decimal.ZERO - - return receiveEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return distributeEnergy(isReceiving = true, howMuch, simulate) - } - - override val batteryLevel: Decimal get() { - var result = Decimal.ZERO - - for (i in 0 until container.containerSize) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - result += it.energyStoredMattery - } - } - } - - return result - } - - override val maxBatteryLevel: Decimal get() { - var result = Decimal.ZERO - - for (i in 0 until container.containerSize) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - result += it.maxEnergyStoredMattery - } - } - } - - return result - } - } - - private val energyReceiver = BatteryBankEnergy(BankMode.RECEIVE) - private val energyExtractor = BatteryBankEnergy(BankMode.EXTRACT) - private val energy = BatteryBankEnergy(BankMode.BIDIRECTIONAL) - - private var resolverEnergyReceiver = LazyOptional.of { energyReceiver } - private var resolverEnergyExtractor = LazyOptional.of { energyExtractor } - private var resolverEnergy = LazyOptional.of { energy } - - private var mekanismResolverEnergyReceiver = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(energyReceiver) } else LazyOptional.empty() - private var mekanismResolverEnergyExtractor = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(energyExtractor) } else LazyOptional.empty() - private var mekanismResolverEnergy = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(energy) } else LazyOptional.empty() - - private var resolverItemHandler = LazyOptional.of { itemHandler } - - private var valid = true - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - container.deserializeNBT(nbt[INVENTORY_KEY]) - super.load(nbt) - } - - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return BatteryBankMenu(containerID, inventory, this) - } - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - resolverEnergyReceiver.invalidate() - resolverEnergyExtractor.invalidate() - resolverEnergy.invalidate() - resolverItemHandler.invalidate() - - if (isMekanismLoaded) { - mekanismResolverEnergyReceiver.invalidate() - mekanismResolverEnergyExtractor.invalidate() - mekanismResolverEnergy.invalidate() - } - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolverEnergyReceiver = LazyOptional.of { energyReceiver } - resolverEnergyExtractor = LazyOptional.of { energyExtractor } - resolverEnergy = LazyOptional.of { energy } - resolverItemHandler = LazyOptional.of { itemHandler } - - if (isMekanismLoaded) { - mekanismResolverEnergyReceiver = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyReceiver) } - mekanismResolverEnergyExtractor = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyExtractor) } - mekanismResolverEnergy = LazyOptional.of { Mattery2MekanismEnergyWrapper(energy) } - } - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - tickOnceServer(this::checkSurroundings) - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY) { - if (side == null) return resolverEnergy.cast() - - if (side == blockState.getValue(RotatableMatteryBlock.FACING)) - return resolverEnergyExtractor.cast() - else - return resolverEnergyReceiver.cast() - } - - if (cap == MatteryCapability.MEKANISM_ENERGY) { - if (side == null) return mekanismResolverEnergy.cast() - - if (side == blockState.getValue(RotatableMatteryBlock.FACING)) - return mekanismResolverEnergyExtractor.cast() - else - return mekanismResolverEnergyReceiver.cast() - } - - if (cap == ForgeCapabilities.ITEM_HANDLER) { - return resolverItemHandler.cast() - } - } - - return super.getCapability(cap,side) - } - - private var consumingCapability = LazyOptional.empty() - - fun checkSurroundings(level: Level) { - if (isRemoved) return - - val tile = level.getBlockEntity(blockPos.offset(blockState.getValue(RotatableMatteryBlock.FACING).normal)) - - if (tile == null) { - consumingCapability = LazyOptional.empty() - return - } - - consumingCapability = getAndBind( - old = consumingCapability, - provider = tile, - capability = ForgeCapabilities.ENERGY, - side = -blockState.getValue(RotatableMatteryBlock.FACING) - ) { - @Suppress("name_shadowing") - val level = this.level - - if (level is ServerLevel && !SERVER_IS_LIVE) - checkSurroundings(level) - } - } - - fun tick() { - if (isBlockedByRedstone) - return - - consumingCapability.ifPresentK { - val (_, maxThroughput) = energy.getDistribution(false) - - if (maxThroughput.isZero) - return@ifPresentK - - val diff = it.receiveEnergy(maxThroughput, true) - - if (!diff.isZero) { - val newExtract = energy.extractEnergyInner(diff, true) - val newReceive = it.receiveEnergy(newExtract, true) - - val extracted = energy.extractEnergyInner(newReceive, false) - val received = it.receiveEnergy(extracted, false) - - if (received < extracted) { - energy.receiveEnergyInner(extracted - received, false) - } - } - } - } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.battery_bank") - const val CAPACITY = 6 * 2 - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt index f086c6229..9301f3efc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt @@ -2,135 +2,89 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.core.BlockPos import net.minecraft.core.Direction -import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.block.CableBlock import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.GraphNodeListener -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.graph.storage.IStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph +import ru.dbotthepony.mc.otm.graph.matter.MatterNode +import ru.dbotthepony.mc.otm.graph.storage.StorageGraph +import ru.dbotthepony.mc.otm.graph.storage.StorageNode import ru.dbotthepony.mc.otm.registry.MBlockEntities -class MatterCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - BlockEntity(MBlockEntities.MATTER_CABLE, p_155229_, p_155230_), IMatterGraphNode, GraphNodeListener { +class MatterCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.MATTER_CABLE, p_155229_, p_155230_) { + val matterNode = object : MatterNode() { + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - private var valid = true - override val matterNode = Graph6Node(this) - private val resolverNode = LazyOptional.of { this } - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap === MatteryCapability.MATTER_NODE) - return resolverNode.cast() + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - return super.getCapability(cap, side) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) + + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) + init { + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) } - override fun onNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], true) - - if (newState !== blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) - } - - override fun onUnNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], false) - - if (newState !== blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } } -class StorageCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - BlockEntity(MBlockEntities.STORAGE_CABLE, p_155229_, p_155230_), IStorageGraphNode, GraphNodeListener { +class StorageCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.STORAGE_CABLE, p_155229_, p_155230_) { + val storageNode = object : StorageNode() { + override fun attachComponents(to: StorageGraph) {} + override fun removeComponents(from: StorageGraph) {} - private var valid = true - private val resolverNode = LazyOptional.of { this } + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - override fun attachComponents(to: StorageNetworkGraph) {} - override fun removeComponents(from: StorageNetworkGraph) {} - override val storageNode = Graph6Node(this) - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap === MatteryCapability.STORAGE_NODE) - return resolverNode.cast() + if (newState !== blockState && SERVER_IS_LIVE) + level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - return super.getCapability(cap, side) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) + + if (newState !== blockState && SERVER_IS_LIVE) + level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - StorageNetworkGraph.discoverFull(this, storageNode) + init { + exposeGlobally(MatteryCapability.STORAGE_NODE, storageNode) } - override fun onNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], true) - - if (newState !== blockState && SERVER_IS_LIVE) - level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) - } - - override fun onUnNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], false) - - if (newState !== blockState && SERVER_IS_LIVE) - level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + override fun setLevel(level: Level) { + super.setLevel(level) + storageNode.discover(this) } override fun setRemoved() { super.setRemoved() - - val level = level!! - - storageNode.destroy { - StorageNetworkGraph(level) - } + storageNode.isValid = false } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ChemicalGeneratorBlockEntity.kt deleted file mode 100644 index 612303fda..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ChemicalGeneratorBlockEntity.kt +++ /dev/null @@ -1,260 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.ForgeHooks -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.energy.IEnergyStorage -import ru.dbotthepony.mc.otm.* -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.capability.* -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.menu.ChemicalGeneratorMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce -import java.lang.ref.WeakReference - -class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryBlockEntity(MBlockEntities.CHEMICAL_GENERATOR, pos, state), IDroppableContainer { - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return ChemicalGeneratorMenu(containerID, inventory, this) - } - - private var valid = true - val container = MatteryContainer(this::setChangedLight, SLOTS) - override val droppableContainer: Container - get() = container - val energy = GeneratorEnergyStorage(this::setChangedLight, CAPACITY, THROUGHPUT) - - private val consumers = ArrayList>() - - override fun setChangedLight() { - super.setChangedLight() - check = true - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid && (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY) && side !== blockState.getValue(RotatableMatteryBlock.FACING)) - return energy.resolver.cast() - - if (valid && cap == ForgeCapabilities.ITEM_HANDLER) - return itemHandler.get().cast() - - return super.getCapability(cap, side) - } - - override fun invalidateCaps() { - super.invalidateCaps() - itemHandler.invalidate() - energy.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - itemHandler.revive() - energy.revive() - valid = true - } - - override fun setLevel(level: Level) { - super.setLevel(level) - tickOnceServer(this::checkSurroundings) - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - - nbt[ENERGY_KEY] = energy.serializeNBT() - nbt[INVENTORY_KEY] = container.serializeNBT() - nbt[WORK_TICKS_KEY] = workTicks - nbt[WORK_TICKS_TOTAL_KEY] = workTicksTotal - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - - nbt.map(ENERGY_KEY, energy::deserializeNBT) - container.deserializeNBT(nbt[INVENTORY_KEY]) - workTicks = nbt.getInt(WORK_TICKS_KEY) - workTicksTotal = nbt.getInt(WORK_TICKS_TOTAL_KEY) - } - - fun checkSurroundings() { - if (!valid) - return - - val known = consumers.clone() as ArrayList<*> - consumers.clear() - val level = level ?: return - - for (direction in Direction.values()) { - // нельзя выталкивать энергию через перед - if (direction == blockState.getValue(RotatableMatteryBlock.FACING)) - continue - - val resolver = level.getBlockEntity(blockPos + direction)?.getEnergySided(-direction) - - resolver?.ifPresentK { - if (!known.contains(resolver)) { - val ref = WeakReference(this) - - resolver.addListener { - if (SERVER_IS_LIVE) - ref.get()?.checkSurroundings() - } - } - - consumers.add(resolver) - } - } - } - - val itemHandler = container.handler(object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - if (slot == SLOT_INPUT) - return ForgeHooks.getBurnTime(stack, null) > 0 - - if (slot == SLOT_RESIDUE) - return false - - return stack.getCapability(ForgeCapabilities.ENERGY).isPresent - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - if (slot == SLOT_RESIDUE) return true - - return slot == SLOT_BATTERY && - (!stack.getCapability(ForgeCapabilities.ENERGY).isPresent || stack.getCapability(ForgeCapabilities.ENERGY).resolve().get().receiveEnergy(Int.MAX_VALUE, true) <= 0) - } - }) - - override fun setBlockState(p_155251_: BlockState) { - super.setBlockState(p_155251_) - - if (valid) - tickOnceServer(this::checkSurroundings) - } - - var workTicks = 0 - private set - - var workTicksTotal = 0 - private set - - private var check = true - - private fun workWithPower(it: IEnergyStorage) { - val extracted = energy.extractEnergyInner(THROUGHPUT, true) - val received = it.receiveEnergy(extracted, false) - - if (!received.isZero) { - energy.extractEnergyInner(received, false) - } - } - - fun tick() { - if (workTicks > 0) { - workTicks-- - energy.receiveEnergyInner(GENERATION_SPEED, false) - - if (workTicks == 0) { - workTicksTotal = 0 - check = true - } - - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) - } - } else if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) - } - - if (workTicks == 0 && !isBlockedByRedstone && check) { - if (!container[SLOT_INPUT].isEmpty) { - val ticks = ForgeHooks.getBurnTime(container[SLOT_INPUT], null) - val residue = container[SLOT_INPUT].item.getCraftingRemainingItem(container[SLOT_INPUT].copy().also { it.count = 1 }) - val canPutResidue = residue.isEmpty || container[SLOT_RESIDUE].isEmpty || ItemStack.isSameItemSameTags(container[SLOT_RESIDUE], residue) && container[SLOT_RESIDUE].count < container[2].maxStackSize - - if (canPutResidue && ticks >= 4 && (energy.batteryLevel < Decimal.ONE || GENERATION_SPEED * (ticks / 4) + energy.batteryLevel <= energy.maxBatteryLevel)) { - workTicksTotal = ticks / 4 - workTicks = ticks / 4 - container[SLOT_INPUT].shrink(1) - - if (!residue.isEmpty) { - if (container[SLOT_RESIDUE].isEmpty) { - container[SLOT_RESIDUE] = residue - } else { - container[SLOT_RESIDUE].count++ - } - } - - container.setChanged(SLOT_INPUT) - } - } - - check = false - } - - if (energy.batteryLevel.isZero) return - - val item = container[SLOT_BATTERY] - - if (!item.isEmpty) { - item.energy?.let(this::workWithPower) - if (energy.batteryLevel.isZero) return - } - - for (consumer in consumers) { - consumer.ifPresent(this::workWithPower) - } - } - - companion object { - private val THROUGHPUT get() = _THROUGHPUT.get() - private val GENERATION_SPEED get() = _GENERATION_SPEED.get() - private val CAPACITY get() = _CAPACITY.get() - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.chemical_generator") - - private var _THROUGHPUT: DecimalConfigValue by WriteOnce() - private var _GENERATION_SPEED: DecimalConfigValue by WriteOnce() - private var _CAPACITY: DecimalConfigValue by WriteOnce() - - const val SLOT_INPUT = 0 - const val SLOT_BATTERY = 1 - const val SLOT_RESIDUE = 2 - const val SLOTS = 3 - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.CHEMICAL_GENERATOR) - - _THROUGHPUT = builder.defineDecimal("throughput", Decimal(160), Decimal.ONE) - _GENERATION_SPEED = builder.defineDecimal("generationSpeed", Decimal(40), Decimal.ONE) - _CAPACITY = builder.defineDecimal("capacity", Decimal(24_000), Decimal.ONE) - - builder.pop() - } - - const val WORK_TICKS_KEY = "workTicks" - const val WORK_TICKS_TOTAL_KEY = "workTicksTotal" - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt deleted file mode 100644 index 5a4d9f90b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt +++ /dev/null @@ -1,413 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.IntTag -import net.minecraft.nbt.ListTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.energy.IEnergyStorage -import ru.dbotthepony.mc.otm.* -import ru.dbotthepony.mc.otm.block.EnergyCounterBlock -import ru.dbotthepony.mc.otm.capability.* -import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.menu.EnergyCounterMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import java.lang.ref.WeakReference -import java.util.* - -class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.ENERGY_COUNTER, p_155229_, p_155230_) { - var passed by synchronizer.fraction() - - private val history = Array(10 * 20) { Decimal.ZERO } - internal var historyTick = 0 - - var lastTick by synchronizer.fraction() - internal set - - var ioLimit: Decimal? = null - - fun resetStats() { - historyTick = 0 - lastTick = Decimal.ZERO - passed = Decimal.ZERO - Arrays.fill(history, Decimal.ZERO) - } - - fun getHistory(ticks: Int): Array { - require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" } - - val history = Array(ticks) { Decimal.ZERO } - - for (i in 0 until ticks) { - var index = (historyTick - i) % this.history.size - if (index < 0) index = this.history.size - 1 - history[i] = this.history[index] - } - - return history - } - - fun calcAverage(ticks: Int): Decimal { - return sumHistory(ticks) / ticks - } - - fun sumHistory(ticks: Int): Decimal { - require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" } - - var value = Decimal.ZERO - - for (i in 0 until ticks) { - var index = (historyTick - i) % history.size - if (index < 0) index = history.size - 1 - value += history[index] - } - - return value - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[PASSED_ENERGY_KEY] = passed.serializeNBT() - - val list = ListTag() - nbt[POWER_HISTORY_KEY] = list - nbt[POWER_HISTORY_POINTER_KEY] = historyTick - - for (num in history) - list.add(num.serializeNBT()) - - ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() } - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - - passed = nbt.getDecimal(PASSED_ENERGY_KEY) - ioLimit = nbt.map(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT) - - nbt.ifHas(POWER_HISTORY_POINTER_KEY, IntTag::class.java) { - historyTick = it.asInt - } - - Arrays.fill(history, Decimal.ZERO) - - for ((i, bytes) in nbt.getByteArrayList(POWER_HISTORY_KEY).withIndex()) { - history[i] = Decimal.deserializeNBT(bytes) - } - } - - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return EnergyCounterMenu(containerID, inventory, this) - } - - private val energyInput = EnergyCounterCap(true) - private val energyOutput = EnergyCounterCap(false) - - private var inputCapability: LazyOptional = LazyOptional.empty() - private var outputCapability: LazyOptional = LazyOptional.empty() - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - tickOnceServer(this::checkSurroundings) - } - - private inner class EnergyCounterCap(val isInput: Boolean) : IMatteryEnergyStorage { - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return extractEnergyInner(howMuch, simulate) - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (isInput) - return Decimal.ZERO - - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() - - val diff: Decimal - - val ioLimit = ioLimit - - if (ioLimit != null) { - diff = it.extractEnergy(howMuch.coerceAtMost(ioLimit), simulate) - } else { - diff = it.extractEnergy(howMuch, simulate) - } - - if (!simulate) { - passed += diff - history[historyTick] += diff - } - - return diff - } - - return Decimal.ZERO - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (!isInput) - return Decimal.ZERO - - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() - - val diff: Decimal - - val ioLimit = ioLimit - - if (ioLimit != null) { - diff = it.receiveEnergy(howMuch.coerceAtMost(ioLimit), simulate) - } else { - diff = it.receiveEnergy(howMuch, simulate) - } - - if (!simulate) { - passed += diff - history[historyTick] += diff - } - - return diff - } - - return Decimal.ZERO - } - - override val batteryLevel: Decimal - get() { - if (isInput) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.batteryLevel - } - - return Decimal(it.energyStored) - } - } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.batteryLevel - } - - return Decimal(it.energyStored) - } - } - - return Decimal.ZERO - } - - override val maxBatteryLevel: Decimal - get() { - if (isInput) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.maxBatteryLevel - } - - return Decimal(it.maxEnergyStored) - } - } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.maxBatteryLevel - } - - return Decimal(it.maxEnergyStored) - } - } - - return Decimal.ZERO - } - - override val missingPower: Decimal - get() { - if (isInput) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.missingPower - } - - return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) - } - } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() - - if (it is IMatteryEnergyStorage) { - return it.missingPower - } - - return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) - } - } - - return Decimal.ZERO - } - - override fun canExtract() = !isInput - override fun canReceive() = isInput - } - - private var resolverInput = LazyOptional.of { energyInput } - private var resolverOutput = LazyOptional.of { energyOutput } - - private var resolverInputMekanism = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(energyInput) } else null - private var resolverOutputMekanism = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(energyOutput) } else null - - private var valid = true - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - resolverInput.invalidate() - resolverInputMekanism?.invalidate() - resolverOutput.invalidate() - resolverOutputMekanism?.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolverInput = LazyOptional.of { energyInput } - resolverOutput = LazyOptional.of { energyOutput } - - if (isMekanismLoaded) { - resolverInputMekanism = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyInput) } - resolverOutputMekanism = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyOutput) } - } - } - - @Suppress("deprecation", "OVERRIDE_DEPRECATION") - override fun setBlockState(new: BlockState) { - val old = blockState - super.setBlockState(new) - - if (new !== old && new.getValue(EnergyCounterBlock.INPUT_DIRECTION) != old.getValue(EnergyCounterBlock.INPUT_DIRECTION)) { - resolverInput.invalidate() - resolverInputMekanism?.invalidate() - resolverOutput.invalidate() - resolverOutputMekanism?.invalidate() - - resolverInput = LazyOptional.of { energyInput } - resolverOutput = LazyOptional.of { energyOutput } - - if (isMekanismLoaded) { - resolverInputMekanism = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyInput) } - resolverOutputMekanism = LazyOptional.of { Mattery2MekanismEnergyWrapper(energyOutput) } - } - - checkSurroundings() - } - } - - private fun getAndBind( - level: Level, - old: LazyOptional, - side: Direction - ): LazyOptional { - val ent = level.getBlockEntity(blockPos.offset(side.normal)) ?: return LazyOptional.empty() - val resolve = ent.getEnergySided(-side) - - if (resolve !== old) { - if (resolve.isPresent) { - val weak = WeakReference(this) - - resolve.addListener { - if (SERVER_IS_LIVE) - weak.get()?.checkSurroundings() - } - } - - return resolve - } - - return old - } - - fun checkSurroundings() { - val level = level - if (isRemoved || level !is ServerLevel) return - - inputCapability = getAndBind( - level, - inputCapability, - blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - ) - - outputCapability = getAndBind( - level, - outputCapability, - -blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - ) - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (side == null || isRemoved) - return super.getCapability(cap, side) - - if (valid) { - if (side == blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)) { - if (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY) { - return resolverInput.cast() - } else if (cap == MatteryCapability.MEKANISM_ENERGY) { - return resolverInputMekanism!!.cast() - } - } else if (side == blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION).opposite) { - if (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY) { - return resolverOutput.cast() - } else if (cap == MatteryCapability.MEKANISM_ENERGY) { - return resolverOutputMekanism!!.cast() - } - } - } - - return super.getCapability(cap, side) - } - - fun tick() { - lastTick = history[historyTick] - historyTick = (historyTick + 1) % history.size - history[historyTick] = Decimal.ZERO - } - - fun clientTick() { - historyTick = (historyTick + 1) % history.size - history[historyTick] = lastTick - passed += lastTick - } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.energy_counter") - const val PASSED_ENERGY_KEY = "passedEnergy" - const val IO_LIMIT_KEY = "IOLimit" - const val POWER_HISTORY_KEY = "passHistory" - const val POWER_HISTORY_POINTER_KEY = "passHistoryPointer" - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyServoBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyServoBlockEntity.kt deleted file mode 100644 index 6be52e29e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyServoBlockEntity.kt +++ /dev/null @@ -1,155 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.energy -import ru.dbotthepony.mc.otm.capability.energyStoredMattery -import ru.dbotthepony.mc.otm.capability.extractEnergy -import ru.dbotthepony.mc.otm.capability.maxEnergyStoredMattery -import ru.dbotthepony.mc.otm.capability.receiveEnergy -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.menu.EnergyServoMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks - -class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.ENERGY_SERVO, blockPos, blockState), IDroppableContainer { - override val defaultDisplayName: Component - get() = MBlocks.ENERGY_SERVO.name - - val container = MatteryContainer(this::setChangedLight, 2) - - override val droppableContainer: Container - get() = container - - val itemHandler = container.handler(object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return when (slot) { - SLOT_DISCHARGE -> stack.isEmpty || stack.energy?.let { it.extractEnergy(Int.MAX_VALUE, true) > 0 } ?: false - SLOT_CHARGE -> stack.isEmpty || stack.energy?.let { it.receiveEnergy(Int.MAX_VALUE, true) > 0 } ?: false - else -> false - } - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return when (slot) { - SLOT_DISCHARGE -> stack.isEmpty || stack.energy?.let { it.extractEnergy(Int.MAX_VALUE, true) <= 0 } ?: false - SLOT_CHARGE -> stack.isEmpty || stack.energy?.let { it.receiveEnergy(Int.MAX_VALUE, true) <= 0 } ?: false - else -> false - } - } - }) - - val energy = object : IMatteryEnergyStorage { - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return extractEnergyInner(howMuch, simulate) - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return container[SLOT_DISCHARGE].energy?.extractEnergy(howMuch, simulate) ?: Decimal.ZERO - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return container[SLOT_CHARGE].energy?.receiveEnergy(howMuch, simulate) ?: Decimal.ZERO - } - - override val batteryLevel: Decimal - get() = container[SLOT_CHARGE].energy?.energyStoredMattery ?: container[SLOT_DISCHARGE].energy?.energyStoredMattery ?: Decimal.ZERO - - override val maxBatteryLevel: Decimal - get() = container[SLOT_CHARGE].energy?.maxEnergyStoredMattery ?: container[SLOT_DISCHARGE].energy?.maxEnergyStoredMattery ?: Decimal.ZERO - } - - private var resolverEnergy = LazyOptional.of { energy } - private var valid = true - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - resolverEnergy.invalidate() - itemHandler.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolverEnergy = LazyOptional.of { energy } - itemHandler.revive() - } - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return EnergyServoMenu(containerID, inventory, this) - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - when (cap) { - MatteryCapability.ENERGY, ForgeCapabilities.ENERGY -> return resolverEnergy.cast() - ForgeCapabilities.ITEM_HANDLER -> return itemHandler.get().cast() - } - } - - return super.getCapability(cap, side) - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } - - fun tick() { - val charge = container[SLOT_CHARGE] - val discharge = container[SLOT_DISCHARGE] - - if (!charge.isEmpty && !discharge.isEmpty) { - val chargeEnergy = charge.energy ?: return - val dischargeEnergy = discharge.energy ?: return - - val extracted = dischargeEnergy.extractEnergy(Decimal.LONG_MAX_VALUE, true) - - if (extracted.isPositive) { - val received = chargeEnergy.receiveEnergy(extracted, true) - - if (received.isPositive) { - val extracted2 = dischargeEnergy.extractEnergy(received, false) - - if (extracted2 == received) { - chargeEnergy.receiveEnergy(dischargeEnergy.extractEnergy(received, false), false) - } - } - } - } - } - - companion object { - const val SLOT_DISCHARGE = 0 - const val SLOT_CHARGE = 1 - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt new file mode 100644 index 000000000..6b4c4db49 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt @@ -0,0 +1,154 @@ +package ru.dbotthepony.mc.otm.block.entity + +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.nbt.DoubleTag +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.ExperienceOrb +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.INeighbourChangeListener +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.registry.MFluids +import java.util.function.DoubleSupplier + +class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Double.POSITIVE_INFINITY }) : IFluidHandler, INBTSerializable, INeighbourChangeListener { + constructor(max: Double) : this({ max }) + + var experience = 0.0 + private set(value) { + require(value >= 0.0) { "Negative experience: $value" } + field = value + } + + fun popExperience(player: ServerPlayer) { + val whole = experience.toInt() + + if (whole > 0) { + experience -= whole + ExperienceOrb.award(player.level as ServerLevel, player.position(), whole) + } + } + + fun storeExperience(experience: Double): Boolean { + check(experience >= 0.0) { "Invalid experience amount to store: $experience" } + val max = maxExperience.asDouble + val overflow = this.experience + experience > max + this.experience = if (overflow) max else this.experience + experience + return !overflow + } + + fun storeExperience(experience: Float): Boolean { + return storeExperience(experience.toDouble()) + } + + fun storeExperience(experience: Double, pointOfReference: BlockEntity): Boolean { + check(experience >= 0.0) { "Invalid experience amount to store: $experience" } + this.experience += experience + + for (dir in Direction.entries) { + val tile = pointOfReference.level?.getBlockEntity(pointOfReference.blockPos + dir) + + if (tile is EssenceStorageBlockEntity) { + tile.experienceStored += this.experience.toLong() + this.experience %= 1.0 + break + } + } + + val max = maxExperience.asDouble + val overflow = this.experience > max + if (overflow) this.experience = max + return !overflow + } + + fun storeExperience(experience: Float, pointOfReference: BlockEntity): Boolean { + return storeExperience(experience.toDouble(), pointOfReference) + } + + fun tryTransferExperience(pointOfReference: BlockEntity) { + if (experience >= 1.0) { + for (dir in Direction.entries) { + val tile = pointOfReference.level?.getBlockEntity(pointOfReference.blockPos + dir) + + if (tile is EssenceStorageBlockEntity) { + tile.experienceStored += experience.toLong() + experience %= 1.0 + break + } + } + } + } + + override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) { + if (experience >= 1.0) { + val tile = level.getBlockEntity(neighbourPos) + + if (tile is EssenceStorageBlockEntity) { + tile.experienceStored += experience.toLong() + experience %= 1.0 + } + } + } + + override fun serializeNBT(): DoubleTag { + return DoubleTag.valueOf(experience) + } + + override fun deserializeNBT(nbt: DoubleTag?) { + experience = (nbt?.asDouble ?: 0.0).coerceAtLeast(0.0) + } + + override fun getTanks(): Int { + return 1 + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (tank != 0) + return FluidStack.EMPTY + + return FluidStack(MFluids.LIQUID_XP, (experience * XP_TO_LIQUID_RATIO).toInt()) + } + + override fun getTankCapacity(tank: Int): Int { + return if (tank == 0) (maxExperience.asDouble * XP_TO_LIQUID_RATIO).toInt() else 0 + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + return tank == 0 && stack.fluid.fluidType == MFluids.LIQUID_XP_TYPE + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + return 0 + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE) + return FluidStack.EMPTY + + return drain(resource.amount, action) + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + val actualDrain = maxDrain.coerceAtMost((experience * XP_TO_LIQUID_RATIO).toInt()).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO } + + if (actualDrain <= 0) + return FluidStack.EMPTY + + if (action.execute()) + experience -= actualDrain / XP_TO_LIQUID_RATIO + + return FluidStack(MFluids.LIQUID_XP, actualDrain) + } + + companion object { + const val XP_TO_LIQUID_RATIO = 10 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt new file mode 100644 index 000000000..4ec6d1285 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt @@ -0,0 +1,526 @@ +package ru.dbotthepony.mc.otm.block.entity + +import com.mojang.datafixers.Products +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.weakEqualDoubles +import ru.dbotthepony.mc.otm.core.math.weakGreaterThan +import ru.dbotthepony.mc.otm.core.math.weakLessThan +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange + +private fun isReason(status: Any?, reason: Any) = status == null || status == reason +private val LOGGER = LogManager.getLogger() + +interface IJob { + val ticks: Double + val powerUsage: Decimal + val experience: Float get() = 0f +} + +open class Job( + final override val ticks: Double, + final override val powerUsage: Decimal = Decimal.ZERO, + final override val experience: Float = 0f +) : IJob { + companion object { + fun basicCodec(builder: RecordCodecBuilder.Instance): Products.P3, Double, Decimal, Float> { + return builder.group( + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("Ticks").forGetter(Job::ticks), + DecimalCodec.fieldOf("EnergyUsage").forGetter(Job::powerUsage), // не надо указывать минимальную энергию как 0, + // ибо мы можем таким образом использовать это для создания работ генератора + Codec.floatRange(0f, Float.MAX_VALUE).optionalFieldOf("Experience", 0f).forGetter(Job::experience), + ) + } + + fun plainCodec(builder: RecordCodecBuilder.Instance): Products.P2, Double, Decimal> { + return builder.group( + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("Ticks").forGetter(Job::ticks), + DecimalCodec.fieldOf("EnergyUsage").forGetter(Job::powerUsage), // не надо указывать минимальную энергию как 0, + // ибо мы можем таким образом использовать это для создания работ генератора + ) + } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + basicCodec(it).apply(it, ::Job) + } + } + } +} + +open class ItemJob( + val itemStack: ItemStack, + ticks: Double, + power: Decimal = Decimal.ZERO, + experience: Float = 0f +) : Job(ticks, power, experience) { + companion object { + fun itemCodec(builder: RecordCodecBuilder.Instance): Products.P4, ItemStack, Double, Decimal, Float> { + return builder.group(ItemStack.CODEC.fieldOf("Item").forGetter(ItemJob::itemStack)).and(basicCodec(builder)) + } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + itemCodec(it).apply(it, ::ItemJob) + } + } + } +} + +class JobStatus( + var requiredPower: Decimal, + val extractedPower: Decimal, + ticksAdvanced: Double, + val job: T, + val workTicks: Double, +) : IJob by job { + constructor(job: T, workTicks: Double) : this(Decimal.ZERO, Decimal.ZERO, 0.0, job, workTicks) + + val workProgress: Float + get() { + return ((workTicks + workTicks) / job.ticks).coerceAtMost(1.0).toFloat() + } + + var success = true + + var ticksAdvanced = ticksAdvanced + set(value) { + require(value >= 0.0) { "Invalid amount of ticks to advance: $value" } + field = value + } + + var throttleTicks = 0 + set(value) { + require(value >= 0) { "Invalid amount of ticks to throttle: $value" } + if (value != 0) success = false + field = value + } + + var idleReason: Any? = null + set(value) { + if (value != null) success = false + field = value + } + + /** + * You don't have to actually call this, JobStatus is in this state by default + * + * But this is handy to call this when doing early returns, to clearly state that "we are in success" + */ + fun success() { + this.success = true + this.throttleTicks = 0 + this.idleReason = null + } + + fun throttle() { + this.success = false + this.throttleTicks = 100 + } + + fun throttleFast() { + this.success = false + this.throttleTicks = 20 + } + + fun noItem(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.ITEM + this.throttleTicks = throttleTicks + } + + fun noPower(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.POWER + this.throttleTicks = throttleTicks + } + + fun noMatter(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.MATTER + this.throttleTicks = throttleTicks + } + + fun noPattern(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.PATTERN + this.throttleTicks = throttleTicks + } + + fun observe(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.OBSERVING + this.throttleTicks = throttleTicks + } + + fun failure(reason: Any) { + this.success = false + idleReason = reason + } + + fun failure(throttleTicks: Int = 0) { + this.success = false + this.throttleTicks = throttleTicks + } + + fun scale(value: Decimal) { + require(value >= Decimal.ZERO) { "Attempted to scale by negative value: $value" } + if (value == Decimal.ONE) + return + + if (value.isZero) { + ticksAdvanced = 0.0 + requiredPower = Decimal.ZERO + success = false + } else { + requiredPower *= value + ticksAdvanced *= value.toDouble() + } + } + + fun scale(value: Double) { + require(value >= 0.0) { "Attempted to scale by negative value: $value" } + if (value == 1.0) + return + + if (value == 0.0) { + ticksAdvanced = 0.0 + requiredPower = Decimal.ZERO + success = false + } else { + requiredPower *= value + ticksAdvanced *= value + } + } +} + +data class JobContainer(val job: JobType? = null, val idleReason: Any? = null, val throttleTicks: Int = 0) { + init { + require(throttleTicks >= 0) { "Negative amount of ticks to throttle: $throttleTicks" } + + if (job != null && idleReason != null) { + throw IllegalArgumentException("Can't have both job and idle reason specified") + } + + if (job != null && throttleTicks != 0) { + throw IllegalArgumentException("Can't have both job and throttle ticks specified") + } + } + + companion object { + fun success(job: JobType): JobContainer { + return JobContainer(job, null) + } + + fun failure(reason: Any?): JobContainer { + return JobContainer(null, reason) + } + + private val empty = JobContainer(null, null) + private val noItem = JobContainer(null, MachineJobEventLoop.IdleReason.ITEM) + private val noEnergy = JobContainer(null, MachineJobEventLoop.IdleReason.POWER) + private val noPattern = JobContainer(null, MachineJobEventLoop.IdleReason.PATTERN) + private val noMatter = JobContainer(null, MachineJobEventLoop.IdleReason.MATTER) + private val observe = JobContainer(null, MachineJobEventLoop.IdleReason.OBSERVING) + + @Suppress("unchecked_cast") + fun failure(): JobContainer { + return empty as JobContainer + } + + @Suppress("unchecked_cast") + fun noItem(): JobContainer { + return noItem as JobContainer + } + + @Suppress("unchecked_cast") + fun noEnergy(): JobContainer { + return noEnergy as JobContainer + } + + @Suppress("unchecked_cast") + fun noMatter(): JobContainer { + return noMatter as JobContainer + } + + @Suppress("unchecked_cast") + fun noPattern(): JobContainer { + return noPattern as JobContainer + } + + @Suppress("unchecked_cast") + fun observe(): JobContainer { + return observe as JobContainer + } + } +} + +abstract class MachineJobEventLoop(val codec: Codec) : INBTSerializable { + protected abstract val energy: IMatteryEnergyStorage? + protected abstract val isBlockedByRedstone: Boolean + protected abstract val upgrades: IMatteryUpgrade? + + var currentJob: JobType? = null + set(value) { + if (field != value) { + val old = field + field = value + jobUpdated(value, old) + } + } + + var workTicks = 0.0 + protected set + var throttleTicks = 0 + protected set + var idleTicksAnim = 0 + private set + var workingTicksAnim = 0 + private set + var errorTicksAnim = 0 + private set + + /** + * Can be whatever you want, but [IdleReason] certainly contains all cases + */ + var idleReason: Any? = null + protected set + + open var isIdling = false + + open fun notify(event: Any) { + if (isReason(idleReason, event)) { + isIdling = false + throttleTicks = 0 + } + } + + private var lastTickWasError = false + + val isUnableToProcess: Boolean + get() = throttleTicks > 0 || lastTickWasError + + val workProgress: Float + get() { + val currentJob = currentJob ?: return 0.0f + return (workTicks / currentJob.ticks).coerceAtMost(1.0).toFloat() + } + + enum class IdleReason { + ITEM, + POWER, + MATTER, + PATTERN, + OBSERVING + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { nbt -> + nbt["WorkTicks"] = workTicks + + currentJob?.let { + codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map( + { + nbt["Job"] = it + }, + { + LOGGER.error("Failed to serialize job data, it will not persist", RuntimeException(it.message())) + } + ) + } + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + nbt ?: return + + workTicks = nbt.getDouble("WorkTicks") + currentJob = null + + if ("Job" in nbt) { + codec.decode(NbtOps.INSTANCE, nbt["Job"]!!).get().map( + { + currentJob = it.first + }, + { + LOGGER.error("Failed to deserialize job data from storage", RuntimeException(it.message())) + } + ) + } + + if (currentJob == null) + workTicks = 0.0 + } + + protected open fun jobUpdated(new: JobType?, old: JobType?) {} + + /** + * Called whenever reaching desired amount of ticks at job + */ + protected abstract fun onJobFinish(status: JobStatus) + + /** + * Called when there is nothing to do and we are not idling + */ + protected abstract fun computeNextJob(): JobContainer + + protected open fun onJobTick(status: JobStatus) {} + + /** + * Advances job loop by specified [ticks]. If machine is speed up by upgrades, + * [ticks] should reflect this, same as slowdown. + */ + fun think(ticks: Double = 1.0) { + if (throttleTicks > 0) { + workingTicksAnim = 0 + idleTicksAnim = 0 + throttleTicks-- + errorTicksAnim++ + + if (throttleTicks > 0) + return + else + isIdling = false + } + + lastTickWasError = false + + if (isIdling) { + workingTicksAnim = 0 + errorTicksAnim = 0 + idleTicksAnim++ + return + } + + val upgrades = upgrades ?: IMatteryUpgrade.Companion + var availableTicks = ticks * (1.0 + upgrades.speedBonus.coerceIn(MachinesConfig.Upgrades.MIN_SPEED, MachinesConfig.Upgrades.MAX_SPEED)) + val energy = energy + + while (!isIdling && weakGreaterThan(availableTicks, 0.0) && throttleTicks <= 0) { + if (isBlockedByRedstone) { + isIdling = true + break + } + + var currentJob = currentJob + + if (currentJob == null) { + val (job, reason, throttleTicks) = computeNextJob() + + if (job == null) { + this.throttleTicks = throttleTicks + this.idleReason = reason + this.isIdling = reason != null + workingTicksAnim = 0 + break + } + + this.currentJob = job + currentJob = job + } + + if (!currentJob.powerUsage.isZero && (energy == null || energy.batteryLevel.isZero)) { + idleReason = IdleReason.POWER + isIdling = true + idleTicksAnim++ + lastTickWasError = true + break + } + + idleTicksAnim = 0 + + if (weakLessThan(workTicks, currentJob.ticks)) { + val ticksLeft = (currentJob.ticks - workTicks).coerceAtMost(availableTicks) + val ticksAdvanced: Double + + var requiredPower: Decimal? = null + var extractedPower: Decimal? = null + + if (currentJob.powerUsage.isZero) { + ticksAdvanced = ticksLeft + } else { + requiredPower = currentJob.powerUsage * (Decimal.ONE + upgrades.energyConsumed.coerceIn(MachinesConfig.Upgrades.MIN_ENERGY, MachinesConfig.Upgrades.MAX_ENERGY)) * ticksLeft + + if (requiredPower.isPositive) { + extractedPower = energy!!.extractEnergy(requiredPower, true) + ticksAdvanced = (extractedPower / requiredPower).toDouble().coerceIn(0.0, 1.0) * ticksLeft + } else if (requiredPower.isZero) { + ticksAdvanced = ticksLeft + } else { + extractedPower = -energy!!.receiveEnergy(-requiredPower, true) + ticksAdvanced = (extractedPower / requiredPower).toDouble().coerceIn(0.0, 1.0) * ticksLeft + } + } + + if (weakEqualDoubles(ticksAdvanced, 0.0)) { + break + } + + val status = JobStatus(requiredPower ?: Decimal.ZERO, extractedPower ?: Decimal.ZERO, ticksAdvanced, currentJob, workTicks) + onJobTick(status) + + if (!status.success) { + throttleTicks += status.throttleTicks + lastTickWasError = true + + if (status.idleReason != null) { + idleReason = status.idleReason + isIdling = true + } + + break + } else if (status.requiredPower.isPositive && energy == null) { + idleReason = IdleReason.POWER + isIdling = true + idleTicksAnim++ + break + } + + workingTicksAnim++ + errorTicksAnim = 0 + + workTicks += status.ticksAdvanced + availableTicks -= status.ticksAdvanced + + if (energy != null && status.requiredPower.isPositive) { + energy.extractEnergy(status.requiredPower, false) + } else if (energy != null && status.requiredPower.isNegative) { + energy.receiveEnergy(-status.requiredPower, false) + } + + continue + } + + val status = JobStatus(currentJob, workTicks) + onJobFinish(status) + + if (status.success) { + this.currentJob = null + workTicks = 0.0 + errorTicksAnim = 0 + } else { + throttleTicks += status.throttleTicks + lastTickWasError = true + + if (status.idleReason != null) { + idleReason = status.idleReason + isIdling = true + } + + errorTicksAnim++ + break + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index ba983af03..06a2416f5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -1,181 +1,828 @@ package ru.dbotthepony.mc.otm.block.entity -import net.minecraft.world.level.block.entity.BlockEntityType +import com.google.common.collect.ImmutableList +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.longs.Long2ObjectFunction +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import net.minecraft.core.BlockPos -import net.minecraft.world.level.block.state.BlockState -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.MenuProvider -import java.lang.Runnable -import net.minecraft.server.level.ServerLevel -import net.minecraft.client.multiplayer.ClientLevel import net.minecraft.core.Direction import net.minecraft.core.SectionPos -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.common.capabilities.ICapabilityProvider -import java.lang.ref.WeakReference -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.core.Vec3i import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.StringTag -import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.chunk.LevelChunk +import net.minecraft.world.phys.Vec3 import net.minecraftforge.common.capabilities.Capability -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 -import ru.dbotthepony.mc.otm.core.ifHas -import ru.dbotthepony.mc.otm.core.position -import ru.dbotthepony.mc.otm.core.set +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.util.LazyOptional +import net.minecraftforge.energy.IEnergyStorage +import net.minecraftforge.event.TickEvent.LevelTickEvent +import net.minecraftforge.event.entity.player.PlayerEvent +import net.minecraftforge.event.level.ChunkWatchEvent +import net.minecraftforge.event.level.LevelEvent +import net.minecraftforge.event.server.ServerStoppingEvent +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE +import ru.dbotthepony.mc.otm.block.INeighbourChangeListener +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.core.ISubscriptable +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.util.IntCounter +import ru.dbotthepony.mc.otm.core.util.Savetables +import ru.dbotthepony.mc.otm.core.util.TickList import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import ru.dbotthepony.mc.otm.network.WorldNetworkChannel -import ru.dbotthepony.mc.otm.oncePre +import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import ru.dbotthepony.mc.otm.once +import ru.dbotthepony.mc.otm.onceServer +import ru.dbotthepony.mc.otm.sometimeServer +import java.lang.ref.WeakReference +import java.util.* +import java.util.function.Consumer +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.stream.Stream +import kotlin.collections.ArrayList +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty -abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : SynchronizedBlockEntity(p_155228_, p_155229_, p_155230_), MenuProvider { - var customDisplayName: Component? = null - var redstoneSignal = 0 - set(level) { - val old = isBlockedByRedstone - field = level - val state = isBlockedByRedstone +/** + * Absolute barebone (lol) block entity class in Overdrive that Matters, providing bare minimum (lulmao, minecraft engine) functionality + */ +abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_), INeighbourChangeListener { + private var isSynchronizing = false - if (old != state) { - redstoneStatusUpdated(state, old) + /** + * "shortcut" for getting [BlockRotation] + * + * if block has no rotation, returns [BlockRotation.NORTH] + */ + open val blockRotation: BlockRotation get() { + return blockState[(blockState.block as? RotatableMatteryBlock ?: return BlockRotation.NORTH).rotationProperty] + } + + private val _droppableContainers = ObjectArraySet() + private val _neighbourChangeListeners = ObjectArraySet() + val droppableContainers: Set = Collections.unmodifiableSet(_droppableContainers) + val neighbourChangeListeners: Set = Collections.unmodifiableSet(_neighbourChangeListeners) + + protected fun addDroppableContainer(container: Container) { + _droppableContainers.add(container) + } + + protected fun addNeighbourListener(listener: INeighbourChangeListener) { + if (listener === this) + throw IllegalArgumentException("are you drunk") + + _neighbourChangeListeners.add(listener) + } + + open fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {} + open fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {} + + private val _sides = EnumMap(RelativeSide::class.java) + val sides: Map = Collections.unmodifiableMap(_sides) + + fun side(side: RelativeSide) = sides[side]!! + + private data class SidelessCap(val cap: T, var optional: LazyOptional) + private val sidelessCaps = Reference2ObjectOpenHashMap, SidelessCap<*>>() + protected val tickList = TickList() + protected val blockStateChangesCounter = IntCounter() + protected val dirtyListeners = ISubscriptable.Impl() + private val waitForServerLevel = ArrayList<() -> Unit>() + + /** + * Shared savetables, written both to level storage and to item tag + */ + protected val savetables = Savetables() + + /** + * Level-only savetables, written only to level storage + */ + protected val savetablesLevel = Savetables() + + open fun tick() { + tickList.tick() + + if (synchronizer.isNotEmpty) + synchronizeToPlayers(false) + } + + /** + * exposes capability when no side is specified + */ + protected fun exposeSideless(capability: Capability, value: T) { + check(!sidelessCaps.containsKey(capability)) { "Already has globally exposed $capability!" } + sidelessCaps[capability] = SidelessCap(value, LazyOptional.of { value }) + setChanged() + } + + /** + * Exposes capability unconditionally, on all sides and sideless + */ + protected fun exposeGlobally(capability: Capability, value: T, predicate: Predicate = Predicate { true }) { + exposeSideless(capability, value) + + for (side in _sides.values) + if (predicate.test(side.side)) + side.Cap(capability, value) + } + + protected fun exposeEnergyGlobally(value: IMatteryEnergyStorage, predicate: Predicate = Predicate { true }) { + exposeGlobally(ForgeCapabilities.ENERGY, value, predicate) + exposeGlobally(MatteryCapability.ENERGY, value, predicate) + } + + protected fun exposeEnergySideless(value: IMatteryEnergyStorage) { + exposeSideless(ForgeCapabilities.ENERGY, value) + exposeSideless(MatteryCapability.ENERGY, value) + } + + protected fun exposeEnergy(side: RelativeSide, value: IMatteryEnergyStorage): ImmutableList> { + return immutableList { + val thisSide = _sides[side]!! + + accept(thisSide.Cap(ForgeCapabilities.ENERGY, value)) + accept(thisSide.Cap(MatteryCapability.ENERGY, value)) + } + } + + protected fun waitForServerLevel(lambda: () -> Unit) { + if (level is ServerLevel) { + lambda.invoke() + } else if (level == null) { + waitForServerLevel.add(lambda) + } + } + + interface SideListener : Supplier>, ISubscriptable> + + inner class Side(val side: RelativeSide) { + init { + check(!_sides.containsKey(side)) { "dafuq are you trying to do" } + _sides[side] = this + } + + private val caps = Reference2ObjectOpenHashMap, Cap<*>>() + private val subscriptions = Reference2ObjectOpenHashMap, SubRef<*>>() + private val knownLOs = WeakHashSet>() + + private inner class SubRef(value: LazyOptional) : SideListener { + var value: LazyOptional = value + set(value) { + if (value !== field) { + field = value + listeners.accept(value) + } + } + + private val listeners = ISubscriptable.Impl>() + + override fun addListener(listener: Consumer>): ISubscriptable.L { + val l = listeners.addListener(listener) + if (level is ServerLevel) listener.accept(value) + return l + } + + override fun get(): LazyOptional { + return value + } + + fun unset() { + value = LazyOptional.empty() } } - var redstoneSetting: RedstoneSetting = RedstoneSetting.LOW - set(setting) { - val old = isBlockedByRedstone - field = setting - val state = isBlockedByRedstone - if (old != state) { - redstoneStatusUpdated(state, old) + fun track(capability: Capability): SideListener { + var subref = subscriptions[capability] as SideListener? + + if (subref == null) { + subref = SubRef(LazyOptional.empty()) as SubRef + subscriptions[capability] = subref + level?.once { updateTracked(capability) } + } + + return subref + } + + fun trackEnergy(): SideListener { + return track(ForgeCapabilities.ENERGY) + } + + fun updateTracked() { + for (key in subscriptions.keys) { + // Concurrent Modification safety: + // we do not add nor remove keys from map, we only update values + updateTracked(key) } } - val isBlockedByRedstone: Boolean - get() { - when (redstoneSetting) { - RedstoneSetting.LOW -> { - return redstoneSignal > 0 + private fun updateTracked(capability: Capability<*>) { + if (isRemoved || !SERVER_IS_LIVE) return + val dir = blockRotation.side2Dir(side) + val targetPos = blockPos + dir.normal + + val chunk = level + ?.chunkSource + ?.getChunkNow(SectionPos.blockToSectionCoord(targetPos.x), SectionPos.blockToSectionCoord(targetPos.z)) + + val subref = subscriptions[capability] as SubRef + + if (chunk == null) { + subref.unset() + level?.once { updateTracked(capability) } + return + } + + val entity = chunk.getBlockEntity(targetPos) + + if (entity == null) { + subref.unset() + return + } + + val new = entity.getCapability(capability, dir.opposite) + + if (!new.isPresent) { + subref.unset() + return + } + + if (subref.value !== new) { + if (knownLOs.add(new)) { + val ref = WeakReference(this) + + new.addListener { + ref.get()?.updateTracked(capability) + } } - RedstoneSetting.HIGH -> { - return redstoneSignal <= 0 - } - - RedstoneSetting.IGNORED -> { - return false - } + subref.value = new as LazyOptional } } - protected open val defaultDisplayName: Component - get() = level?.getBlockState(blockPos)?.block?.name ?: TextComponent("null at $blockPos") + operator fun get(capability: Capability): Cap? { + return caps[capability] as Cap? + } - abstract override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? + fun invalidate() { + for (cap in caps.values) + cap.invalidate() + } - protected fun tickOnce(func: Runnable) { - level?.oncePre { if (!isRemoved) func.run() } - } + fun revive() { + for (cap in caps.values) + cap.revive() + } - protected fun tickOnceServer(func: Runnable) { - (level as? ServerLevel)?.oncePre { if (!isRemoved) func.run() } - } + inner class Cap(val type: Capability, val capability: T) { + init { + check(!caps.containsKey(type)) { "Already has capability $type on side $side" } + caps[type] = this + } - protected fun tickOnceClient(func: Runnable) { - (level as? ClientLevel)?.oncePre { if (!isRemoved) func.run() } - } + var isExposed = true + private set + var isValid = true + private set + var isRemoved = false + private set - protected fun tickOnce(func: (Level) -> Unit) { - val level = level - level?.oncePre { if (!isRemoved) func.invoke(level) } - } + var optional: LazyOptional by object : ReadWriteProperty> { + private var value: LazyOptional? = null - protected fun tickOnceServer(func: (ServerLevel) -> Unit) { - val level = level as? ServerLevel ?: return - level.oncePre { if (!isRemoved) func.invoke(level) } - } + override fun getValue(thisRef: Any?, property: KProperty<*>): LazyOptional { + if (value == null) { + value = LazyOptional.of { capability } + } - protected fun tickOnceClient(func: (ClientLevel) -> Unit) { - val level = level as? ClientLevel ?: return - level.oncePre { if (!isRemoved) func.invoke(level) } - } + return value!! + } - protected fun getAndBind( - old: LazyOptional, - provider: ICapabilityProvider?, - capability: Capability, - side: Direction, - invalidate: Runnable - ): LazyOptional { - val get = provider?.getCapability(capability, side) ?: LazyOptional.empty() + override fun setValue(thisRef: Any?, property: KProperty<*>, value: LazyOptional) { + this.value = value + } + } + private set - if (old !== get) { - if (get.isPresent) { - val ref = WeakReference(invalidate) - get.addListener { - ref.get()?.run() + fun remove() { + if (!isRemoved) { + isRemoved = true + val removed = caps.remove(type) + check(removed == this) { "$removed != $this" } + optional.invalidate() } } - return get.cast() + fun close() { + if (!isRemoved && isExposed) { + isExposed = false + optional.invalidate() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } + } + } + + fun expose() { + if (!isRemoved && !isExposed) { + isExposed = true + + if (isValid) { + optional = LazyOptional.of { capability } + + if (SERVER_IS_LIVE) + level?.once { setChanged() } + } + } + } + + fun invalidate() { + if (!isRemoved && isValid) { + isValid = false + optional.invalidate() + + if (SERVER_IS_LIVE) + level?.once { setChanged() } + } + } + + fun revive() { + if (!isRemoved && !isValid) { + isValid = true + + if (isExposed) { + optional = LazyOptional.of { capability } + + if (SERVER_IS_LIVE) + level?.once { setChanged() } + } + } + } + } + } + + val front = Side(RelativeSide.FRONT) + val back = Side(RelativeSide.BACK) + val left = Side(RelativeSide.LEFT) + val right = Side(RelativeSide.RIGHT) + val top = Side(RelativeSide.TOP) + val bottom = Side(RelativeSide.BOTTOM) + + override fun getCapability(cap: Capability, side: Direction?): LazyOptional { + if (side != null) { + return _sides[blockRotation.dir2Side(side)]!![cap]?.optional ?: super.getCapability(cap, side) } - return get.cast() + return sidelessCaps[cap]?.optional?.cast() ?: super.getCapability(cap, side) } - override fun getDisplayName(): Component { - return customDisplayName ?: defaultDisplayName + override fun invalidateCaps() { + super.invalidateCaps() + + for (side in sides.values) + side.invalidate() } - protected open fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) {} + override fun reviveCaps() { + super.reviveCaps() - override fun saveAdditional(nbt: CompoundTag) { + for (side in sides.values) + side.revive() + } + + final override fun saveAdditional(nbt: CompoundTag) { super.saveAdditional(nbt) + saveShared(nbt) + saveLevel(nbt) + } - val customDisplayName = customDisplayName - if (customDisplayName != null) - nbt.putString("Name", Component.Serializer.toJson(customDisplayName)) + /** + * Saved both to item dropped, and to level storage + */ + open fun saveShared(nbt: CompoundTag) { + savetables.serializeNBT(nbt) + } - nbt[REDSTONE_SETTING_KEY] = redstoneSetting.ordinal.toByte() - nbt[REDSTONE_SIGNAL_KEY] = redstoneSignal.toByte() + /** + * Only saved to level storage, discarded when dropped as item + */ + open fun saveLevel(nbt: CompoundTag) { + savetablesLevel.serializeNBT(nbt) } override fun load(nbt: CompoundTag) { super.load(nbt) + savetables.deserializeNBT(nbt) + savetablesLevel.deserializeNBT(nbt) + } - nbt.ifHas("Name", StringTag::class.java) { - customDisplayName = Component.Serializer.fromJson(it.asString) + @Suppress("OVERRIDE_DEPRECATION") + override fun setBlockState(pBlockState: BlockState) { + blockStateChangesCounter.increment() + val old = blockRotation + @Suppress("DEPRECATION") + super.setBlockState(pBlockState) + val new = blockRotation + + if (old != new) { + for (side in _sides.values) { + side.updateTracked() + side.invalidate() + } } - redstoneSetting = RedstoneSetting[nbt.getByte(REDSTONE_SETTING_KEY).toInt()] - redstoneSignal = nbt.getByte(REDSTONE_SIGNAL_KEY).toInt() + for (side in _sides.values) { + side.revive() + } + } + + override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) { + _neighbourChangeListeners.forEach { it.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) } + val dir = vec2Dir[vecKey(neighbourPos - blockPos)] ?: return + _sides[blockRotation.dir2Side(dir)]!!.updateTracked() + } + + override fun setChanged() { + super.setChanged() + + dirtyListeners.accept(Unit) } // Just to mark chunk unsaved - open fun setChangedLight() { + open fun markDirtyFast() { val level = level + if (level is ServerLevel) { level.chunkSource.getChunkNow( SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z))?.isUnsaved = true } + + dirtyListeners.accept(Unit) } + val synchronizer = FieldSynchronizer { + if (isSynchronizing || tickList.ticks != 0) + return@FieldSynchronizer + + if (isRemoved) markSynchronizerClean() + + if (level?.isClientSide == false && (_subCache == null || (_subCache ?: throw ConcurrentModificationException()).players.isNotEmpty())) { + ru.dbotthepony.mc.otm.onceServer { + synchronizeToPlayers(true) + } + } else { + markSynchronizerClean() + } + } + + private fun markSynchronizerClean() { + synchronizer.markClean() + } + + init { + synchronizer.defaultEndpoint.markUnused() + } + + override fun setLevel(level: Level) { + val old = this.level + super.setLevel(level) + unsubscribe() + _subCache = null + + if (!level.isClientSide) { + subscribe() + + if (old != null) { + for (side in _sides.values) { + side.updateTracked() + side.invalidate() + } + + for (side in _sides.values) { + side.revive() + } + } else { + level.once { + for (side in _sides.values) { + side.updateTracked() + } + } + } + + waitForServerLevel.forEach { it.invoke() } + } else { + waitForServerLevel.clear() + } + } + + override fun setRemoved() { + super.setRemoved() + unsubscribe() + } + + override fun clearRemoved() { + super.clearRemoved() + + if (level?.isClientSide == false) { + subscribe() + } + } + + private var _subCache: ChunkSubscribers? = null + private val subscription get() = _subCache ?: subscribe() + private var playerListUpdated = false + + private fun unsubscribe() { + val subCache = _subCache ?: return + + if (subCache.unsubscribe(this)) { + val level = subCache.level.get() + + if (level != null) { + playerMap.get(level)?.remove(subCache.chunkPos) + } + } + + playerListUpdated = false + } + + private fun subscribe(): ChunkSubscribers { + val level = level + check(level is ServerLevel) { "Invalid realm" } + unsubscribe() + + val subs = playerMap + .computeIfAbsent(level) { Long2ObjectOpenHashMap() } + .computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { ChunkSubscribers(level, ChunkPos(blockPos).toLong()) }) + + subs.subscribe(this) + _subCache = subs + playerListUpdated = true + return subs + } + + private fun synchronizeToPlayers(calledBySynchronizer: Boolean) { + isSynchronizing = true + + try { + check(level is ServerLevel) { "Invalid realm or Level is null" } + synchronizer.observe() + val subscription = subscription + + if (subscription.players.isNotEmpty() && (playerListUpdated || synchronizer.isDirty)) { + playerListUpdated = false + + for (player in subscription.players) { + if (player !in subscription.veto) { + val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload() + + if (payload != null) { + GenericNetworkChannel.send(player, BlockEntitySyncPacket(blockPos, payload.array, payload.length)) + } + } + } + + synchronizer.markClean() + } else if (calledBySynchronizer) { + synchronizer.markClean() + } + } finally { + isSynchronizing = false + } + } + + private class ChunkSubscribers(level: ServerLevel, val chunkPos: Long) { + val level = WeakReference(level) + val blockEntities = WeakHashSet(linked = true, initialCapacity = 0) + val players = ObjectLinkedOpenHashSet(0) + val veto = ObjectLinkedOpenHashSet(0) + val blockEntitiesWithObservers = WeakHashSet(linked = true, initialCapacity = 0) + + operator fun component1() = blockEntities + operator fun component2() = players + + val hasObservers: Boolean get() { + return blockEntities.any { it.synchronizer.hasObservers } + } + + private fun recheckPlayer(player: ServerPlayer) { + sometimeServer { + if (player in players && !player.hasDisconnected()) { + veto.remove(player) + + blockEntities.forEach { + it.playerListUpdated = true + it.synchronizeToPlayers(false) + } + } else if (player in players && player.hasDisconnected()) { + unsubscribe(player) + } + } + } + + fun subscribe(player: ServerPlayer) { + if (players.add(player)) { + veto.add(player) + recheckPlayer(player) + } + } + + fun unsubscribe(player: ServerPlayer): Boolean { + if (players.remove(player)) { + veto.remove(player) + + blockEntities.forEach { + it.synchronizer.removeEndpointFor(player) + } + + return true + } + + return false + } + + fun subscribe(blockEntity: MatteryBlockEntity) { + if (!blockEntities.add(blockEntity)) return + + onceServer { + blockEntity.synchronizeToPlayers(false) + } + + if (blockEntity.synchronizer.hasObservers && blockEntity.tickList.ticks == 0) { + blockEntitiesWithObservers.add(blockEntity) + + tickingMap + .computeIfAbsent(level.get() ?: throw NullPointerException("Level got GCd!")) { WeakHashSet(linked = true, initialCapacity = 2) } + .add(this) + } + } + + fun unsubscribe(blockEntity: MatteryBlockEntity): Boolean { + blockEntities.remove(blockEntity) + blockEntitiesWithObservers.remove(blockEntity) + + return players.isEmpty() && blockEntities.isEmpty() + } + + val isEmpty: Boolean get() { + return players.isEmpty() && blockEntities.isEmpty() + } + } + + /** + * Why track player-tracked chunks? + * + * because minecraft itself doesn't track them, well, + * in "section partitioning" way. + * + * just look at [net.minecraft.server.level.PlayerMap] + * + * the [net.minecraft.server.level.PlayerMap.getPlayers] straight ignores chunk position + * and just fetches all players + * + * even if they did not ignore that argument, you still have to fetch player *list* though + * [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want + */ companion object { - const val REDSTONE_SETTING_KEY = "redstoneSetting" - const val REDSTONE_SIGNAL_KEY = "redstoneSignal" + const val REDSTONE_CONTROL_KEY = "redstoneControl" const val INVENTORY_KEY = "container" const val MATTER_STORAGE_KEY = "matterStorage" const val ENERGY_KEY = "energyStorage" const val BATTERY_KEY = "batteryInventory" const val LOOT_TABLE_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_TAG const val LOOT_TABLE_SEED_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG + + private val playerMap = WeakHashMap>() + private val tickingMap = WeakHashMap>() + + private val vec2Dir = Int2ObjectOpenHashMap() + + /** + * Returns stream of players watching (tracking) specified [chunkPos] in [level] + */ + fun watchingPlayers(chunkPos: Long, level: Level): Stream { + if (level !is ServerLevel) + return Stream.empty() + + val subs = playerMap[level] ?: return Stream.empty() + val chunk = subs[chunkPos] ?: return Stream.empty() + return chunk.players.stream() + } + + /** + * Returns stream of players watching (tracking) specified [chunkPos] in [level] + */ + fun watchingPlayers(chunkPos: ChunkPos, level: Level) = watchingPlayers(chunkPos.toLong(), level) + + /** + * Returns stream of players watching (tracking) specified [blockPos] in [level] + */ + fun watchingPlayers(blockPos: BlockPos, level: Level) = watchingPlayers(ChunkPos(blockPos), level) + + /** + * Returns stream of players watching (tracking) specified [blockPos] in [level] + */ + fun watchingPlayers(blockPos: Vec3, level: Level) = watchingPlayers(ChunkPos.asLong(SectionPos.blockToSectionCoord(blockPos.x), SectionPos.blockToSectionCoord(blockPos.z)), level) + + /** + * Returns stream of players watching (tracking) specified [chunk] + */ + fun watchingPlayers(chunk: LevelChunk) = watchingPlayers(chunk.pos, chunk.level) + + private fun vecKey(value: Vec3i): Int { + if (value.x !in -1 .. 1) return -1 + if (value.y !in -1 .. 1) return -1 + if (value.z !in -1 .. 1) return -1 + val x = if (value.x < 0) 2 else value.x + val y = if (value.y < 0) 2 else value.y + val z = if (value.z < 0) 2 else value.z + return x or (y shl 4) or (z shl 8) + } + + init { + for (dir in Direction.entries) { + vec2Dir[vecKey(dir.normal)] = dir + } + } + + fun onLevelUnload(event: LevelEvent.Unload) { + val level = event.level as? ServerLevel ?: return + playerMap.remove(level) + tickingMap.remove(level) + } + + fun onServerStopping(event: ServerStoppingEvent) { + playerMap.clear() + tickingMap.clear() + } + + fun postLevelTick(event: LevelTickEvent) { + val level = event.level as? ServerLevel ?: return + val ticking = tickingMap[level] ?: return + + ticking.removeIf { + val shouldRemove = it.blockEntitiesWithObservers.isEmpty() + + if (!shouldRemove && it.players.isNotEmpty()) { + it.blockEntitiesWithObservers.forEach { + it.synchronizeToPlayers(false) + } + } + + shouldRemove + } + + if (ticking.isEmpty()) { + tickingMap.remove(level) + } + } + + fun onWatch(event: ChunkWatchEvent.Watch) { + playerMap + .computeIfAbsent(event.level) { Long2ObjectOpenHashMap() } + .computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { ChunkSubscribers(event.level, it) }) + .subscribe(event.player) + } + + fun onForget(event: ChunkWatchEvent.UnWatch) { + val levelMap = playerMap[event.level] ?: return + val subs = levelMap.get(event.pos.toLong()) ?: return + + if (subs.unsubscribe(event.player) && subs.isEmpty) { + levelMap.remove(event.pos.toLong()) + } + } + + fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) { + for (tree in playerMap.values) { + tree.values.removeIf { + it.unsubscribe(event.entity as ServerPlayer) + it.isEmpty + } + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt new file mode 100644 index 000000000..9eeab72a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt @@ -0,0 +1,777 @@ +package ru.dbotthepony.mc.otm.block.entity + +import com.google.common.collect.ImmutableSet +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.core.BlockPos +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.MenuProvider +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.item.EmptyItemHandler +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.item.UnmodifiableItemHandler +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.moveBetweenSlots +import ru.dbotthepony.mc.otm.capability.moveEnergy +import ru.dbotthepony.mc.otm.capability.moveFluid +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.nbt.getJson +import ru.dbotthepony.mc.otm.core.nbt.putJson +import ru.dbotthepony.mc.otm.core.util.ITickable +import ru.dbotthepony.mc.otm.once + +/** + * Device block entity base, implementing [MenuProvider] and [IRedstoneControlled], and also tracks custom display name + */ +@Suppress("LiftReturnOrAssignment") +abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) + : MatteryBlockEntity(blockEntityType, blockPos, blockState), MenuProvider, IRedstoneControlled { + var customDisplayName: Component? = null + + override val redstoneControl: AbstractRedstoneControl = RedstoneControl { new, old -> + markDirtyFast() + + if (new != old) + redstoneStatusUpdated(new, old) + } + + init { + savetables.stateful(::redstoneControl, REDSTONE_CONTROL_KEY) + } + + protected open val defaultDisplayName: Component + get() = level?.getBlockState(blockPos)?.block?.name ?: TextComponent("null at $blockPos") + + abstract override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? + + override fun getDisplayName(): Component { + return customDisplayName ?: defaultDisplayName + } + + protected open fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) {} + + override fun saveShared(nbt: CompoundTag) { + super.saveShared(nbt) + + if (customDisplayName != null) + nbt.putJson("Name", Component.Serializer.toJsonTree(customDisplayName!!)) + } + + override fun load(nbt: CompoundTag) { + super.load(nbt) + customDisplayName = nbt.getJson("Name")?.let(Component.Serializer::fromJson) + } + + override fun setLevel(level: Level) { + super.setLevel(level) + + level.once { + if (!isRemoved && this.level == level) { + redstoneControl.redstoneSignal = level.getBestNeighborSignal(blockPos) + } + } + } + + inner class ConfigurableFluidHandler( + val capability: T, + + val possibleModes: FlowDirection = FlowDirection.BI_DIRECTIONAL, + + val frontDefault: FlowDirection = possibleModes, + val backDefault: FlowDirection = possibleModes, + val leftDefault: FlowDirection = possibleModes, + val rightDefault: FlowDirection = possibleModes, + val topDefault: FlowDirection = possibleModes, + val bottomDefault: FlowDirection = possibleModes, + ) { + init { + exposeSideless(ForgeCapabilities.FLUID_HANDLER, capability) + } + + val front = Piece(RelativeSide.FRONT).also { it.flow = frontDefault } + val back = Piece(RelativeSide.BACK).also { it.flow = backDefault } + val left = Piece(RelativeSide.LEFT).also { it.flow = leftDefault } + val right = Piece(RelativeSide.RIGHT).also { it.flow = rightDefault } + val top = Piece(RelativeSide.TOP).also { it.flow = topDefault } + val bottom = Piece(RelativeSide.BOTTOM).also { it.flow = bottomDefault } + + val pieces = immutableMap { + put(RelativeSide.FRONT, front) + put(RelativeSide.BACK, back) + put(RelativeSide.LEFT, left) + put(RelativeSide.RIGHT, right) + put(RelativeSide.TOP, top) + put(RelativeSide.BOTTOM, bottom) + } + + val defaults = immutableMap { + put(RelativeSide.FRONT, frontDefault) + put(RelativeSide.BACK, backDefault) + put(RelativeSide.LEFT, leftDefault) + put(RelativeSide.RIGHT, rightDefault) + put(RelativeSide.TOP, topDefault) + put(RelativeSide.BOTTOM, bottomDefault) + } + + inner class Piece(val side: RelativeSide) : IFluidHandler, ITickable { + private val ticker = tickList.Ticker(this) + private val controller = side(side).Cap(ForgeCapabilities.FLUID_HANDLER, this) + private val neighbour = side(side).track(ForgeCapabilities.FLUID_HANDLER) + + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && flow != FlowDirection.NONE && !redstoneControl.isBlockedByRedstone && neighbour.get().isPresent + } + + init { + // https://tenor.com/view/simp-metal-gear-liquid-snake-running-gif-16717852 + savetables.enum(::flow, "fluid_${side}_flow", FlowDirection::valueOf) + savetables.bool(::automatePull, "fluid_${side}_pull") + savetables.bool(::automatePush, "fluid_${side}_push") + } + + var flow by synchronizer.enum(possibleModes, setter = { value, access, setByRemote -> + require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" } + + if (access.read() != value) { + access.write(value) + markDirtyFast() + + if (value == FlowDirection.NONE) { + controller.close() + } else { + controller.close() + controller.expose() + } + + updateTickerState() + } + }) + + // var automatePull by synchronizer.bool().property + var automatePull = false + set(value) { + field = value + markDirtyFast() + updateTickerState() + } + + // var automatePush by synchronizer.bool().property + var automatePush = false + set(value) { + field = value + markDirtyFast() + updateTickerState() + } + + init { + waitForServerLevel { + redstoneControl.addListener { + updateTickerState() + } + + neighbour.addListener { + updateTickerState() + } + + updateTickerState() + } + } + + override fun tick() { + if (flow == FlowDirection.NONE || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone) + return + + neighbour.get().ifPresentK { + if (flow.input && automatePull) { + moveFluid(source = it, destination = capability) + } + + if (flow.output && automatePush) { + moveFluid(source = capability, destination = it) + } + } + } + + override fun getTanks(): Int { + if (flow == FlowDirection.NONE) { + return 0 + } else { + return capability.getTanks() + } + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (flow == FlowDirection.NONE) { + return FluidStack.EMPTY + } else { + return capability.getFluidInTank(tank) + } + } + + override fun getTankCapacity(tank: Int): Int { + if (flow == FlowDirection.NONE) { + return 0 + } else { + return capability.getTankCapacity(tank) + } + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + if (flow.input) { + return capability.isFluidValid(tank, stack) + } else { + return false + } + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (flow.input) { + return capability.fill(resource, action) + } else { + return 0 + } + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (flow.output) { + return capability.drain(resource, action) + } else { + return FluidStack.EMPTY + } + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + if (flow.output) { + return capability.drain(maxDrain, action) + } else { + return FluidStack.EMPTY + } + } + } + } + + inner class ConfigurableEnergy( + val energy: T, + + val modesFront: FlowDirection = energy.energyFlow, + val modesBack: FlowDirection = energy.energyFlow, + val modesLeft: FlowDirection = energy.energyFlow, + val modesRight: FlowDirection = energy.energyFlow, + val modesTop: FlowDirection = energy.energyFlow, + val modesBottom: FlowDirection = energy.energyFlow, + + val frontDefault: FlowDirection = modesFront, + val backDefault: FlowDirection = modesBack, + val leftDefault: FlowDirection = modesLeft, + val rightDefault: FlowDirection = modesRight, + val topDefault: FlowDirection = modesTop, + val bottomDefault: FlowDirection = modesBottom, + + /** + * If battery level can update without calling [markDirtyFast]/[setChanged] + */ + val volatileEnergyValues: Boolean = false + ) { + constructor( + energy: T, + possibleModes: FlowDirection, + frontDefault: FlowDirection = possibleModes, + backDefault: FlowDirection = possibleModes, + leftDefault: FlowDirection = possibleModes, + rightDefault: FlowDirection = possibleModes, + topDefault: FlowDirection = possibleModes, + bottomDefault: FlowDirection = possibleModes, + volatileEnergyValues: Boolean = false + ) : this( + energy, + modesFront = possibleModes, frontDefault = frontDefault, + modesBack = possibleModes, backDefault = backDefault, + modesLeft = possibleModes, leftDefault = leftDefault, + modesRight = possibleModes, rightDefault = rightDefault, + modesTop = possibleModes, topDefault = topDefault, + modesBottom = possibleModes, bottomDefault = bottomDefault, + volatileEnergyValues = volatileEnergyValues, + ) + + init { + exposeEnergySideless(energy) + } + + val front = Piece(RelativeSide.FRONT, modesFront).also { it.energyFlow = frontDefault } + val back = Piece(RelativeSide.BACK, modesBack).also { it.energyFlow = backDefault } + val left = Piece(RelativeSide.LEFT, modesLeft).also { it.energyFlow = leftDefault } + val right = Piece(RelativeSide.RIGHT, modesRight).also { it.energyFlow = rightDefault } + val top = Piece(RelativeSide.TOP, modesTop).also { it.energyFlow = topDefault } + val bottom = Piece(RelativeSide.BOTTOM, modesBottom).also { it.energyFlow = bottomDefault } + + val pieces = immutableMap { + put(RelativeSide.FRONT, front) + put(RelativeSide.BACK, back) + put(RelativeSide.LEFT, left) + put(RelativeSide.RIGHT, right) + put(RelativeSide.TOP, top) + put(RelativeSide.BOTTOM, bottom) + } + + val defaults = immutableMap { + put(RelativeSide.FRONT, frontDefault) + put(RelativeSide.BACK, backDefault) + put(RelativeSide.LEFT, leftDefault) + put(RelativeSide.RIGHT, rightDefault) + put(RelativeSide.TOP, topDefault) + put(RelativeSide.BOTTOM, bottomDefault) + } + + fun invalidate(force: Boolean = false) { + for (piece in pieces.values) { + piece.invalidate(force) + } + } + + inner class Piece(val side: RelativeSide, val possibleModes: FlowDirection) : IMatteryEnergyStorage, ITickable { + private val capControllers = exposeEnergy(side, this@Piece) + private val neighbour = side(side).trackEnergy() + + override var batteryLevel: Decimal by energy::batteryLevel + override val maxBatteryLevel: Decimal by energy::maxBatteryLevel + override val missingPower: Decimal by energy::missingPower + + override val canSetBatteryLevel: Boolean by energy::canSetBatteryLevel + + private val ticker = tickList.Ticker(this) + + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && + energyFlow != FlowDirection.NONE && + !redstoneControl.isBlockedByRedstone && + neighbour.get().isPresent && + (volatileEnergyValues || energy.batteryLevel.isPositive) + } + + // var automatePull by synchronizer.bool().property + var automatePull = false + set(value) { + field = value + markDirtyFast() + updateTickerState() + } + + // var automatePush by synchronizer.bool().property + var automatePush = false + set(value) { + field = value + markDirtyFast() + updateTickerState() + } + + init { + savetables.enum(::energyFlow, "energy_${side}_flow", FlowDirection::valueOf) + savetables.bool(::automatePull, "energy_${side}_pull") + savetables.bool(::automatePush, "energy_${side}_push") + + dirtyListeners.addListener { + updateTickerState() + } + + waitForServerLevel { + redstoneControl.addListener { + updateTickerState() + } + + neighbour.addListener { + updateTickerState() + } + + updateTickerState() + } + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return energy.extractEnergy(howMuch, simulate) + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return energy.receiveEnergy(howMuch, simulate) + } + + override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (energyFlow.output) + return energy.extractEnergyChecked(howMuch, simulate) + + return Decimal.ZERO + } + + override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (energyFlow.input) + return energy.receiveEnergyChecked(howMuch, simulate) + + return Decimal.ZERO + } + + override fun drainBattery(): Boolean { + return energy.drainBattery() + } + + override fun fillBattery(): Boolean { + return energy.fillBattery() + } + + override fun tick() { + neighbour.get().ifPresentK { + if (energyFlow.input && automatePull) { + moveEnergy(source = it, destination = energy, simulate = false) + } + + if (energyFlow.output && automatePush) { + moveEnergy(source = energy, destination = it, simulate = false) + } + } + } + + override var energyFlow by synchronizer.enum(possibleModes, setter = { value, access, setByRemote -> + require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" } + + if (access.read() != value) { + access.write(value) + markDirtyFast() + + if (value == FlowDirection.NONE) { + for (controller in capControllers) + controller.close() + } else { + for (controller in capControllers) { + controller.close() + controller.expose() + } + } + + updateTickerState() + } + }) + + fun invalidate(force: Boolean = false) { + if (force) { + for (controller in capControllers) { + controller.close() + controller.expose() + } + + if (energyFlow == FlowDirection.NONE) { + for (controller in capControllers) { + controller.close() + } + } + } else { + if (energyFlow != FlowDirection.NONE) { + for (controller in capControllers) { + controller.close() + controller.expose() + } + } + } + } + } + } + + enum class ItemHandlerMode(val translationKey: String) { + DISABLED("otm.gui.side_mode.disabled"), + INPUT("otm.gui.side_mode.input"), + OUTPUT("otm.gui.side_mode.output"), + INPUT_OUTPUT("otm.gui.side_mode.input_output"), + BATTERY("otm.gui.side_mode.battery"); + } + + inner class ConfigurableItemHandler( + input: IItemHandler? = null, + output: IItemHandler? = null, + inputOutput: IItemHandler? = null, + val battery: IItemHandler? = null, + val frontDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.FRONT), + val backDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.BACK), + val leftDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.LEFT), + val rightDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.RIGHT), + val topDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.TOP), + val bottomDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.BOTTOM), + ) { + val sideless: IItemHandler + val possibleViews: ImmutableSet + val inputOutput: IItemHandler? + val input: IItemHandler? + val output: IItemHandler? + + init { + if ((input != null || output != null) && inputOutput != null) + throw IllegalArgumentException("Either specify input or/and output separately, or specify inputOutput") + + if (inputOutput != null) { + this.input = inputOutput + this.output = inputOutput + } else { + this.input = input + this.output = output + } + + val builder = ImmutableSet.Builder() + + builder.add(ItemHandlerMode.DISABLED) + + if (input != null) builder.add(ItemHandlerMode.INPUT) + if (output != null) builder.add(ItemHandlerMode.OUTPUT) + if (input != null && output != null) builder.add(ItemHandlerMode.INPUT_OUTPUT) + + if (inputOutput != null) { + builder.add(ItemHandlerMode.INPUT) + builder.add(ItemHandlerMode.OUTPUT) + builder.add(ItemHandlerMode.INPUT_OUTPUT) + } + + if (battery != null) builder.add(ItemHandlerMode.BATTERY) + + possibleViews = builder.build() + + if (input != null && output != null) { + this.inputOutput = CombinedItemHandler(input, output) + } else { + this.inputOutput = inputOutput + } + + val caps = ArrayList() + + if (input != null) caps.add(input) + if (output != null) caps.add(output) + if (inputOutput != null) caps.add(inputOutput) + if (battery != null) caps.add(battery) + + sideless = UnmodifiableItemHandler(CombinedItemHandler(caps)) + exposeSideless(ForgeCapabilities.ITEM_HANDLER, sideless) + } + + val front = Piece(RelativeSide.FRONT).also { it.mode = frontDefault } + val back = Piece(RelativeSide.BACK).also { it.mode = backDefault } + val left = Piece(RelativeSide.LEFT).also { it.mode = leftDefault } + val right = Piece(RelativeSide.RIGHT).also { it.mode = rightDefault } + val top = Piece(RelativeSide.TOP).also { it.mode = topDefault } + val bottom = Piece(RelativeSide.BOTTOM).also { it.mode = bottomDefault } + + val pieces = immutableMap { + put(RelativeSide.FRONT, front) + put(RelativeSide.BACK, back) + put(RelativeSide.LEFT, left) + put(RelativeSide.RIGHT, right) + put(RelativeSide.TOP, top) + put(RelativeSide.BOTTOM, bottom) + } + + val defaults = immutableMap { + put(RelativeSide.FRONT, frontDefault) + put(RelativeSide.BACK, backDefault) + put(RelativeSide.LEFT, leftDefault) + put(RelativeSide.RIGHT, rightDefault) + put(RelativeSide.TOP, topDefault) + put(RelativeSide.BOTTOM, bottomDefault) + } + + inner class Piece(val side: RelativeSide) : IItemHandler, ITickable { + private var currentHandler: IItemHandler = EmptyItemHandler + set(value) { + field = value + updateTickerState() + } + + private val capController = side(side).Cap(ForgeCapabilities.ITEM_HANDLER, this) + private val neighbour = side(side).track(ForgeCapabilities.ITEM_HANDLER) + private val ticker = tickList.Ticker(this) + + private var innerSlotPull = 0 + private var outerSlotPull = 0 + + private var innerSlotPush = 0 + private var outerSlotPush = 0 + + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && mode != ItemHandlerMode.DISABLED && !redstoneControl.isBlockedByRedstone && currentHandler.slots != 0 && neighbour.get().isPresent + } + + init { + capController.close() + } + + var mode by synchronizer.enum(ItemHandlerMode.DISABLED, setter = { value, access, setByRemote -> + require(value in possibleViews) { "View type $value is not allowed (allowed views: $possibleViews)" } + + if (access.read() != value) { + access.write(value) + markDirtyFast() + + if (value == ItemHandlerMode.DISABLED) { + capController.close() + } else { + capController.close() + capController.expose() + } + + currentHandler = when (value) { + ItemHandlerMode.DISABLED -> EmptyItemHandler + ItemHandlerMode.INPUT -> input!! + ItemHandlerMode.OUTPUT -> output!! + ItemHandlerMode.INPUT_OUTPUT -> inputOutput!! + ItemHandlerMode.BATTERY -> battery!! + } + } + }) + + var automatePull = false + set(value) { + if (field != value) { + field = value + markDirtyFast() + + if (value) { + innerSlotPush = 0 + outerSlotPush = 0 + } + + updateTickerState() + } + } + + var automatePush = false + set(value) { + if (field != value) { + field = value + markDirtyFast() + + if (value) { + innerSlotPush = 0 + outerSlotPush = 0 + } + + updateTickerState() + } + } + + init { + savetables.bool(::automatePull, "itemhandler_${side}_automatePull") + savetables.bool(::automatePush, "itemhandler_${side}_automatePush") + savetables.enum(::mode, "itemhandler_${side}_mode", ItemHandlerMode::valueOf) + + waitForServerLevel { + redstoneControl.addListener { + updateTickerState() + } + + neighbour.addListener { + updateTickerState() + } + + updateTickerState() + } + } + + override fun tick() { + if (mode == ItemHandlerMode.DISABLED || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone || currentHandler.slots == 0) + return + + neighbour.get().ifPresentK { + if (it.slots == 0) + return + + if (automatePull) { + if (innerSlotPull !in 0 until currentHandler.slots) + innerSlotPull = 0 + + if (outerSlotPull !in 0 until it.slots) + outerSlotPull = 0 + + val (outerSlotPull, innerSlotPull) = moveBetweenSlots(it, outerSlotPull, currentHandler, innerSlotPull) + this.innerSlotPull = innerSlotPull + this.outerSlotPull = outerSlotPull + } + + if (automatePush) { + if (innerSlotPush !in 0 until currentHandler.slots) + innerSlotPush = 0 + + if (outerSlotPush !in 0 until it.slots) + outerSlotPush = 0 + + val (innerSlotPush, outerSlotPush) = moveBetweenSlots(currentHandler, innerSlotPush, it, outerSlotPush) + this.innerSlotPush = innerSlotPush + this.outerSlotPush = outerSlotPush + } + } + } + + override fun getSlots(): Int { + return currentHandler.slots + } + + override fun getStackInSlot(slot: Int): ItemStack { + return currentHandler.getStackInSlot(slot) + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return currentHandler.insertItem(slot, stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return currentHandler.extractItem(slot, amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + return currentHandler.getSlotLimit(slot) + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return currentHandler.isItemValid(slot, stack) + } + } + } + + companion object { + private fun determineDefaultMode(input: IItemHandler?, output: IItemHandler?, inputOutput: IItemHandler?, battery: IItemHandler?, side: RelativeSide): ItemHandlerMode { + if (side == RelativeSide.BACK && battery != null) { + return ItemHandlerMode.BATTERY + } + + if (input == null && output == null && inputOutput == null) { + return ItemHandlerMode.DISABLED + } else if (inputOutput != null) { + return ItemHandlerMode.INPUT_OUTPUT + } else if (input != null && output != null) { + return when (side) { + RelativeSide.FRONT, RelativeSide.BACK -> ItemHandlerMode.DISABLED + RelativeSide.RIGHT, RelativeSide.TOP -> ItemHandlerMode.INPUT + RelativeSide.LEFT, RelativeSide.BOTTOM -> ItemHandlerMode.OUTPUT + } + } else if (output != null) { + if (side == RelativeSide.TOP) { + return ItemHandlerMode.DISABLED + } else { + return ItemHandlerMode.OUTPUT + } + } else { + if (side == RelativeSide.BOTTOM) { + return ItemHandlerMode.DISABLED + } else { + return ItemHandlerMode.INPUT + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt index 4200a73ad..ee7854a35 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.world.item.BlockItem @@ -11,91 +10,48 @@ import net.minecraft.world.item.TooltipFlag import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.ifHas +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.nbt.map -abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(p_155228_, p_155229_, p_155230_) { - abstract val energy: BlockEnergyStorageImpl - private var valid = true - val batteryContainer = MatteryContainer(this::setChangedLight, 1) +abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(p_155228_, p_155229_, p_155230_) { + val batteryContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val batteryItemHandler = batteryContainer.handler(HandlerFilter.Dischargeable) + open val energy: IMatteryEnergyStorage? + get() = null - protected fun batteryChargeLoop() { - var hit = false + init { + savetables.stateful(::batteryContainer, BATTERY_KEY) + } - for (stack in batteryContainer) { - if (!stack.isEmpty) { - hit = true - break - } - } + override fun tick() { + super.tick() - if (!hit) { - return - } + if (batteryContainer.isEmpty) return - var demand = energy.receiveEnergyOuter(energy.missingPower, true) + val energy = energy ?: return + var demand = energy.receiveEnergy(energy.missingPower, true) if (demand.isZero) return for (stack in batteryContainer) { - if (!stack.isEmpty) { - stack.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - val diff = it.extractEnergyOuter(demand, false) - energy.receiveEnergyInner(diff, false) - demand -= diff - } else { - val diff = it.extractEnergy(demand, false) - energy.receiveEnergyInner(diff, false) - demand -= diff - } - } - - if (demand <= Decimal.ZERO) - return + stack.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + val diff = it.extractEnergy(demand, false) + energy.receiveEnergy(diff, false) + demand -= diff } + + if (demand <= Decimal.ZERO) + return } } - override fun invalidateCaps() { - super.invalidateCaps() - energy.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - energy.revive() - valid = true - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid && (cap == MatteryCapability.ENERGY || cap == ForgeCapabilities.ENERGY)) - return energy.resolver.cast() - - return super.getCapability(cap, side) - } - - public override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[ENERGY_KEY] = energy.serializeNBT() - nbt[BATTERY_KEY] = batteryContainer.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - nbt.ifHas(ENERGY_KEY, CompoundTag::class.java, energy::deserializeNBT) - batteryContainer.deserializeNBT(nbt[BATTERY_KEY]) - super.load(nbt) - } - companion object { fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { val tag = itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag ?: return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt index f37d6dc87..a89abd728 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt @@ -1,396 +1,171 @@ package ru.dbotthepony.mc.otm.block.entity -import net.minecraft.ChatFormatting -import net.minecraft.world.level.block.entity.BlockEntityType +import com.google.common.collect.ImmutableList +import com.mojang.serialization.Codec import net.minecraft.core.BlockPos -import net.minecraft.world.level.block.state.BlockState import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.block.Block -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.core.map +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.nbt.getCompoundList +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.countingLazy -private fun isReason(status: Any?, reason: Any) = status == null || status == reason - -abstract class MatteryWorkerBlockEntity( +/** + * Simple machine, which can work on only one job type. + * + * From technical point, this is a specialized use case of [MachineJobEventLoop]. + */ +abstract class MatteryWorkerBlockEntity( type: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState, - val jobDeserializer: (tag: CompoundTag) -> JobType? + jobCodec: Codec, + maxJobs: Int = 1, ) : MatteryPoweredBlockEntity(type, blockPos, blockState) { - open class Job { - open val ticks: Double - open val powerUsage: Decimal + val jobEventLoops: ImmutableList> = immutableList(maxJobs) { id -> + object : MachineJobEventLoop(jobCodec) { + override val energy: IMatteryEnergyStorage? + get() = this@MatteryWorkerBlockEntity.energy + override val isBlockedByRedstone: Boolean + get() = redstoneControl.isBlockedByRedstone + override val upgrades: IMatteryUpgrade? + get() = this@MatteryWorkerBlockEntity.upgrades - constructor( - ticks: Double, - powerUsage: Decimal = Decimal.ZERO - ) { - this.ticks = ticks - this.powerUsage = powerUsage - } + override fun onJobFinish(status: JobStatus) { + return this@MatteryWorkerBlockEntity.onJobFinish(status, id) + } - constructor( - tag: CompoundTag - ) : this(tag.getDouble(TICKS_KEY), tag.getDecimal(POWER_KEY)) + override fun computeNextJob(): JobContainer { + return this@MatteryWorkerBlockEntity.computeNextJob(id) + } - open fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it[TICKS_KEY] = ticks - it[POWER_KEY] = powerUsage + override fun jobUpdated(new: JobType?, old: JobType?) { + this@MatteryWorkerBlockEntity.jobUpdated(new, old, id) + } + + override fun onJobTick(status: JobStatus) { + super@MatteryWorkerBlockEntity.markDirtyFast() + return this@MatteryWorkerBlockEntity.onJobTick(status, id) } } - - companion object { - const val TICKS_KEY = "ticks" - const val POWER_KEY = "power" - } } - @Suppress("LeakingThis") - open class ItemJob : Job { - open val itemStack: ItemStack + open val upgrades: IMatteryUpgrade? get() = null - constructor( - itemStack: ItemStack, - ticks: Double, - power: Decimal = Decimal.ZERO - ) : super(ticks, power) { - this.itemStack = itemStack - } + var balanceInputs = false - constructor( - tag: CompoundTag - ) : super(tag) { - this.itemStack = (tag["item"] as? CompoundTag)?.let { ItemStack.of(it) } ?: ItemStack.EMPTY - } + init { + savetables.bool(::balanceInputs) + } - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[ITEM_KEY] = itemStack.serializeNBT() + protected open fun jobUpdated(new: JobType?, old: JobType?, id: Int) {} + protected abstract fun onJobFinish(status: JobStatus, id: Int) + protected abstract fun computeNextJob(id: Int): JobContainer + + protected open fun onJobTick(status: JobStatus, id: Int) {} + + override fun saveShared(nbt: CompoundTag) { + super.saveShared(nbt) + nbt["jobs"] = ListTag().also { + for ((i, job) in jobEventLoops.withIndex()) { + it.add(job.serializeNBT().also { + it["_id"] = i + }) } } - - companion object { - const val ITEM_KEY = "item" - } - } - - enum class IdleReason { - ITEM, - POWER, - MATTER, - - /** - * Observing external factor, such as waiting for matter/item network tasks - */ - OBSERVING - } - - data class Status( - val success: Boolean, - val throttleTicks: Int = 0, - val idleReason: IdleReason? = null, - val newDrainedPower: Decimal? = null - ) { - companion object { - val SUCCESS = Status(true) - val FAILURE = Status(false) - val FAILURE_ITEM = Status(false, 20, IdleReason.ITEM) - val FAILURE_MATTER = Status(false, 20, IdleReason.MATTER) - val FAILURE_WAIT = Status(false, 100) - val FAILURE_WAIT_FAST = Status(false, 20) - } - } - - var workTicks = 0.0 - protected set - - var throttleTicks = 0 - protected set - - protected open fun jobUpdated(oldJob: JobType?, newJob: JobType?) {} - - var currentJob: JobType? = null - protected set(value) { - if (field != value) { - val old = field - field = value - jobUpdated(old, value) - } - } - - /** - * Can be whatever you want, but [IdleReason] certainly contains all cases - */ - var idleReason: Any? = null - private set - - var isIdling = false - protected set - - val isUnableToProcess: Boolean get() = throttleTicks > 0 - - val workProgress: Float - get() { - val currentJob = currentJob ?: return 0.0f - return (workTicks / currentJob.ticks).coerceAtMost(1.0).toFloat() - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[WORK_TICKS_KEY] = workTicks - currentJob?.let { nbt[JOB_KEY] = it.serializeNBT() } } override fun load(nbt: CompoundTag) { super.load(nbt) - workTicks = nbt.getDouble(WORK_TICKS_KEY) - currentJob = nbt.map(JOB_KEY, jobDeserializer::invoke) + for (v in nbt.getCompoundList("jobs")) { + if ("_id" in v) { + val id = v.getInt("_id") - if (currentJob == null) - workTicks = 0.0 + if (id in jobEventLoops.indices) { + jobEventLoops[id].deserializeNBT(v) + } + } + } } override fun setChanged() { super.setChanged() - isIdling = false + jobEventLoops.forEach { it.isIdling = false } } - override fun setChangedLight() { - super.setChangedLight() - isIdling = false + override fun markDirtyFast() { + super.markDirtyFast() + jobEventLoops.forEach { it.isIdling = false } } - protected fun powerLevelUpdated() { - super.setChangedLight() - - if (isReason(idleReason, IdleReason.POWER)) { - isIdling = false - throttleTicks = 0 - } + protected fun energyLevelUpdated() { + super.markDirtyFast() + jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.POWER) } } protected fun itemContainerUpdated() { super.setChanged() - - if (isReason(idleReason, IdleReason.ITEM)) { - isIdling = false - throttleTicks = 0 - } + jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.ITEM) } } protected fun matterLevelUpdated() { - super.setChangedLight() - - if (isReason(idleReason, IdleReason.MATTER)) { - isIdling = false - throttleTicks = 0 - } + super.markDirtyFast() + jobEventLoops.forEach { it.notify(MachineJobEventLoop.IdleReason.MATTER) } } - /** - * Called whenever reaching desired amount of ticks at job - */ - protected abstract fun onJobFinish(job: JobType): Status - - /** - * [Pair.second] is reason why job can't be performed - * - * If not null, it is written to [idleReason] - */ - protected abstract fun computeNextJob(): Pair - - protected open fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType): Status { - return Status.SUCCESS - } - - private var idleTicksAnim = 0 - private var workingTicksAnim = 0 - private var errorTicksAnim = 0 - override fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) { super.redstoneStatusUpdated(newBlocked, oldBlocked) - isIdling = newBlocked + jobEventLoops.forEach { it.isIdling = newBlocked } } - protected fun workerLoop() { - if (errorTicksAnim > 20 && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.ERROR) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.ERROR), Block.UPDATE_CLIENTS) - } + private val hasWorkerState by countingLazy(blockStateChangesCounter) { + this.blockState.hasProperty(WorkerState.WORKER_STATE) + } - if (throttleTicks > 0) { - workingTicksAnim = 0 - idleTicksAnim = 0 - throttleTicks-- - errorTicksAnim++ + private val isWorkingState by countingLazy(blockStateChangesCounter) { + hasWorkerState && this.blockState.getValue(WorkerState.WORKER_STATE) == WorkerState.WORKING + } - if (throttleTicks > 0) - return - else - isIdling = false - } + private val isErrorState by countingLazy(blockStateChangesCounter) { + hasWorkerState && this.blockState.getValue(WorkerState.WORKER_STATE) == WorkerState.ERROR + } - if (isIdling) { - workingTicksAnim = 0 - errorTicksAnim = 0 - idleTicksAnim++ + private val isIdleState by countingLazy(blockStateChangesCounter) { + hasWorkerState && this.blockState.getValue(WorkerState.WORKER_STATE) == WorkerState.IDLE + } - if (idleTicksAnim > 20 && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue( - WorkerState.WORKER_STATE - ) != WorkerState.IDLE - ) { + override fun tick() { + super.tick() + jobEventLoops.forEach { it.think() } + + if (hasWorkerState) { + if (jobEventLoops.any { it.workingTicksAnim > 20 } && !isWorkingState) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) + } else if (jobEventLoops.any { it.errorTicksAnim > 20 } && !isErrorState) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.ERROR), Block.UPDATE_CLIENTS) + } else if (jobEventLoops.all { it.idleTicksAnim > 20 } && !isIdleState) { level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) } - - return } - - var availableTicks = 1.0 - - while (!isIdling && weakGreaterThan(availableTicks, 0.0) && throttleTicks <= 0) { - if (isBlockedByRedstone) { - isIdling = true - break - } - - var currentJob = currentJob - - if (currentJob == null) { - val (job, reason) = computeNextJob() - - if (job == null) { - idleReason = reason - isIdling = reason != null - workingTicksAnim = 0 - break - } - - this.currentJob = job - currentJob = job - } - - if (!currentJob.powerUsage.isZero && energy.batteryLevel.isZero) { - idleReason = IdleReason.POWER - isIdling = true - idleTicksAnim++ - - if (idleTicksAnim > 20 && blockState.hasProperty(WorkerState.WORKER_STATE) && blockState.getValue( - WorkerState.WORKER_STATE - ) != WorkerState.IDLE - ) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) - } - - break - } - - idleTicksAnim = 0 - - if (weakLessThan(workTicks, currentJob.ticks)) { - val ticksLeft = currentJob.ticks - workTicks - val ticksAdvanced: Double - - var requiredPower: Decimal? = null - var extractedPower: Decimal? = null - - if (currentJob.powerUsage.isZero) { - ticksAdvanced = availableTicks.coerceAtMost(ticksLeft) - } else { - requiredPower = currentJob.powerUsage * ticksLeft.coerceAtMost(availableTicks) - extractedPower = energy.extractEnergyInner(requiredPower, true) - ticksAdvanced = (extractedPower / requiredPower).toDouble().coerceAtMost(ticksLeft).coerceAtMost(availableTicks) - } - - if (weakEqualDoubles(ticksAdvanced, 0.0)) { - break - } - - val status = onWorkTick(requiredPower ?: Decimal.ZERO, extractedPower ?: Decimal.ZERO, ticksAdvanced, currentJob) - - if (!status.success) { - throttleTicks += status.throttleTicks - - if (status.idleReason != null) { - idleReason = status.idleReason - isIdling = true - } - - break - } - - workingTicksAnim++ - errorTicksAnim = 0 - - workTicks += ticksAdvanced - availableTicks -= ticksAdvanced - - extractedPower = status.newDrainedPower ?: extractedPower - - if (extractedPower != null) { - energy.extractEnergyInner(extractedPower, false) - } - - continue - } - - val status = onJobFinish(currentJob) - - if (status.success) { - this.currentJob = null - workTicks = 0.0 - errorTicksAnim = 0 - } else { - throttleTicks += status.throttleTicks - - if (status.idleReason != null) { - idleReason = status.idleReason - isIdling = true - } - - errorTicksAnim++ - break - } - } - - if (workingTicksAnim > 20 && - errorTicksAnim == 0 && - blockState.hasProperty(WorkerState.WORKER_STATE) && - blockState.getValue(WorkerState.WORKER_STATE) != WorkerState.WORKING - ) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) - } - } - - fun basicTicker() { - batteryChargeLoop() - workerLoop() } companion object { - const val WORK_TICKS_KEY = "workTicks" - const val JOB_KEY = "job" + private val LOGGER = LogManager.getLogger() fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { - val tag = itemStack.tag ?: return - - val subtag = tag.get("BlockEntityTag") as? CompoundTag - - if (subtag != null) { - if (subtag.contains(WORK_TICKS_KEY) && !subtag.contains(JOB_KEY)) { - if (subtag.getDouble(WORK_TICKS_KEY) != 0.0) - tooltips.add(TranslatableComponent("otm.item.worker.work_ticks_mono", "%.1f".format(subtag.getDouble(WORK_TICKS_KEY))).withStyle(ChatFormatting.GRAY)) - } else if (subtag.contains(WORK_TICKS_KEY) && tag.contains(JOB_KEY)) { - tooltips.add(TranslatableComponent("otm.item.worker.work_ticks", - "%.1f".format(subtag.getDouble(WORK_TICKS_KEY)), - "%.1f".format(Job(subtag.getCompound(JOB_KEY)).ticks)).withStyle(ChatFormatting.GRAY)) - } - } + itemStack.tag ?: return WorkerEnergyStorage.appendHoverText(itemStack, blockGetter, tooltips, flag) MatteryPoweredBlockEntity.appendHoverText(itemStack, blockGetter, tooltips, flag) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/PlatePressBlockEntity.kt deleted file mode 100644 index d64a5e533..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/PlatePressBlockEntity.kt +++ /dev/null @@ -1,108 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.menu.PlatePressMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MRecipes -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set - -class PlatePressBlockEntity( - p_155229_: BlockPos, - p_155230_: BlockState -) : MatteryWorkerBlockEntity(MBlockEntities.PLATE_PRESS, p_155229_, p_155230_, ::ItemJob), IDroppableContainer { - val container = MatteryContainer(this::setChangedLight, 2) - override val energy = WorkerEnergyStorage(this::setChangedLight, ServerConfig.PLATE_PRESS) - - override val droppableContainer: Container - get() = container - val itemHandler = container.handler(object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return slot != SLOT_OUTPUT - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return slot != SLOT_INPUT - } - }) - - override fun invalidateCaps() { - super.invalidateCaps() - itemHandler.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - itemHandler.revive() - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (cap == ForgeCapabilities.ITEM_HANDLER) - return itemHandler.get().cast() - - return super.getCapability(cap, side) - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } - - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return PlatePressMenu(containerID, inventory, this) - } - - override fun onJobFinish(job: ItemJob): Status { - if (job.itemStack.isEmpty) - return Status.SUCCESS - - if (!container.fullyAddItem(job.itemStack, start = SLOT_OUTPUT, end = SLOT_OUTPUT)) - return Status.FAILURE_ITEM - - return Status.SUCCESS - } - - override fun computeNextJob(): Pair { - if (energy.batteryLevel.isZero) { - return null to IdleReason.POWER - } - - val recipe = level?.recipeManager?.getRecipeFor(MRecipes.PLATE_PRESS, container, level!!)?.orElse(null) ?: return null to IdleReason.ITEM - container[SLOT_INPUT].shrink(1) - return ItemJob(recipe.resultItem, recipe.workTime.toDouble(), BASELINE_CONSUMPTION) to null - } - - companion object { - private val BASELINE_CONSUMPTION = Decimal(15) - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.plate_press") - const val SLOT_INPUT = 0 - const val SLOT_OUTPUT = 1 - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt new file mode 100644 index 000000000..04ff7785f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt @@ -0,0 +1,114 @@ +package ru.dbotthepony.mc.otm.block.entity + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer +import net.minecraft.nbt.CompoundTag +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.core.IBooleanSubscriptable +import ru.dbotthepony.mc.otm.core.ISubscriptable +import ru.dbotthepony.mc.otm.core.nbt.mapString +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer + +interface IRedstoneControlled { + val redstoneControl: AbstractRedstoneControl +} + +abstract class AbstractRedstoneControl : INBTSerializable, IBooleanSubscriptable { + abstract var redstoneSetting: RedstoneSetting + abstract var redstoneSignal: Int + protected val listeners = IBooleanSubscriptable.Impl() + + val isBlockedByRedstone: Boolean get() = !redstoneSetting.test(redstoneSignal) + + final override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return listeners.addListener(listener) + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it[SETTING_KEY] = redstoneSetting.toString() + it[SIGNAL_KEY] = redstoneSignal + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + if (nbt == null) { + redstoneSetting = RedstoneSetting.LOW + redstoneSignal = 0 + return + } + + redstoneSetting = nbt.mapString(SETTING_KEY, RedstoneSetting::valueOf, RedstoneSetting.LOW) + redstoneSignal = nbt.getInt(SIGNAL_KEY) + } + + companion object { + const val SETTING_KEY = "RedstoneSetting" + const val SIGNAL_KEY = "RedstoneSignal" + } +} + +class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) -> Unit) : AbstractRedstoneControl() { + override var redstoneSetting: RedstoneSetting = RedstoneSetting.LOW + set(level) { + if (level == field) return + val old = isBlockedByRedstone + field = level + val state = isBlockedByRedstone + + if (state != old) { + valueChanges.invoke(state, old) + listeners.accept(state) + } + } + + override var redstoneSignal: Int = 0 + set(setting) { + if (setting == field) return + val old = isBlockedByRedstone + field = setting + val state = isBlockedByRedstone + + if (state != old) { + valueChanges.invoke(state, old) + listeners.accept(state) + } + } +} + +class SynchronizedRedstoneControl( + synchronizer: FieldSynchronizer, + private val valueChanges: (new: Boolean, old: Boolean) -> Unit, +) : AbstractRedstoneControl() { + override var redstoneSetting: RedstoneSetting by synchronizer.enum(RedstoneSetting.LOW, setter = { value, access, setByRemote -> + if (access.read() == value) return@enum + if (setByRemote) { + access.write(value) + } else { + val old = isBlockedByRedstone + access.write(value) + val state = isBlockedByRedstone + + if (state != old) { + valueChanges.invoke(state, old) + listeners.accept(state) + } + } + }) + + override var redstoneSignal: Int by synchronizer.int(0, setter = { value, access, setByRemote -> + if (access.readInt() == value) return@int + if (setByRemote) { + access.write(value) + } else { + val old = isBlockedByRedstone + access.write(value) + val state = isBlockedByRedstone + + if (state != old) { + valueChanges.invoke(state, old) + listeners.accept(state) + } + } + }).property +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneSetting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneSetting.kt index 43a75fdf8..14569fe14 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneSetting.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneSetting.kt @@ -2,11 +2,25 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.network.chat.Component import ru.dbotthepony.mc.otm.core.TranslatableComponent +import java.util.function.IntPredicate +import java.util.function.Predicate -enum class RedstoneSetting(val label: Component, val description: Component) { - IGNORED(TranslatableComponent("otm.gui.redstone.ignored"), TranslatableComponent("otm.gui.redstone.ignored.description")), - LOW(TranslatableComponent("otm.gui.redstone.low"), TranslatableComponent("otm.gui.redstone.low.description")), - HIGH(TranslatableComponent("otm.gui.redstone.high"), TranslatableComponent("otm.gui.redstone.high.description")); +enum class RedstoneSetting(val label: Component, val description: Component): IntPredicate { + IGNORED(TranslatableComponent("otm.gui.redstone.ignored"), TranslatableComponent("otm.gui.redstone.ignored.description")) { + override fun test(t: Int): Boolean { + return true + } + }, + LOW(TranslatableComponent("otm.gui.redstone.low"), TranslatableComponent("otm.gui.redstone.low.description")) { + override fun test(t: Int): Boolean { + return t <= 0 + } + }, + HIGH(TranslatableComponent("otm.gui.redstone.high"), TranslatableComponent("otm.gui.redstone.high.description")) { + override fun test(t: Int): Boolean { + return t > 0 + } + }; operator fun next(): RedstoneSetting { return values[(ordinal + 1) % values.size] @@ -22,4 +36,4 @@ enum class RedstoneSetting(val label: Component, val description: Component) { return values[index] } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt deleted file mode 100644 index 88315d951..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt +++ /dev/null @@ -1,380 +0,0 @@ -package ru.dbotthepony.mc.otm.block.entity - -import it.unimi.dsi.fastutil.longs.Long2ObjectFunction -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap -import net.minecraft.core.BlockPos -import net.minecraft.server.level.ServerLevel -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.level.ChunkPos -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityType -import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.event.TickEvent -import net.minecraftforge.event.TickEvent.LevelTickEvent -import net.minecraftforge.event.entity.player.PlayerEvent -import net.minecraftforge.event.level.ChunkWatchEvent -import net.minecraftforge.event.level.LevelEvent -import net.minecraftforge.event.server.ServerStoppingEvent -import ru.dbotthepony.mc.otm.core.forValidRefs -import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import ru.dbotthepony.mc.otm.network.WorldNetworkChannel -import ru.dbotthepony.mc.otm.onceServer -import java.lang.ref.WeakReference -import java.util.WeakHashMap - -abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_) { - private var isSynchronizing = false - - val synchronizer = FieldSynchronizer { - if (isSynchronizing) - return@FieldSynchronizer - - if (!isRemoved && level?.isClientSide == false && (_subCache == null || (_subCache ?: throw ConcurrentModificationException()).players.isNotEmpty())) { - onceServer { - synchronizeToPlayers(true) - } - } else { - markSynchronizerClean() - } - } - - private fun markSynchronizerClean() { - synchronizer.markClean() - } - - init { - synchronizer.defaultEndpoint.markUnused() - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - unsubscribe() - _subCache = null - - if (!p_155231_.isClientSide) { - subscribe() - } - } - - override fun setRemoved() { - super.setRemoved() - unsubscribe() - } - - override fun clearRemoved() { - super.clearRemoved() - - if (level?.isClientSide == false) { - subscribe() - } - } - - private var _subCache: ChunkSubscribers? = null - - private fun unsubscribe() { - val subCache = _subCache ?: return - - if (subCache.unsubscribe(this)) { - val level = subCache.level.get() - - if (level != null) { - playerMap.get(level)?.remove(subCache.chunkPos) - } - } - - lastSubscriptionChangeset = -1 - } - - private fun subscribe(): ChunkSubscribers { - val level = level - check(level is ServerLevel) { "Invalid realm" } - unsubscribe() - - val subs = playerMap - .computeIfAbsent(level) { Long2ObjectOpenHashMap() } - .computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) }) - - subs.subscribe(this) - _subCache = subs - return subs - } - - private val subscription get() = _subCache ?: subscribe() - private var lastSubscriptionChangeset = -1 - - fun synchronizeToPlayers() { - synchronizeToPlayers(false) - } - - protected fun synchronizeToPlayers(calledBySynchronizer: Boolean) { - isSynchronizing = true - - try { - if (synchronizer.isEmpty || isRemoved) { - if (calledBySynchronizer) synchronizer.markClean() - return - } - - check(level is ServerLevel) { "Invalid realm or Level is null" } - - synchronizer.observe() - - val subscription = subscription - - if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) { - if (calledBySynchronizer) synchronizer.markClean() - return - } - - lastSubscriptionChangeset = subscription.changeset - - for (player in subscription.players) { - val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload() - - if (payload != null) { - WorldNetworkChannel.send(player, BlockEntitySyncPacket(blockPos, payload.array, payload.length)) - } - } - - synchronizer.markClean() - } finally { - isSynchronizing = false - } - } - - private data class ChunkSubscribers( - val blockEntities: ArrayList> = ArrayList(0), - val players: ArrayList = ArrayList(1), - val observingBlockEntities: ArrayList> = ArrayList(0), - val level: WeakReference, - val chunkPos: Long, - var changeset: Int = 0 - ) { - fun cleanUpBlocks() { - val listIterator = blockEntities.listIterator() - - for (block in listIterator) { - if (block.get() == null) { - listIterator.remove() - } - } - } - - val hasObservers: Boolean get() { - val listIterator = blockEntities.listIterator() - - for (value in listIterator) { - val ref = value.get() - - if (ref == null) { - listIterator.remove() - } else if (ref.synchronizer.hasObservers) { - return true - } - } - - return false - } - - fun subscribe(blockEntity: SynchronizedBlockEntity) { - val iterator = blockEntities.listIterator() - - for (value in iterator) { - val ref = value.get() - - if (ref === blockEntity) { - return - } else if (ref == null) { - iterator.remove() - } - } - - val bref = WeakReference(blockEntity) - blockEntities.add(bref) - changeset++ - - onceServer { - blockEntity.synchronizeToPlayers() - } - - if (blockEntity.synchronizer.hasObservers) { - observingBlockEntities.add(bref) - val listing = tickingMap.computeIfAbsent(level.get() ?: throw NullPointerException("Level got GCd!")) { ArrayList(1) } - val tickerIterator = listing.listIterator() - - for (value in tickerIterator) { - val ref = value.get() - - if (ref === this) { - return - } else if (ref == null) { - tickerIterator.remove() - } - } - - listing.add(WeakReference(this)) - } - } - - fun unsubscribe(blockEntity: SynchronizedBlockEntity): Boolean { - val listIterator = blockEntities.listIterator() - - for (value in listIterator) { - if (value.get() === blockEntity) { - listIterator.remove() - changeset++ - break - } - } - - if (blockEntity.synchronizer.hasObservers) { - val tickerIterator = observingBlockEntities.listIterator() - - for (value in tickerIterator) { - val ref = value.get() - - if (ref === blockEntity) { - tickerIterator.remove() - break - } else if (ref == null) { - tickerIterator.remove() - } - } - } - - if (players.isNotEmpty()) { - return false - } - - cleanUpBlocks() - return blockEntities.isEmpty() - } - - val isEmpty: Boolean get() { - if (players.isNotEmpty()) { - return false - } - - cleanUpBlocks() - return blockEntities.isEmpty() - } - } - - /** - * Why track player-tracked chunks? - * - * because minecraft itself doesn't track them, well, - * in "section partitioning" way. - * - * just look at [net.minecraft.server.level.PlayerMap] - * - * the [net.minecraft.server.level.PlayerMap.getPlayers] straight ignores chunk position - * and just fetches all players - * - * even if they did not ignore that argument, you still have to fetch player *list* though - * [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want - */ - companion object { - private val playerMap = WeakHashMap>() - private val tickingMap = WeakHashMap>>() - - fun onLevelUnload(event: LevelEvent.Unload) { - val level = event.level as? ServerLevel ?: return - playerMap.remove(level) - tickingMap.remove(level) - } - - fun onServerStopping(event: ServerStoppingEvent) { - playerMap.clear() - tickingMap.clear() - } - - fun postLevelTick(event: LevelTickEvent) { - if (event.phase == TickEvent.Phase.END) { - val level = event.level as? ServerLevel ?: return - val listing = tickingMap[level] ?: return - val listIterator = listing.listIterator() - var hitAnyObservers = false - - for (value in listIterator) { - val ref = value.get() - - if (ref == null) { - listIterator.remove() - } else if (ref.players.isNotEmpty()) { - var hitObservers = false - - ref.observingBlockEntities.forValidRefs { - hitObservers = true - hitAnyObservers = true - it.synchronizeToPlayers() - } - - if (!hitObservers) { - listIterator.remove() - } - } - } - - if (!hitAnyObservers) { - tickingMap.remove(level) - } - } - } - - fun onWatch(event: ChunkWatchEvent.Watch) { - playerMap - .computeIfAbsent(event.level) { Long2ObjectOpenHashMap() } - .computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) }) - .let { - val (blocks, players) = it - - if (event.player !in players) { - players.add(event.player) - it.changeset++ - - onceServer(20) { - if (!event.player.hasDisconnected()) { - blocks.forValidRefs { - it.synchronizeToPlayers(false) - } - } - } - } - } - } - - fun onForget(event: ChunkWatchEvent.UnWatch) { - val subs = playerMap.get(event.level)?.get(event.pos.toLong()) ?: return - - if (subs.players.remove(event.player)) { - if (subs.isEmpty) { - playerMap.get(event.level)?.remove(event.pos.toLong()) - } else { - subs.changeset++ - subs.blockEntities.forValidRefs { - it.synchronizer.removeEndpointFor(event.player) - } - } - } - } - - fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) { - for (tree in playerMap.values) { - val iterator = tree.iterator() - - for (entry in iterator) { - if (entry.value.players.remove(event.entity)) { - entry.value.changeset++ - - if (entry.value.isEmpty) { - iterator.remove() - } - } - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index 9cc7d00f4..b20cbcc60 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity.blackhole +import it.unimi.dsi.fastutil.objects.ObjectArraySet import net.minecraft.client.Minecraft import net.minecraft.core.BlockPos import net.minecraft.nbt.CompoundTag @@ -10,7 +11,6 @@ import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.item.ItemEntity import net.minecraft.world.entity.player.Player -import net.minecraft.world.level.Explosion import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Blocks @@ -18,27 +18,36 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.levelgen.structure.BoundingBox import net.minecraft.world.phys.AABB import net.minecraft.world.phys.Vec3 +import net.minecraftforge.common.Tags import ru.dbotthepony.mc.otm.block.BlackHoleBlock -import ru.dbotthepony.mc.otm.block.entity.GravitationStabilizerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.SynchronizedBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue.Companion.queueForLevel -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.config.ServerConfig +import ru.dbotthepony.mc.otm.core.getExplosionResistance +import ru.dbotthepony.mc.otm.core.gracefulBlockBreak +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MItems -import ru.dbotthepony.mc.otm.registry.MRegistry -import ru.dbotthepony.mc.otm.core.getSphericalBlockPositions -import ru.dbotthepony.mc.otm.core.mapIf -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.math.getSphericalBlockPositions +import ru.dbotthepony.mc.otm.core.math.times +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.registry.MDamageTypes import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger -import java.util.LinkedList import kotlin.math.pow import kotlin.math.roundToInt import kotlin.math.sqrt -class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : SynchronizedBlockEntity(MBlockEntities.BLACK_HOLE, p_155229_, p_155230_) { - var mass by synchronizer.fraction(BASELINE_MASS, setter = setter@{ mass, field, setByRemote -> +class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.BLACK_HOLE, p_155229_, p_155230_) { + var mass by synchronizer.decimal(BASELINE_MASS, setter = setter@{ mass, field, setByRemote -> + if (setByRemote) { + field.write(mass) + return@setter + } + if (mass <= Decimal.ZERO) { collapse() return@setter @@ -46,76 +55,40 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro field.write(mass) setChanged() - var massForDiv = mass - - when (if (level?.isClientSide == false) stabilizers.size else stabilizerClientCount) { - 1 -> {massForDiv /= 4} - 2 -> {massForDiv /= 16} - 3 -> {massForDiv /= 64} - 4 -> {massForDiv /= 512} - 5 -> {massForDiv /= 2048} - 6 -> {massForDiv /= 16384} - } - - gravitationStrength = sqrt(massForDiv.div(BASELINE_MASS).toDouble()).coerceIn(0.2, 40.0) - - affectedBounds = BoundingBox( - (-30 * gravitationStrength).toInt(), - (-30 * gravitationStrength).toInt(), - (-30 * gravitationStrength).toInt(), - (30 * gravitationStrength).toInt(), - (30 * gravitationStrength).toInt(), - (30 * gravitationStrength).toInt() - ).moved( - blockPos.x, blockPos.y, blockPos.z - ) - - affectedBoundsAABB = AABB.of(affectedBounds) + updateGravStrength() }) - override fun getRenderBoundingBox(): AABB { - return AABB(blockPos.offset(-GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE), blockPos.offset(GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE)) - } - - var gravitationStrength = 1.0 + var gravitationStrength by synchronizer.double(1.0).property + private set + var affectedBounds = BoundingBox(0, 0, 0, 1, 1, 1) + private set + var affectedBoundsAABB: AABB = AABB.of(affectedBounds) private set - private var suppressUpdates = true - var spinDirection = false + var spinDirection by synchronizer.bool().property + private var sphereIterator: Iterator? = null private var sleepTicks = 4 + private val stabilizers = ObjectArraySet(6) - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) + init { + updateGravStrength() + } + + override fun setLevel(level: Level) { + super.setLevel(level) sleepTicks = 4 } - private fun updateMass() { - mass = mass - } - - private val stabilizers = LinkedList() - private var stabilizerClientCount by synchronizer.int(setter = setter@{ value, field, setByRemote -> - field.write(value) - - if (setByRemote) { - updateMass() - } - }) - fun stabilizerAttached(stabilizer: GravitationStabilizerBlockEntity) { - if (stabilizer in stabilizers) - return - - stabilizers.add(stabilizer) - mass = mass - stabilizerClientCount = stabilizers.size + if (stabilizers.add(stabilizer)) { + updateGravStrength() + } } fun stabilizerDetached(stabilizer: GravitationStabilizerBlockEntity) { if (stabilizers.remove(stabilizer)) { - mass = mass - stabilizerClientCount = stabilizers.size + updateGravStrength() } } @@ -144,82 +117,107 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro } else { level.explode( null, - MRegistry.DAMAGE_HAWKING_RADIATION, + MDamageTypes.HAWKING_RADIATION, null, blockPos.x + 0.5, blockPos.y + 0.5, blockPos.z + 0.5, gravitationStrength.toFloat() * 60, false, - Explosion.BlockInteraction.DESTROY + Level.ExplosionInteraction.BLOCK // TODO: 1.19.3 ) } } - public override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) + private fun updateGravStrength() { + var massForDiv = mass + + when (stabilizers.size) { + 0 -> {} + 1 -> massForDiv /= 4 + 2 -> massForDiv /= 16 + 3 -> massForDiv /= 64 + 4 -> massForDiv /= 512 + 5 -> massForDiv /= 2048 + else -> massForDiv /= 16384 + } + + gravitationStrength = sqrt(massForDiv.div(BASELINE_MASS).toDouble()).coerceIn(0.2, 40.0) + affectedBounds = BoundingBox( + (-30 * gravitationStrength).toInt(), + (-30 * gravitationStrength).toInt(), + (-30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt(), + (30 * gravitationStrength).toInt() + ).moved( + blockPos.x, blockPos.y, blockPos.z + ) + + affectedBoundsAABB = AABB.of(affectedBounds) + } + + override fun getRenderBoundingBox(): AABB { + return AABB(blockPos.offset(-GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE, -GravitationStabilizerBlockEntity.RANGE), blockPos.offset( + GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE, GravitationStabilizerBlockEntity.RANGE)) + } + + override fun saveLevel(nbt: CompoundTag) { + super.saveLevel(nbt) nbt["mass"] = mass.serializeNBT() nbt["spin_direction"] = spinDirection } - override fun load(tag: CompoundTag) { - super.load(tag) - mass = tag.mapIf("mass", Decimal::deserializeNBT) ?: BASELINE_MASS - spinDirection = tag.getBoolean("spin_direction") + override fun load(nbt: CompoundTag) { + super.load(nbt) + mass = nbt.map("mass", Decimal::deserializeNBT) ?: BASELINE_MASS + spinDirection = nbt.getBoolean("spin_direction") } - var affectedBounds = BoundingBox(0, 0, 0, 1, 1, 1) - private set - var affectedBoundsAABB: AABB = AABB.of(affectedBounds) - private set - - private fun setDeltaMovement(living: Entity, center: Vec3, distance: Double, weaker: Boolean) { + private fun setDeltaMovement(living: Entity, center: Vec3, distance: Double) { //final double mult = Math.min(2, (30 * this.gravitation_strength) / Math.max(1, Math.pow(distance, 2))); // Сила притяжения - val mult = Math.pow((1 - distance / (30 * gravitationStrength)).coerceAtLeast(0.0), 2.0) * gravitationStrength / 8 + var mult = (1 - distance / (30 * gravitationStrength)).coerceAtLeast(0.0).pow(3.0) * gravitationStrength / 16 + + if (living is Player) { + mult *= ServerConfig.Blackhole.PLAYER_FORCE_MULTIPLIER + } else if (living.type.`is`(Tags.EntityTypes.BOSSES)) { + mult *= ServerConfig.Blackhole.BOSS_FORCE_MULTIPLIER + } else { + mult *= ServerConfig.Blackhole.MOB_FORCE_MULTIPLIER + } // Притяжение к ядру val delta = living.position().vectorTo(center).normalize() if (distance < gravitationStrength) { - living.deltaMovement = living.deltaMovement.add(delta.multiply(mult, mult, mult)) + living.deltaMovement += delta * mult } else { // Закручивание - val rotate = if (spinDirection) delta.yRot((Math.PI / 2).toFloat()) else delta.yRot((-Math.PI / 2).toFloat()) - - if (weaker) - living.deltaMovement = living.deltaMovement - .add(rotate.multiply(mult * 0.2f, mult * 0.2f, mult * 0.2f)) - .add(delta.multiply(mult * 0.33f, mult * 0.33f, mult * 0.33f)) - else - living.deltaMovement = living.deltaMovement - .add(rotate.multiply(mult * 0.2f, mult * 0.2f, mult * 0.2f)) - .add(delta.multiply(mult, mult, mult)) + living.deltaMovement += (if (spinDirection) delta.yRot((Math.PI / 2).toFloat()) else delta.yRot((-Math.PI / 2).toFloat())) * (mult * 0.2f) + delta * mult } } fun clientTick() { - sleepTicks-- - if (sleepTicks > 0) return - val ply = Minecraft.getInstance().player!! + if (--sleepTicks > 0) return + val ply = Minecraft.getInstance().player ?: return val center = Vec3.atCenterOf(blockPos) if (!ply.abilities.mayfly && ply.getItemBySlot(EquipmentSlot.CHEST).item != MItems.PORTABLE_GRAVITATION_STABILIZER) { val distance = ply.position().distanceTo(center) - setDeltaMovement(ply, center, distance, true) + setDeltaMovement(ply, center, distance) } for (item in level!!.getEntitiesOfClass(ItemEntity::class.java, affectedBoundsAABB)) { val distance = item.position().distanceTo(center) - setDeltaMovement(item, center, distance, false) + setDeltaMovement(item, center, distance) } } - fun tick() { - sleepTicks-- - if (sleepTicks > 0) return + override fun tick() { + super.tick() + if (--sleepTicks > 0) return val level = level as? ServerLevel ?: return - suppressUpdates = false val center = Vec3.atCenterOf(blockPos) @@ -230,24 +228,33 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro val distance = living.position().distanceTo(center) if (living !is Player || !living.abilities.mayfly && living.getItemBySlot(EquipmentSlot.CHEST).item != MItems.PORTABLE_GRAVITATION_STABILIZER) { - setDeltaMovement(living, center, distance, living !is Player) + setDeltaMovement(living, center, distance) if (living is ServerPlayer) { BlackHoleTrigger.trigger(living) } } - if (distance < gravitationStrength + 1) { - living.hurt(MRegistry.DAMAGE_EVENT_HORIZON, (gravitationStrength / distance).toFloat()) + if (distance < gravitationStrength + 1.0) { + val source = MDamageTypes.EVENT_HORIZON + val damage = (gravitationStrength / distance).toFloat() + + if (living is Player) { + living.hurt(source, damage * ServerConfig.Blackhole.PLAYER_DAMAGE_SCALE.toFloat()) + } else if (living.type.`is`(Tags.EntityTypes.BOSSES)) { + living.hurt(source, damage * ServerConfig.Blackhole.BOSS_DAMAGE_SCALE.toFloat()) + } else { + living.hurt(source, damage * ServerConfig.Blackhole.MOB_DAMAGE_SCALE.toFloat()) + } } } for (item in level.getEntitiesOfClass(ItemEntity::class.java, affectedBoundsAABB)) { val distance = item.position().distanceTo(center) - setDeltaMovement(item, center, distance, false) + setDeltaMovement(item, center, distance) if (distance < gravitationStrength + 1) { - if (item.hurt(MRegistry.DAMAGE_EVENT_HORIZON, (gravitationStrength / distance).toFloat()) && item.isRemoved) { + if (item.hurt(MDamageTypes.EVENT_HORIZON, (gravitationStrength / distance).toFloat()) && item.isRemoved) { if (item.item.item === MItems.GRAVITATIONAL_DISRUPTOR) { collapse() } else { @@ -266,65 +273,41 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro this.mass += HAWKING_MASS_LOSE_STEP } - if (gravitationStrength > 0.4) { - val sphere = getSphericalBlockPositions((gravitationStrength * 12.0).roundToInt()) + if (ServerConfig.Blackhole.DESTROY_BLOCKS && gravitationStrength > 0.4) { + var sphereIterator = sphereIterator - if (sphere.size != lastSphereSizeOuter) { - lastSphereSizeOuter = sphere.size - sphereIndexOuter = 0 + if (sphereIterator == null || !sphereIterator.hasNext()) { + sphereIterator = getSphericalBlockPositions((gravitationStrength * 18.0).roundToInt()) + this.sphereIterator = sphereIterator } var iterations = 0 - while (iterations < ITERATIONS) { + while (iterations < ITERATIONS && sphereIterator.hasNext()) { iterations++ - val pos = sphere[sphereIndexOuter] + blockPos - sphereIndexOuter = (sphereIndexOuter + 1) % lastSphereSizeOuter - - if (sphereIndexOuter == 0 && lastSphereSizeOuter <= ITERATIONS) - break - + val pos = sphereIterator.next() + blockPos val getBlock = level.getBlockState(pos) if (!getBlock.isAir && getBlock.block !is BlackHoleBlock) { val speed = getBlock.getDestroySpeed(level, pos) - - val eResist = try { - getBlock.getExplosionResistance(level, pos, null) - } catch (err: NullPointerException) { - getBlock.block.explosionResistance - // Потому что возможно какой-либо мод не ожидает что Explosion == null - // особенно учитывая что интерфейс IForgeBlock не имеет @ParamsAreNonnullByDefault - // и аргумент не помечен как @Nullable - // тем самым имеет тип Explosion! который указывается как Explosion? .. Explosion!! - } catch (err: IllegalArgumentException) { - getBlock.block.explosionResistance - } - + val eResist = getBlock.getExplosionResistance(level, pos) var strengthLinear = sqrt(blockPos.distSqr(pos)) if (strengthLinear < gravitationStrength) { strengthLinear *= 1.0 + 8.0 * (gravitationStrength - strengthLinear) / gravitationStrength } - if (speed >= 0f && (speed * 2f).coerceAtLeast(eResist / 3f) < gravitationStrength * (12.0 / strengthLinear)) { - Block.dropResources(getBlock, level, pos, level.getBlockEntity(pos)) - getBlock.block.destroy(level, pos, getBlock) - level.setBlock(pos, getBlock.fluidState.createLegacyBlock(), Block.UPDATE_ALL) + val blockStrength = (speed * 4f).coerceAtLeast(eResist) + + if (speed >= 0f && blockStrength.pow(0.5f).toDouble() < gravitationStrength * (12.0 / strengthLinear)) { + level.gracefulBlockBreak(pos, getBlock) } } } } } - private var lastSphereSizeOuter = 0 - private var sphereIndexOuter = 0 - - init { - mass = mass - } - companion object { const val ITERATIONS = 30_000 val BASELINE_MASS = Decimal(50_000) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt index 0cdad6434..cb821cc50 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/ExplosionDebuggerBlockEntity.kt @@ -7,8 +7,8 @@ import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.phys.Vec3 -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.registry.MBlockEntities class BlockEntityExplosionDebugger(p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(MBlockEntities.DEBUG_EXPLOSION_SMALL, p_155229_, p_155230_) { @@ -42,7 +42,7 @@ class BlockEntitySphereDebugger(p_155229_: BlockPos, p_155230_: BlockState) : Bl for (normal in ExplosionRayHive.evenlyDistributedPoints(400)) { val multiplied = normal * 20.0 - level!!.setBlock(blockPos + BlockPos(multiplied.x, multiplied.y, multiplied.z), Blocks.COAL_BLOCK.defaultBlockState(), Block.UPDATE_ALL) + level!!.setBlock(blockPos + BlockPos(multiplied.x.toInt(), multiplied.y.toInt(), multiplied.z.toInt()), Blocks.COAL_BLOCK.defaultBlockState(), Block.UPDATE_ALL) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt index 128e2bd75..3f3b8ba2b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/Explosions.kt @@ -21,8 +21,16 @@ import net.minecraftforge.eventbus.api.SubscribeEvent import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.left +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.rotateAroundAxis +import ru.dbotthepony.mc.otm.core.math.up +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.registry.MDamageTypes import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -118,7 +126,7 @@ class ExplosionSphere(val hive: ExplosionSphereHive, var pos: Vec3, var stepVelo val block = level.getBlockState(finalPos) if (!block.isAir && block.block !is BlockExplosionDebugger) { - val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(),false, Explosion.BlockInteraction.BREAK) + val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(), false, Explosion.BlockInteraction.DESTROY_WITH_DECAY) val explosionResistance = block.getExplosionResistance(level, blockPos, explosion) if (explosionResistance > force) { @@ -258,7 +266,7 @@ class ExplosionRay(val hive: ExplosionRayHive, var pos: Vec3, var stepVelocity: val block = level.getBlockState(blockPos) if (!block.isAir && block.block !is BlockExplosionDebugger) { - val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(),false, Explosion.BlockInteraction.BREAK) + val explosion = Explosion(level, null, null, null, pos.x, pos.y, pos.z, force.toFloat(), false, Explosion.BlockInteraction.DESTROY_WITH_DECAY) val explosionResistance = block.getExplosionResistance(level, blockPos, explosion) if (explosionResistance > force) { @@ -528,14 +536,14 @@ private data class QueuedExplosion(val x: Double, val y: Double, val z: Double, fun explode(level: Level) { level.explode( null, - MRegistry.DAMAGE_HAWKING_RADIATION, + MDamageTypes.HAWKING_RADIATION, BlackHoleExplosionDamageCalculator, x, y, z, radius, false, - Explosion.BlockInteraction.DESTROY + Level.ExplosionInteraction.BLOCK // TODO: 1.19.3 ) } @@ -593,7 +601,7 @@ class ExplosionQueue(private val level: ServerLevel) : SavedData() { } fun explode(x: Double, y: Double, z: Double, radius: Float) { - if (level.isOutsideBuildHeight(BlockPos(x, y + 24, z)) || level.isOutsideBuildHeight(BlockPos(x, y - 24, z))) + if (level.isOutsideBuildHeight(BlockPos(x.toInt(), y.toInt() + 24, z.toInt())) || level.isOutsideBuildHeight(BlockPos(x.toInt(), y.toInt() - 24, z.toInt()))) return explosions.add(QueuedExplosion(x, y, z, radius)) @@ -661,8 +669,7 @@ class ExplosionQueue(private val level: ServerLevel) : SavedData() { val factory = ExplosionQueue(level) factory.load(it) factory - }, - { ExplosionQueue(level) }, + }, { ExplosionQueue(level) }, "otm_blackhole_explosion_queue" ) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt new file mode 100644 index 000000000..e9ce7b301 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt @@ -0,0 +1,148 @@ +package ru.dbotthepony.mc.otm.block.entity.cable + +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE +import ru.dbotthepony.mc.otm.block.CableBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.config.CablesConfig +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.graph.GraphNode +import ru.dbotthepony.mc.otm.onceServer +import java.util.Collections +import java.util.EnumMap + +// after some thinking, team decided to settle with IC2's side (techreborn, gregtech, integrated dynamics*, pipez*, p2p tunnels, ...) of implementation, +// where cables have no residue capacitance, and never pull/push energy by themselves + +// this allows simpler implementation and faster code, while also reducing possibility of duplication exploits + +abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(type, blockPos, blockState) { + inner class CableSide(val side: RelativeSide) : IMatteryEnergyStorage { + var isEnabled = true + set(value) { + field = value + + if (value) { + node.graph.livelyNodes.add(node) + } + } + + init { + check(side !in energySidesInternal) + energySidesInternal[side] = this + sides[side]!!.Cap(ForgeCapabilities.ENERGY, this) + } + + val neighbour = sides[side]!!.trackEnergy() + + init { + waitForServerLevel { + neighbour.addListener { + if (isEnabled) { + if (it.isPresent) { + if (it.resolve().get() !is CableSide) { + node.graph.livelyNodes.add(node) + } + } + + onceServer { + updateBlockState(blockRotation.side2Dir(side), it.isPresent || node.neighboursView[GraphNode.link(blockRotation.side2Dir(side))] != null) + } + } + } + } + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return node.graph.receiveEnergy(howMuch, simulate, node, side) + } + + override var batteryLevel: Decimal + get() = Decimal.ZERO + set(value) {} + + override val maxBatteryLevel: Decimal get() = Decimal.POSITIVE_INFINITY + override var energyFlow: FlowDirection = FlowDirection.INPUT + private set + + override val canSetBatteryLevel: Boolean + get() = false + + override val missingPower: Decimal + get() = Decimal.POSITIVE_INFINITY + } + + inner class Node : GraphNode(::EnergyCableGraph) { + val sides get() = energySides + + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + updateBlockState(link.direction, true) + } + } + + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + updateBlockState(link.direction, false) + } + } + + val blockEntity get() = this@EnergyCableBlockEntity + val canTraverse get() = energyThroughput > Decimal.ZERO + val energyThroughput get() = this@EnergyCableBlockEntity.energyThroughput + val position: BlockPos get() = this@EnergyCableBlockEntity.blockPos + } + + private fun updateBlockState(side: Direction, status: Boolean) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[side]!!, status) + + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + + // whenever this changes, graph#invalidatePathCache() MUST be called + abstract val energyThroughput: Decimal + + final override val blockRotation: BlockRotation + get() = BlockRotation.NORTH + + private val energySidesInternal = EnumMap(RelativeSide::class.java) + val energySides: Map = Collections.unmodifiableMap(energySidesInternal) + val node = Node() + + override fun setLevel(level: Level) { + super.setLevel(level) + node.discover(this, MatteryCapability.ENERGY_CABLE_NODE) + } + + override fun setRemoved() { + super.setRemoved() + node.isValid = false + } + + init { + sides.keys.forEach { CableSide(it) } + exposeGlobally(MatteryCapability.ENERGY_CABLE_NODE, node) + } +} + +class SimpleEnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState, val config: CablesConfig.E) : EnergyCableBlockEntity(type, blockPos, blockState) { + override val energyThroughput: Decimal + get() = config.throughput +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt new file mode 100644 index 000000000..8e61e22ce --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -0,0 +1,127 @@ +package ru.dbotthepony.mc.otm.block.entity.cable + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import ru.dbotthepony.mc.otm.capability.receiveEnergy +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.graph.GraphNodeList +import kotlin.math.ln + +class EnergyCableGraph : GraphNodeList() { + val livelyNodes = ObjectOpenHashSet() + + private val pathCache = Object2ObjectOpenHashMap, Decimal?>() + + private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable { + var heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.energyThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble()) + + override fun compareTo(other: SearchNode): Int { + return heuristics.compareTo(other.heuristics) + } + } + + fun invalidatePathCache() { + pathCache.clear() + } + + private fun getPath(a: EnergyCableBlockEntity.Node, b: EnergyCableBlockEntity.Node): Decimal? { + if (!a.canTraverse || !b.canTraverse) + return null + + val key = a to b + + if (key in pathCache) + return pathCache[key] + + // no free paths available, try to find extra one + // while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search), + // on small cable networks simple flooding will do just fine, if we consider overloaded cables as closed flood gates + val openNodes = ArrayList() + val seenNodes = ObjectOpenHashSet() + + openNodes.add(SearchNode(a, b)) + + while (openNodes.isNotEmpty()) { + val first = openNodes.min() + openNodes.remove(first) + + if (first.node === b) { + // solution found + val solution = ArrayList() + + var last = first.parent + solution.add(first.node) + + while (last != null) { + solution.add(last.node) + last = last.parent + } + + val calc = solution.minOf { it.energyThroughput } + pathCache[key] = calc + return calc + } else { + for (neighbour in first.node.neighboursView.values) { + if (!seenNodes.add(neighbour) || !neighbour.canTraverse) continue + openNodes.add(SearchNode(neighbour, b, first)) + } + } + } + + // solution does not exist + pathCache[key] = null + return null + } + + override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) { + livelyNodes.remove(node) + invalidatePathCache() + } + + override fun onNodeAdded(node: EnergyCableBlockEntity.Node) { + livelyNodes.add(node) + invalidatePathCache() + } + + fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal { + val itr = livelyNodes.iterator() + var received = Decimal.ZERO + var residue = howMuch.coerceAtMost(fromNode.energyThroughput) + + for (node in itr) { + var hit = false + + for (side in node.sides.values) { + if (side.isEnabled) { + if (fromNode === node && side.side === fromSide) { + hit = true + continue + } + + side.neighbour.get().ifPresentK { + if (it !is EnergyCableBlockEntity.CableSide) { + val limit = getPath(fromNode, node) + hit = true + + if (limit != null) { + val thisReceived = it.receiveEnergy(residue.coerceAtMost(limit), simulate) + received += thisReceived + residue -= thisReceived + if (!residue.isPositive) return received + } + } + } + } + } + + if (!hit) { + itr.remove() + } + } + + return received + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/CargoCrateBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt similarity index 67% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/CargoCrateBlockEntity.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt index 236c50821..921d991b8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/CargoCrateBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt @@ -1,8 +1,7 @@ -package ru.dbotthepony.mc.otm.block.entity +package ru.dbotthepony.mc.otm.block.entity.decorative import net.minecraft.advancements.CriteriaTriggers import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.LongTag import net.minecraft.nbt.StringTag @@ -11,7 +10,6 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer import net.minecraft.sounds.SoundSource -import net.minecraft.world.Container import net.minecraft.world.Containers import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player @@ -25,27 +23,27 @@ import net.minecraft.world.level.storage.loot.LootContext import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.phys.Vec3 -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.block.CargoCrateBlock -import ru.dbotthepony.mc.otm.block.IDroppableContainer +import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks +import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.menu.CargoCrateMenu +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MSoundEvents class CargoCrateBlockEntity( p_155229_: BlockPos, p_155230_: BlockState -) : MatteryBlockEntity(MBlockEntities.CARGO_CRATE, p_155229_, p_155230_), IDroppableContainer { - val container = MatteryContainer(this::setChanged, CAPACITY) +) : MatteryDeviceBlockEntity(MBlockEntities.CARGO_CRATE, p_155229_, p_155230_) { + val container = MatteryContainer(this::setChanged, CAPACITY).also(::addDroppableContainer) + private var interactingPlayers = 0 - val handler = container.handler(object : MatteryContainerHooks { + + val handler = container.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { return lootTable == null } @@ -55,13 +53,7 @@ class CargoCrateBlockEntity( } }) - override fun beforeDroppingItems( - oldBlockState: BlockState, - level: Level, - blockPos: BlockPos, - newBlockState: BlockState, - movedByPiston: Boolean - ) { + override fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) { unpackLootTable() } @@ -72,13 +64,10 @@ class CargoCrateBlockEntity( var lootTable: ResourceLocation? = null var lootTableSeed: Long? = null - override val droppableContainer: Container - get() = container - fun onPlayerOpen() { val level = level - if (interactingPlayers++ == 0 && level != null) { + if (interactingPlayers++ == 0 && level != null && !isRemoved && level.getBlockState(blockPos).block is CargoCrateBlock) { level.setBlock(blockPos, blockState.setValue(CargoCrateBlock.IS_OPEN, true), Block.UPDATE_CLIENTS) level.playSound(null, blockPos, MSoundEvents.CARGO_CRATE_OPEN, SoundSource.BLOCKS, 1f, 0.8f + level.random.nextFloat() * 0.2f) level.gameEvent(GameEvent.CONTAINER_OPEN, blockPos, GameEvent.Context.of(blockState)) @@ -88,41 +77,25 @@ class CargoCrateBlockEntity( fun onPlayerClose() { val level = level - if (--interactingPlayers == 0 && level != null) { + if (--interactingPlayers == 0 && level != null && !isRemoved && level.getBlockState(blockPos).block is CargoCrateBlock) { level.setBlock(blockPos, blockState.setValue(CargoCrateBlock.IS_OPEN, false), Block.UPDATE_CLIENTS) level.gameEvent(GameEvent.CONTAINER_CLOSE, blockPos, GameEvent.Context.of(blockState)) } } - override fun invalidateCaps() { - super.invalidateCaps() - handler.invalidate() + init { + exposeGlobally(ForgeCapabilities.ITEM_HANDLER, handler) + savetablesLevel.stateful(::container, INVENTORY_KEY) } - override fun reviveCaps() { - super.reviveCaps() - handler.revive() - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (cap == ForgeCapabilities.ITEM_HANDLER) - return handler.get().cast() - - return super.getCapability(cap, side) - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - + override fun saveLevel(nbt: CompoundTag) { + super.saveLevel(nbt) lootTable?.let { nbt[LOOT_TABLE_KEY] = it.toString() } lootTableSeed?.let { nbt[LOOT_TABLE_SEED_KEY] = it } } override fun load(nbt: CompoundTag) { super.load(nbt) - container.deserializeNBT(nbt[INVENTORY_KEY]) - lootTable = nbt.map(LOOT_TABLE_KEY) { it: StringTag -> ResourceLocation.tryParse(it.asString) } lootTableSeed = (nbt[LOOT_TABLE_SEED_KEY] as LongTag?)?.asLong } @@ -141,16 +114,16 @@ class CargoCrateBlockEntity( this.lootTable = null this.lootTableSeed = null - val context = LootContext.Builder(level as ServerLevel) + val params = LootContext.Builder(level as ServerLevel) .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(this.worldPosition)) - .withOptionalRandomSeed(lootTableSeed) if (ply != null) { - context.withLuck(ply.luck).withParameter(LootContextParams.THIS_ENTITY, ply) + params.withLuck(ply.luck).withParameter(LootContextParams.THIS_ENTITY, ply) } + params.withOptionalRandomSeed(lootTableSeed) Containers.dropContents(level as ServerLevel, blockPos, container) - loot.fill(container, context.create(LootContextParamSets.CHEST)) + loot.fill(container, params.create(LootContextParamSets.CHEST)) } override val defaultDisplayName: Component diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt new file mode 100644 index 000000000..16b68cb47 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/DevChestBlockEntity.kt @@ -0,0 +1,67 @@ +package ru.dbotthepony.mc.otm.block.entity.decorative + +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap +import net.minecraft.core.BlockPos +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.items.IItemHandler +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.IdMappingEvent +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.core.getID +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class DevChestBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.DEV_CHEST, blockPos, blockState), IItemHandler { + override fun getSlots(): Int { + return cache().size + } + + override fun getStackInSlot(slot: Int): ItemStack { + return cache().getOrNull(slot) ?: ItemStack.EMPTY + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return stack + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return getStackInSlot(slot).copyWithCount(amount) + } + + override fun getSlotLimit(slot: Int): Int { + return getStackInSlot(slot).maxStackSize + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return false + } + + init { + exposeGlobally(ForgeCapabilities.ITEM_HANDLER, this) + } + + companion object { + private val cache = ArrayList() + + @Suppress("SameReturnValue") + private fun cache(): List { + if (cache.isNotEmpty()) { + return cache + } + + val sorted = Int2ObjectAVLTreeMap() + + for (item in ForgeRegistries.ITEMS.values) { + check(sorted.put(ForgeRegistries.ITEMS.getID(item), ItemStack(item, 1).also { it.count = item.getMaxStackSize(it) }) == null) + } + + cache.addAll(sorted.values) + return cache + } + + fun mappingsChanged(event: IdMappingEvent) { + cache.clear() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt new file mode 100644 index 000000000..c3e321746 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt @@ -0,0 +1,200 @@ +package ru.dbotthepony.mc.otm.block.entity.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler +import ru.dbotthepony.mc.otm.capability.moveFluid +import ru.dbotthepony.mc.otm.config.ItemsConfig +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec +import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.FLUID_TANK, blockPos, blockState) { + val fluid = BlockMatteryFluidHandler(::onChanged, ItemsConfig::FLUID_TANK_CAPACITY) + var synchronizedFluid by synchronizer.Field(FluidStack.EMPTY, FluidStackValueCodec, setter = { value, access, remote -> + access.write(value) + + level?.lightEngine?.checkBlock(blockPos) + }) + + val fillInput = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val drainInput = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val output = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + + val itemConfig = ConfigurableItemHandler( + input = CombinedItemHandler( + drainInput.handler(HandlerFilter.DrainableFluidContainers), + fillInput.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + if (fluid.isEmpty) { + return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.tanks > 0 }.orElse(false) + } + + return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0 }.orElse(false) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return !canInsert(slot, stack) + } + }) + ), + output = output.handler(HandlerFilter.OnlyOut), + frontDefault = ItemHandlerMode.INPUT_OUTPUT, + backDefault = ItemHandlerMode.INPUT_OUTPUT, + leftDefault = ItemHandlerMode.INPUT_OUTPUT, + rightDefault = ItemHandlerMode.INPUT_OUTPUT, + topDefault = ItemHandlerMode.INPUT_OUTPUT, + bottomDefault = ItemHandlerMode.INPUT_OUTPUT, + ) + + val fluidConfig = ConfigurableFluidHandler(fluid) + + init { + savetables.stateful(::fluid, FLUID_KEY) + savetables.stateful(::fillInput) + savetables.stateful(::drainInput) + savetables.stateful(::output) + } + + private fun onChanged(new: FluidStack, old: FluidStack) { + synchronizedFluid = new.copy() + markDirtyFast() + } + + private fun drainItem() { + val item = drainInput[0] + + if (item.isNotEmpty) { + val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() + + if (cap == null) { + if (output.consumeItem(item, simulate = false)) { + drainInput.setChanged(0) + } + + return + } + + if (fluid.isNotFull) { + if (item.count == 1) { + val moved0 = moveFluid(source = cap, destination = fluid) + + if (moved0.isNotEmpty) { + drainInput[0] = cap.container + + if (output.consumeItem(drainInput[0], simulate = false)) { + drainInput.setChanged(0) + } + } + } else { + val moved0 = moveFluid(source = cap, destination = fluid, actuallyFill = false) + + if (moved0.isNotEmpty) { + if (output.consumeItem(cap.container, simulate = true)) { + val cap1 = item.copyWithCount(1).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: throw ConcurrentModificationException() + + val moved1 = moveFluid(source = cap1, destination = fluid) + + if (moved1 != moved0 || moved1.amount != moved0.amount) { + LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from ${cap.container}, moved $moved1 from ${cap1.container} during execution. This is likely a bug in OTM or other mod!") + } else { + item.count-- + drainInput.setChanged(0) + + if (!output.consumeItem(cap1.container, simulate = false)) { + LOGGER.error("Unable to insert ${cap1.container} into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!") + (level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), cap1.container)) + } + } + } + } + } + } + } + } + + private fun fillItem() { + val item = fillInput[0] + + if (item.isNotEmpty) { + val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() + + if (cap == null) { + if (output.consumeItem(item, simulate = false)) { + fillInput.setChanged(0) + } + + return + } + + if (fluid.isNotEmpty) { + if (item.count == 1) { + val moved0 = moveFluid(source = fluid, destination = cap) + + if (moved0.isNotEmpty) { + fillInput[0] = cap.container + + if (output.consumeItem(fillInput[0], simulate = false)) { + fillInput.setChanged(0) + } + } + } else { + val moved0 = moveFluid(source = fluid, destination = cap, actuallyDrain = false) + + if (moved0.isNotEmpty) { + if (output.consumeItem(cap.container, simulate = true)) { + val cap1 = item.copyWithCount(1).getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: throw ConcurrentModificationException() + + val moved1 = moveFluid(source = fluid, destination = cap1) + + if (moved1 != moved0 || moved1.amount != moved0.amount) { + LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from ${cap.container}, moved $moved1 from ${cap1.container} during execution. This is likely a bug in OTM or other mod!") + } else { + item.count-- + fillInput.setChanged(0) + + if (!output.consumeItem(cap1.container, simulate = false)) { + LOGGER.error("Unable to insert ${cap1.container} into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!") + (level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), cap1.container)) + } + } + } + } + } + } + } + } + + override fun tick() { + super.tick() + + drainItem() + fillItem() + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return FluidTankMenu(containerID, inventory, this) + } + + companion object { + const val FLUID_KEY = "fluid" + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt new file mode 100644 index 000000000..3e2884aa4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/HoloSignBlockEntity.kt @@ -0,0 +1,81 @@ +package ru.dbotthepony.mc.otm.block.entity.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.MenuProvider +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.block.entity.IRedstoneControlled +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.block.entity.SynchronizedRedstoneControl +import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu +import ru.dbotthepony.mc.otm.once +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MBlocks + +class HoloSignBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.HOLO_SIGN, blockPos, blockState), MenuProvider, IRedstoneControlled { + override val redstoneControl = SynchronizedRedstoneControl(synchronizer) { _, _ -> setChanged() } + + var signText by synchronizer.string("", setter = { value, access, remote -> + setChanged() + access.write(value) + }) + + var isLocked = false + + init { + savetables.string(::signText) + savetablesLevel.bool(::isLocked) + savetables.stateful(::redstoneControl) + } + + override fun createMenu(p_39954_: Int, p_39955_: Inventory, p_39956_: Player): AbstractContainerMenu { + return HoloSignMenu(p_39954_, p_39955_, this) + } + + override fun getDisplayName(): Component { + return MBlocks.HOLO_SIGN.name + } + + override fun setLevel(level: Level) { + super.setLevel(level) + + level.once { + if (!isRemoved && this.level == level) { + redstoneControl.redstoneSignal = level.getBestNeighborSignal(blockPos) + } + } + } + + override fun load(nbt: CompoundTag) { + super.load(nbt) + + if (!isLocked) + signText = truncate(signText) + } + + companion object { + const val DEFAULT_MAX_NEWLINES = 8 + const val DEFAULT_MAX_LINE_LENGTH = 15 + private val NEWLINES = Regex("\r?\n") + + fun truncate(input: String): String { + val lines = input.split(NEWLINES) + val result = ArrayList(lines.size.coerceAtMost(DEFAULT_MAX_NEWLINES)) + + for (i in 0 until lines.size.coerceAtMost(DEFAULT_MAX_NEWLINES)) { + if (lines[i].length > DEFAULT_MAX_LINE_LENGTH) { + result.add(lines[i].substring(0, DEFAULT_MAX_LINE_LENGTH)) + } else { + result.add(lines[i]) + } + } + + return result.joinToString("\n") + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt new file mode 100644 index 000000000..7a5e54219 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/InfiniteWaterSourceBlockEntity.kt @@ -0,0 +1,61 @@ +package ru.dbotthepony.mc.otm.block.entity.decorative + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Fluids +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class InfiniteWaterSourceBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.INFINITE_WATER_SOURCE, blockPos, blockState), IFluidHandler { + override fun getTanks(): Int { + return 1 + } + + override fun getFluidInTank(tank: Int): FluidStack { + return FluidStack(Fluids.WATER, Int.MAX_VALUE) + } + + override fun getTankCapacity(tank: Int): Int { + return Int.MAX_VALUE + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + return false + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + return 0 + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (resource.fluid == Fluids.WATER || resource.fluid == Fluids.FLOWING_WATER) { + return resource.copy() + } + + return FluidStack.EMPTY + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + return FluidStack(Fluids.WATER, maxDrain) + } + + init { + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, this) + + for (side in sides.values) { + val tracker = side.track(ForgeCapabilities.FLUID_HANDLER) + + val ticker = tickList.Ticker { + tracker.get().ifPresentK { + it.fill(FluidStack(Fluids.WATER, Int.MAX_VALUE), IFluidHandler.FluidAction.EXECUTE) + } + } + + tracker.addListener { ticker.isEnabled = it.isPresent } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt new file mode 100644 index 000000000..7a65e1450 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt @@ -0,0 +1,285 @@ +package ru.dbotthepony.mc.otm.block.entity.decorative + +import com.google.common.collect.ImmutableList +import com.mojang.datafixers.util.Either +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import net.minecraft.core.BlockPos +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Fluids +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.mapPresent +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import java.util.* + +class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.PAINTER, blockPos, blockState), IFluidHandler { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return PainterMenu(containerID, inventory, this) + } + + val dyeInput = MatteryContainer(this::markDirtyFast, 1) + // null - water + val dyeStored = Object2IntArrayMap() + + var isBulk = false + set(value) { + field = value + markDirtyFast() + } + + override fun getTanks(): Int { + return 1 + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (tank != 1) return FluidStack.EMPTY + if (waterStored() == 0) return FluidStack.EMPTY + return FluidStack(Fluids.WATER, waterStored()) + } + + override fun getTankCapacity(tank: Int): Int { + if (tank == 1) return MAX_WATER_STORAGE + return 0 + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + return tank == 0 && stack.fluid == Fluids.WATER + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (resource.isEmpty || resource.fluid != Fluids.WATER || waterStored() >= MAX_WATER_STORAGE) return 0 + val diff = (waterStored() + resource.amount).coerceAtMost(MAX_WATER_STORAGE) - waterStored() + if (action.simulate() || diff == 0) return diff + dyeStored[null] = waterStored() + diff + return diff + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + return FluidStack.EMPTY + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + return FluidStack.EMPTY + } + + init { + addDroppableContainer(dyeInput) + savetables.stateful(dyeInput, INVENTORY_KEY) + savetables.bool(::isBulk) + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, this) + } + + fun takeDyes(dyes: Map) { + for ((dye, amount) in dyes) { + if (dye == null) { + for (i in 0 until amount) { + if (waterStored() >= 1) { + dyeStored[dye] = waterStored() - 1 + } + } + } else { + for (i in 0 until amount) { + mixer(dye)?.mix(dyeStored) + + if (dyeStored(dye) > 0) { + dyeStored[dye] = dyeStored(dye) - 1 + } + } + } + } + + markDirtyFast() + } + + val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + if (waterStored() < MAX_WATER_STORAGE) { + stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.SIMULATE) + + if (drain.isNotEmpty) { + return true + } + } + } + + val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return false + return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE + } + + override fun modifyInsertCount(slot: Int, stack: ItemStack, existing: ItemStack, simulate: Boolean): Int { + if (!stack.equals(existing, false)) + return super.modifyInsertCount(slot, stack, existing, simulate) + + val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return 0 + return stack.count.coerceAtMost((MAX_STORAGE - dyeStored(dye)) / HUE_PER_ITEM - existing.count) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return false + } + })) + + fun waterStored(): Int { + return dyeStored.getInt(null) + } + + fun dyeStored(dye: DyeColor?): Int { + return dyeStored.getInt(dye) + } + + override fun saveShared(nbt: CompoundTag) { + super.saveShared(nbt) + + nbt["dyes"] = CompoundTag().also { + for ((k, v) in dyeStored) { + it[k?.getName() ?: "water"] = v + } + } + } + + override fun load(nbt: CompoundTag) { + super.load(nbt) + + dyeStored.clear() + + nbt.mapPresent("dyes") { it: CompoundTag -> + for (k in it.allKeys) { + if (k == "water") + dyeStored[null] = it.getInt("water") + else + dyeStored[DyeColor.entries.firstOrNull { it.getName() == k } ?: continue] = it.getInt(k) + } + } + } + + override fun tick() { + super.tick() + + for (slot in dyeInput.slotIterator()) { + if (waterStored() < MAX_WATER_STORAGE) { + slot.item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.EXECUTE) + + if (drain.isNotEmpty) { + dyeStored[null] = waterStored() + drain.amount + slot.item = it.container + } + } + } + + val dye = DyeColor.entries.firstOrNull { slot.item.`is`(it.tag) } ?: continue + val stored = dyeStored(dye) + + if (stored + HUE_PER_ITEM <= MAX_STORAGE) { + slot.item.shrink(1) + slot.setChanged() + dyeStored[dye] = stored + HUE_PER_ITEM + } + } + } + + class Mixer(val color: DyeColor, vararg mixing: ImmutableList>) : Map.Entry { + val mixing: ImmutableList>> = ImmutableList.copyOf(mixing) + + override val key: DyeColor + get() = color + override val value: Mixer + get() = this + + private fun mix(input: Object2IntMap, seen: MutableSet, stack: MutableSet) { + if (input.getInt(color) > 0 || color in seen || color in stack) return + stack.add(color) + + for (ingredients in mixing) { + val copy = Object2IntArrayMap(input) + var i = 0 + val volumes = Int2IntArrayMap() + + // страховка от взаимной блокировки когда последующий ингредиент требует предыдущий. + // создаёт горку из значений + for (i2 in ingredients.indices) + volumes[i2] = 1 + + while (!ingredients.all { copy.getInt(it.orElse(null)) > 0 } && i++ < 20) { + ingredients.withIndex().forEach { + for (t in 0 until volumes[it.index]) + it.value.ifPresent { mixer(it)?.mix(copy, seen, stack) } + } + + var increase = i + val iter = ingredients.indices.iterator() + + while (iter.hasNext() && increase-- > 0) { + volumes[iter.nextInt()]++ + } + } + + if (ingredients.all { copy.getInt(it.orElse(null)) > 0 }) { + ingredients.forEach { copy[it.orElse(null)] = copy.getInt(it.orElse(null)) - 1 } + copy[color] = ingredients.size + + input.putAll(copy) + stack.remove(color) + return + } + } + + stack.remove(color) + seen.add(color) + } + + fun mix(input: Object2IntMap) { + mix(input, EnumSet.noneOf(DyeColor::class.java), EnumSet.noneOf(DyeColor::class.java)) + } + + fun isAvailable(input: Object2IntMap): Boolean { + if (input.getInt(color) > 0) return true + return Object2IntArrayMap(input).also(::mix).getInt(color) > 0 + } + } + + companion object { + fun mixer(color: DyeColor): Mixer? { + return MIXING[color] + } + + val MIXING = immutableMap { + put(Mixer(DyeColor.BLACK, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.RED, immutableList(Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.GREEN, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.BLUE, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA)))) + put(Mixer(DyeColor.LIGHT_BLUE, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLUE)))) + put(Mixer(DyeColor.LIME, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.GREEN)))) + put(Mixer(DyeColor.ORANGE, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.RED), Optional.empty()))) + put(Mixer(DyeColor.PURPLE, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA), Optional.empty()))) + put(Mixer(DyeColor.BROWN, immutableList(Optional.of(DyeColor.YELLOW), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.BLACK), Optional.empty()))) + put(Mixer(DyeColor.PINK, immutableList(Optional.of(DyeColor.YELLOW), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.WHITE), Optional.empty()))) + put(Mixer(DyeColor.GRAY, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLACK), Optional.empty()))) + put(Mixer(DyeColor.LIGHT_GRAY, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLACK), Optional.empty()))) + } + + const val MAX_STORAGE = 256 + const val HUE_PER_ITEM = 32 + const val MAX_WATER_STORAGE = 4_000 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt index dbe384158..412486113 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt @@ -1,11 +1,7 @@ package ru.dbotthepony.mc.otm.block.entity.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -13,62 +9,146 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.block.IDroppableContainer import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler -import ru.dbotthepony.mc.otm.capability.matter.MatterDirection -import ru.dbotthepony.mc.otm.capability.matter.MatterHandlerImpl +import ru.dbotthepony.mc.otm.capability.item.ProxiedItemHandler +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.menu.MatterBottlerMenu +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode -class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryPoweredBlockEntity(MBlockEntities.MATTER_BOTTLER, p_155229_, p_155230_), IMatterGraphNode, IDroppableContainer { +class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : + MatteryPoweredBlockEntity(MBlockEntities.MATTER_BOTTLER, blockPos, blockState) { - override val matterNode = Graph6Node(this) - private val resolverNode = LazyOptional.of { this } - override val energy = WorkerEnergyStorage(this, ENERGY_VALUES) + val upgrades = UpgradeContainer(::markDirtyFast, 3, UpgradeType.BASIC_MATTER) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, upgrades.transform(MachinesConfig.MatterBottler.VALUES))) + val energyConfig = ConfigurableEnergy(energy) + + private inner class Container : MatteryContainer(3) { + init { + addDroppableContainer(this) + } + + override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return 1 + } + + override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { + markDirtyFast() + updateBlockState() + } + } + + val unbottling: MatteryContainer = Container() + val bottling: MatteryContainer = Container() var isBottling: Boolean = true set(value) { field = value - this.setChangedLight() + initialCapacity = null + workProgress = 0f + this.markDirtyFast() + + if (value) { + inputHandler.parent = bottlingHandler + outputHandler.parent = unbottlingHandler + } else { + inputHandler.parent = unbottlingHandler + outputHandler.parent = bottlingHandler + } + + updateBlockState() } - fun switchWorkFlow() { - isBottling = !isBottling - updateBlockState() + var spitItemsWhenCantWork = false + set(value) { + field = value + this.markDirtyFast() + } + + val bottlingHandler = bottling.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.input && it.missingMatter.isPositive }.orElse(false) + } + }) + + val unbottlingHandler = unbottling.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return !isBottling && stack.getCapability(MatteryCapability.MATTER).map { it.matterFlow.output && it.storedMatter.isPositive }.orElse(false) + } + }) + + private val inputHandler = ProxiedItemHandler(bottlingHandler) + private val outputHandler = ProxiedItemHandler(unbottlingHandler) + + val itemConfig = ConfigurableItemHandler( + input = inputHandler, + output = outputHandler, + battery = batteryItemHandler + ) + + val matter: ProfiledMatterStorage = ProfiledMatterStorage(object : MatterStorageImpl(this::markDirtyFast, FlowDirection.BI_DIRECTIONAL, upgrades.matterCapacity(MachinesConfig.MatterBottler.VALUES::matterCapacity)) { + override val matterFlow: FlowDirection get() { + return if (this@MatterBottlerBlockEntity.isBottling) FlowDirection.INPUT else FlowDirection.OUTPUT + } + }) + + val matterNode = SimpleMatterNode(matter = matter) + + init { + exposeGlobally(MatteryCapability.MATTER, matter) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + + savetables.bool(::isBottling) + savetables.bool(::spitItemsWhenCantWork) + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::matter, MATTER_STORAGE_KEY) + savetables.stateful(::bottling) + savetables.stateful(::unbottling) + savetables.stateful(::upgrades) } - private fun updateBlockState() { + var workProgress: Float = 0f + private set + + private var initialCapacity: Decimal? = null + + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return MatterBottlerMenu(containerID, inventory, this) + } + + override fun setRemoved() { + super.setRemoved() + matterNode.isValid = false + } + + private fun updateBlockState(container: MatteryContainer) { val level = level as? ServerLevel ?: return - var state = blockState - val initial = if (isBottling) 0 else 3 - for (i in initial .. initial + 2) { + for (i in 0 .. 2) { val desired = !container.getItem(i).isEmpty && container.getItem(i).getCapability(MatteryCapability.MATTER).isPresent - if (state.getValue(MatterBottlerBlock.SLOT_PROPERTIES[i - initial]) != desired) { - state = state.setValue(MatterBottlerBlock.SLOT_PROPERTIES[i - initial], desired) + if (state.getValue(MatterBottlerBlock.SLOT_PROPERTIES[i]) != desired) { + state = state.setValue(MatterBottlerBlock.SLOT_PROPERTIES[i], desired) } } @@ -77,295 +157,133 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } } - // TODO: оно должно что то делать - // true - continue even if empty when bottling / if full while unbottling - // false - spit into output slot - //private var work_behavior = true + private fun updateBlockState() { + if (isBottling) + updateBlockState(bottling) + else + updateBlockState(unbottling) + } - val container: MatteryContainer = object : MatteryContainer(this::setChangedLight, 6) { - override fun getMaxStackSize(slot: Int): Int { - return 1 - } - - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - updateBlockState() + private fun blockstateToWorking() { + if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.WORKING) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) } } - override val droppableContainer: Container - get() = container - - val itemHandler = container.handler(object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - if (isBottling) { - return slot < 3 && stack.getCapability(MatteryCapability.MATTER).isPresent - } - - return slot >= 3 && stack.getCapability(MatteryCapability.MATTER).isPresent - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - if (isBottling) { - return slot >= 3 - } - - return slot < 3 - } - }) - - val matter: MatterHandlerImpl = object : MatterHandlerImpl(this::setChangedLight, MatterDirection.BIDIRECTIONAL, ::CAPACITY) { - override val direction: MatterDirection get() { - return if (this@MatterBottlerBlockEntity.isBottling) MatterDirection.RECEIVE else MatterDirection.EXTRACT + private fun blockstateToIdle() { + if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) } } - private var initialCapacity: Decimal? = null - private var lastWorkStack: ItemStack? = null - - override fun getMatterHandler(): IMatterHandler { - return matter - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) - } - - private var valid = true - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - itemHandler.invalidate() - matter.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - itemHandler.revive() - matter.revive() - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == MatteryCapability.MATTER) { - return matter.get().cast() - } - - if (cap == ForgeCapabilities.ITEM_HANDLER) { - return itemHandler.get().cast() - } - - if (cap == MatteryCapability.MATTER_NODE) { - return resolverNode.cast() - } - } - - return super.getCapability(cap, side) - } - - override val defaultDisplayName: Component - get() = MBlocks.MATTER_BOTTLER.name - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return MatterBottlerMenu(containerID, inventory, this) - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - nbt[IS_BOTTLING_KEY] = isBottling - nbt[MATTER_STORAGE_KEY] = matter.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - isBottling = nbt.getBoolean(IS_BOTTLING_KEY) - nbt.map(MATTER_STORAGE_KEY, matter::deserializeNBT) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } - - fun getWorkProgress(): Float { - val lastWorkStack = lastWorkStack ?: return 0f - val initialCapacity = initialCapacity ?: return 0f - val cap = lastWorkStack.getCapability(MatteryCapability.MATTER).orNull() ?: return 0f - - if (this.isBottling) { - if (cap.maxStoredMatter - initialCapacity <= Decimal.ZERO) { - return 0f - } - - return ((cap.storedMatter - initialCapacity) / (cap.maxStoredMatter - initialCapacity)).toFloat() - } - - if (initialCapacity <= Decimal.ZERO) { - return 0f - } - - return (Decimal.ONE - cap.storedMatter / initialCapacity).toFloat() - } - - override fun setRemoved() { - super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) - } - - fun tick() { - batteryChargeLoop() - - if (isBlockedByRedstone) { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) { - level?.setBlock( - blockPos, - blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), - Block.UPDATE_CLIENTS - ) - } + override fun tick() { + super.tick() + if (redstoneControl.isBlockedByRedstone) { + blockstateToIdle() return } - var work_stack: ItemStack? = null - var capability: IMatterHandler? = null - val align = if (isBottling) 0 else 3 - var work_slot = -1 - val unexpectedDirection = if (isBottling) MatterDirection.EXTRACT else MatterDirection.RECEIVE + if (isBottling) { + var any = false + var idle = false - for (i in align until align + 3) { - val itemStack = container.getItem(i) + for (slot in bottling.slotIterator()) { + val item = slot.item + item.getCapability(MatteryCapability.MATTER).ifPresentK { + if (!it.missingMatter.isPositive) { + unbottling.consumeItem(item, false) + slot.setChanged() + } else { + any = true + initialCapacity = initialCapacity ?: it.storedMatter + val rate = MachinesConfig.MatterBottler.RATE * (1.0 + upgrades.speedBonus) - if (!itemStack.isEmpty) { - val cap = itemStack.getCapability(MatteryCapability.MATTER).orNull() ?: continue + if (matter.storedMatter < rate) { + matter.receiveMatter(matterNode.graph.extractMatter(matter.missingMatter + .coerceAtMost(rate * 200) + .coerceAtMost(it.missingMatter - matter.storedMatter), false), false) + } - if (cap.direction !== unexpectedDirection) { - if (this.isBottling && cap.missingMatter > Decimal.ZERO || !this.isBottling && cap.storedMatter > Decimal.ZERO) { - work_stack = itemStack - capability = cap - work_slot = i - break - } - } - } - } + if (matter.storedMatter.isPositive) { + matter.extractMatter(it.receiveMatter(rate.coerceAtMost(matter.storedMatter), false), false) - if (work_stack == null) { - lastWorkStack = null - initialCapacity = null - } else if (work_stack != lastWorkStack) { - lastWorkStack = work_stack - initialCapacity = capability!!.storedMatter - } + if (!it.missingMatter.isPositive) { + initialCapacity = null + workProgress = 0f + } else { + workProgress = ((it.storedMatter - initialCapacity!!) / it.maxStoredMatter).toFloat() + } + } else { + idle = true - val graph = matterNode.graph as MatterNetworkGraph? - - if (capability != null) { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.WORKING) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) - } - - if (isBottling) { - if (matter.storedMatter < MATTER_EXCHANGE_RATE && graph != null) { - val extracted = graph.extractMatter( - matter.missingMatter.coerceAtMost(MATTER_EXCHANGE_RATE * EXTRACTION_TICKS).coerceAtMost(capability.missingMatter - matter.storedMatter), true - ) - - if (extracted > Decimal.ZERO) { - val received = matter.receiveMatterOuter(extracted, false) - graph.extractMatter(received, false) - } - } - - if (matter.storedMatter > Decimal.ZERO) { - val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true) - - if (!energyExtracted.isZero) { - val matter = capability.receiveMatterOuter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.storedMatter) * energyExtracted / ENERGY_CONSUMPTION, true) - - if (!matter.isZero) { - energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE, false) - - capability.receiveMatterOuter(matter, false) - this.matter.extractMatterInner(matter, false) - - if (capability.missingMatter.isZero) { - for (i in 3..5) { - if (container.getItem(i).isEmpty) { - container.setItem(work_slot, ItemStack.EMPTY) - container.setItem(i, work_stack!!) - break - } - } + if (spitItemsWhenCantWork) { + unbottling.consumeItem(item, false) + slot.setChanged() } } } } + + if (any) { + break + } + } + + if (any && !idle) { + blockstateToWorking() } else { - val energyExtracted = energy.extractEnergyInner(ENERGY_CONSUMPTION, true) - - if (!energyExtracted.isZero) { - val matter = capability.extractMatterOuter(MATTER_EXCHANGE_RATE.coerceAtMost(matter.missingMatter) * energyExtracted / ENERGY_CONSUMPTION, true) - - if (!matter.isZero) { - this.energy.extractEnergyInner(ENERGY_CONSUMPTION * matter / MATTER_EXCHANGE_RATE,false) - - capability.extractMatterOuter(matter, false) - this.matter.receiveMatterInner(matter, false) - - if (capability.storedMatter.isZero) { - for (i in 2 downTo 0) { - if (container.getItem(i).isEmpty) { - container.setItem(work_slot, ItemStack.EMPTY) - container.setItem(i, work_stack!!) - break - } - } - } - } - } + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + blockstateToIdle() } } else { - level!!.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + + if (!matter.missingMatter.isPositive) { + if (spitItemsWhenCantWork) { + for (slot in unbottling.slotIterator()) { + bottling.consumeItem(slot.item, false) + slot.setChanged() + } + } + + blockstateToIdle() + return + } + + var any = false + + for (slot in unbottling.slotIterator()) { + val item = slot.item + + item.getCapability(MatteryCapability.MATTER).ifPresentK { + if (!it.storedMatter.isPositive) { + bottling.consumeItem(item, false) + slot.setChanged() + } else { + any = true + initialCapacity = initialCapacity ?: it.storedMatter + matter.receiveMatter(it.extractMatter(MachinesConfig.MatterBottler.RATE, false), false) + + if (!it.storedMatter.isPositive) { + initialCapacity = null + workProgress = 0f + } else { + workProgress = 1f - (it.storedMatter / initialCapacity!!).toFloat() + } + } + } + + if (any) { + break + } + } + + if (any) { + blockstateToWorking() + } else { + blockstateToIdle() + } } - - if (!isBottling && !matter.storedMatter.isZero && graph != null) { - val diff = matter.extractMatterInner(matter.storedMatter, true) - val diff2 = graph.receiveMatter(diff, true) - matter.extractMatterInner(diff2, false) - graph.receiveMatter(diff2, false) - } - } - - companion object { - val MATTER_EXCHANGE_RATE get() = _MATTER_EXCHANGE_RATE.get() - val ENERGY_CONSUMPTION get() = _ENERGY_CONSUMPTION.get() - val EXTRACTION_TICKS get() = _EXTRACTION_TICKS.get() - val CAPACITY get() = _CAPACITY.get() - - private var _MATTER_EXCHANGE_RATE: DecimalConfigValue by WriteOnce() - private var _ENERGY_CONSUMPTION: DecimalConfigValue by WriteOnce() - private var _EXTRACTION_TICKS: DecimalConfigValue by WriteOnce() - private var _CAPACITY: DecimalConfigValue by WriteOnce() - - var ENERGY_VALUES: ConciseBalanceValues by WriteOnce() - private set - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.MATTER_BOTTLER) - - ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder) - - _MATTER_EXCHANGE_RATE = builder.defineDecimal("matterExchangeRate", Decimal("0.04"), Decimal("0.0001")) - _ENERGY_CONSUMPTION = builder.defineDecimal("energyConsumption", Decimal(20), Decimal.ONE) - _EXTRACTION_TICKS = builder.defineDecimal("extractionTicks", Decimal(200), Decimal.ONE) - _CAPACITY = builder.defineDecimal("matterCapacity", Decimal(4_000), Decimal.ONE_TENTH) - - builder.pop() - } - - const val IS_BOTTLING_KEY = "isBottling" } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt index 73d01b49b..b225e7f1a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt @@ -1,73 +1,65 @@ package ru.dbotthepony.mc.otm.block.entity.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level -import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.BatteryBankBlock -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler -import ru.dbotthepony.mc.otm.capability.matter.MatterDirection +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.menu.MatterCapacitorBankMenu +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode +import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set -class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.MATTER_CAPACITOR_BANK, p_155229_, p_155230_), IMatterGraphNode, IMatterHandler, IDroppableContainer { - var gaugeLevel by synchronizer.float() +class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.MATTER_CAPACITOR_BANK, p_155229_, p_155230_), IMatterStorage { + var gaugeLevel by synchronizer.float().property private set - override val matterNode = Graph6Node(this) - private val resolverNode = LazyOptional.of { this } + val matterNode = SimpleMatterNode(matter = this) - override val storedMatter: Decimal get() { + override val canSetMatterLevel: Boolean + get() = false + + override var storedMatter: Decimal + get() { + var summ = Decimal.ZERO + + for (stack in container) + if (!stack.isEmpty) + stack.getCapability(MatteryCapability.MATTER).ifPresentK { + summ += it.storedMatter + } + + return summ + } + set(value) { + throw UnsupportedOperationException() + } + + override val maxStoredMatter: Decimal + get() { var summ = Decimal.ZERO for (stack in container) if (!stack.isEmpty) - stack.getCapability(MatteryCapability.MATTER).ifPresent { - summ += it.storedMatter - } - - return summ - } - - override val maxStoredMatter: Decimal get() { - var summ = Decimal.ZERO - - for (stack in container) - if (!stack.isEmpty) - stack.getCapability(MatteryCapability.MATTER).ifPresent { + stack.getCapability(MatteryCapability.MATTER).ifPresentK { summ += it.maxStoredMatter } return summ } - override fun receiveMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveMatterInner(howMuch, simulate) - } - - override fun receiveMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { if (!howMuch.isPositive) return Decimal.ZERO @@ -78,7 +70,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) for (stack in container) { if (!stack.isEmpty) { stack.getCapability(MatteryCapability.MATTER).ifPresent { - val diff = it.receiveMatterOuter(howMuch, simulate) + val diff = it.receiveMatterChecked(howMuch, simulate) summ += diff howMuch -= diff } @@ -96,11 +88,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) return summ } - override fun extractMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return extractMatterInner(howMuch, simulate) - } - - override fun extractMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { if (!howMuch.isPositive) return Decimal.ZERO @@ -111,7 +99,7 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) for (stack in container) { if (!stack.isEmpty) { stack.getCapability(MatteryCapability.MATTER).ifPresent { - val diff = it.extractMatterOuter(howMuch, simulate) + val diff = it.extractMatterChecked(howMuch, simulate) summ += diff howMuch -= diff } @@ -129,95 +117,56 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) return summ } - override val direction: MatterDirection get() { - return MatterDirection.BIDIRECTIONAL - } + override val matterFlow: FlowDirection + get() = FlowDirection.BI_DIRECTIONAL - private var resolver = LazyOptional.of { this } - - val container = object : MatteryContainer(this::setChangedLight, 6 * 2) { + val container = object : MatteryContainer(this::markDirtyFast, BatteryBankBlockEntity.CAPACITY) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { super.setChanged(slot, new, old) - val level = level + capacitorStatus[slot].boolean = new.getCapability(MatteryCapability.MATTER).isPresent + gaugeLevel = (storedMatter / maxStoredMatter).toFloat() + } - if (level != null) { - var state = blockState + override fun getMaxStackSize(): Int = 1 + }.also(::addDroppableContainer) - for (i in BatteryBankBlock.BATTERY_SLOTS_PROPS.indices) { - state = state.setValue( - BatteryBankBlock.BATTERY_SLOTS_PROPS[i], - getItem(i).getCapability(MatteryCapability.MATTER).isPresent - ) - } + val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(MatteryCapability.MATTER).isPresent + } - if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + stack.getCapability(MatteryCapability.MATTER).ifPresentK { + if (it.storedMatter.isPositive) { + return false } } - gaugeLevel = (storedMatter / maxStoredMatter).toFloat() + return true } + })) + + val capacitorStatus = immutableList(BatteryBankBlockEntity.CAPACITY) { + synchronizer.bool(false) } - override val droppableContainer: Container - get() = container - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) + init { + savetables.stateful(::container, INVENTORY_KEY) + exposeGlobally(MatteryCapability.MATTER, this) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterCapacitorBankMenu(containerID, inventory, this) } - private var valid = true - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - resolver.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolver = LazyOptional.of { this } - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap === MatteryCapability.MATTER) return resolver.cast() - if (cap === MatteryCapability.MATTER_NODE) return resolverNode.cast() - } - - return super.getCapability(cap, side) - } - override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) - } - - override fun getMatterHandler(): IMatterHandler { - return this - } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.matter_capacitor_bank") + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt index e691f44ad..d454e94b3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt @@ -1,219 +1,120 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.items.IItemHandler -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.block.IDroppableContainer +import ru.dbotthepony.mc.otm.block.entity.Job +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler -import ru.dbotthepony.mc.otm.capability.matter.MatterDirection -import ru.dbotthepony.mc.otm.capability.matter.MatterHandlerImpl +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.item.MatterDustItem -import ru.dbotthepony.mc.otm.menu.MatterDecomposerMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MItems -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.matter.MatterManager - -fun moveMatterAsDustIntoContainer(_matterValue: Decimal, container: MatteryContainer, OUTPUT_DUST_MAIN: Int, OUTPUT_DUST_STACKING: Int): Decimal { - var matterValue = _matterValue - val item = MItems.MATTER_DUST as MatterDustItem - - while (matterValue > Decimal.ZERO) { - val stack = container[OUTPUT_DUST_MAIN] - - // первый слот пустой - if (stack.isEmpty) { - container[OUTPUT_DUST_MAIN] = ItemStack(item, 1).also { - matterValue -= item.addMatterValue(it, matterValue, false) - } - // первый слот не пустой, но мы можем влить туда материю - } else if (!item.isFull(stack) && stack.count == 1) { - matterValue -= item.addMatterValue(stack, matterValue, false) - container.setChanged(OUTPUT_DUST_MAIN) - // первый слот не пустой и мы не можем влить туда материю - } else { - val stack2 = container[OUTPUT_DUST_STACKING] - - // второй слот пустой - if (stack2.isEmpty) { - container[OUTPUT_DUST_STACKING] = ItemStack(item, 1).also { - matterValue -= item.addMatterValue(it, matterValue, false) - } - // второй слот не пустой, но мы можем влить туда материю - } else if (!item.isFull(stack2)) { - if (stack2.count != 1) { - return matterValue - } - - matterValue -= item.addMatterValue(stack2, matterValue, false) - container.setChanged(OUTPUT_DUST_STACKING) - } - - // можем ли мы стакнуть материю из второго слота в первый? - if (!stack2.isEmpty && item.isFull(stack2)) { - if (ItemStack.isSameItemSameTags(stack, stack2) && container.getMaxStackSizeWithItem(OUTPUT_DUST_MAIN) >= stack.count + 1) { - stack.count++ - stack2.count-- - - container.setChanged(OUTPUT_DUST_MAIN) - container.setChanged(OUTPUT_DUST_STACKING) - } else { - return matterValue - } - } - } - } - - return Decimal.ZERO -} +import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MItems class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.MATTER_DECOMPOSER, pos, state, ::DecomposerJob), IMatterGraphNode, IDroppableContainer { - - class DecomposerJob : Job { - val toDust: Boolean - var matterValue: Decimal - - constructor(tag: CompoundTag) : super(tag) { - toDust = tag.getBoolean(TO_DUST_KEY) - matterValue = tag.getDecimal(MATTER_VALUE_KEY) - } - - constructor(toDust: Boolean, matterValue: Decimal, ticks: Double) : super(ticks, BASE_CONSUMPTION) { - this.toDust = toDust - this.matterValue = matterValue - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[TO_DUST_KEY] = toDust - it[MATTER_VALUE_KEY] = matterValue - } - } + : MatteryWorkerBlockEntity(MBlockEntities.MATTER_DECOMPOSER, pos, state, DecomposerJob.CODEC) { + class DecomposerJob( + val toDust: Boolean, + var matterValue: Decimal, + ticks: Double, + ) : Job(ticks, MachinesConfig.MATTER_DECOMPOSER.energyConsumption) { companion object { - const val TO_DUST_KEY = "toDust" - const val MATTER_VALUE_KEY = "matterValue" + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + Codec.BOOL.fieldOf("toDust").forGetter(DecomposerJob::toDust), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(DecomposerJob::matterValue), + Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(DecomposerJob::ticks) + ).apply(it, ::DecomposerJob) + } + } } } - override val energy = WorkerEnergyStorage(this, ENERGY_VALUES) - private var valid = true - override val matterNode = Graph6Node(this) + override val upgrades = UpgradeContainer(this::markDirtyFast, 4, UpgradeType.REPLICATOR) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, upgrades.transform(MachinesConfig.MATTER_DECOMPOSER))) + val energyConfig = ConfigurableEnergy(energy) - val matter = MatterHandlerImpl(this::setChangedLight, MatterDirection.EXTRACT, ::CAPACITY) + init { + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::upgrades) + } - private var resolverMatter = LazyOptional.of { matter } - private var resolverNode = LazyOptional.of { this } + val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.OUTPUT, upgrades.matterCapacity(MachinesConfig.MATTER_DECOMPOSER::matterCapacity))) + val matterNode = SimpleMatterNode(matter = matter) + + init { + exposeGlobally(MatteryCapability.MATTER, matter) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + savetables.stateful(::matter, MATTER_STORAGE_KEY) + } // вход, выход - val container = MatteryContainer(this::setChangedLight, 3) + val inputContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val outputContainer = MatteryContainer(::markDirtyFast, 2).also(::addDroppableContainer) - override val droppableContainer: Container - get() = container - - private val itemHandler = LazyOptional.of { - container.handler(object : MatteryContainerHooks { + val itemConfig = ConfigurableItemHandler( + input = inputContainer.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return slot == INPUT_SLOT && MatterManager.canDecompose(stack) + return MatterManager.canDecompose(stack) } override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return slot != INPUT_SLOT + return false } - }) - } + }), - override val defaultDisplayName: Component - get() = MBlocks.MATTER_DECOMPOSER.name + output = outputContainer.handler(HandlerFilter.OnlyOut) + ) + + init { + savetables.stateful(::inputContainer) + savetables.stateful(::outputContainer) + } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterDecomposerMenu(containerID, inventory, this) } - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - nbt[MATTER_STORAGE_KEY] = matter.serializeNBT() - } + override fun onJobFinish(status: JobStatus, id: Int) { + if (status.job.toDust) { + status.job.matterValue = MItems.MATTER_DUST.moveIntoContainer(status.job.matterValue, outputContainer, 0, 1) - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - nbt.map(MATTER_STORAGE_KEY, matter::deserializeNBT) - } + if (!status.job.matterValue.isZero) + status.throttleFast() + } else { + status.job.matterValue -= matter.receiveMatter(status.job.matterValue, false) - override fun reviveCaps() { - valid = true - super.reviveCaps() - resolverMatter = LazyOptional.of { matter } - } - - override fun invalidateCaps() { - valid = false - super.invalidateCaps() - resolverMatter.invalidate() - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == MatteryCapability.MATTER) return resolverMatter.cast() - if (cap == MatteryCapability.MATTER_NODE) return resolverNode.cast() - if (cap == ForgeCapabilities.ITEM_HANDLER) return itemHandler.cast() + if (status.job.matterValue.isPositive) + status.noMatter() } - - return super.getCapability(cap, side) } - override fun onJobFinish(job: DecomposerJob): Status { - if (job.toDust) { - job.matterValue = moveMatterAsDustIntoContainer(job.matterValue, container, OUTPUT_DUST_MAIN, OUTPUT_DUST_STACKING) - - if (!job.matterValue.isZero) { - return Status.FAILURE_WAIT_FAST - } - - return Status.SUCCESS - } - - job.matterValue -= matter.receiveMatterInner(job.matterValue, false) - - if (job.matterValue.isPositive) { - return Status.FAILURE_MATTER - } - - return Status.SUCCESS - } - - override fun computeNextJob(): Pair { - val stack = container[INPUT_SLOT] + override fun computeNextJob(id: Int): JobContainer { + val stack = inputContainer[0] if (!stack.isEmpty) { val copy = stack.copy() @@ -223,67 +124,34 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) val matter = MatterManager.get(copy) stack.count-- - return DecomposerJob((level?.random?.nextDouble() ?: 1.0) <= 0.2, matter.matter, matter.complexity) to null + return JobContainer.success( + DecomposerJob( + (level?.random?.nextDouble() ?: 1.0) <= 0.2 * upgrades.failureMultiplier, + matter.matter, + matter.complexity * MachinesConfig.MATTER_DECOMPOSER.workTimeMultiplier + ) + ) } } - return null to IdleReason.ITEM + return JobContainer.noItem() } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } - override fun getMatterHandler(): IMatterHandler { - return matter - } - - fun tick() { - batteryChargeLoop() - workerLoop() - - val grid = matterNode.graph as MatterNetworkGraph? ?: return + override fun tick() { + super.tick() if (!matter.storedMatter.isZero) { - val diff = matter.extractMatterInner(matter.storedMatter, true) - val diff2 = grid.receiveMatter(diff, true) - - matter.extractMatterInner(diff2, false) - grid.receiveMatter(diff2, false) - } - } - - companion object { - const val INPUT_SLOT = 0 - const val OUTPUT_DUST_MAIN = 1 - const val OUTPUT_DUST_STACKING = 2 - - val CAPACITY get() = _CAPACITY.get() - val BASE_CONSUMPTION get() = _BASE_CONSUMPTION.get() - - private var _CAPACITY: DecimalConfigValue by WriteOnce() - private var _BASE_CONSUMPTION: DecimalConfigValue by WriteOnce() - - var ENERGY_VALUES: ConciseBalanceValues by WriteOnce() - private set - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.MATTER_DECOMPOSER) - - ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(400_000), throughput = Decimal(2_000)) - - _CAPACITY = builder.defineDecimal("matterCapacity", Decimal(20_000), Decimal.ONE_TENTH) - _BASE_CONSUMPTION = builder.defineDecimal("baseConsumption", Decimal(240), Decimal.ONE) - - builder.pop() + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt new file mode 100644 index 000000000..59130c1e3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt @@ -0,0 +1,168 @@ +package ru.dbotthepony.mc.otm.block.entity.matter + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.ShadowCraftingContainer +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.graph.matter.MatterNode +import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MRecipes + +class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity(MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) { + class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double, experience: Float) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption, experience = experience) { + val matterPerTick = matter / ticks + + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + ItemStack.CODEC.fieldOf("itemStack").forGetter(ItemJob::itemStack), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(Job::matter), + Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(ItemJob::ticks), + Codec.FLOAT.minRange(0f).optionalFieldOf("experience", 0f).forGetter(Job::experience) + ).apply(it, ::Job) + } + } + } + + override val upgrades = UpgradeContainer(::markDirtyFast, 3, UpgradeType.BASIC_MATTER) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_ENTANGLER))) + val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_ENTANGLER::matterCapacity))) + val node = MatterNode() + + val experience = ExperienceStorage(MachinesConfig.MATTER_ENTANGLER::maxExperienceStored).also(::addNeighbourListener) + val energyConfig = ConfigurableEnergy(energy) + + val inputs = object : MatteryCraftingContainer(::itemContainerUpdated, 3, 3) { + override fun getMaxStackSize(): Int { + return 1 + } + } + + val output = object : MatteryContainer(::itemContainerUpdated, 1) { + override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return Int.MAX_VALUE + } + } + + val itemConfig = ConfigurableItemHandler( + input = inputs.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + val shadow = ShadowCraftingContainer.shadow(inputs, slot, stack) + + return (level ?: return false) + .recipeManager + .byType(MRecipes.MATTER_ENTANGLER) + .values + .any { it.value.preemptivelyMatches(shadow, level!!) } + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return false + } + }), + output = output.handler(HandlerFilter.OnlyOut) + ) + + init { + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience) + + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::matter, MATTER_STORAGE_KEY) + savetables.stateful(::upgrades) + savetables.stateful(::inputs) + savetables.stateful(::output) + savetables.stateful(::experience) + + exposeGlobally(MatteryCapability.MATTER_NODE, node) + exposeGlobally(MatteryCapability.MATTER, matter) + } + + override fun setLevel(level: Level) { + super.setLevel(level) + node.discover(this) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { + return MatterEntanglerMenu(containerID, inventory, this) + } + + override fun onJobTick(status: JobStatus, id: Int) { + val required = status.job.matterPerTick * status.ticksAdvanced + + if (matter.storedMatter < required) { + matter.receiveMatter(node.graph.extractMatter(status.job.matterPerTick.coerceAtLeast(Decimal.TEN).coerceAtMost(matter.missingMatter), false), false) + } + + status.scale(matter.extractMatter(required, false) / required) + } + + override fun tick() { + super.tick() + + if (jobEventLoops[0].currentJob == null) { + matter.extractMatter(node.graph.receiveMatter(matter.storedMatter, false), false) + } + } + + override fun onJobFinish(status: JobStatus, id: Int) { + if (!output.fullyAddItem(status.job.itemStack)) { + status.noItem() + } else { + experience.storeExperience(status.job.experience, this) + } + } + + override fun computeNextJob(id: Int): JobContainer { + if (!energy.batteryLevel.isPositive) + return JobContainer.noEnergy() + + val recipe = (level ?: return JobContainer.failure()) + .recipeManager + .byType(MRecipes.MATTER_ENTANGLER) + .values + .firstOrNull { it.value.matches(inputs, level!!) } ?: return JobContainer.noItem() + + val result = recipe.value.assemble(inputs) + + inputs.forEach { it.shrink(1) } + inputs.setChanged() + + return JobContainer.success( + Job( + result, + recipe.value.matter, + recipe.value.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier, + recipe.value.experience + ) + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt index 26494ce9d..ae73612fd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt @@ -1,91 +1,99 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.level.block.state.BlockState -import ru.dbotthepony.mc.otm.menu.MatterPanelMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.capability.MatteryCapability -import java.util.HashMap -import java.util.UUID import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag -import net.minecraft.nbt.Tag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level -import net.minecraftforge.common.capabilities.Capability -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import ru.dbotthepony.mc.otm.core.nbt.getBoolean +import ru.dbotthepony.mc.otm.core.nbt.getCompoundList +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.mapString +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.ItemSorter +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode +import ru.dbotthepony.mc.otm.menu.IItemSortingSettings import ru.dbotthepony.mc.otm.registry.MBlockEntities -import java.util.ArrayList +import java.util.* import java.util.stream.Stream -class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryBlockEntity(MBlockEntities.MATTER_PANEL, p_155229_, p_155230_), IMatterGraphNode, IReplicationTaskProvider { +class MatterPanelBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.MATTER_PANEL, blockPos, blockState), IReplicationTaskProvider { + inner class PlayerSettings : IItemSortingSettings { + override var sorting: ItemSorter = ItemSorter.DEFAULT + set(value) { + field = value + markDirtyFast() + } - private val listeners = ArrayList() - override val matterNode = Graph6Node(this) + override var isAscending: Boolean = true + set(value) { + field = value + markDirtyFast() + } + } + + var isProvidingTasks = true + set(value) { + if (field != value) { + field = value + + if (value) { + _tasks.values.forEach { matterNode.graph.onMatterTaskCreated(it) } + } else { + _tasks.values.forEach { if (it.inProgress == 0) matterNode.graph.onMatterTaskRemoved(it) } + } + + markDirtyFast() + } + } + + private val playerSettings = Object2ObjectOpenHashMap() + + fun getPlayerSettings(ply: Player): PlayerSettings { + return playerSettings.computeIfAbsent(ply.uuid, Object2ObjectFunction { PlayerSettings() }) + } + + private val listeners = WeakHashSet() + val matterNode = SimpleMatterNode(tasks = this) fun attachMenu(menu: MatterPanelMenu) { listeners.add(menu) } - fun deatachMenu(menu: MatterPanelMenu) { + fun detachMenu(menu: MatterPanelMenu) { listeners.remove(menu) } - override val defaultDisplayName: Component - get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterPanelMenu(containerID, inventory, this) } - private var valid = true + init { + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + exposeGlobally(MatteryCapability.TASK, this) - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - resolver.invalidate() + savetables.bool(::isProvidingTasks) } - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolver = LazyOptional.of { this } - } - - private var resolver = LazyOptional.of { this } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid && (cap === MatteryCapability.MATTER_NODE || cap === MatteryCapability.TASK)) - return resolver.cast() - - return super.getCapability(cap, side) - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) - } - - override fun getTaskHandler(): IReplicationTaskProvider { - return this + matterNode.isValid = false } private val _tasks = HashMap() @@ -99,18 +107,18 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } override fun allocateTask(simulate: Boolean): ReplicationTaskAllocation? { - val graph = matterNode.graph as MatterNetworkGraph? ?: return null + if (!isProvidingTasks) return null for ((key, task) in _tasks) { if (task.required > 0) { - val pattern = task.patternId?.let(graph::getPattern) ?: continue + val pattern = task.patternId.map(matterNode.graph::getPattern).orElse(null) ?: continue if (!simulate) { val new = task.allocate() _tasks[key] = new listeners.forEach { it.taskUpdated(new) } - graph.onMatterTaskUpdated(new, task) - setChanged() + matterNode.graph.onMatterTaskUpdated(new, task) + markDirtyFast() } return ReplicationTaskAllocation(task, pattern) @@ -123,28 +131,26 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : override fun notifyTaskCompletion(taskId: UUID): Boolean { var localTask = _tasks[taskId] ?: return false val oldTask = localTask - localTask = localTask.finish() - val graph = matterNode.graph as MatterNetworkGraph? // Задача полностью выполнена if (localTask.required <= 0 && localTask.inProgress <= 0) { _tasks.remove(taskId) - graph?.onMatterTaskFinished(localTask) + matterNode.graph.onMatterTaskFinished(localTask) listeners.forEach { it.taskRemoved(localTask) } } else { // Задача обновлена _tasks[taskId] = localTask - graph?.onMatterTaskUpdated(localTask, oldTask) + matterNode.graph.onMatterTaskUpdated(localTask, oldTask) listeners.forEach { it.taskUpdated(localTask) } } - setChanged() + markDirtyFast() return true } - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) + override fun saveShared(nbt: CompoundTag) { + super.saveShared(nbt) val list = ListTag() @@ -152,13 +158,21 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : list.add(task.serializeNBT()) } - nbt.put("tasks", list) + nbt["tasks"] = list + + val settings = CompoundTag() + + for ((uuid, value) in playerSettings) { + settings[uuid.toString()] = value.serializeNBT() + } + + nbt["settings"] = settings } override fun load(nbt: CompoundTag) { super.load(nbt) _tasks.clear() - val list = nbt.getList("tasks", Tag.TAG_COMPOUND.toInt()) + val list = nbt.getCompoundList("tasks") for (tag in list) { val task = ReplicationTask.deserializeNBT(tag) @@ -167,46 +181,50 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : _tasks[task.id] = task } } + + playerSettings.clear() + + nbt.map("settings") { it: CompoundTag -> + for (k in it.allKeys) { + playerSettings.computeIfAbsent(UUID.fromString(k), Object2ObjectFunction { PlayerSettings() }) + .deserializeNBT(it.getCompound(k)) + } + } } override fun getTask(id: UUID): ReplicationTask? { - return _tasks[id]?.asImmutable() + return _tasks[id] } fun removeTask(id: UUID) { val task = _tasks[id] ?: return _tasks.remove(id) - (matterNode.graph as MatterNetworkGraph?)?.onMatterTaskRemoved(task) + matterNode.graph.onMatterTaskRemoved(task) listeners.forEach { it.taskRemoved(task) } - setChanged() + markDirtyFast() } - fun addTask(state: IPatternState, count: Int): IReplicationTask<*> { - val task = ReplicationTask(UUID.randomUUID(), state.id, state.item, 0, 0, count) + fun addTask(state: PatternState, count: Int): ReplicationTask { + val task = ReplicationTask(UUID.randomUUID(), Optional.of(state.id), state.item, 0, 0, count) _tasks[task.id] = task - (matterNode.graph as MatterNetworkGraph?)?.onMatterTaskCreated(task) + if (isProvidingTasks) + matterNode.graph.onMatterTaskCreated(task) listeners.forEach { it.taskUpdated(task) } - setChanged() + markDirtyFast() return task } override fun dropAllTasks() { - val graph = matterNode.graph as MatterNetworkGraph? - for (task in _tasks.values) { - graph?.onMatterTaskRemoved(task) - listeners.forEach { menu: MatterPanelMenu -> menu.taskUpdated(task) } + matterNode.graph.onMatterTaskRemoved(task) + listeners.forEach { it.taskRemoved(task) } } _tasks.clear() } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.matter_panel") - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt new file mode 100644 index 000000000..489a00ccc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt @@ -0,0 +1,285 @@ +package ru.dbotthepony.mc.otm.block.entity.matter + +import net.minecraft.core.BlockPos +import net.minecraft.nbt.StringTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.capability.matter.PatternState +import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.graph.matter.MatterNode +import ru.dbotthepony.mc.otm.matter.IMatterValue +import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.MATTER_RECONSTRUCTOR, blockPos, blockState) { + val repairContainer = MatteryContainer(::containerChanged, 1).also(::addDroppableContainer) + + private var matterPerTick = Decimal.ZERO + private var progressPerTick = 0.0 + private var repairProgress = 0.0 + private var failureChance = 0.0 + private var lastItem: Item? = null + private var initialDamage = 0.0 + + var visualItemStack by synchronizer.item(observe = false) + private set + + var visualProgress = 0f + private set + + var isUnableToProcess = false + private set + + val upgrades = UpgradeContainer(this::markDirtyFast, 3, UpgradeType.REPLICATOR) + val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MatterReconstructor.VALUES::matterCapacity))) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::markDirtyFast, upgrades.transform(MachinesConfig.MatterReconstructor.VALUES))) + + val matterNode = object : MatterNode() { + override fun getMatterHandler(): IMatterStorage { + return matter + } + + override fun onPatternAdded(state: PatternState) { + containerChanged() + } + + override fun onPatternRemoved(state: PatternState) { + containerChanged() + } + + override fun onPatternUpdated(newState: PatternState, oldState: PatternState) { + containerChanged() + } + } + + init { + exposeGlobally(MatteryCapability.MATTER, matter) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + + savetables.stateful(::repairContainer) + savetables.stateful(::matter) + savetables.stateful(::energy) + savetables.stateful(::upgrades) + + savetables.decimal(::matterPerTick) + savetables.double(::progressPerTick) + savetables.double(::repairProgress) + + savetables.Stateless(::lastItem, type = StringTag::class.java) + .withSerializer { it?.registryName?.toString()?.let(StringTag::valueOf) } + .withDeserializer { ResourceLocation.tryParse(it.asString)?.let { ForgeRegistries.ITEMS.getValue(it) } } + } + + val energyConfig = ConfigurableEnergy(energy) + val itemConfig = ConfigurableItemHandler( + inputOutput = repairContainer.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + if (!stack.isRepairable || !stack.isDamaged) { + return false + } + + if (MachinesConfig.MatterReconstructor.ALLOW_TO_SKIP_ANVIL && !MachinesConfig.MatterReconstructor.ONLY_ANVIL) { + if (MatterManager.get(stack.item).hasMatterValue) { + return true + } + } + + return matterNode.graph + .patterns + .filter { stack.item.isValidRepairItem(stack, ItemStack(it.item, 1)) } + .findFirst().orElse(null).let { + if (it == null) { + IMatterValue.ZERO + } else { + MatterManager.get(it.item) + } + }.hasMatterValue + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return progressPerTick <= 0.0 || matterPerTick <= Decimal.ZERO + } + }) + ) + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return MatterReconstructorMenu(containerID, inventory, this) + } + + override fun setRemoved() { + super.setRemoved() + matterNode.isValid = false + } + + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) + } + + private var changeset = 0 + + private fun containerChanged() { + matterPerTick = Decimal.ZERO + progressPerTick = 0.0 + + val item = repairContainer[0] + val thisChangeset = ++changeset + + tickList.once { + if (thisChangeset != changeset) return@once + + if (lastItem != item.item) { + lastItem = item.item + repairProgress = 0.0 + initialDamage = item.damageValue.toDouble() + visualItemStack = item + visualProgress = 0f + } + + if (item.isEmpty || !item.isRepairable || !item.isDamaged) { + matterPerTick = Decimal.ZERO + progressPerTick = 0.0 + initialDamage = 0.0 + visualItemStack = item + visualProgress = 0f + } else { + if (MachinesConfig.MatterReconstructor.ALLOW_TO_SKIP_ANVIL && !MachinesConfig.MatterReconstructor.ONLY_ANVIL) { + val matter = MatterManager.get(item.item) + + if (matter.hasMatterValue) { + failureChance = MachinesConfig.MatterReconstructor.FAILURE_CHANCE + progressPerTick = (item.maxDamage / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + matterPerTick = (matter.matter / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + return@once + } + } + + val found = matterNode.graph.patterns.filter { item.item.isValidRepairItem(item, ItemStack(it.item, 1)) }.findFirst().orElse(null) + + if (found != null) { + failureChance = (1.0 - found.researchPercent) + MachinesConfig.MatterReconstructor.FAILURE_CHANCE + + if (!MachinesConfig.MatterReconstructor.ONLY_ANVIL) { + val matter = MatterManager.get(item.item) + + if (matter.hasMatterValue) { + progressPerTick = (item.maxDamage / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + matterPerTick = (matter.matter / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + return@once + } + } + + @Suppress("name_shadowing") + val matter = MatterManager.get(found.item) * 2 + + progressPerTick = (item.maxDamage / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + matterPerTick = (matter.matter / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR + } else { + matterPerTick = Decimal.ZERO + progressPerTick = 0.0 + initialDamage = 0.0 + visualItemStack = item + visualProgress = 0f + } + } + } + } + + override fun tick() { + super.tick() + + if (!redstoneControl.isBlockedByRedstone) { + isUnableToProcess = false + + val item = repairContainer[0] + + val thisProgressPerTick = progressPerTick * (1.0 + upgrades.speedBonus) + val matterPerTick = matterPerTick * (1.0 + upgrades.speedBonus) + val energyConsumption = MachinesConfig.MatterReconstructor.VALUES.energyConsumption * (1.0 + upgrades.speedBonus) * (upgrades.energyConsumed + Decimal.ONE) + + if (!item.isEmpty && matterPerTick.isPositive && thisProgressPerTick > 0.0 && item.isRepairable && item.isDamaged) { + var progressPerTick = (repairProgress + thisProgressPerTick).coerceAtMost(item.damageValue.toDouble()) - repairProgress + if (progressPerTick <= 0.0) return + + if (energyConsumption.isPositive) { + if (!energy.batteryLevel.isPositive) return + val multEnergy = energyConsumption * (progressPerTick / thisProgressPerTick) + progressPerTick *= (energy.extractEnergy(multEnergy, true) / multEnergy).toDouble() + + if (progressPerTick <= 0.0) { + isUnableToProcess = true + return + } + } + + if (matter.storedMatter < matterPerTick) { + val toDrain = (matterPerTick * 200 + .coerceAtMost(item.damageValue) - matter.storedMatter) + .coerceAtLeast(Decimal.ZERO) + .coerceAtMost(matter.missingMatter) + + matter.receiveMatter(matterNode.graph.extractMatter(toDrain, false), false) + } + + val toDrain = matterPerTick * (progressPerTick / thisProgressPerTick) + val drain = matter.extractMatter(toDrain, true) + + if (!drain.isPositive) { + isUnableToProcess = true + return + } + + progressPerTick *= (drain / toDrain).toDouble() + + if (progressPerTick <= 0.0) { + isUnableToProcess = true + return + } + + if (failureChance * upgrades.failureMultiplier <= 0.0 || level!!.random.nextDouble() >= failureChance * upgrades.failureMultiplier) + repairProgress += progressPerTick + + energy.extractEnergy(energyConsumption * (progressPerTick / thisProgressPerTick), false) + matter.extractMatter(matterPerTick * (progressPerTick / thisProgressPerTick), false) + + if (repairProgress >= 1.0) { + item.damageValue = (item.damageValue - repairProgress.toInt()).coerceAtLeast(0) + repairProgress %= 1.0 + } + + visualItemStack = item + visualProgress = 1f - ((item.damageValue - repairProgress) / initialDamage).toFloat() + } else { + initialDamage = 0.0 + visualItemStack = item + visualProgress = 0f + + if (matter.storedMatter.isPositive) { + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt index dab3fded8..1602236fb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt @@ -1,98 +1,59 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.ForgeConfigSpec.ConfigValue -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.block.IDroppableContainer +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.Job import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler -import ru.dbotthepony.mc.otm.capability.matter.MatterDirection -import ru.dbotthepony.mc.otm.capability.matter.MatterHandlerImpl +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.item.MatterDustItem -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.menu.MatterRecyclerMenu +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.graph.matter.MatterGraph +import ru.dbotthepony.mc.otm.item.matter.MatterDustItem +import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.core.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.defineDecimal -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce -import ru.dbotthepony.mc.otm.core.getDecimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, ::RecyclerJob), IMatterGraphNode, IDroppableContainer { + : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, RecyclerJob.CODEC) { - class RecyclerJob : Job { - var totalMatter: Decimal - - constructor( - ticks: Double, - powerUsage: Decimal, - totalMatter: Decimal - ) : super(ticks, powerUsage) { - this.totalMatter = totalMatter - } - - constructor(tag: CompoundTag) : super(tag) { - this.totalMatter = tag.getDecimal(KEY) - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[KEY] = totalMatter + class RecyclerJob(ticks: Double, powerUsage: Decimal, var totalMatter: Decimal) : Job(ticks, powerUsage) { + companion object { + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + plainCodec(it).and(DecimalCodec.minRange(Decimal.ZERO).fieldOf("totalMatter").forGetter(RecyclerJob::totalMatter)).apply(it, ::RecyclerJob) + } } } - - companion object { - const val KEY = "totalMatter" - } } - val matter = MatterHandlerImpl( - this::matterLevelUpdated, - MatterDirection.EXTRACT, - ::CAPACITY - ) + override val upgrades = UpgradeContainer(this::markDirtyFast, 3, UpgradeType.BASIC_MATTER) + val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.OUTPUT, upgrades.matterCapacity(MachinesConfig.MatterRecycler.VALUES::matterCapacity))) + val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer) - val container = MatteryContainer(this::itemContainerUpdated, 1) + val matterNode = SimpleMatterNode(matter = matter) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MatterRecycler.VALUES))) - override val droppableContainer: Container - get() = container - override val matterNode = Graph6Node(this) - private var resolverNode = LazyOptional.of { this } - private var valid = true - override val energy = WorkerEnergyStorage(this::powerLevelUpdated, ENERGY_CONFIG) - - override fun getMatterHandler(): IMatterHandler { - return matter - } - - private val itemHandler = container.handler(object : MatteryContainerHooks { + val itemConfig = ConfigurableItemHandler(input = container.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { return stack.item is MatterDustItem } @@ -100,135 +61,93 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { return false } - }) + })) - override fun invalidateCaps() { - super.invalidateCaps() - matter.invalidate() - itemHandler.invalidate() - resolverNode.invalidate() - } + val energyConfig = ConfigurableEnergy(energy) - override fun reviveCaps() { - super.reviveCaps() - matter.revive() - itemHandler.revive() - resolverNode = LazyOptional.of { this } + init { + exposeGlobally(MatteryCapability.MATTER, matter) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::container, INVENTORY_KEY) + savetables.stateful(::matter, MATTER_STORAGE_KEY) + savetables.stateful(::upgrades) } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[MATTER_STORAGE_KEY] = matter.serializeNBT() - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(MATTER_STORAGE_KEY, matter::deserializeNBT) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (!valid) - return super.getCapability(cap, side) - - return when (cap) { - ForgeCapabilities.ITEM_HANDLER -> itemHandler.get().cast() - MatteryCapability.MATTER -> matter.get().cast() - MatteryCapability.MATTER_NODE -> resolverNode.cast() - else -> super.getCapability(cap, side) - } - } - - override val defaultDisplayName: Component - get() = MBlocks.MATTER_RECYCLER.name - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterRecyclerMenu(containerID, inventory, this) } - override fun onJobFinish(job: RecyclerJob): Status { - // вся логика в onWorkTick - return Status.SUCCESS + override fun onJobFinish(status: JobStatus, id: Int) { + val job = status.job + + if (job.totalMatter.isPositive) { + val received = matter.receiveMatter(job.totalMatter, true) + + if (job.totalMatter != received) + return status.noMatter() + + matter.receiveMatter(job.totalMatter, false) + job.totalMatter -= received + } } - override fun computeNextJob(): Pair { + override fun computeNextJob(id: Int): JobContainer { if (matter.missingMatter.isZero) - return null to IdleReason.ITEM + return JobContainer.noMatter() val stack = container[0] - if (stack.isEmpty || stack.item !is MatterDustItem) { - return null to IdleReason.ITEM - } + if (stack.isEmpty || stack.item !is MatterDustItem) + return JobContainer.noItem() + + val dustMatter = (stack.item as MatterDustItem).getMatterValue(stack.copy().also { it.count = 1 }) ?: return JobContainer.noItem() - val dustMatter = (stack.item as MatterDustItem).getMatterValue(stack.copy().also { it.count = 1 }) ?: return null to IdleReason.ITEM stack.shrink(1) container.setChanged(0) - return RecyclerJob(dustMatter.matter.toDouble() * TICKS_PER_MATTER, POWER_CONSUMPTION, dustMatter.matter * (0.4 + level!!.random.nextDouble() * 0.6)) to null + + val actualMatter = dustMatter.matter * (0.4 + level!!.random.nextDouble() * 0.6) + + return JobContainer.success( + RecyclerJob( + (actualMatter / (MachinesConfig.MatterRecycler.MATTER_PER_TICK * (1.0 + upgrades.speedBonus))).toDouble(), + MachinesConfig.MatterRecycler.VALUES.energyConsumption * (1.0 + upgrades.speedBonus), + actualMatter + ) + ) } - override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: RecyclerJob): Status { - val receive = job.totalMatter / job.ticks + override fun onJobTick(status: JobStatus, id: Int) { + val job = status.job + val toReceive = job.totalMatter.coerceAtMost(MachinesConfig.MatterRecycler.MATTER_PER_TICK * status.ticksAdvanced * (1.0 + upgrades.speedBonus)) - if (receive.isZero) - return Status.SUCCESS + if (toReceive.isZero) + return status.success() - val received = matter.receiveMatterInner(receive, true) - - if (receive != received) - return Status.FAILURE_MATTER - - matter.receiveMatterInner(receive, false) + val received = matter.receiveMatter(toReceive, false) + status.scale(received / toReceive) job.totalMatter -= received - return Status.SUCCESS } - fun tick() { - basicTicker() + override fun tick() { + super.tick() - val graph = matterNode.graph as MatterNetworkGraph? ?: return + val graph = matterNode.graph as MatterGraph? ?: return val received = graph.receiveMatter(matter.storedMatter, false) if (!received.isZero) { - matter.extractMatterInner(received, false) - } - } - - companion object { - private var _CAPACITY: DecimalConfigValue by WriteOnce() - private var _POWER_CONSUMPTION: DecimalConfigValue by WriteOnce() - private var _TICKS_PER_MATTER: ConfigValue by WriteOnce() - - var ENERGY_CONFIG: ConciseBalanceValues by WriteOnce() - private set - - private val CAPACITY get() = _CAPACITY.get() - private val POWER_CONSUMPTION get() = _POWER_CONSUMPTION.get() - private val TICKS_PER_MATTER: Double get() = _TICKS_PER_MATTER.get() - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.MATTER_RECYCLER) - - ENERGY_CONFIG = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(80_000)) - - _CAPACITY = builder.defineDecimal("matterCapacity", Decimal(2_000), Decimal.ONE) - _POWER_CONSUMPTION = builder.defineDecimal("powerConsumption", Decimal(80), Decimal.ONE) - _TICKS_PER_MATTER = builder.define("ticksPerMatter", 2.0) - - builder.pop() + matter.extractMatter(received, false) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt index ca1961477..e95210e30 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt @@ -1,330 +1,204 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.block.IDroppableContainer +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.matter.* +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHandler -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.menu.MatterReplicatorMenu -import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.UUIDCodec +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MItems +import java.util.* class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryWorkerBlockEntity(MBlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_, { - try { - ReplicatorJob(it) - } catch(err: NoSuchElementException) { - null - } - }), IMatterGraphNode, IDroppableContainer { - - class ReplicatorJob : ItemJob { - val matterPerTick: Decimal - val task: ReplicationTask - var matterValue: Decimal - val pattern: PatternState? - val asDust: Boolean - - constructor(tag: CompoundTag) : super(tag) { - matterPerTick = tag.getDecimal(MATTER_PER_TICK_KEY) - matterValue = tag.getDecimal(MATTER_VALUE_KEY) - pattern = tag.map(PATTERN_KEY, PatternState::deserializeNBT) - asDust = tag.getBoolean(AS_DUST_KEY) - task = tag.map(TASK_KEY, ReplicationTask::deserializeNBT) ?: throw NoSuchElementException("Unable to deserialize matter task") - } - - constructor( - itemStack: ItemStack, - matterPerTick: Decimal, - task: ReplicationTask, - matterValue: Decimal, - pattern: PatternState?, - asDust: Boolean, - ticks: Double, - ) : super(itemStack, ticks, BASE_CONSUMPTION) { - this.matterPerTick = matterPerTick - this.task = task - this.matterValue = matterValue - this.pattern = pattern - this.asDust = asDust - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[MATTER_PER_TICK_KEY] = this.matterPerTick - it[TASK_KEY] = this.task.serializeNBT() - it[MATTER_VALUE_KEY] = this.matterValue - - if (this.pattern != null) - it[PATTERN_KEY] = this.pattern.serializeNBT() - - it[AS_DUST_KEY] = this.asDust - } - } + MatteryWorkerBlockEntity(MBlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_, ReplicatorJob.CODEC) { + class ReplicatorJob( + itemStack: ItemStack, + val matterPerTick: Decimal, + val task: UUID, + var matterValue: Decimal, + val pattern: Optional, + val asDust: Boolean, + ticks: Double, + ) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_REPLICATOR.energyConsumption) { companion object { - const val MATTER_PER_TICK_KEY = "matterPerTick" - const val MATTER_VALUE_KEY = "matterValue" - const val PATTERN_KEY = "pattern" - const val AS_DUST_KEY = "asDust" - const val TASK_KEY = "task" + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + ItemStack.CODEC.fieldOf("Item").forGetter(ReplicatorJob::itemStack), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterPerTick").forGetter(ReplicatorJob::matterPerTick), + UUIDCodec.fieldOf("task").forGetter(ReplicatorJob::task), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(ReplicatorJob::matterValue), + PatternState.CODEC.optionalFieldOf("pattern").forGetter(ReplicatorJob::pattern), + Codec.BOOL.fieldOf("asDust").forGetter(ReplicatorJob::asDust), + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("Ticks").forGetter(ReplicatorJob::ticks), + ).apply(it, ::ReplicatorJob) + } + } } } - override val energy = WorkerEnergyStorage(this::powerLevelUpdated, ENERGY_VALUES) - override val matterNode = Graph6Node(this) - private val resolverNode = LazyOptional.of { this } + override val upgrades = UpgradeContainer(this::markDirtyFast, 3, UpgradeType.REPLICATOR) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_REPLICATOR))) + val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_REPLICATOR::matterCapacity))) + val outputContainer = MatteryContainer(::itemContainerUpdated, 3).also(::addDroppableContainer) + val dustContainer = MatteryContainer(::itemContainerUpdated, 2).also(::addDroppableContainer) - val matter = MatterHandlerImpl( - this::matterLevelUpdated, - MatterDirection.RECEIVE, - ::MATTER_CAPACITY - ) + val energyConfig = ConfigurableEnergy(energy) + val itemConfig = ConfigurableItemHandler(output = CombinedItemHandler(outputContainer.handler(HandlerFilter.OnlyOut), dustContainer.handler(HandlerFilter.OnlyOut))) - val container = MatteryContainer(this::itemContainerUpdated, 5) - private val itemHandler = container.handler(MatteryContainerHandler.OnlyOut) + val matterNode = object : MatterNode() { + override fun getMatterHandler(): IMatterStorage { + return matter + } - override val defaultDisplayName: Component - get() = MBlocks.MATTER_REPLICATOR.name + override fun onMatterTaskCreated(task: ReplicationTask) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.OBSERVING) + } - override val droppableContainer: Container - get() = container + override fun onMatterTaskUpdated(newState: ReplicationTask, oldState: ReplicationTask) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.OBSERVING) + } + + override fun onPatternAdded(state: PatternState) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.OBSERVING) + } + } + + init { + exposeGlobally(MatteryCapability.MATTER, matter) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::matter, MATTER_STORAGE_KEY) + savetables.stateful(::outputContainer, INVENTORY_KEY) + savetables.stateful(::dustContainer) + savetables.stateful(::upgrades) + } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterReplicatorMenu(containerID, inventory, this) } - override fun onJobFinish(job: ReplicatorJob): Status { + override fun onJobFinish(status: JobStatus, id: Int) { + val job = status.job + if (job.asDust) { - job.matterValue = moveMatterAsDustIntoContainer(job.matterValue, container, OUTPUT_DUST_MAIN, OUTPUT_DUST_STACKING) + job.matterValue = MItems.MATTER_DUST.moveIntoContainer(job.matterValue, dustContainer, 0, 1) if (!job.matterValue.isZero) { - return Status.FAILURE_WAIT + return status.throttle() } - (matterNode.graph as MatterNetworkGraph?)?.notifyTaskCompletion(job.task.id) - return Status.SUCCESS - } + matterNode.graph.notifyTaskCompletion(job.task) + } else { + if (!outputContainer.fullyAddItem(job.itemStack)) { + return status.noItem() + } - if (!container.fullyAddItem(job.itemStack, FIRST_ACTUAL_OUTPUT_SLOT .. LAST_ACTUAL_OUTPUT_SLOT)) { - return Status.FAILURE_ITEM - } - - (matterNode.graph as MatterNetworkGraph?)?.notifyTaskCompletion(job.task.id) - return Status.SUCCESS - } - - override fun onMatterTaskCreated(task: IReplicationTask<*>) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false - } - } - - override fun > onMatterTaskUpdated(newState: T, oldState: T) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false - } - } - - override fun onPatternAdded(state: IPatternState) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false + matterNode.graph.notifyTaskCompletion(job.task) } } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } - override fun jobUpdated(oldJob: ReplicatorJob?, newJob: ReplicatorJob?) { - visualItemStack = newJob?.itemStack ?: ItemStack.EMPTY + override fun jobUpdated(new: ReplicatorJob?, old: ReplicatorJob?, id: Int) { + visualItemStack = new?.itemStack ?: ItemStack.EMPTY visualProgress = 0f } var visualItemStack by synchronizer.item(observe = false) private set - var visualProgress by synchronizer.float() + var visualProgress by synchronizer.float().property private set var renderRotation = 0f var lastRender = 0L var particleRenderScore = 0L - override fun computeNextJob(): Pair { - if (energy.batteryLevel < BASE_CONSUMPTION) { - return null to IdleReason.POWER + override fun tick() { + super.tick() + + if (jobEventLoops[0].currentJob == null && matter.storedMatter.isPositive) { + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + } + } + + override fun computeNextJob(id: Int): JobContainer { + if (energy.batteryLevel < MachinesConfig.MATTER_REPLICATOR.energyConsumption) { + return JobContainer.noEnergy() } - val graph = matterNode.graph as MatterNetworkGraph? ?: return null to null - val allocation = graph.allocateTask(simulate = false) ?: return null to IdleReason.OBSERVING + val allocation = matterNode.graph.allocateTask(simulate = false) ?: return JobContainer.observe() val stack = allocation.task.stack(1) val matter = MatterManager.get(stack) // ???????? - if (!matter.hasMatterValue) return null to null + if (!matter.hasMatterValue) return JobContainer.failure() val ticks = matter.complexity - return ReplicatorJob( + return JobContainer.success(ReplicatorJob( itemStack = stack, matterPerTick = matter.matter / ticks, - task = allocation.task.asImmutable(), + task = allocation.task.id, matterValue = matter.matter, - pattern = allocation.pattern?.asImmutable(), - asDust = (level?.random?.nextDouble() ?: 1.0) > (allocation.pattern?.researchPercent ?: 2.0), + pattern = Optional.ofNullable(allocation.pattern), + asDust = (level?.random?.nextDouble() ?: 1.0) * upgrades.failureMultiplier > (allocation.pattern?.researchPercent ?: 2.0), ticks = ticks, - ) to null + )) } - override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: ReplicatorJob): Status { - val drainPerTick = currentJob!!.matterPerTick * ticksAdvanced - val graph = matterNode.graph as MatterNetworkGraph? ?: return Status.FAILURE_WAIT_FAST + override fun onJobTick(status: JobStatus, id: Int) { + val job = status.job + val drainPerTick = job.matterPerTick * status.ticksAdvanced - if (matter.extractMatterInner(drainPerTick, true) < drainPerTick) { - // в машине недостаточно материи + if (matter.extractMatter(drainPerTick, true) < drainPerTick) { + val toDrain = (drainPerTick * Decimal(200)) + .coerceAtMost(job.matterPerTick * (status.ticks - status.workTicks + status.ticksAdvanced)) + .coerceAtLeast(Decimal.ONE) + .coerceAtMost(matter.missingMatter) - if (drainPerTick > matter.maxStoredMatter) { - // в тик требуется больше материи, чем её может хранить репликатор - val toExtract = drainPerTick - matter.extractMatterInner(drainPerTick, true) - val drain = graph.extractMatter(toExtract, true) - - if (drain != toExtract) { - // недостаточно материи в сети - return Status.FAILURE_MATTER - } - - // достаточно материи в сети + внутри машины - matter.extractMatterInner(drainPerTick, false) - graph.extractMatter(drain, false) - return Status.SUCCESS - } else { - // в тик требуется меньше материи, чем её может хранить репликатор - // примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше - val drain = graph.extractMatter(matter.missingMatter.coerceAtMost(drainPerTick * DRAIN_MULT), true) - - if (drain.isZero) { - // в сети нет материи - return Status.FAILURE_MATTER - } - - val received = matter.receiveMatterOuter(drain, false) - graph.extractMatter(received, false) - - // получили материю, проверяем возможность работы - if (matter.extractMatterInner(drainPerTick, true) >= drainPerTick) { - matter.extractMatterInner(drainPerTick, false) - return Status.SUCCESS - } else { - // :( - return Status.FAILURE_WAIT - } - } + matter.receiveMatter(matterNode.graph.extractMatter(toDrain, false), false) } - // в машине достаточно материи - matter.extractMatterInner(drainPerTick, false) - visualProgress = workProgress - return Status.SUCCESS - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - nbt[MATTER_STORAGE_KEY] = matter.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - nbt.map(MATTER_STORAGE_KEY, matter::deserializeNBT) - } - - private var valid = true - - override fun invalidateCaps() { - super.invalidateCaps() - valid = false - itemHandler.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - itemHandler.revive() - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == MatteryCapability.MATTER_NODE) return resolverNode.cast() - if (cap == ForgeCapabilities.ITEM_HANDLER) return itemHandler.get().cast() - } - - return super.getCapability(cap, side) - } - - companion object { - private val BASE_CONSUMPTION get() = _BASE_CONSUMPTION.get() - private val DRAIN_MULT get() = _DRAIN_MULT.get() - private val MATTER_CAPACITY get() = _MATTER_CAPACITY.get() - - private var _BASE_CONSUMPTION: DecimalConfigValue by WriteOnce() - private var _DRAIN_MULT: DecimalConfigValue by WriteOnce() - private var _MATTER_CAPACITY: DecimalConfigValue by WriteOnce() - - var ENERGY_VALUES: ConciseBalanceValues by WriteOnce() - private set - - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.MATTER_REPLICATOR) - - ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(200_000), throughput = Decimal(4_000)) - - _BASE_CONSUMPTION = builder.defineDecimal("basePowerConsumption", Decimal(400), Decimal.ONE) - _DRAIN_MULT = builder.comment("How much 'ticks' of replication should replicator drain matter from network when running low on internal matter buffer. This is ultimately a performance value.").defineDecimal("drainMultiplier", Decimal(200), Decimal.ONE) - _MATTER_CAPACITY = builder.defineDecimal("matterCapacity", Decimal(400), Decimal.ONE_TENTH) - - builder.pop() - } - - const val FIRST_ACTUAL_OUTPUT_SLOT = 0 - const val LAST_ACTUAL_OUTPUT_SLOT = 2 - const val OUTPUT_DUST_MAIN = 3 - const val OUTPUT_DUST_STACKING = 4 + status.scale(matter.extractMatter(drainPerTick, false) / drainPerTick) + visualProgress = status.workProgress } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt index b9bb50646..dc22e5692 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt @@ -1,145 +1,95 @@ package ru.dbotthepony.mc.otm.block.entity.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.block.IDroppableContainer +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import ru.dbotthepony.mc.otm.capability.BlockEnergyStorageImpl import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IPatternState +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.matter.PatternState +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainerHooks -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.defineDecimal -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.menu.MatterScannerMenu +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.matter.MatterManager import java.util.* import kotlin.math.pow class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryWorkerBlockEntity(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ::ItemJob), IMatterGraphNode, IDroppableContainer { + MatteryWorkerBlockEntity(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ItemJob.CODEC) { - val container = MatteryContainer(this::itemContainerUpdated, 1) - override val energy = WorkerEnergyStorage(this::powerLevelUpdated, ENERGY_VALUES) + override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC) + val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_SCANNER))) - private val itemHandler = container.handler(object : MatteryContainerHooks { + val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { return MatterManager.canDecompose(stack) } override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return isIdling + return jobEventLoops[0].isIdling } - }) + })) - override val droppableContainer: Container - get() = container + val energyConfig = ConfigurableEnergy(energy) - // IMatterGraphNode - override fun onPatternAdded(state: IPatternState) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false + val matterNode = object : MatterNode() { + override fun onPatternAdded(state: PatternState) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN) + } + + override fun onPatternRemoved(state: PatternState) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN) + } + + override fun onPatternUpdated(newState: PatternState, oldState: PatternState) { + jobEventLoops[0].notify(MachineJobEventLoop.IdleReason.PATTERN) } } - override fun onPatternRemoved(state: IPatternState) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false - } - } + init { + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) - override fun onPatternUpdated(newState: IPatternState, oldState: IPatternState) { - if (idleReason == IdleReason.OBSERVING) { - isIdling = false - } - } - // /IMatterGraphNode - - private var valid = true - override val matterNode: Graph6Node = Graph6Node(this) - private var resolverNode = LazyOptional.of { this } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == ForgeCapabilities.ITEM_HANDLER) return itemHandler.get().cast() - if (cap == MatteryCapability.MATTER_NODE) return resolverNode.cast() - } - - return super.getCapability(cap, side) + savetables.stateful(::container, INVENTORY_KEY) + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::upgrades) } override fun invalidateCaps() { super.invalidateCaps() - valid = false - itemHandler.invalidate() - resolverNode.invalidate() - matterNode.destroy(::MatterNetworkGraph) - } - - override fun reviveCaps() { - super.reviveCaps() - valid = true - itemHandler.revive() - resolverNode = LazyOptional.of { this } + matterNode.isValid = false } override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override val defaultDisplayName: Component - get() = MBlocks.MATTER_SCANNER.name - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterScannerMenu(containerID, inventory, this) } - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt.put(INVENTORY_KEY, container.serializeNBT()) - } + override fun onJobFinish(status: JobStatus, id: Int) { + val stack = status.job.itemStack + if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return status.success() - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } + var findState: PatternState? = null - override fun onJobFinish(job: ItemJob): Status { - val grid = matterNode.graph as MatterNetworkGraph? ?: return Status.FAILURE_WAIT - - val stack = job.itemStack - if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return Status.SUCCESS - - var findState: IPatternState? = null - - for (state in grid.patterns.filter { it.item === stack.item }) { + for (state in matterNode.graph.patterns.filter { it.item === stack.item }) { if (findState == null && state.researchPercent < 1.0) { findState = state } else if (findState != null && findState.researchPercent < state.researchPercent && state.researchPercent < 1.0) { @@ -149,37 +99,33 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val researchAdvance = MatterManager.getResearchAdvance(stack.item) - val new: IPatternState = + val new: PatternState = if (findState != null) { PatternState(findState.id, stack.item, (findState.researchPercent + researchAdvance).coerceAtMost(1.0)) } else { PatternState(UUID.randomUUID(), stack.item, researchAdvance) } - if (!grid.insertPattern(new, onlyUpdate = false, simulate = false).isFailed) { - return Status.SUCCESS - } else { - return Status.FAILURE_WAIT + if (matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = false).isFailed) { + status.throttle() } } - override fun computeNextJob(): Pair { + override fun computeNextJob(id: Int): JobContainer { if (energy.batteryLevel.isZero) { - return null to IdleReason.POWER + return JobContainer.noEnergy() } - val grid = matterNode.graph as MatterNetworkGraph? ?: return null to null - val stack = container.getItem(0) - if (stack.isEmpty || !MatterManager.canDecompose(stack)) return null to IdleReason.ITEM + if (stack.isEmpty || !MatterManager.canDecompose(stack)) return JobContainer.noItem() - var findState: IPatternState? = null + var findState: PatternState? = null - for (state in grid.patterns.filter { it.item === stack.item }) { + for (state in matterNode.graph.patterns.filter { it.item === stack.item }) { if (state.researchPercent < 1.0) { findState = state } else if (state.researchPercent >= 1.0) { - return null to IdleReason.ITEM + return JobContainer.noItem() } else if (findState != null && findState.researchPercent < state.researchPercent) { findState = state } @@ -187,46 +133,42 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val researchAdvance = MatterManager.getResearchAdvance(stack.item) - val new: IPatternState = + val new: PatternState = if (findState != null) { PatternState(findState.id, stack.item, (findState.researchPercent + researchAdvance).coerceAtMost(1.0)) } else { PatternState(UUID.randomUUID(), stack.item, researchAdvance) } - if (!grid.insertPattern(new, onlyUpdate = false, simulate = true).isFailed) { + if (!matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = true).isFailed) { val copy = stack.copy().also { it.count = 1 } stack.shrink(1) container.setChanged() val complexity = MatterManager.get(copy).complexity - return ItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)), BASE_CONSUMPTION) to null + return JobContainer.success(ItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)) * MachinesConfig.MATTER_SCANNER.workTimeMultiplier, MachinesConfig.MATTER_SCANNER.energyConsumption)) } - return null to IdleReason.ITEM + return JobContainer.noItem() } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) { - MatterNetworkGraph.discoverFull(this, matterNode) - } + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } - companion object { - private val BASE_CONSUMPTION get() = _BASE_CONSUMPTION.get() - private var _BASE_CONSUMPTION: DecimalConfigValue by WriteOnce() - var ENERGY_VALUES: ConciseBalanceValues by WriteOnce() - private set + override fun jobUpdated(new: ItemJob?, old: ItemJob?, id: Int) { + visualItemStack = new?.itemStack ?: ItemStack.EMPTY + visualProgress = 0f + } - fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.push(MNames.MATTER_SCANNER) + var visualItemStack by synchronizer.item(observe = false) + private set - ENERGY_VALUES = BlockEnergyStorageImpl.makeConfigEntry(builder, capacity = Decimal(80_000), throughput = Decimal(400)) - _BASE_CONSUMPTION = builder.defineDecimal("baseConsumption", Decimal(40), Decimal.ONE) + var visualProgress by synchronizer.float().property + private set - builder.pop() - } + override fun onJobTick(status: JobStatus, id: Int) { + visualProgress = status.workProgress } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt index 07a081342..af25c1045 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.block.entity.matter -import com.google.common.collect.Streams import javax.annotation.ParametersAreNonnullByDefault import net.minecraft.core.BlockPos import net.minecraft.world.level.block.state.BlockState @@ -8,58 +7,44 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.block.matter.PatternStorageBlock -import net.minecraft.nbt.CompoundTag -import net.minecraftforge.common.util.LazyOptional import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu -import ru.dbotthepony.mc.otm.menu.PatternStorageMenu +import ru.dbotthepony.mc.otm.menu.matter.PatternStorageMenu import net.minecraft.MethodsReturnNonnullByDefault -import net.minecraft.core.Direction -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.Container import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity -import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.core.iterator -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.core.map +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.core.collect.filterNotNull +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.filterNotNull +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.core.set -import java.util.ArrayList import java.util.stream.Stream @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryBlockEntity(MBlockEntities.PATTERN_STORAGE, p_155229_, p_155230_), IMatterGraphNode, IPatternStorage, IDroppableContainer { + MatteryDeviceBlockEntity(MBlockEntities.PATTERN_STORAGE, p_155229_, p_155230_), IPatternStorage { - override val matterNode = Graph6Node(this) - private val resolverPatterns = LazyOptional.of { this } - private val resolverNode = LazyOptional.of { this } + val matterNode = SimpleMatterNode(patterns = this) - val patternContainer: MatteryContainer = object : MatteryContainer(this::setChanged, 8) { + val container: MatteryContainer = object : MatteryContainer(this::setChanged, 8) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - val grid = matterNode.graph as MatterNetworkGraph? - - if (grid != null && !ItemStack.isSameItemSameTags(new, old)) { + if (!ItemStack.isSameItemSameTags(new, old)) { if (!old.isEmpty) { old.getCapability(MatteryCapability.PATTERN).ifPresent { cap: IPatternStorage -> - cap.patterns.forEach { grid.onPatternRemoved(it) } + cap.patterns.forEach { matterNode.graph.onPatternRemoved(it) } } } if (!new.isEmpty) { new.getCapability(MatteryCapability.PATTERN).ifPresent { cap: IPatternStorage -> - cap.patterns.forEach { grid.onPatternAdded(it) } + cap.patterns.forEach { matterNode.graph.onPatternAdded(it) } } } @@ -69,10 +54,8 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : super.setChanged(slot, new, old) } - override fun getMaxStackSize(): Int { - return 1 - } - } + override fun getMaxStackSize(): Int = 1 + }.also(::addDroppableContainer) private fun updateBlockstate() { val level = level ?: return @@ -82,7 +65,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : for (i in 0..7) { state = state.setValue( PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[i], - this.patternContainer.getItem(i).getCapability(MatteryCapability.PATTERN).isPresent + this.container.getItem(i).getCapability(MatteryCapability.PATTERN).isPresent ) } @@ -91,79 +74,41 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } } - private val resolverItem = - patternContainer.handler { slot: Int, stack: ItemStack -> stack.getCapability(MatteryCapability.PATTERN).isPresent } + val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(HandlerFilter.IsPattern.and(HandlerFilter.OnlyIn))) - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = patternContainer.serializeNBT() + override fun setLevel(level: Level) { + super.setLevel(level) + matterNode.discover(this) } - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, patternContainer::deserializeNBT) - } - - override val droppableContainer: Container - get() = patternContainer - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - MatterNetworkGraph.discoverFull(this, matterNode) - } - - override fun getPatternHandler(): IPatternStorage { - return this - } - - private var valid = true - override fun invalidateCaps() { super.invalidateCaps() - valid = false - resolverItem.invalidate() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun reviveCaps() { - super.reviveCaps() - valid = true - resolverItem.revive() + init { + exposeGlobally(MatteryCapability.PATTERN, this) + exposeGlobally(MatteryCapability.MATTER_NODE, matterNode) + + savetables.stateful(::container, INVENTORY_KEY) } - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid) { - if (cap == MatteryCapability.PATTERN) return resolverPatterns.cast() - if (cap == MatteryCapability.MATTER_NODE) return resolverNode.cast() - if (cap == ForgeCapabilities.ITEM_HANDLER) return resolverItem.get().cast() - } - - return super.getCapability(cap, side) - } - - override val defaultDisplayName: Component - get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return PatternStorageMenu(containerID, inventory, this) } - override val patterns: Stream get() { - val streams = ArrayList>() - - for (provider in this.patternContainer.iterator(MatteryCapability.PATTERN)) { - streams.add(provider.second.patterns) - } - - return Streams.concat(*streams.toTypedArray()) + override val patterns: Stream get() { + return container.stream() + .filter { it.isNotEmpty } + .map { it.getCapability(MatteryCapability.PATTERN).orNull() } + .filterNotNull() + .flatMap { it.patterns } } override val patternCapacity: Int get() { var stored = 0L - for ((_, pattern) in this.patternContainer.iterator(MatteryCapability.PATTERN)) + for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull()) stored += pattern.patternCapacity.toLong() return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt() @@ -172,7 +117,7 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : override val storedPatterns: Int get() { var stored = 0L - for ((_, pattern) in this.patternContainer.iterator(MatteryCapability.PATTERN)) + for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull()) stored += pattern.storedPatterns.toLong() return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt() @@ -180,25 +125,21 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : override fun setRemoved() { super.setRemoved() - matterNode.destroy(::MatterNetworkGraph) + matterNode.isValid = false } - override fun insertPattern(pattern: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { - for (pair in this.patternContainer.iterator(MatteryCapability.PATTERN)) { - val status = pair.second.insertPattern(pattern, onlyUpdate, simulate) + override fun insertPattern(pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { + for (spattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN).orNull() }.filterNotNull()) { + val status = spattern.insertPattern(pattern, onlyUpdate, simulate) if (!status.isFailed) { if (!simulate) { setChanged() - val graph = matterNode.graph as MatterNetworkGraph? - - if (graph != null) { - if (status.isInserted) { - graph.onPatternAdded(status.newState!!) - } else { - graph.onPatternUpdated(status.newState!!, status.oldState!!) - } + if (status.isInserted) { + matterNode.graph.onPatternAdded(status.newState!!) + } else { + matterNode.graph.onPatternUpdated(status.newState!!, status.oldState!!) } } @@ -208,8 +149,4 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : return PatternInsertFailure } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.pattern_storage") - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt index 4ab254faa..ca660924f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt @@ -1,103 +1,93 @@ package ru.dbotthepony.mc.otm.block.entity.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.graph.storage.StorageNode import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.menu.DriveRackMenu -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.storage.* +import ru.dbotthepony.mc.otm.storage.optics.priority +import ru.dbotthepony.mc.otm.storage.optics.powered +import ru.dbotthepony.mc.otm.storage.optics.flow -class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, p_155229_, p_155230_) { - override val energy = WorkerEnergyStorage(this, ServerConfig.DRIVE_RACK) +class DriveRackBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, blockPos, blockState) { + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.DRIVE_RACK)) + val cell = StorageNode(energy) + val energyConfig = ConfigurableEnergy(energy) + + var insertPriority = 0 + set(value) { + field = value + markDirtyFast() + } + + var extractPriority = 0 + set(value) { + field = value + markDirtyFast() + } + + var mode = FlowDirection.BI_DIRECTIONAL + set(value) { + field = value + markDirtyFast() + } val container: MatteryContainer = object : MatteryContainer(this::setChanged, 4) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { super.setChanged(slot, new, old) - old.getCapability(MatteryCapability.DRIVE).ifPresent { - cell.computeIfAbsent(it.storageType) { - PoweredVirtualComponent(it, energy) - }.remove(it) + old.getCapability(MatteryCapability.DRIVE).ifPresentK { + cell.removeStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode)) } - new.getCapability(MatteryCapability.DRIVE).ifPresent { - cell.computeIfAbsent(it.storageType) { - PoweredVirtualComponent(it, energy) - }.add(it) + new.getCapability(MatteryCapability.DRIVE).ifPresentK { + cell.addStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode)) } } + }.also(::addDroppableContainer) + + init { + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::container, INVENTORY_KEY) + exposeGlobally(MatteryCapability.STORAGE_NODE, cell) + savetables.int(::insertPriority) + savetables.int(::extractPriority) + savetables.enum(::mode, map = FlowDirection::valueOf) + + redstoneControl.addListener { + cell.isDetached = it + } } - val cell = BasicStorageGraphNode(energy) - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) + override fun setLevel(level: Level) { + super.setLevel(level) + cell.discover(this) } - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - StorageNetworkGraph.discoverFull(this, cell.storageNode) - } - - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() cell.tickEnergyDemanding() } - override val defaultDisplayName: Component - get() = MBlocks.DRIVE_RACK.name - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return DriveRackMenu(containerID, inventory, this) } - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - return if (cap === MatteryCapability.STORAGE_NODE) { - cell.get().cast() - } else super.getCapability(cap, side) - } - - override fun invalidateCaps() { - super.invalidateCaps() - cell.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - cell.revive() - } - override fun setRemoved() { super.setRemoved() - cell.destroy(level) + cell.isValid = false } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt index 3f17d6fd1..d6dee99b5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt @@ -1,97 +1,100 @@ package ru.dbotthepony.mc.otm.block.entity.storage -import net.minecraft.MethodsReturnNonnullByDefault +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.core.BlockPos -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.world.Container +import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.block.IDroppableContainer -import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity -import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.menu.DriveViewerMenu +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.registry.MBlocks -import javax.annotation.ParametersAreNonnullByDefault +import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu +import java.util.UUID -class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, p_155229_, p_155230_), IDroppableContainer { - override fun setChanged() { - super.setChanged() +class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, blockPos, blockState) { + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyUpdated, MachinesConfig.DRIVE_VIEWER)) + val energyConfig = ConfigurableEnergy(energy) - tickOnceServer { - var state = blockState - - if (container.getItem(0).getCapability(MatteryCapability.DRIVE).isPresent && energy.batteryLevel >= OverdriveThatMatters.INSTANCE.ITEM_STORAGE().energyPerOperation) { - state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING) - } else { - state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE) - } - - if (state !== blockState) { - it.setBlock(blockPos, state, Block.UPDATE_CLIENTS) - } - } - } - - override val energy = WorkerEnergyStorage(this, ServerConfig.DRIVE_VIEWER) - - val container: MatteryContainer = object : MatteryContainer(this::setChanged, 1) { + val container: MatteryContainer = object : MatteryContainer(this::markDirtyFast, 1) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { super.setChanged(slot, new, old) - tickOnceServer { - if (!isRemoved) { - var state = blockState + val level = level - if (new.getCapability(MatteryCapability.DRIVE).isPresent) { - state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, true) - } else { - state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, false) + if (level is ServerLevel) { + tickList.once { + val isPresent = new.getCapability(MatteryCapability.DRIVE).isPresent + var state = this@DriveViewerBlockEntity.blockState.setValue(DriveViewerBlock.DRIVE_PRESENT, isPresent) + + if (!isPresent) { + state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE) + } else if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone) { + state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING) } - if (state !== blockState) { - it.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + if (state !== this@DriveViewerBlockEntity.blockState) { + level.setBlock(this@DriveViewerBlockEntity.blockPos, state, Block.UPDATE_CLIENTS) } } } } + }.also(::addDroppableContainer) + + interface ISettings { + var sorting: ItemStorageStackSorter + var isAscending: Boolean } - override val droppableContainer: Container - get() = container - override val defaultDisplayName: Component - get() = MBlocks.DRIVE_VIEWER.name + inner class Settings : ISettings { + override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT + set(value) { + field = value + markDirtyFast() + } - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { + override var isAscending: Boolean = true + set(value) { + field = value + markDirtyFast() + } + } + + val settings = Object2ObjectOpenHashMap() + + fun getSettingsFor(player: Player): ISettings { + return settings.computeIfAbsent(player.uuid, Object2ObjectFunction { Settings() }) + } + + private fun energyUpdated() { + val state = blockState.setValue(WorkerState.SEMI_WORKER_STATE, if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone && blockState[DriveViewerBlock.DRIVE_PRESENT]) WorkerState.WORKING else WorkerState.IDLE) + + if (state != blockState) { + level?.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + } else { + markDirtyFast() + } + } + + init { + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::container, INVENTORY_KEY) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return DriveViewerMenu(containerID, inventory, this) } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[INVENTORY_KEY] = container.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(INVENTORY_KEY, container::deserializeNBT) - } - - fun tick() { - batteryChargeLoop() - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt index 62bd31287..062e42e86 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt @@ -1,168 +1,72 @@ package ru.dbotthepony.mc.otm.block.entity.storage +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.core.NonNullList import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.StringTag -import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.inventory.CraftingContainer import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.CraftingRecipe import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.common.ForgeHooks -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.util.INBTSerializable -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.network.NetworkEvent -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.ServerConfig import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.get -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.core.ifHas -import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu -import ru.dbotthepony.mc.otm.network.MatteryPacket +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.graph.storage.StorageNode +import ru.dbotthepony.mc.otm.graph.storage.StorageGraph import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.getEnum -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.registry.MBlocks +import ru.dbotthepony.mc.otm.core.nbt.mapString +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu import ru.dbotthepony.mc.otm.storage.* +import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent import java.math.BigInteger import java.util.* -import java.util.function.Supplier import kotlin.collections.HashMap +import ru.dbotthepony.mc.otm.client.render.Widgets8 +import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.util.slotIterator +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter +import ru.dbotthepony.mc.otm.core.value -class ItemMonitorPlayerSettings : INBTSerializable, MatteryPacket { - enum class IngredientPriority(val component: Component) { - // Refill everything from system - SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system")), - - // Refill everything from player's inventory - INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory")), - - // Refill everything from system, if can't refill from player's inventory - SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first")), - - // Refill everything from player's inventory, if can't refill from system - INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first")), - - // Do not refill (?) - DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not")), - } - - enum class ResultTarget(val component: Component) { - // Everything goes into storage system - ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system")), - - // Result goes to player inventory - // Crafting remainder goes to storage system - MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed")), - - // Everything goes into player inventory - ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory")), - } - - enum class Amount(val component: Component) { - ONE(TranslatableComponent("otm.gui.item_monitor.amount.one")), - STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack")), - FULL(TranslatableComponent("otm.gui.item_monitor.amount.full")) - } - - var ingredientPriority = IngredientPriority.SYSTEM - var resultTarget = ResultTarget.MIXED - var craftingAmount = Amount.STACK - - override fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it[INGREDIENT_PRIORITY_KEY] = ingredientPriority.name - it[RESULT_TARGET_KEY] = resultTarget.name - it[QUICK_CRAFT_AMOUNT_KEY] = craftingAmount.name - } - } - - override fun deserializeNBT(nbt: CompoundTag) { - ingredientPriority = nbt.getEnum(INGREDIENT_PRIORITY_KEY) - resultTarget = nbt.getEnum(RESULT_TARGET_KEY) - craftingAmount = nbt.getEnum(QUICK_CRAFT_AMOUNT_KEY) - } - - fun read(buff: FriendlyByteBuf) { - ingredientPriority = buff.readEnum(IngredientPriority::class.java) - resultTarget = buff.readEnum(ResultTarget::class.java) - craftingAmount = buff.readEnum(Amount::class.java) - } - - fun read(other: ItemMonitorPlayerSettings) { - ingredientPriority = other.ingredientPriority - resultTarget = other.resultTarget - craftingAmount = other.craftingAmount - } - - override fun write(buff: FriendlyByteBuf) { - buff.writeEnum(ingredientPriority) - buff.writeEnum(resultTarget) - buff.writeEnum(craftingAmount) - } - - private fun playClient() { - val ply = minecraft.player ?: throw IllegalStateException("Player is missing") - val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings but container is missing or not of right type ({})!", ply.containerMenu) - - container.settings.read(this) - } - - // spectators are allowed because they change their own setting which is invisible to everyone else - private fun playServer(ply: ServerPlayer) { - val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings from {} but container is missing or not of right type ({})!", ply, ply.containerMenu) - container.settings.read(this) - (container.tile as ItemMonitorBlockEntity).setChangedLight() - } - - override fun play(context: Supplier) { - context.get().packetHandled = true - val ply = context.get().sender - - if (ply != null) { - playServer(ply) - } else { - playClient() - } - } - - companion object { - fun read(buff: FriendlyByteBuf): ItemMonitorPlayerSettings { - return ItemMonitorPlayerSettings().also { it.read(buff) } - } - - private val LOGGER = LogManager.getLogger() - const val INGREDIENT_PRIORITY_KEY = "ingredientPriority" - const val RESULT_TARGET_KEY = "resultTarget" - const val QUICK_CRAFT_AMOUNT_KEY = "quickCraftAmount" - } +interface IItemMonitorPlayerSettings { + var ingredientPriority: ItemMonitorPlayerSettings.IngredientPriority + var resultTarget: ItemMonitorPlayerSettings.ResultTarget + var craftingAmount: ItemMonitorPlayerSettings.Amount + var sorting: ItemStorageStackSorter + var ascendingSort: Boolean } -private fun takeOne(inventory: Inventory, item: ItemStack): Boolean { - for (i in 0 until inventory.containerSize) { - if (!inventory[i].isEmpty && ItemStack.isSameItemSameTags(inventory[i], item)) { - inventory.removeItem(i, 1) +private fun takeOne(inventory: Container?, item: ItemStack): Boolean { + val iterator = inventory?.slotIterator() ?: return false + + for (slot in iterator) { + val stack = slot.item + + if (stack.equals(item, false)) { + stack.shrink(1) + slot.setChanged() return true } } @@ -170,113 +74,197 @@ private fun takeOne(inventory: Inventory, item: ItemStack): Boolean { return false } -private fun takeOne(id: UUID?, view: IStorageProvider): Boolean { - val extracted = view.extractStack(id ?: return false, BigInteger.ONE, false) - - if (!extracted.isEmpty) { - return true - } - - return false +private fun takeOne(id: UUID?, view: IStorageProvider?): Boolean { + return view?.extractStack(id ?: return false, BigInteger.ONE, false)?.isNotEmpty == true } -class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, p_155229_, p_155230_), - IStorageEventConsumer { +class ItemMonitorPlayerSettings : INBTSerializable, IItemMonitorPlayerSettings { + interface Setting { + val component: Component + val icon: IGUIRenderable + val winding: UVWindingOrder + } - override val energy = WorkerEnergyStorage(this, ServerConfig.ITEM_MONITOR) + enum class IngredientPriority(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { + // Refill everything from system + SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system"), lazy { Widgets8.WHITE_ARROW_DOWN }, UVWindingOrder.FLIP) { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + return takeOne(id, view) + } + }, - var poweredView: PoweredVirtualComponent? = null + // Refill everything from player's inventory + INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory"), lazy { Widgets8.WHITE_ARROW_DOWN }) { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + return takeOne(inventory, item) + } + }, + + // Refill everything from system, if can't refill from player's inventory + SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first"), lazy { Widgets8.ARROW_SIDEWAYS }, UVWindingOrder.FLIP) { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + return takeOne(id, view) || takeOne(inventory, item) + } + }, + + // Refill everything from player's inventory, if can't refill from system + INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first"), lazy { Widgets8.ARROW_SIDEWAYS }) { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + return takeOne(inventory, item) || takeOne(id, view) + } + }, + + // Do not refill (?) + DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not"), lazy { Widgets8.MINUS }) { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + return false + } + }; + + override val icon: IGUIRenderable by icon + + abstract fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean + } + + enum class ResultTarget(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { + // Everything goes into storage system + ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system"), lazy { Widgets8.ARROW_PAINTED_UP }), + + // Result goes to player inventory + // Crafting remainder goes to storage system + MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed"), lazy { Widgets8.ARROW_SIDEWAYS }), + + // Everything goes into player inventory + ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory"), lazy { Widgets8.ARROW_PAINTED_UP }, UVWindingOrder.FLIP); + + override val icon: IGUIRenderable by icon + } + + enum class Amount(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { + ONE(TranslatableComponent("otm.gui.item_monitor.amount.one"), lazy { Widgets8.ONE }), + STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack"), lazy { Widgets8.S }), + FULL(TranslatableComponent("otm.gui.item_monitor.amount.full"), lazy { Widgets8.F }); + + override val icon: IGUIRenderable by icon + } + + override var ingredientPriority = IngredientPriority.SYSTEM + override var resultTarget = ResultTarget.MIXED + override var craftingAmount = Amount.STACK + override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT + override var ascendingSort: Boolean = false + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["ingredientPriority"] = ingredientPriority.name + it["resultTarget"] = resultTarget.name + it["quickCraftAmount"] = craftingAmount.name + it["sorting"] = sorting.name + it["ascendingSort"] = ascendingSort + } + } + + override fun deserializeNBT(nbt: CompoundTag) { + ingredientPriority = nbt.mapString("ingredientPriority", IngredientPriority::valueOf, IngredientPriority.SYSTEM) + resultTarget = nbt.mapString("resultTarget", ResultTarget::valueOf, ResultTarget.MIXED) + craftingAmount = nbt.mapString("quickCraftAmount", Amount::valueOf, Amount.STACK) + sorting = nbt.mapString("sorting", ItemStorageStackSorter::valueOf, ItemStorageStackSorter.DEFAULT) + ascendingSort = nbt.getBoolean("ascendingSort") + } +} + +class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, blockPos, blockState), IStorageEventConsumer { + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.ITEM_MONITOR)) + val energyConfig = ConfigurableEnergy(energy) + + init { + savetables.stateful(::energy, ENERGY_KEY) + } + + var poweredView: PoweredVirtualComponent? = null private set - val cell = object : BasicStorageGraphNode(energy) { - override fun attachComponents(to: StorageNetworkGraph) { + val cell = object : StorageNode(energy) { + override fun attachComponents(to: StorageGraph) { super.attachComponents(to) - poweredView = PoweredVirtualComponent(to.getVirtualComponent(ITEM_STORAGE), energy) + poweredView = PoweredVirtualComponent(to.getVirtualComponent(StorageStack.ITEMS), ::energy) } - override fun removeComponents(from: StorageNetworkGraph) { + override fun removeComponents(from: StorageGraph) { super.removeComponents(from) - poweredView?.removeListeners() + // poweredView?.removeListeners() poweredView = null } } - private val settings = HashMap() - private val craftingAmount = Object2ObjectArrayMap() - private val lastCraftingRecipe = Object2ObjectArrayMap() + init { + exposeGlobally(MatteryCapability.STORAGE_NODE, cell) + } - fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount[ply] ?: 0 + private val settings = HashMap() + private val craftingAmount = Object2IntOpenHashMap() + private val lastCraftingRecipe = Object2ObjectArrayMap() + private var inProcessOfCraft = false + private val craftingGridTuples = arrayOfNulls(3 * 3) + + fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount.getInt(ply) fun lastCraftingRecipe(ply: Player) = lastCraftingRecipe[ply] // a lot of code is hardcoded to take CraftingContainer as it's input // hence we are forced to work around this by providing proxy container - val craftingGrid = object : MatteryContainer(this, 3 * 3) { + val craftingGrid = object : MatteryCraftingContainer(::markDirtyFast, 3, 3) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - craftingGridDummy[slot] = new + markDirtyFast() - if (!inProcessOfCraft) - scanCraftingGrid() + if (!inProcessOfCraft) { + scanCraftingGrid(false) + } } + }.also(::addDroppableContainer) + + private fun scanCraftingGrid(justCheckForRecipeChange: Boolean): Boolean { + val level = level ?: return false + val server = level.server ?: return false + + var craftingRecipe = craftingRecipe + if (craftingRecipe != null && craftingRecipe.matches(craftingGrid, level)) return true + if (justCheckForRecipeChange) return false + + craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGrid, level).orElse(null)?.value + Arrays.fill(craftingGridTuples, null) + val poweredView = poweredView + + if (craftingRecipe != null && poweredView != null) { + for (i in craftingGridTuples.indices) { + val item = craftingGrid[i] + + if (!item.isEmpty) { + craftingGridTuples[i] = poweredView[ItemStorageStack.unsafe(item)] + } + } + } + + this.craftingRecipe = craftingRecipe + return false } - private var inProcessOfCraft = false + override val storageType: StorageStack.Type + get() = StorageStack.ITEMS - private fun scanCraftingGrid() { - val server = level?.server ?: return - val oldRecipe = craftingRecipe + override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { + if (craftingRecipe != null) { + for (i in craftingGridTuples.indices) { + val item = craftingGrid[i] - if (oldRecipe != null && oldRecipe.matches(craftingGridDummy, level!!)) { - return - } - - craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridDummy, level!!).orElse(null) - val craftingRecipe = craftingRecipe - - if (oldRecipe != craftingRecipe) { - Arrays.fill(craftingGridTuples, null) - - val poweredView = poweredView - - if (craftingRecipe != null && poweredView != null) { - for (i in craftingGridTuples.indices) { - val item = craftingGrid[i] - - if (!item.isEmpty) { - craftingGridTuples[i] = poweredView[ItemStackWrapper(item).key()] - } + if (!item.isEmpty && stack.equalsWithoutCount(ItemStorageStack.unsafe(item))) { + craftingGridTuples[i] = id } } } } - private val craftingGridTuples = arrayOfNulls(3 * 3) - - private val craftingMenu = object : AbstractContainerMenu(null, Int.MIN_VALUE) { - override fun stillValid(p_38874_: Player): Boolean { - return true - } - - override fun quickMoveStack(p_38941_: Player, p_38942_: Int): ItemStack { - return ItemStack.EMPTY - } - } - - private val craftingGridDummy = CraftingContainer(craftingMenu, 3, 3) - - override val storageType: StorageStackType - get() = ITEM_STORAGE - - override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageProvider) { - // no op - } - - override fun changeStack(stack: ItemStackWrapper, id: UUID, oldCount: BigInteger) { - // no op - } - - override fun removeStack(stack: ItemStackWrapper, id: UUID) { + override fun onStackChanged(stack: ItemStorageStack, id: UUID) {} + override fun onStackRemoved(id: UUID) { for (i in craftingGridTuples.indices) { if (craftingGridTuples[i] == id) { craftingGridTuples[i] = null @@ -300,137 +288,112 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : override fun getContainerSize() = 1 override fun isEmpty() = craftingRecipe == null - override fun stillValid(p_18946_: Player) = true + override fun stillValid(player: Player) = true - override fun getItem(p_18941_: Int): ItemStack { - require(p_18941_ == 0) { "Invalid slot ID: $p_18941_" } + override fun getItem(slot: Int): ItemStack { + require(slot == 0) { "Invalid slot: $slot" } return craftingRecipe?.resultItem?.copy() ?: ItemStack.EMPTY } override fun removeItem(index: Int, amount: Int): ItemStack { require(index == 0) { "Invalid index $index" } - val craftingRecipe = craftingRecipe - val craftingPlayer = craftingPlayer + val level = level ?: return ItemStack.EMPTY + val craftingRecipe = craftingRecipe ?: return ItemStack.EMPTY + val craftingPlayer = craftingPlayer ?: return ItemStack.EMPTY - if (craftingRecipe == null || craftingPlayer == null || craftingRecipe.resultItem.count != amount) { - return ItemStack.EMPTY + try { + ForgeHooks.setCraftingPlayer(craftingPlayer) + + if (craftingRecipe.resultItem.count != amount) { + return ItemStack.EMPTY + } + } finally { + ForgeHooks.setCraftingPlayer(null) } - var crafts = craftingAmount[craftingPlayer] ?: 0 - crafts++ - craftingAmount[craftingPlayer] = crafts + craftingAmount[craftingPlayer] = craftingAmount.getInt(craftingPlayer) + 1 lastCraftingRecipe[craftingPlayer] = craftingRecipe + tickList.namedTimer(craftingPlayer, 4) { + craftingAmount.removeInt(craftingPlayer) + lastCraftingRecipe.remove(craftingPlayer) + } + val settings = getSettings(craftingPlayer) inProcessOfCraft = true try { ForgeHooks.setCraftingPlayer(craftingPlayer) - val remainingItems: NonNullList = craftingRecipe.getRemainingItems(craftingGridDummy) - ForgeHooks.setCraftingPlayer(null) + val residue: NonNullList + val result: ItemStack - check(remainingItems.size == craftingGrid.containerSize) { "${remainingItems.size} != ${craftingGrid.containerSize} !!!" } + try { + residue = craftingRecipe.getRemainingItems(craftingGrid) + result = craftingRecipe.resultItem + } finally { + ForgeHooks.setCraftingPlayer(null) + } + check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" } + + val combinedInventory = craftingPlayer.matteryPlayer?.inventoryAndExopack + val copy = craftingGrid.iterator(false).map { it.copy() }.toList() + + // удаляем по одному предмету из сетки крафта for (slot in 0 until craftingGrid.containerSize) { - val oldItem = craftingGrid[slot].let { if (!it.isEmpty || it.count < 2) it.copy() else it } + craftingGrid.removeItem(slot, 1) - if (!craftingGrid[slot].isEmpty) { - craftingGrid.removeItem(slot, 1) + if (residue[slot].isNotEmpty) { + craftingGrid[slot] = residue[slot] } + } - var newItem = craftingGrid[slot] - - if (newItem.isEmpty) { - when (settings.ingredientPriority) { - ItemMonitorPlayerSettings.IngredientPriority.SYSTEM -> { - if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.INVENTORY -> { - if (takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> { - if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - - if (newItem.isEmpty && takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST -> { - if (takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } else if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.DO_NOT -> { /* no op */ } + if (!scanCraftingGrid(true)) { + // заполняем опустевшие слоты + for (slot in 0 until craftingGrid.containerSize) { + if ( + craftingGrid[slot].isEmpty && + copy[slot].isNotEmpty && + settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot]) + ) { + craftingGrid[slot] = copy[slot].copyWithCount(1) } } + } - val remainder = remainingItems[slot] + if (!scanCraftingGrid(true)) { + // избавляемся от остатков, если они мешают + for (slot in 0 until craftingGrid.containerSize) { + if (residue[slot].isNotEmpty) { + var remaining = residue[slot] - if (!remainder.isEmpty) { - when (settings.resultTarget) { - ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, ItemMonitorPlayerSettings.ResultTarget.MIXED -> { - val remaining = poweredView?.insertStack(ItemStackWrapper(remainder), false)?.stack ?: remainder - - if (!remaining.isEmpty) { - if (newItem.isEmpty) { - craftingGrid[slot] = remaining - } else if (ItemStack.isSameItemSameTags(newItem, remaining)) { - newItem.grow(remaining.count) - craftingGrid.setChanged(slot) - } else if (!craftingPlayer.inventory.add(remaining)) { - craftingPlayer.drop(remaining, false) - } - } + if (settings.resultTarget != ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY) { + remaining = poweredView?.insertStack(ItemStorageStack(remaining), false)?.toItemStack() ?: remaining } - ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> { - if (!craftingPlayer.inventory.add(remainder)) { - val remaining = poweredView?.insertStack(ItemStackWrapper(remainder), false)?.stack ?: remainder + remaining = combinedInventory?.addItem(remaining, false) ?: remaining - if (!remaining.isEmpty) { - if (newItem.isEmpty) { - craftingGrid[slot] = remaining - } else if (ItemStack.isSameItemSameTags(newItem, remaining)) { - newItem.grow(remaining.count) - craftingGrid.setChanged(slot) - } else { - craftingPlayer.drop(remaining, false) - } - } - } + if (remaining.isNotEmpty) { + craftingPlayer.spawnAtLocation(remaining) + } + + if (copy[slot].isNotEmpty && settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])) { + craftingGrid[slot] = copy[slot].copyWithCount(1) } } } } + + return result.copy() } finally { inProcessOfCraft = false - scanCraftingGrid() + scanCraftingGrid(false) } - - return craftingRecipe.resultItem.copy() } - override fun removeItemNoUpdate(p_18951_: Int): ItemStack { + override fun removeItemNoUpdate(slot: Int): ItemStack { throw UnsupportedOperationException() } @@ -445,8 +408,8 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val craftingResultContainer = CraftingResultContainer() - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) + override fun saveLevel(nbt: CompoundTag) { + super.saveLevel(nbt) nbt.put("player_settings", CompoundTag().also { for ((key, value) in this.settings) { @@ -464,63 +427,33 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : val settings = nbt.getCompound("player_settings") for (key in settings.allKeys) { - val uuid = UUID.fromString(key) - val deserialized = ItemMonitorPlayerSettings() - deserialized.deserializeNBT(settings.getCompound(key)) - - check(this.settings.put(uuid, deserialized) == null) { "Duplicate UUID??? $uuid" } + check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(settings.getCompound(key)) }) == null) } - craftingGrid.deserializeNBT(nbt["crafting_grid"]) + craftingGrid.deserializeNBT(nbt["crafting_grid"] as? CompoundTag) } fun getSettings(ply: ServerPlayer): ItemMonitorPlayerSettings { return settings.computeIfAbsent(ply.uuid) { ItemMonitorPlayerSettings() } } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() cell.tickEnergyDemanding() - - if (craftingAmount.size != 0) - craftingAmount.clear() - - if (lastCraftingRecipe.size != 0) - lastCraftingRecipe.clear() } - override val defaultDisplayName: Component - get() = MBlocks.ITEM_MONITOR.name - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return ItemMonitorMenu(containerID, inventory, this) } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) - StorageNetworkGraph.discoverFull(this, cell.storageNode) - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - return if (cap === MatteryCapability.STORAGE_NODE) { - cell.get().cast() - } else super.getCapability(cap, side) - } - - override fun invalidateCaps() { - super.invalidateCaps() - cell.invalidate() - } - - override fun reviveCaps() { - super.reviveCaps() - cell.revive() + override fun setLevel(level: Level) { + super.setLevel(level) + cell.discover(this) + scanCraftingGrid(false) } override fun setRemoved() { super.setRemoved() - cell.destroy(level) + cell.isValid = false } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt index 711a617e4..b3a21ef86 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt @@ -2,11 +2,9 @@ package ru.dbotthepony.mc.otm.block.entity.storage import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -14,38 +12,33 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.items.IItemHandler import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.block.CableBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport.Companion.FILTER_KEY +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.GraphNodeListener -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.menu.StorageBusMenu +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.math.isPositive +import ru.dbotthepony.mc.otm.core.math.toIntSafe +import ru.dbotthepony.mc.otm.graph.storage.StorageNode +import ru.dbotthepony.mc.otm.menu.storage.StorageBusMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.registry.MNames import ru.dbotthepony.mc.otm.storage.* -import java.lang.ref.WeakReference import java.math.BigInteger import java.util.* import java.util.stream.Stream -import kotlin.NoSuchElementException -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -private class SlotTuple(val slot: Int, val stack: ItemStack) -private class TrackedTuple(override val stack: ItemStackWrapper, override val id: UUID) : IStorageTuple { +private data class SlotTuple(val slot: Int, val stack: ItemStack) + +private class TrackedTuple(override var stack: ItemStorageStack, override val id: UUID) : IStorageTuple { // do not use hash map because we need keys to be iterated in natural order val children = Int2ObjectAVLTreeMap() @@ -54,180 +47,125 @@ private class TrackedTuple(override val stack: ItemStackWrapper, override val id } } -private fun Long.clamp(): Int { - if (this > Int.MAX_VALUE) { - return Int.MAX_VALUE - } - - return this.toInt() -} - class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.STORAGE_BUS, blockPos, blockState) { - override val defaultDisplayName: Component - get() = MBlocks.STORAGE_BUS.name - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return StorageBusMenu(containerID, inventory, this) } - override val energy = WorkerEnergyStorage(this::setChangedLight, ServerConfig.STORAGE_INTERFACES) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.STORAGE_INTERFACES)) + val energyConfig = ConfigurableEnergy(energy, modesFront = FlowDirection.NONE) - val cell: BasicStorageGraphNode = object : BasicStorageGraphNode(energy), GraphNodeListener { - override fun onNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], true) + val cell: StorageNode = object : StorageNode(energy) { + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - override fun onUnNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } - val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> - component?.scan() - setChangedLight() - } - - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) { - StorageNetworkGraph.discoverFull(this, cell.storageNode) - } - - tickOnceServer(this::checkSurroundings) - } - - private var valid = true - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - return if (valid && cap === MatteryCapability.STORAGE_NODE && side != blockState.getValue(RotatableMatteryBlock.FACING_FULL)) { - cell.get().cast() - } else super.getCapability(cap, side) - } - - override fun invalidateCaps() { - super.invalidateCaps() - cell.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - cell.revive() - valid = true - } - - private var neighbour: LazyOptional? = null private var component: ItemHandlerComponent? = null - fun tick() { - batteryChargeLoop() + var insertPriority = 0 + set(value) { + field = value + markDirtyFast() + } + + var extractPriority = 0 + set(value) { + field = value + markDirtyFast() + } + + var mode: FlowDirection = FlowDirection.BI_DIRECTIONAL + set(value) { + field = value + markDirtyFast() + } + + init { + savetables.stateful(::energy, ENERGY_KEY) + savetables.int(::insertPriority) + savetables.int(::extractPriority) + exposeGlobally(MatteryCapability.STORAGE_NODE, cell) { it != RelativeSide.FRONT } + + side(RelativeSide.FRONT).track(ForgeCapabilities.ITEM_HANDLER).addListener { + component?.let(cell::removeStorageComponent) + component = if (it.isPresent) { + ItemHandlerComponent(it.orThrow()).also { if (!redstoneControl.isBlockedByRedstone) cell.addStorageComponent(it) } + } else { + null + } + } + + redstoneControl.addListener { + val component = component ?: return@addListener + + if (it) { + cell.removeStorageComponent(component) + } else { + cell.addStorageComponent(component) + } + } + } + + val filter = ItemFilter(MAX_FILTERS) { + component?.scan() + markDirtyFast() + } + + init { + savetables.stateful(::filter, FILTER_KEY) + } + + override fun setLevel(level: Level) { + super.setLevel(level) + cell.discover(this) + } + + override fun tick() { + super.tick() component?.scan() cell.tickEnergyDemanding() } override fun setRemoved() { super.setRemoved() - cell.destroy(level) - valid = false - } - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[FILTER_KEY] = filter.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(FILTER_KEY, filter::deserializeNBT) - } - - fun checkSurroundings() { - if (isRemoved) - return - - val front = blockPos + blockState.getValue(RotatableMatteryBlock.FACING_FULL) - val storage = level?.getBlockEntity(front)?.getCapability(ForgeCapabilities.ITEM_HANDLER, -blockState.getValue(RotatableMatteryBlock.FACING_FULL))?.let { if (it.isPresent) it else null } - - if (neighbour != storage) { - neighbour = storage - - if (storage != null) { - val ref = WeakReference(this) - - storage.addListener { - val self = ref.get() ?: return@addListener - - if (self.neighbour === it && SERVER_IS_LIVE) { - self.checkSurroundings() - } - } - - component?.let(cell::removeStorageComponent) - component = ItemHandlerComponent(storage.orThrow()) - component!!.let(cell::addStorageComponent) - } else { - component?.let(cell::removeStorageComponent) - component = null - } - } + cell.isValid = false } companion object { const val MAX_FILTERS = 6 * 3 } - private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent { - private inner class EventsSnapshot { - val index = HashMap() + private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent { + private fun updateTuple(tuple: TrackedTuple, diff: BigInteger) { + val stack = tuple.stack + tuple.stack = tuple.stack.grow(diff) - fun change(key: UUID, diff: BigInteger) { - if (diff.isZero) - return - - val value = index[key] - - if (value == null) { - index[key] = diff - } else { - val newvalue = value + diff - - if (newvalue.isZero) { - index.remove(key) - } else { - index[key] = newvalue - } + if (tuple.stack.isNotEmpty) { + for (listener in listeners) { + listener.onStackChanged(tuple) } - } - - fun apply() { - for ((key, value) in index) { - val tuple = checkNotNull(this@ItemHandlerComponent.index[key]) { "Tuple with ID $key is missing!" } - - val count = tuple.stack.count - tuple.stack.count += value - - if (tuple.stack.count.isPositive) { - for (listener in listeners) { - listener.changeStack(tuple, count) - } - } else { - for (listener in listeners) { - listener.removeStack(tuple) - } - - this@ItemHandlerComponent.index.remove(tuple.id) - - val tuplekey = tuple.stack.key() - tuples.remove(tuplekey) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple") - } + } else { + for (listener in listeners) { + listener.onStackRemoved(tuple) } + + id2tuples.remove(tuple.id) + items2tuples.remove(stack) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple") } } @@ -295,14 +233,17 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } } - private var snapshot: EventsSnapshot? = null + override val storageType: StorageStack.Type + get() = StorageStack.ITEMS - override val storageType: StorageStackType - get() = ITEM_STORAGE + private val listeners = ArrayList>() - private val listeners = ArrayList>() + override val insertPriority: Int + get() = this@StorageBusBlockEntity.insertPriority + override val extractPriority: Int + get() = this@StorageBusBlockEntity.extractPriority - override fun addListener(listener: IStorageEventConsumer): Boolean { + override fun addListener(listener: IStorageEventConsumer): Boolean { if (!listeners.contains(listener)) { listeners.add(listener) return true @@ -311,139 +252,92 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter return false } - override fun removeListener(listener: IStorageEventConsumer): Boolean { + override fun removeListener(listener: IStorageEventConsumer): Boolean { return listeners.remove(listener) } - private var scanned = arrayOfNulls(0) - private var scannedMap = arrayOfNulls(0) - private val tuples = HashMap() - private val index = HashMap() + private var slot2itemStack = arrayOfNulls(0) + private var slot2tuple = arrayOfNulls(0) + private val items2tuples = Object2ObjectOpenCustomHashMap(StorageStack.Companion) + private val id2tuples = HashMap() private fun removeTracked(slot: Int) { - scanned[slot] = null - val scannedMap = checkNotNull(scannedMap[slot]) { "Not tracking slot $slot" } - this.scannedMap[slot] = null + slot2itemStack[slot] = null + val tuple = checkNotNull(slot2tuple[slot]) { "Not tracking slot $slot" } + slot2tuple[slot] = null - val item = scannedMap.children[slot] ?: throw IllegalStateException("${scannedMap.id} does not track $slot") - scannedMap.children.remove(slot) - val count = scannedMap.stack.count - - val snapshot = snapshot - - if (snapshot != null) { - snapshot.change(scannedMap.id, -item.stack.count.toBigInteger()) - } else { - scannedMap.stack.count -= item.stack.count.toBigInteger() - - if (scannedMap.stack.count.isPositive) { - for (listener in listeners) { - listener.changeStack(scannedMap, count) - } - } else { - for (listener in listeners) { - listener.removeStack(scannedMap) - } - - index.remove(scannedMap.id) - - val key = scannedMap.stack.key() - tuples.remove(key) ?: throw IllegalStateException("Item tuple is not present for slot $slot at ${scannedMap.stack}") - } - } + val slotTuple = tuple.children[slot] ?: throw IllegalStateException("${tuple.id} does not track $slot") + tuple.children.remove(slot) + updateTuple(tuple, -slotTuple.stack.count.toBigInteger()) } private fun diffTracked(slot: Int, diff: Int) { - val scannedMap = checkNotNull(scannedMap[slot]) { "Not tracking slot $slot" } - val item = checkNotNull(scannedMap.children[slot]) { "${scannedMap.id} does not track $slot" } - - val oldCount = scannedMap.stack.count - item.stack.count += diff - - val snapshot = snapshot - - if (snapshot != null) { - snapshot.change(scannedMap.id, diff.toBigInteger()) - } else { - scannedMap.stack.count += diff.toBigInteger() - - for (listener in listeners) { - listener.changeStack(scannedMap.stack, scannedMap.id, oldCount) - } - } + val tuple = checkNotNull(slot2tuple[slot]) { "Not tracking slot $slot" } + val slotTuple = checkNotNull(tuple.children[slot]) { "${tuple.id} does not track $slot" } + slotTuple.stack.count += diff + updateTuple(tuple, diff.toBigInteger()) } private fun addTracked(slot: Int, stack: ItemStack) { - check(scannedMap[slot] == null) { "Already tracking slot $slot" } + check(slot2tuple[slot] == null) { "Already tracking slot $slot" } - val storageStack = ItemStackWrapper(stack) - val key = storageStack.key() - var tuple: TrackedTuple? = tuples[key] + val storageStack = ItemStorageStack(stack) + var tuple = items2tuples[storageStack] val added = tuple == null - var oldCount = BigInteger.ZERO - if (added) { + if (tuple == null) { tuple = TrackedTuple(storageStack, UUID.randomUUID()) - index[tuple.id] = tuple - tuples[key] = tuple + id2tuples[tuple.id] = tuple + items2tuples[storageStack] = tuple } else { - oldCount = tuple!!.stack.count - - if (snapshot == null) - tuple.stack.count += stack.count.toBigInteger() + tuple.stack = tuple.stack.grow(stack.count.toBigInteger()) } tuple.children[slot] = SlotTuple(slot, stack.copy()) - scanned[slot] = tuple.children[slot].stack - scannedMap[slot] = tuple + slot2itemStack[slot] = tuple.children[slot].stack + slot2tuple[slot] = tuple if (added) { for (listener in listeners) { - listener.addStack(tuple.stack, tuple.id, this) + listener.onStackAdded(tuple.stack, tuple.id, this) } } else { - val snapshot = snapshot - - if (snapshot != null) { - snapshot.change(tuple.id, stack.count.toBigInteger()) - } else { - for (listener in listeners) { - listener.changeStack(tuple.stack, tuple.id, oldCount) - } + for (listener in listeners) { + listener.onStackChanged(tuple.stack, tuple.id) } } } private fun sizeScan() { - if (scanned.size != parent.slots) { - val old = scanned - val oldMap = scannedMap + if (slot2itemStack.size != parent.slots) { + val old = slot2itemStack + val oldMap = slot2tuple - if (scanned.size < parent.slots) { + if (slot2itemStack.size < parent.slots) { // grow - scanned = arrayOfNulls(parent.slots) - scannedMap = arrayOfNulls(parent.slots) + slot2itemStack = arrayOfNulls(parent.slots) + slot2tuple = arrayOfNulls(parent.slots) for ((i, item) in old.withIndex()) { - scanned[i] = item + slot2itemStack[i] = item } for ((i, item) in oldMap.withIndex()) { - scannedMap[i] = item + slot2tuple[i] = item } } else { // shrink - for (i in parent.slots until scanned.size) { - if (scannedMap[i] != null) + for (i in parent.slots until slot2itemStack.size) { + if (slot2tuple[i] != null) removeTracked(i) } - scanned = arrayOfNulls(parent.slots) - scannedMap = arrayOfNulls(parent.slots) + slot2itemStack = arrayOfNulls(parent.slots) + slot2tuple = arrayOfNulls(parent.slots) for (i in 0 until parent.slots) { - scanned[i] = old[i] - scannedMap[i] = oldMap[i] + slot2itemStack[i] = old[i] + slot2tuple[i] = oldMap[i] } } } @@ -451,7 +345,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter fun scan(slot: Int) { val current = parent[slot].let { if (it.isEmpty || !filter.match(it)) null else it } - val last = scanned[slot] + val last = slot2itemStack[slot] if (current == null && last != null) { removeTracked(slot) @@ -470,42 +364,27 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter fun scan() { sizeScan() - snapshot = EventsSnapshot() - for (slot in 0 until parent.slots) { scan(slot) } - - snapshot!!.apply() - snapshot = null } - override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper { - if (energy.batteryLevel.isZero || !filter.match(stack.item)) + override fun insertStack(stack: ItemStorageStack, simulate: Boolean): ItemStorageStack { + if (redstoneControl.isBlockedByRedstone || energy.batteryLevel.isZero || !filter.match(stack.toItemStack()) || !mode.input) return stack - val maxPossibleDemand = ITEM_STORAGE.energyPerOperation * stack.count - val maxExtractEnergy = energy.extractEnergyInner(maxPossibleDemand, true) + val required = StorageStack.ITEMS.energyPerInsert(stack) + if (energy.extractEnergy(required, true) != required) return stack - var leftover: ItemStackWrapper = stack.copy() - var additional: BigInteger = BigInteger.ZERO + var leftover = stack - if (maxExtractEnergy != maxPossibleDemand) { - additional = (maxExtractEnergy / ITEM_STORAGE.energyPerOperation).whole - leftover.count -= additional - } - - for (slot in PrioritizedSlotIterator(tuples[stack.key()])) { + for (slot in PrioritizedSlotIterator(items2tuples[stack])) { val oldCount = leftover.count - leftover = ItemStackWrapper(parent.insertItem(slot, leftover.stack, simulate)) + leftover = ItemStorageStack.unsafe(parent.insertItem(slot, leftover.toItemStack(), simulate)) if (oldCount != leftover.count && !simulate) { - energy.extractEnergyInner(ITEM_STORAGE.energyPerOperation * (oldCount - leftover.count), false) - - if (scanned.size <= slot) { - sizeScan() - } - + energy.extractEnergy(StorageStack.ITEMS.energyPerInsert(stack.copy(oldCount - leftover.count)), false) + if (slot2itemStack.size <= slot) sizeScan() scan(slot) } @@ -514,74 +393,55 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } } - return leftover.also { - if (additional != BigInteger.ZERO) { - it.count += additional - } - } + return leftover } - override fun get(id: UUID): ItemStackWrapper { - return index[id]?.stack ?: ItemStackWrapper.EMPTY + override fun get(id: UUID): ItemStorageStack { + return id2tuples[id]?.stack ?: ItemStorageStack.EMPTY } - override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): ItemStackWrapper { - if (!amount.isPositive) - return ItemStackWrapper.EMPTY + override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): ItemStorageStack { + if (redstoneControl.isBlockedByRedstone || !amount.isPositive || !energy.batteryLevel.isPositive || !mode.output) + return ItemStorageStack.EMPTY - val maxPossibleDemand = ITEM_STORAGE.energyPerOperation * amount - val maxExtractEnergy = energy.extractEnergyInner(maxPossibleDemand, true) + var total = BigInteger.ZERO + val tuple = id2tuples[id] ?: return ItemStorageStack.EMPTY + val slots = tuple.children.values.iterator() + val lstack = tuple.stack + val affectedSlots = IntArrayList() - @Suppress("NAME_SHADOWING") - var amount = amount + while (amount > total && slots.hasNext() && energy.batteryLevel.isPositive) { + val (slot, stack) = slots.next() + val extracted = parent.extractItem(slot, stack.count.coerceAtMost((amount - total).toIntSafe()), true) + val required = StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted)) - if (maxPossibleDemand != maxExtractEnergy) { - amount = (maxExtractEnergy / ITEM_STORAGE.energyPerOperation).whole - } + if (extracted.isNotEmpty && lstack.equalsWithoutCount(ItemStorageStack.unsafe(extracted)) && energy.extractEnergy(required, true) == required) { + if (simulate) { + total += extracted.count.toBigInteger() + } else { + affectedSlots.add(slot) + val extracted2 = parent.extractItem(slot, extracted.count, false) + total += extracted2.count.toBigInteger() - val intAmount = amount.toLong() - val tuple = index[id] ?: return ItemStackWrapper.EMPTY - var totalExtracted = 0L - - val copy = tuple.stack.copy() - - for (stack in tuple.children.values) { - val extracted = parent.extractItem(stack.slot, stack.stack.count.coerceAtMost((intAmount - totalExtracted).clamp()), true) - - if (!extracted.isEmpty && tuple.stack.sameItem(extracted)) { - if (!simulate) { - parent.extractItem(stack.slot, extracted.count, false) - } - - totalExtracted += extracted.count - - if (extracted.count != 0 && !simulate) { - energy.extractEnergyInner(ITEM_STORAGE.energyPerOperation * extracted.count, false) - } - - if (totalExtracted >= intAmount) { - break + if (extracted2.count == extracted.count) { + energy.extractEnergy(required, false) + } else { + energy.extractEnergy(StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted2)), false) + } } } } - if (totalExtracted == 0L) { - return ItemStackWrapper.EMPTY - } - - if (!simulate) { - scan() - } - - copy.count = totalExtracted.toBigInteger() - return copy + val i = affectedSlots.intIterator() + while (i.hasNext()) scan(i.nextInt()) + return lstack.copy(total) } - override val stacks: Stream> get() { - val listing = ArrayList>(index.size) + override val stacks: Stream> get() { + val listing = ArrayList>(id2tuples.size) - for (tuple in index.values) { - listing.add(StorageTuple(tuple.id, tuple.stack)) + for (tuple in id2tuples.values) { + listing.add(IStorageTuple.I(tuple.id, tuple.stack)) } return listing.stream() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index 478eba4d8..e03e29597 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -1,10 +1,8 @@ package ru.dbotthepony.mc.otm.block.entity.storage +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -13,164 +11,118 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.items.IItemHandler -import ru.dbotthepony.mc.otm.* +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.block.CableBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity -import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.ItemFilter -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.graph.GraphNodeListener -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.menu.StorageExporterMenu -import ru.dbotthepony.mc.otm.menu.StorageImporterMenu +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.graph.storage.StorageNode +import ru.dbotthepony.mc.otm.menu.storage.StorageImporterExporterMenu +import ru.dbotthepony.mc.otm.once import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.storage.* +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.storage.StorageStack import java.math.BigInteger import java.util.* -import java.util.stream.Stream -import kotlin.collections.HashSet +import kotlin.collections.ArrayList -abstract class AbstractStorageImportExport( +abstract class AbstractStorageImportExport( blockType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState, - energyValues: ConciseBalanceValues = ServerConfig.STORAGE_INTERFACES + energyValues: EnergyBalanceValues = MachinesConfig.STORAGE_INTERFACES ) : MatteryPoweredBlockEntity(blockType, blockPos, blockState) { - final override val energy = WorkerEnergyStorage(this::setChangedLight, energyValues) + final override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::markDirtyFast, energyValues)) + val energyConfig = ConfigurableEnergy(energy, modesFront = FlowDirection.NONE) - val cell: BasicStorageGraphNode = object : BasicStorageGraphNode(energy), GraphNodeListener { - override fun onNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], true) + val cell: StorageNode = object : StorageNode(energy) { + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + level?.once { + if (!isRemoved) { + val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } + } } - override fun onUnNeighbour(node: Graph6Node<*>, direction: Direction) { - val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction.ordinal], false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + level?.once { + if (!isRemoved) { + val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } + } + } } } - private var valid = true - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - return if (valid && cap === MatteryCapability.STORAGE_NODE && side != blockState.getValue(RotatableMatteryBlock.FACING_FULL)) { - cell.get().cast() - } else super.getCapability(cap, side) + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return StorageImporterExporterMenu(containerID, inventory, this) } - override fun invalidateCaps() { - super.invalidateCaps() - cell.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - cell.revive() - valid = true + init { + savetables.stateful(::energy, ENERGY_KEY) + exposeGlobally(MatteryCapability.STORAGE_NODE, cell) { it != RelativeSide.FRONT } } override fun setRemoved() { super.setRemoved() - cell.destroy(level) - valid = false + cell.isValid = false } - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) { - StorageNetworkGraph.discoverFull(this, cell.storageNode) - } - - tickOnceServer(this::checkSurroundings) + override fun setLevel(level: Level) { + super.setLevel(level) + cell.discover(this) } - protected var target: LazyOptional = LazyOptional.empty() - protected abstract val targetCapability: Capability - - fun checkSurroundings() { - target = getAndBind( - target, - level?.getBlockEntity(blockPos + blockState.getValue(RotatableMatteryBlock.FACING_FULL).normal), - targetCapability, - -blockState.getValue(RotatableMatteryBlock.FACING_FULL), - ) { tickOnceServer(this::checkSurroundings) } - } + protected val target = front.track(ForgeCapabilities.ITEM_HANDLER) abstract val filter: ItemFilter - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[FILTER_KEY] = filter.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - nbt.map(FILTER_KEY, filter::deserializeNBT) + init { + savetables.stateful(::filter, FILTER_KEY) } companion object { - val MAX_POWER = Decimal(10_000) const val FILTER_KEY = "filter" + const val MAX_FILTERS = 6 * 3 } } -class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) - : AbstractStorageImportExport(MBlockEntities.STORAGE_IMPORTER, blockPos, blockState), - IItemHandler { - override val defaultDisplayName: Component - get() = MACHINE_NAME - - override val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> - setChangedLight() - } - - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return StorageImporterMenu(containerID, inventory, this) +class StorageImporterBlockEntity( + blockPos: BlockPos, blockState: BlockState +) : AbstractStorageImportExport(MBlockEntities.STORAGE_IMPORTER, blockPos, blockState), IItemHandler { + override val filter = ItemFilter(MAX_FILTERS) { + markDirtyFast() } private var lastSlot = 0 private var nextTick = INTERVAL - private val enoughEnergy get() = energy.batteryLevel >= ITEM_STORAGE.energyPerOperation - - override val targetCapability: Capability - get() = ForgeCapabilities.ITEM_HANDLER - - private var valid = true - private var resolverItemHandler = LazyOptional.of { this } - - override fun invalidateCaps() { - super.invalidateCaps() - resolverItemHandler.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - resolverItemHandler = LazyOptional.of { this } - valid = true - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (valid && cap == ForgeCapabilities.ITEM_HANDLER && side == blockState.getValue(RotatableMatteryBlock.FACING_FULL)) { - return resolverItemHandler.cast() - } - - return super.getCapability(cap, side) + init { + front.Cap(ForgeCapabilities.ITEM_HANDLER, this) } override fun getSlots(): Int { @@ -181,23 +133,39 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) return ItemStack.EMPTY } + private fun acceptItem(stack: ItemStack, simulate: Boolean): ItemStack { + val wrapped = ItemStorageStack(stack) + + val view = cell.graph.getVirtualComponent(StorageStack.ITEMS) + val leftover = view.insertStack(wrapped, true) + if (leftover == wrapped) return stack + + val required = StorageStack.ITEMS.energyPerInsert(wrapped) + + if (energy.extractEnergy(required, true) == required) { + if (!simulate) { + val leftover2 = view.insertStack(wrapped, false) + + if (leftover == leftover2) { + energy.extractEnergy(required, false) + return leftover.toItemStack() + } else { + energy.extractEnergy(StorageStack.ITEMS.energyPerInsert(leftover2), false) + return leftover2.toItemStack() + } + } + + return leftover.toItemStack() + } + + return stack + } + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { - if (!filter.match(stack)) + if (redstoneControl.isBlockedByRedstone || !filter.match(stack)) return stack - val view = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE) ?: return stack - val maxMove = energy.extractStepInnerBi(ITEM_STORAGE.energyPerOperation, stack.count, true) - - if (maxMove == BigInteger.ZERO) - return stack - - val leftover = view.insertStack(ItemStackWrapper(stack).also { it.count = maxMove }, simulate) - - if (simulate) - return leftover.stack - - energy.extractStepInner(ITEM_STORAGE.energyPerOperation, maxMove - leftover.count, false) - return leftover.stack + return acceptItem(stack, simulate) } override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { @@ -212,36 +180,33 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) return filter.match(stack) } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() + + if (redstoneControl.isBlockedByRedstone) + return + cell.tickEnergyDemanding() nextTick-- - if (nextTick <= 0 && target.isPresent && enoughEnergy) { - val graph = cell.storageGraph ?: return - val items = graph.getVirtualComponent(ITEM_STORAGE) + val target = target.get().orNull() - val resolved = target.orThrow() - - if (lastSlot >= resolved.slots) { + if (nextTick <= 0 && target != null) { + if (lastSlot >= target.slots) { lastSlot = 0 } - val maxMove = energy.extractStepInner(ITEM_STORAGE.energyPerOperation, MAX_MOVE_PER_OPERATION, true) - var extracted = resolved.extractItem(lastSlot, maxMove, true) + val extracted = target.extractItem(lastSlot, MAX_MOVE_PER_OPERATION, true) if (extracted.isEmpty || !filter.match(extracted)) { lastSlot++ } else { - val leftOver = items.insertStack(ItemStackWrapper(extracted), true) + val leftover = acceptItem(extracted, true) - if (leftOver.count.toInt() != extracted.count) { - extracted = resolved.extractItem(lastSlot, extracted.count - leftOver.count.toInt(), false) - energy.extractStepInner(ITEM_STORAGE.energyPerOperation, extracted.count, false) - items.insertStack(ItemStackWrapper(extracted), false) - } else { - nextTick += INTERVAL * 4 + if (leftover.count != extracted.count) { + val extracted2 = target.extractItem(lastSlot, extracted.count - leftover.count, false) + acceptItem(extracted2, false) } } } @@ -253,123 +218,107 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) companion object { const val MAX_MOVE_PER_OPERATION = 4 - private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_IMPORTER}") private const val INTERVAL = 5 - const val MAX_FILTERS = 6 * 3 } } class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : - AbstractStorageImportExport(MBlockEntities.STORAGE_EXPORTER, blockPos, blockState), - IStorageEventConsumer { + AbstractStorageImportExport(MBlockEntities.STORAGE_EXPORTER, blockPos, blockState), + IStorageEventConsumer { override val defaultDisplayName: Component get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { - return StorageExporterMenu(containerID, inventory, this) - } + private val relevantTuples = ObjectOpenHashSet() - private val relevantTuples = HashSet() - - override val storageType: StorageStackType - get() = ITEM_STORAGE + override val storageType: StorageStack.Type + get() = StorageStack.ITEMS init { cell.addStorageComponent(this) } - override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageProvider) { - if (!filter.match(stack.item)) { + override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { + if (!filter.match(stack.toItemStack())) { return } relevantTuples.add(id) } - override fun changeStack(stack: ItemStackWrapper, id: UUID, oldCount: BigInteger) { + override fun onStackChanged(stack: ItemStorageStack, id: UUID) { // no-op } - override fun removeStack(stack: ItemStackWrapper, id: UUID) { + override fun onStackRemoved(id: UUID) { relevantTuples.remove(id) } - override val filter = ItemFilter(MAX_FILTERS) { _, _, _ -> + override val filter = ItemFilter(MAX_FILTERS) { relevantTuples.clear() - val component = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE) ?: return@ItemFilter + val component = cell.graph.getVirtualComponent(StorageStack.ITEMS) for (tuple in component.stacks) { - addStack(tuple, component) + onStackAdded(tuple, component) } lastSlot = 0 - setChangedLight() + markDirtyFast() }.also { it.isWhitelist = true } private var lastSlot = 0 private var nextTick = INTERVAL - private val enoughEnergy get() = energy.batteryLevel >= ITEM_STORAGE.energyPerOperation - override val targetCapability: Capability - get() = ForgeCapabilities.ITEM_HANDLER + override fun tick() { + super.tick() - private val exportStacks: Stream> - get() { - val view = cell.storageGraph?.getVirtualComponent(ITEM_STORAGE) ?: return Stream.empty() - return relevantTuples.stream().map { it to view[it] } - } + if (redstoneControl.isBlockedByRedstone) + return - fun tick() { - batteryChargeLoop() cell.tickEnergyDemanding() nextTick-- - if (nextTick <= 0 && target.isPresent && enoughEnergy) { - val graph = cell.storageGraph ?: return - val items = graph.getVirtualComponent(ITEM_STORAGE) + val target = target.get().orNull() - val resolved = target.orThrow() + if (nextTick <= 0 && target != null) { + val items = cell.graph.getVirtualComponent(StorageStack.ITEMS) - if (lastSlot >= resolved.slots) { + if (lastSlot >= target.slots) { lastSlot = 0 } - var hit = false + val any = ArrayList(relevantTuples).any { id -> + val stack = items[id] - for (stack in exportStacks) { - if (!resolved.isItemValid(lastSlot, stack.second.item)) { - continue - } + if (stack.isEmpty || !target.isItemValid(lastSlot, stack.toItemStack())) return@any false - val exportAmountA = items.extractStack(stack.first, stack.second.count.coerceAtMost( - MAX_MOVE_PER_OPERATION - ), true).count + val extracted = items.extractStack(id, stack.count.coerceAtMost(MAX_MOVE_PER_OPERATION), true) + if (extracted.isEmpty) return@any false - if (exportAmountA == BigInteger.ZERO) { - continue - } + val required = StorageStack.ITEMS.energyPerOperation(extracted) + if (energy.extractEnergy(required, true) != required) return@any false - var exportAmount = energy.extractStepInner(ITEM_STORAGE.energyPerOperation, exportAmountA, true) + val toInsert = extracted.toItemStack() + val leftover = target.insertItem(lastSlot, toInsert, true) - if (exportAmount == 0) { - break - } + if (leftover.count != toInsert.count) { + val extracted2 = items.extractStack(id, (toInsert.count - leftover.count).toBigInteger(), false) + energy.extractEnergy(StorageStack.ITEMS.energyPerOperation(extracted2), false) + val leftover2 = target.insertItem(lastSlot, extracted2.toItemStack(), false) - val leftover = resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, true) + if (leftover2.isNotEmpty) { + items.insertStack(ItemStorageStack.unsafe(leftover2), false) + } - if (leftover.count != exportAmount) { - hit = true - exportAmount = items.extractStack(stack.first, (exportAmount - leftover.count).toBigInteger(), false).count.toInt() - resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, false) - energy.extractStepInner(ITEM_STORAGE.energyPerOperation, exportAmount, false) - break + true + } else { + false } } - if (!hit) { + if (!any) { lastSlot++ } } @@ -383,6 +332,5 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : val MAX_MOVE_PER_OPERATION: BigInteger = BigInteger.valueOf(4L) private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_EXPORTER}") private const val INTERVAL = 5 - const val MAX_FILTERS = 6 * 3 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt index dbd3a52fe..b10d647ae 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt @@ -1,98 +1,72 @@ package ru.dbotthepony.mc.otm.block.entity.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.transferInner -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.menu.StoragePowerSupplierMenu +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.transferChecked +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.graph.storage.StorageNode +import ru.dbotthepony.mc.otm.menu.storage.StoragePowerSupplierMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.getDecimal -import ru.dbotthepony.mc.otm.core.set class StoragePowerSupplierBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.STORAGE_POWER_SUPPLIER, blockPos, blockState) { - override val defaultDisplayName: Component - get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return StoragePowerSupplierMenu(containerID, inventory, this) } - val cell = BasicStorageGraphNode() + val cell = StorageNode() + override val energy = WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.STORAGE_POWER_SUPPLIER) + + init { + savetables.stateful(::energy, ENERGY_KEY) + + exposeEnergyGlobally(energy) + exposeGlobally(MatteryCapability.STORAGE_NODE, cell) + } var powerPassed = Decimal.ZERO private set - override fun setLevel(p_155231_: Level) { - super.setLevel(p_155231_) - - if (p_155231_ is ServerLevel) { - StorageNetworkGraph.discoverFull(this, cell.storageNode) - } + init { + savetables.decimal(::powerPassed, POWER_PASSED_KEY) } - private var valid = true - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - return if (valid && cap === MatteryCapability.STORAGE_NODE) { - cell.get().cast() - } else super.getCapability(cap, side) - } - - override fun invalidateCaps() { - super.invalidateCaps() - cell.invalidate() - valid = false - } - - override fun reviveCaps() { - super.reviveCaps() - cell.revive() - valid = true + override fun setLevel(level: Level) { + super.setLevel(level) + cell.discover(this) } override fun setRemoved() { super.setRemoved() - cell.destroy(level) - valid = false + cell.isValid = false } - fun tick() { - batteryChargeLoop() + override fun tick() { + super.tick() + + if (redstoneControl.isBlockedByRedstone) + return if (energy.batteryLevel.isZero) return - val graph = cell.storageGraph ?: return - - if (graph.powerDemandingNodes.isEmpty()) + if (cell.graph.powerDemandingNodes.isEmpty()) return var demand = Decimal.ZERO var i = 0 - val available = energy.batteryLevel.coerceAtMost(ServerConfig.STORAGE_POWER_SUPPLIER.throughput) + val available = energy.batteryLevel.coerceAtMost(MachinesConfig.STORAGE_POWER_SUPPLIER.energyThroughput) - for (demanding in graph.powerDemandingNodes) { - val received = demanding.receiveEnergyOuter(available, true) + for (demanding in cell.graph.powerDemandingNodes) { + val received = demanding.receiveEnergy(available, true) if (received.isPositive) { demand += received @@ -103,32 +77,19 @@ class StoragePowerSupplierBlockEntity(blockPos: BlockPos, blockState: BlockState if (demand.isZero) { return } else if (demand < available) { - for (demanding in graph.powerDemandingNodes) { - powerPassed += energy.transferInner(demanding, available, false) + for (demanding in cell.graph.powerDemandingNodes) { + powerPassed += energy.transferChecked(demanding, available, false) } } else { val forEach = available / i - for (demanding in graph.powerDemandingNodes) { - powerPassed += energy.transferInner(demanding, forEach, false) + for (demanding in cell.graph.powerDemandingNodes) { + powerPassed += energy.transferChecked(demanding, forEach, false) } } } - override val energy = WorkerEnergyStorage(this, ServerConfig.STORAGE_POWER_SUPPLIER) - - override fun saveAdditional(nbt: CompoundTag) { - super.saveAdditional(nbt) - nbt[POWER_PASSED_KEY] = powerPassed.serializeNBT() - } - - override fun load(nbt: CompoundTag) { - super.load(nbt) - powerPassed = nbt.getDecimal(POWER_PASSED_KEY) - } - companion object { - private val MACHINE_NAME = TranslatableComponent("block.${OverdriveThatMatters.MOD_ID}.${MNames.STORAGE_POWER_SUPPLIER}") const val POWER_PASSED_KEY = "powerPassed" } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt new file mode 100644 index 000000000..83946a3ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidChargerBlockEntity.kt @@ -0,0 +1,158 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.Vec3 +import ru.dbotthepony.mc.otm.block.entity.AbstractRedstoneControl +import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProxiedEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid +import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class AndroidChargerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ANDROID_CHARGER, blockPos, blockState) { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return AndroidChargerMenu(containerID, inventory, this) + } + + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, MachinesConfig.ANDROID_CHARGER)) + val energyConfig = ConfigurableEnergy(energy, modesTop = FlowDirection.NONE) + + init { + savetables.stateful(energyConfig::energy, ENERGY_KEY) + } + + override fun tick() { + super.tick() + + val level = level ?: return + var available = energyConfig.energy.extractEnergy(energyConfig.energy.batteryLevel, true) + if (!available.isPositive) return + + val ents = level.getEntitiesInEllipsoid(blockPos.center, Vec3(MachinesConfig.AndroidCharger.RADIUS_WIDTH, MachinesConfig.AndroidCharger.RADIUS_HEIGHT, MachinesConfig.AndroidCharger.RADIUS_WIDTH)) { it is Player } + + ents.sort() + + for ((ent) in ents) { + val ply = ent.matteryPlayer ?: continue + + if (ply.isAndroid) { + val received = ply.androidEnergy.receiveEnergyChecked(available, false) + available -= received + energyConfig.energy.extractEnergy(received, false) + if (!available.isPositive) return + } + + if (ply.hasExopack) { + val received = ply.exopackEnergy.receiveEnergyChecked(available, false) + available -= received + energyConfig.energy.extractEnergy(received, false) + if (!available.isPositive) return + } + } + } +} + +class AndroidChargerMiddleBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ANDROID_CHARGER_MIDDLE, blockPos, blockState) { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { + if (proxy.parent == null || lastTileEntity == null) return null + return AndroidChargerMenu(containerID, inventory, this) + } + + private val proxy = ProxiedEnergyStorage>() + var lastTileEntity: AndroidChargerBlockEntity? = null + private set + val energyConfig = ConfigurableEnergy( + proxy, + modesBottom = FlowDirection.NONE, + modesTop = FlowDirection.NONE, + modesBack = FlowDirection.INPUT, + modesFront = FlowDirection.INPUT, + modesLeft = FlowDirection.INPUT, + modesRight = FlowDirection.INPUT, + ) + + override val redstoneControl = object : AbstractRedstoneControl() { + override var redstoneSetting: RedstoneSetting + get() = lastTileEntity?.redstoneControl?.redstoneSetting ?: RedstoneSetting.IGNORED + set(value) {} + override var redstoneSignal: Int + get() = lastTileEntity?.redstoneControl?.redstoneSignal ?: 0 + set(value) {} + } + + override fun getDisplayName(): Component { + return lastTileEntity?.getDisplayName() ?: super.getDisplayName() + } + + override fun tick() { + super.tick() + + if (proxy.parent == null || lastTileEntity?.isRemoved == true) { + lastTileEntity = level?.getBlockEntity(blockPos.below()) as? AndroidChargerBlockEntity + val energy = lastTileEntity?.energyConfig?.energy + + if (proxy.parent !== energy) { + proxy.parent = energy + energyConfig.invalidate(true) + } + } + } +} + +class AndroidChargerTopBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ANDROID_CHARGER_TOP, blockPos, blockState) { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { + if (proxy.parent == null || lastTileEntity == null) return null + return AndroidChargerMenu(containerID, inventory, this) + } + + private val proxy = ProxiedEnergyStorage>() + var lastTileEntity: AndroidChargerBlockEntity? = null + private set + val energyConfig = ConfigurableEnergy( + proxy, + modesBottom = FlowDirection.NONE, + modesTop = FlowDirection.INPUT, + modesBack = FlowDirection.INPUT, + modesFront = FlowDirection.INPUT, + modesLeft = FlowDirection.INPUT, + modesRight = FlowDirection.INPUT, + ) + + override val redstoneControl = object : AbstractRedstoneControl() { + override var redstoneSetting: RedstoneSetting + get() = lastTileEntity?.redstoneControl?.redstoneSetting ?: RedstoneSetting.IGNORED + set(value) {} + override var redstoneSignal: Int + get() = lastTileEntity?.redstoneControl?.redstoneSignal ?: 0 + set(value) {} + } + + override fun getDisplayName(): Component { + return lastTileEntity?.getDisplayName() ?: super.getDisplayName() + } + + override fun tick() { + super.tick() + + if (proxy.parent == null || lastTileEntity?.isRemoved == true) { + lastTileEntity = level?.getBlockEntity(blockPos.below(2)) as? AndroidChargerBlockEntity + val energy = lastTileEntity?.energyConfig?.energy + + if (proxy.parent !== energy) { + proxy.parent = energy + energyConfig.invalidate(true) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt new file mode 100644 index 000000000..95c323e12 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt @@ -0,0 +1,90 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.MenuProvider +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.AABB +import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity +import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.moveEnergy +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : + MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return AndroidStationMenu(containerID, inventory, this) + } + + override val energy: ProfiledEnergyStorage = ProfiledEnergyStorage(object : WorkerEnergyStorage(::markDirtyFast, MachinesConfig.AndroidStation.VALUES) { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return super.extractEnergy(howMuch, simulate).also { + if (!simulate && this.batteryLevel.isZero) { + if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) + } + } + } + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return super.receiveEnergy(howMuch, simulate).also { + if (!simulate && it.isPositive) { + if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { + level?.setBlock(blockPos, blockState.setValue( + WorkerState.SEMI_WORKER_STATE, + WorkerState.WORKING + ), Block.UPDATE_CLIENTS) + } + } + } + } + }) + + val energyConfig = ConfigurableEnergy(energy) + + init { + savetables.stateful(::energy, ENERGY_KEY) + } + + private var tickedOnce = false + + override fun tick() { + super.tick() + + if (!tickedOnce) { + tickedOnce = true + + if (energy.batteryLevel.isPositive && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) + } else if (energy.batteryLevel.isZero && blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) + } + } + + if (redstoneControl.isBlockedByRedstone) return + val level = level ?: return + val x = blockPos.x.toDouble() + val y = blockPos.y.toDouble() + val z = blockPos.z.toDouble() + + for (ent in level.getEntitiesOfClass(ServerPlayer::class.java, AABB(x, y, z, x + 1.0, y + 2.0, z + 1.0))) { + ent.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { + if (it.isAndroid) + moveEnergy(energy, it.androidEnergy, amount = energy.batteryLevel, simulate = false, ignoreFlowRestrictions = true) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt new file mode 100644 index 000000000..fa1f08a61 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt @@ -0,0 +1,196 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.energy +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energyStoredMattery +import ru.dbotthepony.mc.otm.capability.matteryEnergy +import ru.dbotthepony.mc.otm.capability.maxEnergyStoredMattery +import ru.dbotthepony.mc.otm.capability.transcieveEnergy +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import java.util.function.Supplier + +class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.BATTERY_BANK, p_155229_, p_155230_), IMatteryEnergyStorage { + var gaugeLevel by synchronizer.float().property + private set + + // 6 на 2 + val container: MatteryContainer = object : MatteryContainer(::setChanged, CAPACITY) { + override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { + super.setChanged(slot, new, old) + batteryStatus[slot].boolean = new.getCapability(ForgeCapabilities.ENERGY).isPresent + gaugeLevel = batteryLevel.percentage(maxBatteryLevel) + } + + override fun getMaxStackSize(): Int = 1 + }.also(::addDroppableContainer) + + val batteryStatus = immutableList(CAPACITY) { + synchronizer.bool(false) + } + + val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(HandlerFilter.Dischargeable)) + + private var currentChangeSlot = 0 + private var currentDischangeSlot = 0 + + private var lastTickCharged = false + private var lastTickDischarged = false + + init { + savetables.stateful(::container, INVENTORY_KEY) + savetables.int(::currentChangeSlot) + savetables.int(::currentDischangeSlot) + savetables.bool(::lastTickCharged) + savetables.bool(::lastTickDischarged) + } + + private fun distributeEnergy(isReceiving: Boolean, howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) + return Decimal.ZERO + + var summ = Decimal.ZERO + var remaining = howMuch + var currentSlot = if (isReceiving) currentChangeSlot else currentDischangeSlot + + for (i in 0 until container.containerSize) { + val item = container[currentSlot] + + if (!item.isEmpty) { + val energy = item.matteryEnergy ?: item.energy + + if (energy != null) { + val diff = energy.transcieveEnergy(remaining, isReceiving, simulate) + summ += diff + remaining -= diff + + if (!remaining.isPositive) { + break + } + } + } + + currentSlot = (currentSlot + 1) % container.containerSize + } + + if (!simulate && !summ.isZero) { + markDirtyFast() + gaugeLevel = batteryLevel.percentage(maxBatteryLevel) + } + + if (!simulate) { + if (isReceiving) { + this.lastTickCharged = true + if (currentSlot == this.currentChangeSlot) currentSlot = (currentSlot + 1) % container.containerSize + this.currentChangeSlot = currentSlot + } else { + this.lastTickDischarged = true + if (currentSlot == this.currentDischangeSlot) currentSlot = (currentSlot + 1) % container.containerSize + this.currentDischangeSlot = currentSlot + } + } + + return summ + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return distributeEnergy(isReceiving = false, howMuch, simulate) + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return distributeEnergy(isReceiving = true, howMuch, simulate) + } + + override fun tick() { + super.tick() + + if (!lastTickCharged) + currentChangeSlot = (currentChangeSlot + 1) % container.containerSize + + if (!lastTickDischarged) + currentDischangeSlot = (currentDischangeSlot + 1) % container.containerSize + + lastTickCharged = false + lastTickDischarged = false + } + + override val canSetBatteryLevel: Boolean + get() = false + + override val energyFlow: FlowDirection + get() = FlowDirection.BI_DIRECTIONAL + + override var batteryLevel: Decimal + get() { + var result = Decimal.ZERO + + for (i in 0 until container.containerSize) { + val stack = container.getItem(i) + + if (!stack.isEmpty) { + stack.energy?.let { + result += it.energyStoredMattery + } + } + } + + return result + } + set(value) { + throw UnsupportedOperationException("Can't set battery value for battery bank") + } + + override val maxBatteryLevel: Decimal + get() { + var result = Decimal.ZERO + + for (i in 0 until container.containerSize) { + val stack = container.getItem(i) + + if (!stack.isEmpty) { + stack.energy?.let { + result += it.maxEnergyStoredMattery + } + } + } + + return result + } + + val energyConfig = ConfigurableEnergy( + ProfiledEnergyStorage(this), + frontDefault = FlowDirection.OUTPUT, + backDefault = FlowDirection.INPUT, + leftDefault = FlowDirection.INPUT, + rightDefault = FlowDirection.INPUT, + topDefault = FlowDirection.INPUT, + bottomDefault = FlowDirection.INPUT + ) + + init { + energyConfig.front.automatePush = true + savetables.stateful(Supplier { energyConfig.energy.savedata }, "energyUsageHistory") + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return BatteryBankMenu(containerID, inventory, this) + } + + companion object { + const val CAPACITY = 6 * 2 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt new file mode 100644 index 000000000..38597c699 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt @@ -0,0 +1,116 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.ForgeHooks +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.GeneratorEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.core.math.Decimal + +class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.CHEMICAL_GENERATOR, pos, state) { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return ChemicalGeneratorMenu(containerID, inventory, this) + } + + val batteryContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val residueContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val fuelContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + + val batteryItemHandler = batteryContainer.handler(HandlerFilter.Chargeable) + val residueItemHandler = residueContainer.handler(HandlerFilter.OnlyOut) + val fuelItemHandler = fuelContainer.handler(HandlerFilter.ChemicalFuel) + + val energy = ProfiledEnergyStorage(GeneratorEnergyStorage(::markDirtyFast, MachinesConfig.ChemicalGenerator.VALUES::energyCapacity, MachinesConfig.ChemicalGenerator.VALUES::energyThroughput)) + + val itemConfig = ConfigurableItemHandler( + input = fuelItemHandler, + output = residueItemHandler, + battery = batteryItemHandler, + backDefault = ItemHandlerMode.BATTERY) + + val energyConfig = ConfigurableEnergy(energy) + + init { + for (piece in energyConfig.pieces.values) + piece.automatePush = true + + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::batteryContainer) + savetables.stateful(::residueContainer) + savetables.stateful(::fuelContainer) + + savetables.int(::workTicks) + savetables.int(::workTicksTotal) + } + + override fun markDirtyFast() { + super.markDirtyFast() + checkFuelSlot = true + } + + var workTicks = 0 + private set + + var workTicksTotal = 0 + private set + + private var checkFuelSlot = true + + override fun tick() { + super.tick() + + if (workTicks > 0) { + workTicks-- + energy.receiveEnergy(MachinesConfig.ChemicalGenerator.VALUES.energyConsumption, false) + + if (workTicks == 0) { + workTicksTotal = 0 + checkFuelSlot = true + } + + if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) + } + } else if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.IDLE) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) + } + + if (workTicks == 0 && !redstoneControl.isBlockedByRedstone && checkFuelSlot) { + if (!fuelContainer[0].isEmpty) { + val ticks = ForgeHooks.getBurnTime(fuelContainer[0], null) / MachinesConfig.ChemicalGenerator.RATIO + + if ( + ticks > 0 && + (energy.batteryLevel <= Decimal.ONE || MachinesConfig.ChemicalGenerator.VALUES.energyConsumption * ticks + energy.batteryLevel <= energy.maxBatteryLevel) && + residueContainer.fullyAddItem(fuelContainer[0].item.getCraftingRemainingItem(fuelContainer[0].copyWithCount(1))) + ) { + workTicksTotal = ticks + workTicks = workTicksTotal + fuelContainer.removeItem(0, 1) + } + } + + checkFuelSlot = false + } + + if (energy.batteryLevel.isPositive) { + for (item in batteryContainer) { + item.energy?.also { + moveEnergy(energy, it, MachinesConfig.ChemicalGenerator.VALUES.energyThroughput, simulate = false) + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt new file mode 100644 index 000000000..23adfcb8e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState) + : MatteryWorkerBlockEntity(MBlockEntities.COBBLESTONE_GENERATOR, blockPos, blockState, ItemJob.CODEC) { + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return CobblerMenu(containerID, inventory, this) + } + + val container = MatteryContainer(this::itemContainerUpdated, CONTAINER_SIZE).also(::addDroppableContainer) + val itemConfig = ConfigurableItemHandler(output = container.handler(HandlerFilter.OnlyOut)) + + init { + savetables.stateful(::container, INVENTORY_KEY) + } + + override fun onJobFinish(status: JobStatus, id: Int) { + if (!container.fullyAddItem(status.job.itemStack)) { + status.noItem() + } + } + + override fun computeNextJob(id: Int): JobContainer { + return JobContainer.success(ItemJob(ItemStack(Items.COBBLESTONE), 40.0)) + } + + companion object { + const val CONTAINER_SIZE = 9 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt new file mode 100644 index 000000000..445f2ae6d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt @@ -0,0 +1,305 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.IntTag +import net.minecraft.nbt.ListTag +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.getByteArrayList +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.mapPresent +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import java.util.* + +class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ENERGY_COUNTER, p_155229_, p_155230_) { + var passed by synchronizer.decimal() + + override val blockRotation: BlockRotation get() { + return BlockRotation.of(blockState[EnergyCounterBlock.INPUT_DIRECTION]) + } + + private val history = Array(10 * 20) { Decimal.ZERO } + internal var historyTick = 0 + + var lastTick by synchronizer.decimal() + internal set + + var ioLimit: Decimal? = null + + fun resetStats() { + historyTick = 0 + lastTick = Decimal.ZERO + passed = Decimal.ZERO + Arrays.fill(history, Decimal.ZERO) + } + + fun getHistory(ticks: Int): Array { + require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" } + + val history = Array(ticks) { Decimal.ZERO } + + for (i in 0 until ticks) { + var index = (historyTick - i) % this.history.size + if (index < 0) index = this.history.size - 1 + history[i] = this.history[index] + } + + return history + } + + fun calcAverage(ticks: Int): Decimal { + return sumHistory(ticks) / ticks + } + + fun sumHistory(ticks: Int): Decimal { + require(!(ticks < 1 || ticks >= history.size)) { "Invalid history length provided" } + + var value = Decimal.ZERO + + for (i in 0 until ticks) { + var index = (historyTick - i) % history.size + if (index < 0) index = history.size - 1 + value += history[index] + } + + return value + } + + override fun saveShared(nbt: CompoundTag) { + super.saveShared(nbt) + nbt[PASSED_ENERGY_KEY] = passed.serializeNBT() + ioLimit?.let { nbt[IO_LIMIT_KEY] = it.serializeNBT() } + } + + override fun saveLevel(nbt: CompoundTag) { + super.saveLevel(nbt) + val list = ListTag() + nbt[POWER_HISTORY_KEY] = list + nbt[POWER_HISTORY_POINTER_KEY] = historyTick + + for (num in history) + list.add(num.serializeNBT()) + } + + override fun load(nbt: CompoundTag) { + super.load(nbt) + + passed = nbt.getDecimal(PASSED_ENERGY_KEY) + ioLimit = nbt.mapPresent(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT) + + nbt.map(POWER_HISTORY_POINTER_KEY) { it: IntTag -> + historyTick = it.asInt + } + + Arrays.fill(history, Decimal.ZERO) + + for ((i, bytes) in nbt.getByteArrayList(POWER_HISTORY_KEY).withIndex()) { + history[i] = Decimal.deserializeNBT(bytes) + } + } + + override val defaultDisplayName: Component + get() = MACHINE_NAME + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return EnergyCounterMenu(containerID, inventory, this) + } + + private val energyInput = EnergyCounterCap(true) + private val energyOutput = EnergyCounterCap(false) + + private val inputCapability by front.trackEnergy() + private val outputCapability by back.trackEnergy() + + private inner class EnergyCounterCap(isInput: Boolean) : IMatteryEnergyStorage { + override val energyFlow = FlowDirection.input(isInput) + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (redstoneControl.isBlockedByRedstone) + return Decimal.ZERO + + val it = inputCapability.orNull() + + if (it != null) { + val diff: Decimal + + val ioLimit = ioLimit + + if (ioLimit != null) { + diff = it.extractEnergy(howMuch.coerceAtMost(ioLimit), simulate) + } else { + diff = it.extractEnergy(howMuch, simulate) + } + + if (!simulate) { + passed += diff + history[historyTick] += diff + } + + return diff + } + + return Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (redstoneControl.isBlockedByRedstone) + return Decimal.ZERO + + val it = outputCapability.orNull() + + if (it != null) { + val diff: Decimal + + val ioLimit = ioLimit + + if (ioLimit != null) { + diff = it.receiveEnergy(howMuch.coerceAtMost(ioLimit), simulate) + } else { + diff = it.receiveEnergy(howMuch, simulate) + } + + if (!simulate) { + passed += diff + history[historyTick] += diff + } + + return diff + } + + return Decimal.ZERO + } + + override val canSetBatteryLevel: Boolean + get() = false + + override var batteryLevel: Decimal + get() { + if (energyFlow.input) { + val it = outputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.batteryLevel + } + + return Decimal(it.energyStored) + } + } else { + val it = inputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.batteryLevel + } + + return Decimal(it.energyStored) + } + } + + return Decimal.ZERO + } + set(value) { + throw UnsupportedOperationException("Can't set energy on energy counter") + } + + override val maxBatteryLevel: Decimal + get() { + if (energyFlow.input) { + val it = outputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.maxBatteryLevel + } + + return Decimal(it.maxEnergyStored) + } + } else { + val it = inputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.maxBatteryLevel + } + + return Decimal(it.maxEnergyStored) + } + } + + return Decimal.ZERO + } + + override val missingPower: Decimal + get() { + if (energyFlow.input) { + val it = outputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.missingPower + } + + return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) + } + } else { + val it = inputCapability.orNull() + + if (it != null) { + if (it is IMatteryEnergyStorage) { + return it.missingPower + } + + return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) + } + } + + return Decimal.ZERO + } + } + + init { + front.Cap(ForgeCapabilities.ENERGY, energyInput) + front.Cap(MatteryCapability.ENERGY, energyInput) + + back.Cap(ForgeCapabilities.ENERGY, energyOutput) + back.Cap(MatteryCapability.ENERGY, energyOutput) + } + + override fun tick() { + super.tick() + + lastTick = history[historyTick] + historyTick = (historyTick + 1) % history.size + history[historyTick] = Decimal.ZERO + } + + fun clientTick() { + historyTick = (historyTick + 1) % history.size + history[historyTick] = lastTick + passed += lastTick + } + + companion object { + private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.energy_counter") + const val PASSED_ENERGY_KEY = "passedEnergy" + const val IO_LIMIT_KEY = "IOLimit" + const val POWER_HISTORY_KEY = "passHistory" + const val POWER_HISTORY_POINTER_KEY = "passHistoryPointer" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt new file mode 100644 index 000000000..693606ea7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt @@ -0,0 +1,107 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.canSetBatteryMattery +import ru.dbotthepony.mc.otm.capability.energy +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energyStoredMattery +import ru.dbotthepony.mc.otm.capability.extractEnergy +import ru.dbotthepony.mc.otm.capability.maxEnergyStoredMattery +import ru.dbotthepony.mc.otm.capability.receiveEnergy +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ENERGY_SERVO, blockPos, blockState) { + val discharge = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val charge = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + + val energy: ProfiledEnergyStorage = ProfiledEnergyStorage(object : IMatteryEnergyStorage { + override val energyFlow: FlowDirection get() { + val discharge = discharge[0].energy + val charge = charge[0].energy + return FlowDirection.of(input = charge?.canReceive() ?: false, output = discharge?.canExtract() ?: false) + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (redstoneControl.isBlockedByRedstone) + return Decimal.ZERO + + return discharge[0].energy?.extractEnergy(howMuch, simulate) ?: Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (redstoneControl.isBlockedByRedstone) + return Decimal.ZERO + + return charge[0].energy?.receiveEnergy(howMuch, simulate) ?: Decimal.ZERO + } + + override val canSetBatteryLevel: Boolean + get() = charge[0].energy?.canSetBatteryMattery ?: discharge[0].energy?.canSetBatteryMattery ?: false + + override var batteryLevel: Decimal + get() = charge[0].energy?.energyStoredMattery ?: discharge[0].energy?.energyStoredMattery ?: Decimal.ZERO + set(value) { + val energy = charge[0].energy ?: discharge[0].energy ?: throw UnsupportedOperationException("No item in slots") + energy.energyStoredMattery = value + } + + override val maxBatteryLevel: Decimal + get() = charge[0].energy?.maxEnergyStoredMattery ?: discharge[0].energy?.maxEnergyStoredMattery ?: Decimal.ZERO + }) + + val energyConfig = ConfigurableEnergy(energy, possibleModes = FlowDirection.BI_DIRECTIONAL) + + val itemConfig = ConfigurableItemHandler( + input = charge.handler(HandlerFilter.OnlyIn.and(HandlerFilter.Chargeable)), + output = discharge.handler(HandlerFilter.OnlyOut.and(HandlerFilter.Dischargeable)) + ) + + init { + savetables.stateful(::charge) + savetables.stateful(::discharge) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return EnergyServoMenu(containerID, inventory, this) + } + + override fun tick() { + super.tick() + + if (redstoneControl.isBlockedByRedstone) + return + + val charge = charge[0] + val discharge = discharge[0] + + if (!charge.isEmpty && !discharge.isEmpty) { + val chargeEnergy = charge.energy ?: return + val dischargeEnergy = discharge.energy ?: return + + val extracted = dischargeEnergy.extractEnergy(Decimal.LONG_MAX_VALUE, true) + + if (extracted.isPositive) { + val received = chargeEnergy.receiveEnergy(extracted, true) + + if (received.isPositive) { + val extracted2 = dischargeEnergy.extractEnergy(received, false) + + if (extracted2 == received) { + chargeEnergy.receiveEnergy(dischargeEnergy.extractEnergy(received, false), false) + } + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt new file mode 100644 index 000000000..5acf1f262 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt @@ -0,0 +1,149 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.enchantment.Enchantments +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage.Companion.XP_TO_LIQUID_RATIO +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem +import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MFluids + +class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState), IFluidHandler { + var experienceStored = 0L + set(value) { + require(value >= 0L) { "Negative experience: $value" } + field = value + markDirtyFast() + } + + val capsuleContainer = MatteryContainer(::markDirtyFast, 1) + val servoContainer = MatteryContainer(::markDirtyFast, 1) + val mendingContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + + init { + savetables.long(::experienceStored) + savetables.stateful(::capsuleContainer) + savetables.stateful(::servoContainer) + savetables.stateful(::mendingContainer) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + return EssenceStorageMenu(containerID, inventory, this) + } + + val itemConfig = ConfigurableItemHandler( + inputOutput = CombinedItemHandler( + capsuleContainer.handler(HandlerFilter.OnlyIn.and(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.item is EssenceCapsuleItem + } + })), + mendingContainer.handler(object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.isDamaged && stack.getEnchantmentLevel(Enchantments.MENDING) > 0 + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return !stack.isDamaged || experienceStored <= 0 + } + }) + ) + ) + + override fun getTanks(): Int { + return 1 + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (tank != 0) + return FluidStack.EMPTY + + if (experienceStored >= 2_000_000_000L) + return FluidStack(MFluids.LIQUID_XP, 2_000_000_000) + + return FluidStack(MFluids.LIQUID_XP, experienceStored.toInt()) + } + + override fun getTankCapacity(tank: Int): Int { + if (tank != 0) return 0 + return Int.MAX_VALUE + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + return tank == 0 && stack.fluid.fluidType == MFluids.LIQUID_XP_TYPE + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE || resource.amount <= 0) + return 0 + + val actualFill = resource.amount / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO + + if (action.execute()) + experienceStored += actualFill / XP_TO_LIQUID_RATIO + + return actualFill + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE) + return FluidStack.EMPTY + + return drain(resource.amount, action) + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + val stored = experienceStored * XP_TO_LIQUID_RATIO + val actualStored = if (stored >= Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt() + val actualDrain = maxDrain.coerceAtMost(actualStored).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO } + + if (actualDrain <= 0) + return FluidStack.EMPTY + + if (action.execute()) + experienceStored -= actualDrain / XP_TO_LIQUID_RATIO + + return FluidStack(MFluids.LIQUID_XP, actualDrain) + } + + override fun tick() { + super.tick() + + if (!redstoneControl.isBlockedByRedstone) { + val capsule = capsuleContainer[0] + + if (!capsule.isEmpty && capsule.item is EssenceCapsuleItem) { + experienceStored += EssenceCapsuleItem.experienceStored(capsule) + capsuleContainer.clearContent() + } + + val repairStack = mendingContainer[0] + + if (!repairStack.isEmpty && repairStack.isDamaged) { + if (experienceStored > 0) { + val dmgPerExp = repairStack.xpRepairRatio + var repairPoints = dmgPerExp.toInt() + + val diff = dmgPerExp - repairPoints.toFloat() + if (diff > 0f) { + repairPoints += if ((level?.random?.nextFloat() ?: 1f) <= diff) 1 else 0 + } + + experienceStored -= 1 + repairStack.damageValue -= repairPoints + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/GravitationStabilizerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt similarity index 73% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/GravitationStabilizerBlockEntity.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt index 9a14c3332..5e119bf06 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/GravitationStabilizerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/GravitationStabilizerBlockEntity.kt @@ -1,36 +1,32 @@ -package ru.dbotthepony.mc.otm.block.entity +package ru.dbotthepony.mc.otm.block.entity.tech import net.minecraft.core.BlockPos import net.minecraft.core.SectionPos -import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.phys.AABB -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.BlockGravitationStabilizer -import ru.dbotthepony.mc.otm.block.BlockGravitationStabilizerLens -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizerLens +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizer +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.registry.MBlockEntities -class GravitationStabilizerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity( - MBlockEntities.GRAVITATION_STABILIZER, p_155229_, p_155230_) { - - override val defaultDisplayName: Component - get() = MACHINE_NAME - +class GravitationStabilizerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.GRAVITATION_STABILIZER, p_155229_, p_155230_) { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player) = null private var blackHole: BlackHoleBlockEntity? = null fun tick(level: Level) { var findBlackHole: BlackHoleBlockEntity? = null - val dir = blockState.getValue(RotatableMatteryBlock.FACING_FULL).normal + val dir = blockState[BlockRotationFreedom.DIRECTIONAL].normal for (i in 2 .. RANGE) { val pos = blockPos + dir * i @@ -79,8 +75,6 @@ class GravitationStabilizerBlockEntity(p_155229_: BlockPos, p_155230_: BlockStat } companion object { - private val minBBox = BlockPos(1, 1, 1) const val RANGE = 64 - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.gravitation_stabilizer") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt new file mode 100644 index 000000000..2ba1f2a45 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -0,0 +1,109 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import it.unimi.dsi.fastutil.ints.IntArrayList +import net.minecraft.core.BlockPos +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.balance +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.maybe +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu +import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.registry.MRecipes + +class PlatePressBlockEntity( + blockPos: BlockPos, + blockState: BlockState, + val isTwin: Boolean = false, +) : MatteryWorkerBlockEntity(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, blockPos, blockState, ItemJob.CODEC, if (isTwin) 2 else 1) { + override val upgrades = UpgradeContainer(this::markDirtyFast, if (isTwin) 4 else 3, UpgradeType.BASIC_PROCESSING) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(MachinesConfig.PLATE_PRESS))) + val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) + val outputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) + + val experience = ExperienceStorage(MachinesConfig.PLATE_PRESS::maxExperienceStored).also(::addNeighbourListener) + val energyConfig = ConfigurableEnergy(energy) + val itemConfig = ConfigurableItemHandler( + input = inputContainer.handler(HandlerFilter.OnlyIn), + output = outputContainer.handler(HandlerFilter.OnlyOut), + ) + + init { + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience) + + savetables.stateful(::energy, ENERGY_KEY) + savetables.stateful(::inputContainer) + savetables.stateful(::outputContainer) + savetables.stateful(::experience) + savetables.stateful(::upgrades) + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { + if (isTwin) + return TwinPlatePressMenu(containerID, inventory, this) + else + return PlatePressMenu(containerID, inventory, this) + } + + override fun onJobFinish(status: JobStatus, id: Int) { + if (status.job.itemStack.isEmpty) + return status.success() + + if (!outputContainer.fullyAddItem(status.job.itemStack, slots = IntArrayList.of(id)) && !outputContainer.fullyAddItem(status.job.itemStack)) + return status.noItem() + + experience.storeExperience(status.experience, this) + } + + override fun computeNextJob(id: Int): JobContainer { + if (energy.batteryLevel.isZero) { + return JobContainer.noEnergy() + } + + val level = level ?: return JobContainer.failure() + + val recipe = level.recipeManager + .byType(MRecipes.PLATE_PRESS) + .values + .iterator() + .filter { it.value.matches(inputContainer, id) } + .maybe()?.value ?: return JobContainer.noItem() + + val toProcess = inputContainer[id].count.coerceAtMost(1 + upgrades.processingItems) + + inputContainer[id].shrink(toProcess) + inputContainer.setChanged(id) + + return JobContainer.success( + ItemJob( + recipe.getResultItem().copyWithCount(toProcess), + recipe.workTime * MachinesConfig.PLATE_PRESS.workTimeMultiplier, + MachinesConfig.PLATE_PRESS.energyConsumption * toProcess, + experience = recipe.experience.sample(level.random) * toProcess)) + } + + override fun tick() { + if (isTwin && balanceInputs) { + inputContainer.balance() + } + + super.tick() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt new file mode 100644 index 000000000..f88b67c25 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt @@ -0,0 +1,148 @@ +package ru.dbotthepony.mc.otm.block.entity.tech + +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.ExperienceOrb +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.item.crafting.AbstractCookingRecipe +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.config.WorkerBalanceValues +import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.balance +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.maybe +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu +import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe +import ru.dbotthepony.mc.otm.registry.MBlockEntities + +class PoweredFurnaceBlockEntity( + type: BlockEntityType, + blockPos: BlockPos, + blockState: BlockState, + val recipeType: RecipeType, + val secondaryRecipeType: (() -> RecipeType)?, + val config: WorkerBalanceValues +) : MatteryWorkerBlockEntity(type, blockPos, blockState, ItemJob.CODEC, 2) { + override val upgrades = UpgradeContainer(this::markDirtyFast, 2, UpgradeType.BASIC_PROCESSING) + override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config))) + + val inputs = immutableList(2) { MatteryContainer(this::itemContainerUpdated, 1) } + val outputs = immutableList(2) { MatteryContainer(this::itemContainerUpdated, 1) } + + init { + inputs.forEach { addDroppableContainer(it) } + outputs.forEach { addDroppableContainer(it) } + } + + val experience = ExperienceStorage(config::maxExperienceStored).also(::addNeighbourListener) + val energyConfig = ConfigurableEnergy(energy) + val itemConfig = ConfigurableItemHandler( + input = CombinedItemHandler(inputs.map { it.handler(HandlerFilter.OnlyIn) }), + output = CombinedItemHandler(outputs.map { it.handler(HandlerFilter.OnlyOut) }), + battery = batteryItemHandler + ) + + init { + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience) + + savetables.stateful(::upgrades) + savetables.stateful(::energy) + + savetables.stateful(::experience) + + savetables.stateful(inputs, "input") + savetables.stateful(outputs, "output") + } + + private val combined = CombinedContainer(inputs) + + override fun tick() { + if (balanceInputs) { + combined.balance() + } + + super.tick() + } + + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? { + return when (type) { + MBlockEntities.POWERED_FURNACE -> PoweredFurnaceMenu.furnace(containerID, inventory, this) + MBlockEntities.POWERED_BLAST_FURNACE -> PoweredFurnaceMenu.blasting(containerID, inventory, this) + MBlockEntities.POWERED_SMOKER -> PoweredFurnaceMenu.smoking(containerID, inventory, this) + else -> null + } + } + + override fun onJobFinish(status: JobStatus, id: Int) { + if (outputs[id].fullyAddItem(status.job.itemStack)) { + experience.storeExperience(status.experience, this) + } else { + status.noItem() + } + } + + override fun computeNextJob(id: Int): JobContainer { + if (!energy.batteryLevel.isPositive) + return JobContainer.noEnergy() + + if (inputs[id].isEmpty) + return JobContainer.noItem() + + val level = level as? ServerLevel ?: return JobContainer.failure() + + if (secondaryRecipeType != null) { + val recipe = level.recipeManager + .byType(secondaryRecipeType.invoke() as RecipeType) + .values + .iterator() + .filter { it.value.matches(inputs[id], 0) } + .maybe()?.value + + if (recipe != null) { + val toProcess = inputs[id][0].count.coerceAtMost(1 + upgrades.processingItems) + + inputs[id][0].shrink(toProcess) + inputs[id].setChanged(id) + + return JobContainer.success( + ItemJob( + recipe.getResultItem().copyWithCount(toProcess), + recipe.workTime * MachinesConfig.PLATE_PRESS.workTimeMultiplier, + MachinesConfig.PLATE_PRESS.energyConsumption * toProcess, + experience = recipe.experience.sample(level.random) * toProcess)) + } + } + + return level.recipeManager.getRecipeFor(recipeType as RecipeType, inputs[id], level).map { + val output = it.value.assemble(inputs[id]) + val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1) + inputs[id][0].shrink(toProcess) + + JobContainer.success(ItemJob( + output.copyWithCount(toProcess), it.value.cookingTime * config.workTimeMultiplier, config.energyConsumption * toProcess, it.value.experience * toProcess + )) + }.orElse(JobContainer.noItem()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterBottlerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterBottlerBlock.kt index d81a21840..7dd4a1876 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterBottlerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterBottlerBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level @@ -18,11 +17,13 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class MatterBottlerBlock : RotatableMatteryBlock(), EntityBlock { - override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { +class MatterBottlerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterBottlerBlockEntity(blockPos, blockState) } @@ -50,35 +51,23 @@ class MatterBottlerBlock : RotatableMatteryBlock(), EntityBlock { .setValue(SLOT_PROPERTIES[2], false) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_BOTTLER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } companion object { - private val SHAPES: List - val SLOT_PROPERTIES = arrayOf( BooleanProperty.create("bottle_0"), BooleanProperty.create("bottle_1"), BooleanProperty.create("bottle_2") ) - - init { - val def = BlockShapes.MATTER_BOTTLER.computeShape() - - SHAPES = listOf( - def, - def, - def, - BlockShapes.MATTER_BOTTLER.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_BOTTLER.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_BOTTLER.rotate(Direction.EAST).computeShape() - ) - } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt index 7ceaff35d..8dd5f1d73 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt @@ -4,66 +4,34 @@ import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter -import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.EntityBlock import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraft.world.level.block.entity.BlockEntityTicker -import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.block.BatteryBankBlock +import ru.dbotthepony.mc.otm.block.tech.BatteryBankBlock import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.block.entity.SynchronizedBlockEntity import ru.dbotthepony.mc.otm.block.entity.matter.MatterCapacitorBankBlockEntity -import ru.dbotthepony.mc.otm.block.blockServerTicker -import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.shapes.BlockShapes -class MatterCapacitorBankBlock : RotatableMatteryBlock(), EntityBlock { +class MatterCapacitorBankBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterCapacitorBankBlockEntity(blockPos, blockState) } - override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - var state = super.getStateForPlacement(context) ?: return null - - for (prop in BatteryBankBlock.BATTERY_SLOTS_PROPS) - state = state.setValue(prop, false) - - return state - } - - override fun createBlockStateDefinition(builder: StateDefinition.Builder) { - builder.add(*BatteryBankBlock.BATTERY_SLOTS_PROPS) - super.createBlockStateDefinition(builder) - } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_CAPACITOR_BANK.rotateFromNorth(it[rotationProperty]).computeShape() } + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val SHAPES: List - - init { - val def = BlockShapes.MATTER_CAPACITOR_BANK.computeShape() - - SHAPES = listOf( - def, - def, - def, - BlockShapes.MATTER_CAPACITOR_BANK.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_CAPACITOR_BANK.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_CAPACITOR_BANK.rotate(Direction.EAST).computeShape() - ) - } + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterDecomposerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterDecomposerBlock.kt index faf3065c4..8a3034140 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterDecomposerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterDecomposerBlock.kt @@ -4,7 +4,6 @@ import net.minecraft.MethodsReturnNonnullByDefault import javax.annotation.ParametersAreNonnullByDefault import net.minecraft.world.level.block.EntityBlock import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.entity.BlockEntity import ru.dbotthepony.mc.otm.block.entity.matter.MatterDecomposerBlockEntity @@ -18,12 +17,12 @@ import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -class MatterDecomposerBlock : RotatableMatteryBlock(), EntityBlock { +class MatterDecomposerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterDecomposerBlockEntity(blockPos, blockState) } @@ -44,25 +43,15 @@ class MatterDecomposerBlock : RotatableMatteryBlock(), EntityBlock { builder.add(WorkerState.WORKER_STATE) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_DECOMPOSER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } - - companion object { - private val def = BlockShapes.MATTER_DECOMPOSER.computeShape() - - private val SHAPES: List = listOf( - def, - def, - def, - BlockShapes.MATTER_DECOMPOSER.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_DECOMPOSER.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_DECOMPOSER.rotate(Direction.EAST).computeShape() - ) - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterEntanglerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterEntanglerBlock.kt new file mode 100644 index 000000000..96bcf3b1a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterEntanglerBlock.kt @@ -0,0 +1,22 @@ +package ru.dbotthepony.mc.otm.block.matter + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.matter.MatterEntanglerBlockEntity + +class MatterEntanglerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { + return MatterEntanglerBlockEntity(blockPos, blockState) + } + + override fun getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType): BlockEntityTicker? { + if (p_153212_.isClientSide) return null + return BlockEntityTicker { _, _, _, tile -> if (tile is MatterEntanglerBlockEntity) tile.tick() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterPanelBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterPanelBlock.kt index d23b5bcd5..fbabb31e2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterPanelBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterPanelBlock.kt @@ -11,14 +11,13 @@ import net.minecraft.world.phys.shapes.VoxelShape import net.minecraft.world.phys.shapes.Shapes import net.minecraft.world.level.BlockGetter import net.minecraft.world.phys.shapes.CollisionContext -import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.item.context.BlockPlaceContext -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.state.properties.EnumProperty -import ru.dbotthepony.mc.otm.block.MatteryBlock import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom -class MatterPanelBlock : RotatableMatteryBlock(), EntityBlock { +class MatterPanelBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterPanelBlockEntity(blockPos, blockState) } @@ -26,10 +25,10 @@ class MatterPanelBlock : RotatableMatteryBlock(), EntityBlock { private val shapes: ImmutableMap init { - registerDefaultState(getStateDefinition().any().setValue(FACING_FULL, Direction.SOUTH)) + registerDefaultState(getStateDefinition().any().setValue(BlockRotationFreedom.DIRECTIONAL.property, BlockRotation.SOUTH)) shapes = getShapeForEachState { - when (it.getValue(FACING_FULL)) { + when (it[BlockRotationFreedom.DIRECTIONAL.property].front) { Direction.NORTH -> Shapes.box( 0.0, 0.0, @@ -91,10 +90,11 @@ class MatterPanelBlock : RotatableMatteryBlock(), EntityBlock { return shapes[p_60555_]!! } - override val hasFreeRotation: Boolean - get() = true + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - return defaultBlockState().setValue(FACING_FULL, context.clickedFace) + return defaultBlockState().setValue(BlockRotationFreedom.DIRECTIONAL.property, BlockRotation.of(context.clickedFace)) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReconstructorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReconstructorBlock.kt new file mode 100644 index 000000000..3452c1ff2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReconstructorBlock.kt @@ -0,0 +1,42 @@ +package ru.dbotthepony.mc.otm.block.matter + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class MatterReconstructorBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity { + return MatterReconstructorBlockEntity(pPos, pState) + } + + override fun getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType): BlockEntityTicker? { + if (pLevel.isClientSide) + return null + + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is MatterReconstructorBlockEntity) pBlockEntity.tick() } + } + + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_RECONSTRUCTOR.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterRecyclerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterRecyclerBlock.kt index d8c99395a..13d2bcd0f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterRecyclerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterRecyclerBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block @@ -16,10 +15,12 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.matter.MatterRecyclerBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class MatterRecyclerBlock : RotatableMatteryBlock(), EntityBlock { +class MatterRecyclerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return MatterRecyclerBlockEntity(p_153215_, p_153216_) } @@ -40,29 +41,15 @@ class MatterRecyclerBlock : RotatableMatteryBlock(), EntityBlock { return BlockEntityTicker { _, _, _, tile -> if (tile is MatterRecyclerBlockEntity) tile.tick() } } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_RECYCLER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.MATTER_RECYCLER.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.MATTER_RECYCLER.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_RECYCLER.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_RECYCLER.rotate(Direction.EAST).computeShape() - ) - } + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt index 1d15fbbc6..a51b83ae5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterReplicatorBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block @@ -16,10 +15,12 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class MatterReplicatorBlock : RotatableMatteryBlock(), EntityBlock { +class MatterReplicatorBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterReplicatorBlockEntity(blockPos, blockState) } @@ -32,7 +33,7 @@ class MatterReplicatorBlock : RotatableMatteryBlock(), EntityBlock { if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_REPLICATOR) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is MatterReplicatorBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is MatterReplicatorBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { @@ -40,29 +41,15 @@ class MatterReplicatorBlock : RotatableMatteryBlock(), EntityBlock { builder.add(WorkerState.WORKER_STATE) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_REPLICATOR.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.MATTER_REPLICATOR.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.MATTER_REPLICATOR.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_REPLICATOR.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_REPLICATOR.rotate(Direction.EAST).computeShape() - ) - } - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt index 51b3a72c3..53cc306ca 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterScannerBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block @@ -16,10 +15,12 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class MatterScannerBlock : RotatableMatteryBlock(), EntityBlock { +class MatterScannerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return MatterScannerBlockEntity(blockPos, blockState) } @@ -32,7 +33,7 @@ class MatterScannerBlock : RotatableMatteryBlock(), EntityBlock { if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.MATTER_SCANNER) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is MatterScannerBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is MatterScannerBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { @@ -40,29 +41,15 @@ class MatterScannerBlock : RotatableMatteryBlock(), EntityBlock { builder.add(WorkerState.WORKER_STATE) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.MATTER_SCANNER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.MATTER_SCANNER.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.MATTER_SCANNER.rotate(Direction.NORTH).computeShape(), - BlockShapes.MATTER_SCANNER.rotate(Direction.WEST).computeShape(), - BlockShapes.MATTER_SCANNER.rotate(Direction.EAST).computeShape() - ) - } - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/PatternStorageBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/PatternStorageBlock.kt index 5bdcb39ea..d5f36e76a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/PatternStorageBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/PatternStorageBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.matter import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.world.Containers import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter @@ -16,10 +15,12 @@ import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.matter.PatternStorageBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.shapes.BlockShapes -class PatternStorageBlock : RotatableMatteryBlock(), EntityBlock { - override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? { +class PatternStorageBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return PatternStorageBlockEntity(blockPos, blockState) } @@ -48,7 +49,7 @@ class PatternStorageBlock : RotatableMatteryBlock(), EntityBlock { val blockentity = level.getBlockEntity(blockPos) if (blockentity is PatternStorageBlockEntity) { - Containers.dropContents(level, blockPos, blockentity.patternContainer) + Containers.dropContents(level, blockPos, blockentity.container) level.updateNeighbourForOutputSignal(blockPos, this) } } @@ -56,13 +57,16 @@ class PatternStorageBlock : RotatableMatteryBlock(), EntityBlock { super.onRemove(oldBlockState, level, blockPos, newBlockState, movedByPiston) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.PATTERN_STORAGE.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } companion object { @@ -76,20 +80,5 @@ class PatternStorageBlock : RotatableMatteryBlock(), EntityBlock { BooleanProperty.create("disk_6") as BooleanProperty, BooleanProperty.create("disk_7") as BooleanProperty ) - - private val SHAPES: List - - init { - val def = BlockShapes.PATTERN_STORAGE.computeShape() - - SHAPES = listOf( - def, - def, - def, - BlockShapes.PATTERN_STORAGE.rotate(Direction.NORTH).computeShape(), - BlockShapes.PATTERN_STORAGE.rotate(Direction.WEST).computeShape(), - BlockShapes.PATTERN_STORAGE.rotate(Direction.EAST).computeShape() - ) - } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveRackBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveRackBlock.kt index 4ac908099..8f4ecc071 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveRackBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveRackBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag @@ -17,11 +16,13 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.DriveRackBlockEntity -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class DriveRackBlock : RotatableMatteryBlock(), EntityBlock { +class DriveRackBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return DriveRackBlockEntity(blockPos, blockState) } @@ -48,25 +49,15 @@ class DriveRackBlock : RotatableMatteryBlock(), EntityBlock { MatteryPoweredBlockEntity.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.DRIVE_RACK.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val def = BlockShapes.DRIVE_RACK.computeShape() - - private val SHAPES: List = listOf( - def, - def, - def, - BlockShapes.DRIVE_RACK.rotate(Direction.NORTH).computeShape(), - BlockShapes.DRIVE_RACK.rotate(Direction.WEST).computeShape(), - BlockShapes.DRIVE_RACK.rotate(Direction.EAST).computeShape() - ) + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveViewerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveViewerBlock.kt index 6d63d5528..03a0bb47c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveViewerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/DriveViewerBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag @@ -22,11 +21,13 @@ import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class DriveViewerBlock : RotatableMatteryBlock(), EntityBlock { +class DriveViewerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return DriveViewerBlockEntity(blockPos, blockState) } @@ -63,30 +64,19 @@ class DriveViewerBlock : RotatableMatteryBlock(), EntityBlock { return super.getStateForPlacement(context)!!.setValue(DRIVE_PRESENT, false) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.DRIVE_VIEWER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] + return shapes[state]!! } companion object { val DRIVE_PRESENT: BooleanProperty = BooleanProperty.create("drive") - private val SHAPES: Array - - init { - val def = BlockShapes.DRIVE_VIEWER.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.DRIVE_VIEWER.rotate(Direction.NORTH).computeShape(), - BlockShapes.DRIVE_VIEWER.rotate(Direction.WEST).computeShape(), - BlockShapes.DRIVE_VIEWER.rotate(Direction.EAST).computeShape() - ) - } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/ItemMonitorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/ItemMonitorBlock.kt index 1fa5a506e..32dd33264 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/ItemMonitorBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/ItemMonitorBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag @@ -17,11 +16,13 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class ItemMonitorBlock : RotatableMatteryBlock(), EntityBlock { +class ItemMonitorBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return ItemMonitorBlockEntity(blockPos, blockState) } @@ -48,25 +49,15 @@ class ItemMonitorBlock : RotatableMatteryBlock(), EntityBlock { MatteryPoweredBlockEntity.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.ITEM_MONITOR.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val def = BlockShapes.ITEM_MONITOR.computeShape() - - private val SHAPES: List = listOf( - def, - def, - def, - BlockShapes.ITEM_MONITOR.rotate(Direction.NORTH).computeShape(), - BlockShapes.ITEM_MONITOR.rotate(Direction.WEST).computeShape(), - BlockShapes.ITEM_MONITOR.rotate(Direction.EAST).computeShape() - ) + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageBusBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageBusBlock.kt index aa102c300..568ec92e9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageBusBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageBusBlock.kt @@ -23,14 +23,18 @@ import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.StorageCableBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.StorageBusBlockEntity -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -import ru.dbotthepony.mc.otm.core.unaryMinus -import ru.dbotthepony.mc.otm.oncePre +import ru.dbotthepony.mc.otm.core.math.unaryMinus -class StorageBusBlock : RotatableMatteryBlock(), EntityBlock { - override val hasFreeRotation: Boolean get() = true +class StorageBusBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } init { registerDefaultState(defaultBlockState() @@ -47,7 +51,7 @@ class StorageBusBlock : RotatableMatteryBlock(), EntityBlock { } override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - return super.getStateForPlacement(context)?.setValue(FACING_FULL, -context.clickedFace) + return super.getStateForPlacement(context)?.setValue(BlockRotationFreedom.DIRECTIONAL.property, BlockRotation.of(-context.clickedFace)) } override fun appendHoverText( @@ -86,16 +90,7 @@ class StorageBusBlock : RotatableMatteryBlock(), EntityBlock { } private val shapes = getShapeForEachState { - val shapes = StorageCableBlock.getShapeFor(it) - var finalShape = shapes[0] - - for (i in 1 until shapes.size) { - finalShape = Shapes.joinUnoptimized(finalShape, shapes[i], BooleanOp.OR) - } - - finalShape = Shapes.joinUnoptimized(finalShape, BlockShapes.STORAGE_BUS.rotateInv(it.getValue(FACING_FULL)).computeShape(), BooleanOp.OR) - - return@getShapeForEachState finalShape + Shapes.joinUnoptimized(CableBlock.getShapeFor(it, 0.185), BlockShapes.STORAGE_BUS.rotateFromNorth(it[rotationProperty]).computeShape(), BooleanOp.OR) } @Suppress("OVERRIDE_DEPRECATION") @@ -105,28 +100,6 @@ class StorageBusBlock : RotatableMatteryBlock(), EntityBlock { p_60557_: BlockPos, p_60558_: CollisionContext ): VoxelShape { - return shapes[p_60555_] ?: BlockShapes.STORAGE_BUS.computeShape() - } - - @Suppress("OVERRIDE_DEPRECATION") - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - - if (!level.isClientSide) { - val tile = level.getBlockEntity(pos) - - if (tile is StorageBusBlockEntity) { - level.oncePre { - tile.checkSurroundings() - } - } - } + return shapes[p_60555_]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageInterfaces.kt index b4c9de21b..aaef6b7f4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StorageInterfaces.kt @@ -24,14 +24,18 @@ import ru.dbotthepony.mc.otm.block.StorageCableBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.StorageExporterBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.StorageImporterBlockEntity -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -import ru.dbotthepony.mc.otm.core.unaryMinus -import ru.dbotthepony.mc.otm.oncePre +import ru.dbotthepony.mc.otm.core.math.unaryMinus -class StorageImporterBlock : RotatableMatteryBlock(), EntityBlock { - override val hasFreeRotation: Boolean get() = true +class StorageImporterBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return StorageImporterBlockEntity(p_153215_, p_153216_) @@ -72,7 +76,7 @@ class StorageImporterBlock : RotatableMatteryBlock(), EntityBlock { } override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - return super.getStateForPlacement(context)?.setValue(FACING_FULL, -context.clickedFace) + return super.getStateForPlacement(context)?.setValue(BlockRotationFreedom.DIRECTIONAL.property, BlockRotation.of(-context.clickedFace)) } override fun appendHoverText( @@ -87,16 +91,7 @@ class StorageImporterBlock : RotatableMatteryBlock(), EntityBlock { } private val shapes = getShapeForEachState { - val shapes = StorageCableBlock.getShapeFor(it) - var finalShape = shapes[0] - - for (i in 1 until shapes.size) { - finalShape = Shapes.joinUnoptimized(finalShape, shapes[i], BooleanOp.OR) - } - - finalShape = Shapes.joinUnoptimized(finalShape, BlockShapes.STORAGE_IMPORTER.rotateInv(it.getValue(FACING_FULL)).computeShape(), BooleanOp.OR) - - return@getShapeForEachState finalShape + Shapes.joinUnoptimized(CableBlock.getShapeFor(it, 0.185), BlockShapes.STORAGE_IMPORTER.rotateFromNorth(it[rotationProperty]).computeShape(), BooleanOp.OR) } @Suppress("OVERRIDE_DEPRECATION") @@ -106,34 +101,14 @@ class StorageImporterBlock : RotatableMatteryBlock(), EntityBlock { p_60557_: BlockPos, p_60558_: CollisionContext ): VoxelShape { - return shapes[p_60555_] ?: BlockShapes.STORAGE_BUS.computeShape() - } - - @Suppress("OVERRIDE_DEPRECATION") - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - - if (!level.isClientSide) { - val tile = level.getBlockEntity(pos) - - if (tile is StorageImporterBlockEntity) { - level.oncePre { - tile.checkSurroundings() - } - } - } + return shapes[p_60555_]!! } } -class StorageExporterBlock : RotatableMatteryBlock(), EntityBlock { - override val hasFreeRotation: Boolean get() = true +class StorageExporterBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return StorageExporterBlockEntity(p_153215_, p_153216_) @@ -185,20 +160,11 @@ class StorageExporterBlock : RotatableMatteryBlock(), EntityBlock { } override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { - return super.getStateForPlacement(context)?.setValue(FACING_FULL, -context.clickedFace) + return super.getStateForPlacement(context)?.setValue(BlockRotationFreedom.DIRECTIONAL.property, BlockRotation.of(-context.clickedFace)) } private val shapes = getShapeForEachState { - val shapes = StorageCableBlock.getShapeFor(it) - var finalShape = shapes[0] - - for (i in 1 until shapes.size) { - finalShape = Shapes.joinUnoptimized(finalShape, shapes[i], BooleanOp.OR) - } - - finalShape = Shapes.joinUnoptimized(finalShape, BlockShapes.STORAGE_EXPORTER.rotateInv(it.getValue(FACING_FULL)).computeShape(), BooleanOp.OR) - - return@getShapeForEachState finalShape + Shapes.joinUnoptimized(CableBlock.getShapeFor(it, 0.185), BlockShapes.STORAGE_EXPORTER.rotateFromNorth(it[rotationProperty]).computeShape(), BooleanOp.OR) } @Suppress("OVERRIDE_DEPRECATION") @@ -208,28 +174,6 @@ class StorageExporterBlock : RotatableMatteryBlock(), EntityBlock { p_60557_: BlockPos, p_60558_: CollisionContext ): VoxelShape { - return shapes[p_60555_] ?: BlockShapes.STORAGE_BUS.computeShape() - } - - @Suppress("OVERRIDE_DEPRECATION") - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - - if (!level.isClientSide) { - val tile = level.getBlockEntity(pos) - - if (tile is StorageExporterBlockEntity) { - level.oncePre { - tile.checkSurroundings() - } - } - } + return shapes[p_60555_]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StoragePowerSupplierBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StoragePowerSupplierBlock.kt index 55e48e086..0f756e32e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StoragePowerSupplierBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/storage/StoragePowerSupplierBlock.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.storage import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag @@ -17,11 +16,13 @@ import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.StoragePowerSupplierBlockEntity -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class StoragePowerSupplierBlock : RotatableMatteryBlock(), EntityBlock { +class StoragePowerSupplierBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return StoragePowerSupplierBlockEntity(p_153215_, p_153216_) } @@ -48,29 +49,15 @@ class StoragePowerSupplierBlock : RotatableMatteryBlock(), EntityBlock { MatteryPoweredBlockEntity.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.STORAGE_POWER_SUPPLIER.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.STORAGE_POWER_SUPPLIER.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.STORAGE_POWER_SUPPLIER.rotate(Direction.NORTH).computeShape(), - BlockShapes.STORAGE_POWER_SUPPLIER.rotate(Direction.WEST).computeShape(), - BlockShapes.STORAGE_POWER_SUPPLIER.rotate(Direction.EAST).computeShape() - ) - } + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidChargerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidChargerBlock.kt new file mode 100644 index 000000000..94845f127 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidChargerBlock.kt @@ -0,0 +1,177 @@ +package ru.dbotthepony.mc.otm.block.tech + +import net.minecraft.ChatFormatting +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.util.StringRepresentable +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.context.BlockPlaceContext +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.StateDefinition +import net.minecraft.world.level.block.state.properties.EnumProperty +import net.minecraft.world.level.material.Material +import net.minecraft.world.level.material.PushReaction +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerMiddleBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerTopBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.gracefulBlockBreak +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.oncePre +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class AndroidChargerBlock : RotatableMatteryBlock(Properties.of(Material.METAL).destroyTime(2.5f).explosionResistance(40f).requiresCorrectToolForDrops()), EntityBlock { + enum class Type : StringRepresentable { + BASE, + MIDDLE, + TOP; + + override fun getSerializedName(): String { + return name.lowercase() + } + } + + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { + super.createBlockStateDefinition(builder) + builder.add(PART) + } + + init { + registerDefaultState(defaultBlockState().setValue(PART, Type.BASE)) + } + + override fun getStateForPlacement(context: BlockPlaceContext): BlockState? { + val level = context.level + if (level.isOutsideBuildHeight(context.clickedPos + BlockPos(0, 2, 0))) return null + + for (i in 1 .. 2) { + val pos = context.clickedPos + BlockPos(0, i, 0) + if (!level.getBlockState(pos).canBeReplaced(context)) return null + } + + return super.getStateForPlacement(context) + } + + override fun setPlacedBy(level: Level, blockPos: BlockPos, blockState: BlockState, entity: LivingEntity?, itemStack: ItemStack) { + super.setPlacedBy(level, blockPos, blockState, entity, itemStack) + + if (blockState[PART] == Type.BASE) { + level.setBlock(blockPos.above(), blockState.setValue(PART, Type.MIDDLE), UPDATE_ALL) + level.setBlock(blockPos.above().above(), blockState.setValue(PART, Type.TOP), UPDATE_ALL) + } + } + + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity? { + return when (p_153216_[PART]!!) { + Type.BASE -> AndroidChargerBlockEntity(p_153215_, p_153216_) + Type.MIDDLE -> AndroidChargerMiddleBlockEntity(p_153215_, p_153216_) + Type.TOP -> AndroidChargerTopBlockEntity(p_153215_, p_153216_) + } + } + + override fun getTicker(p_153212_: Level, blockState: BlockState, p_153214_: BlockEntityType): BlockEntityTicker? { + if (p_153212_.isClientSide) return null + + return when (blockState[PART]!!) { + Type.BASE -> BlockEntityTicker { _, _, _, t -> if (t is AndroidChargerBlockEntity) t.tick() } + Type.MIDDLE -> BlockEntityTicker { _, _, _, t -> if (t is AndroidChargerMiddleBlockEntity) t.tick() } + Type.TOP -> BlockEntityTicker { _, _, _, t -> if (t is AndroidChargerTopBlockEntity) t.tick() } + } + } + + override fun getDestroyProgress(p_60466_: BlockState, p_60467_: Player, p_60468_: BlockGetter, p_60469_: BlockPos): Float { + // запрет на ломание не нижнего блока игроком + if (p_60466_[PART] != Type.BASE) return 0f + return super.getDestroyProgress(p_60466_, p_60467_, p_60468_, p_60469_) + } + + override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) { + super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) + + level.oncePre { + if (level.getBlockState(pos) == state) { + when (state[PART]!!) { + Type.BASE -> { + val a = level.getBlockState(pos.above()) + val b = level.getBlockState(pos.above().above()) + + if (a.block != this || b.block != this || a[PART] != Type.MIDDLE || b[PART] != Type.TOP) { + level.gracefulBlockBreak(pos, state) + } + } + + Type.MIDDLE -> { + val a = level.getBlockState(pos.below()) + val b = level.getBlockState(pos.above()) + + if (a.block != this || b.block != this || a[PART] != Type.BASE || b[PART] != Type.TOP) { + level.setBlock(pos, Blocks.AIR.defaultBlockState(), UPDATE_ALL) + } + } + + Type.TOP -> { + val a = level.getBlockState(pos.below()) + val b = level.getBlockState(pos.below().below()) + + if (a.block != this || b.block != this || a[PART] != Type.MIDDLE || b[PART] != Type.BASE) { + level.setBlock(pos, Blocks.AIR.defaultBlockState(), UPDATE_ALL) + } + } + } + } + } + } + + override fun appendHoverText(p_49816_: ItemStack, p_49817_: BlockGetter?, p_49818_: MutableList, p_49819_: TooltipFlag) { + super.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) + + p_49818_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY)) + WorkerEnergyStorage.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) + MatteryPoweredBlockEntity.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) + } + + private val shapes = getShapeForEachState(listOf(rotationProperty, PART)) { + val shape = when (it[PART]!!) { + Type.BASE -> BlockShapes.ANDROID_CHARGER_BASE + Type.MIDDLE -> BlockShapes.ANDROID_CHARGER_MIDDLE + Type.TOP -> BlockShapes.ANDROID_CHARGER_TOP + } + + shape.rotateFromNorth(it[rotationProperty]).computeShape() + } + + override fun getShape( + p_60555_: BlockState, + p_60556_: BlockGetter, + p_60557_: BlockPos, + p_60558_: CollisionContext + ): VoxelShape { + return shapes[p_60555_]!! + } + + companion object { + val PART: EnumProperty = EnumProperty.create("part", Type::class.java) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/AndroidStationBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt similarity index 90% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/AndroidStationBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt index 2767cd147..962af67b5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/AndroidStationBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/AndroidStationBlock.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component @@ -19,17 +19,17 @@ import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.block.entity.AndroidStationBlockEntity +import ru.dbotthepony.mc.otm.block.MatteryBlock +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity -import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class AndroidStationBlock : MatteryBlock(), EntityBlock { +class AndroidStationBlock : MatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun use( blockState: BlockState, level: Level, @@ -89,4 +89,4 @@ class AndroidStationBlock : MatteryBlock(), EntityBlock { companion object { private val SHAPE = BlockShapes.ANDROID_STATION.computeShape() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/BatteryBankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/BatteryBankBlock.kt new file mode 100644 index 000000000..c5f7f9c87 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/BatteryBankBlock.kt @@ -0,0 +1,61 @@ +package ru.dbotthepony.mc.otm.block.tech + +import com.google.common.collect.ImmutableList +import net.minecraft.MethodsReturnNonnullByDefault +import javax.annotation.ParametersAreNonnullByDefault +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.item.context.BlockPlaceContext +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.properties.BooleanProperty +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.state.StateDefinition +import net.minecraft.core.BlockPos +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.oncePre +import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +class BatteryBankBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun getTicker( + level: Level, + p_153213_: BlockState, + type: BlockEntityType + ): BlockEntityTicker? { + if (level.isClientSide || type !== MBlockEntities.BATTERY_BANK) + return null + + return BlockEntityTicker { _, _, _, tile -> if (tile is BatteryBankBlockEntity) tile.tick() } + } + + override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { + return BatteryBankBlockEntity(blockPos, blockState) + } + + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.BATTERY_BANK.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } + + override fun faceToPlayer(context: BlockPlaceContext): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/ChemicalGeneratorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/ChemicalGeneratorBlock.kt similarity index 54% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/ChemicalGeneratorBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/ChemicalGeneratorBlock.kt index 294569aca..3ba037ad4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/ChemicalGeneratorBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/ChemicalGeneratorBlock.kt @@ -1,8 +1,7 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.world.item.BlockItem @@ -19,19 +18,23 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.ChemicalGeneratorBlockEntity +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.capability.GeneratorEnergyStorage -import ru.dbotthepony.mc.otm.capability.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.capability.energy.GeneratorEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.map +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.oncePre import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class ChemicalGeneratorBlock : RotatableMatteryBlock(), EntityBlock { +class ChemicalGeneratorBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return ChemicalGeneratorBlockEntity(p_153215_, p_153216_) } @@ -60,58 +63,17 @@ class ChemicalGeneratorBlock : RotatableMatteryBlock(), EntityBlock { ) { super.appendHoverText(itemStack, p_49817_, tooltips, p_49819_) GeneratorEnergyStorage.appendHoverText(itemStack, p_49817_, tooltips, p_49819_) - - val tag = itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag ?: return - val container = MatteryContainer(ChemicalGeneratorBlockEntity.SLOTS) - tag.map(MatteryBlockEntity.INVENTORY_KEY, container::deserializeNBT) - - if (!container[ChemicalGeneratorBlockEntity.SLOT_BATTERY].isEmpty) { - tooltips.add(TranslatableComponent("otm.item.block.stored_battery", container[ChemicalGeneratorBlockEntity.SLOT_BATTERY].displayName).withStyle(ChatFormatting.GRAY)) - - ItemEnergyStorageImpl.appendHoverText(container[ChemicalGeneratorBlockEntity.SLOT_BATTERY], tooltips) - } } - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - - if (!level.isClientSide) { - val tile = level.getBlockEntity(pos) - - if (tile is ChemicalGeneratorBlockEntity) { - level.oncePre { - tile.checkSurroundings() - } - } - } - } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.CHEMICAL_GENERATOR.rotateFromNorth(it[rotationProperty]).computeShape() } + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val def = BlockShapes.CHEMICAL_GENERATOR.computeShape() - - private val SHAPES: List = listOf( - def, - def, - def, - BlockShapes.CHEMICAL_GENERATOR.rotate(Direction.NORTH).computeShape(), - BlockShapes.CHEMICAL_GENERATOR.rotate(Direction.WEST).computeShape(), - BlockShapes.CHEMICAL_GENERATOR.rotate(Direction.EAST).computeShape() - ) + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt new file mode 100644 index 000000000..5da695907 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/CobblerBlock.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.block.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.block.entity.tech.CobblerBlockEntity +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class CobblerBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity { + return CobblerBlockEntity(pPos, pState) + } + + override fun getTicker( + pLevel: Level, + pState: BlockState, + pBlockEntityType: BlockEntityType + ): BlockEntityTicker? { + if (!pLevel.isClientSide) { + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is CobblerBlockEntity) pBlockEntity.tick() } + } + + return null + } + + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.COBBLESTONE_GENERATOR.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyCounterBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyCounterBlock.kt similarity index 87% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyCounterBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyCounterBlock.kt index 0a560257a..6e0e61f94 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyCounterBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyCounterBlock.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos import net.minecraft.core.Direction @@ -16,13 +16,13 @@ import net.minecraft.world.level.block.state.properties.EnumProperty import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.SERVER_IS_LIVE -import ru.dbotthepony.mc.otm.block.entity.EnergyCounterBlockEntity +import ru.dbotthepony.mc.otm.block.MatteryBlock +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity import ru.dbotthepony.mc.otm.once -import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class EnergyCounterBlock : MatteryBlock(), EntityBlock { +class EnergyCounterBlock : MatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity { return EnergyCounterBlockEntity(blockPos, blockState) } @@ -63,23 +63,6 @@ class EnergyCounterBlock : MatteryBlock(), EntityBlock { p_49915_.add(INPUT_DIRECTION, IF_DIRECTION) } - override fun neighborChanged( - state: BlockState, - level: Level, - pos: BlockPos, - neighbour: Block, - neighbourPos: BlockPos, - movedByPiston: Boolean - ) { - super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) - - if (!level.isClientSide && SERVER_IS_LIVE) { - level.once { - (level.getBlockEntity(pos) as? EnergyCounterBlockEntity)?.checkSurroundings() - } - } - } - private val SHAPES = HashMap() init { @@ -97,7 +80,7 @@ class EnergyCounterBlock : MatteryBlock(), EntityBlock { } if (input === Direction.UP || input === Direction.DOWN) { - shape = shape.rotateInv(iface) + shape = shape.rotateFromNorth(iface) } else if (input === Direction.EAST || input === Direction.WEST) { when (iface) { Direction.DOWN -> shape = shape.rotateAroundX(Math.PI / 2) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyServoBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyServoBlock.kt similarity index 57% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyServoBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyServoBlock.kt index d81a6e62c..6a132d543 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/EnergyServoBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EnergyServoBlock.kt @@ -1,27 +1,35 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos -import net.minecraft.core.Direction +import net.minecraft.world.item.DyeColor import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityTicker import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor +import net.minecraft.world.level.material.PushReaction import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.block.entity.EnergyServoBlockEntity +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyServoBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes -class EnergyServoBlock : RotatableMatteryBlock(Properties.of(Material.METAL, MaterialColor.COLOR_BLUE).explosionResistance(12f).destroyTime(2f)), EntityBlock { +class EnergyServoBlock : RotatableMatteryBlock(Properties.of(Material.METAL, DyeColor.BLUE).sound(SoundType.METAL).explosionResistance(12f).destroyTime(2f).requiresCorrectToolForDrops()), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return EnergyServoBlockEntity(p_153215_, p_153216_) } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun getTicker( p_153212_: Level, p_153213_: BlockState, @@ -34,29 +42,15 @@ class EnergyServoBlock : RotatableMatteryBlock(Properties.of(Material.METAL, Mat return null } + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.ENERGY_SERVO.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.ENERGY_SERVO.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.ENERGY_SERVO.rotate(Direction.NORTH).computeShape(), - BlockShapes.ENERGY_SERVO.rotate(Direction.WEST).computeShape(), - BlockShapes.ENERGY_SERVO.rotate(Direction.EAST).computeShape() - ) - } + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt new file mode 100644 index 000000000..41eb4826a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/EssenceStorageBlock.kt @@ -0,0 +1,57 @@ +package ru.dbotthepony.mc.otm.block.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult +import net.minecraft.world.entity.player.Player +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.phys.BlockHitResult +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.shapes.BlockShapes + +class EssenceStorageBlock : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(pPos: BlockPos, pState: BlockState): BlockEntity { + return EssenceStorageBlockEntity(pPos, pState) + } + + override fun getTicker(pLevel: Level, pState: BlockState, pBlockEntityType: BlockEntityType): BlockEntityTicker? { + if (!pLevel.isClientSide) { + return BlockEntityTicker { _, _, _, pBlockEntity -> if (pBlockEntity is EssenceStorageBlockEntity) pBlockEntity.tick() } + } + + return null + } + + @Suppress("OVERRIDE_DEPRECATION") + override fun use(blockState: BlockState, level: Level, blockPos: BlockPos, ply: Player, hand: InteractionHand, blockHitResult: BlockHitResult): InteractionResult { + if (ply.getItemInHand(hand).item == MItems.ESSENCE_SERVO) { + return MItems.ESSENCE_SERVO.useServo(ply, blockPos) + } + + return super.use(blockState, level, blockPos, ply, hand, blockHitResult) + } + + private val shapes = getShapeForEachState(rotationProperty) { BlockShapes.ESSENCE_STORAGE.rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/GravitationStabilizerBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/GravitationStabilizerBlock.kt similarity index 74% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/GravitationStabilizerBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/GravitationStabilizerBlock.kt index 09fa3929d..71ebb5841 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/GravitationStabilizerBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/GravitationStabilizerBlock.kt @@ -1,9 +1,10 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.core.SectionPos import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter @@ -11,6 +12,7 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityTicker import net.minecraft.world.level.block.entity.BlockEntityType @@ -18,28 +20,34 @@ import net.minecraft.world.level.block.state.BlockBehaviour import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor import net.minecraft.world.level.material.PushReaction import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape -import ru.dbotthepony.mc.otm.block.entity.GravitationStabilizerBlockEntity +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.oncePre import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.shapes.BlockShapes import kotlin.math.PI -private val props = BlockBehaviour.Properties.of(Material.STONE, MaterialColor.COLOR_BLUE).requiresCorrectToolForDrops().strength(3f, 600.0f) +private val props = BlockBehaviour.Properties.of(Material.METAL, DyeColor.BLUE).sound(SoundType.METAL).requiresCorrectToolForDrops().strength(3f, 600.0f) class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { return GravitationStabilizerBlockEntity(p_153215_, p_153216_) } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun getTicker( p_153212_: Level, p_153213_: BlockState, @@ -51,7 +59,9 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { return BlockEntityTicker { level, _, _, tile -> if (tile is GravitationStabilizerBlockEntity) tile.tick(level) } } - override val hasFreeRotation: Boolean get() = true + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { super.createBlockStateDefinition(builder) @@ -63,7 +73,7 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { val blockPos = context.clickedPos val level = context.level - for (face in FACING_FULL.possibleValues) { + for (face in BlockRotationFreedom.DIRECTIONAL.possibleValues) { val dir = face.normal for (i in 1 ..GravitationStabilizerBlockEntity.RANGE) { @@ -73,14 +83,14 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { if (!getState.isAir) { if (chunk.getBlockEntity(pos) is BlackHoleBlockEntity) { - state = state.setValue(FACING_FULL, face) + state = state.setValue(BlockRotationFreedom.DIRECTIONAL.property, face) break } } } } - val bbPos = blockPos + state.getValue(FACING_FULL).normal + val bbPos = blockPos + state.getValue(BlockRotationFreedom.DIRECTIONAL.property).normal if (!context.level.isInWorldBounds(bbPos)) return null if (!level.getBlockState(bbPos).canBeReplaced(context)) return null @@ -98,18 +108,14 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { super.setPlacedBy(level, blockPos, blockState, entity, itemStack) if (!level.isClientSide) { - val bbPos = blockPos + blockState.getValue(FACING_FULL).normal + val bbPos = blockPos + blockState.getValue(BlockRotationFreedom.DIRECTIONAL.property).normal val newState = MBlocks.GRAVITATION_STABILIZER_LENS.defaultBlockState() - .setValue(FACING_FULL, blockState.getValue(FACING_FULL)) + .setValue(BlockRotationFreedom.DIRECTIONAL.property, blockState.getValue(BlockRotationFreedom.DIRECTIONAL.property)) level.setBlock(bbPos, newState, UPDATE_ALL) } } - override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { - return PushReaction.BLOCK - } - override fun neighborChanged( state: BlockState, level: Level, @@ -136,7 +142,7 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { p_60557_: BlockPos, p_60558_: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING_FULL).ordinal] + return SHAPES[p_60555_[BlockRotationFreedom.DIRECTIONAL].ordinal] } companion object { @@ -144,9 +150,9 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { BlockShapes.GRAVITATION_STABILIZER.rotateAroundX(PI / 2).computeShape(), BlockShapes.GRAVITATION_STABILIZER.rotateAroundX(-PI / 2).computeShape(), BlockShapes.GRAVITATION_STABILIZER.computeShape(), - BlockShapes.GRAVITATION_STABILIZER.rotate(Direction.NORTH).computeShape(), - BlockShapes.GRAVITATION_STABILIZER.rotate(Direction.WEST).computeShape(), - BlockShapes.GRAVITATION_STABILIZER.rotate(Direction.EAST).computeShape() + BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.NORTH).computeShape(), + BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.WEST).computeShape(), + BlockShapes.GRAVITATION_STABILIZER.rotateFromSouth(Direction.EAST).computeShape() ) fun getBoundingBlock(level: Level, blockState: BlockState, blockPos: BlockPos): BlockState { @@ -154,23 +160,21 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock { } fun getBoundingBlockPos(blockState: BlockState, blockPos: BlockPos): BlockPos { - return blockPos + blockState.getValue(FACING_FULL).normal + return blockPos + blockState.getValue(BlockRotationFreedom.DIRECTIONAL.property).normal } } } class BlockGravitationStabilizerLens : RotatableMatteryBlock(props) { - override val hasFreeRotation: Boolean get() = true + override fun rotationFreedom(): BlockRotationFreedom { + return BlockRotationFreedom.DIRECTIONAL + } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { super.createBlockStateDefinition(builder) builder.add(WorkerState.SEMI_WORKER_STATE) } - override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { - return PushReaction.BLOCK - } - override fun neighborChanged( state: BlockState, level: Level, @@ -197,7 +201,7 @@ class BlockGravitationStabilizerLens : RotatableMatteryBlock(props) { p_60557_: BlockPos, p_60558_: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING_FULL).ordinal] + return SHAPES[p_60555_[BlockRotationFreedom.DIRECTIONAL].front.ordinal] } companion object { @@ -206,16 +210,16 @@ class BlockGravitationStabilizerLens : RotatableMatteryBlock(props) { } fun getBoundingBlockPos(blockState: BlockState, blockPos: BlockPos): BlockPos { - return blockPos + blockState.getValue(FACING_FULL).opposite.normal + return blockPos + blockState.getValue(BlockRotationFreedom.DIRECTIONAL.property).front.opposite.normal } private val SHAPES = arrayOf( BlockShapes.GRAVITATION_STABILIZER_LENS.rotateAroundX(PI / 2).computeShape(), BlockShapes.GRAVITATION_STABILIZER_LENS.rotateAroundX(-PI / 2).computeShape(), BlockShapes.GRAVITATION_STABILIZER_LENS.computeShape(), - BlockShapes.GRAVITATION_STABILIZER_LENS.rotate(Direction.NORTH).computeShape(), - BlockShapes.GRAVITATION_STABILIZER_LENS.rotate(Direction.WEST).computeShape(), - BlockShapes.GRAVITATION_STABILIZER_LENS.rotate(Direction.EAST).computeShape() + BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.NORTH).computeShape(), + BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.WEST).computeShape(), + BlockShapes.GRAVITATION_STABILIZER_LENS.rotateFromSouth(Direction.EAST).computeShape() ) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/PhantomAttractorBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PhantomAttractorBlock.kt similarity index 81% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/PhantomAttractorBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PhantomAttractorBlock.kt index a85210898..189be0d22 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/PhantomAttractorBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PhantomAttractorBlock.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerLevel @@ -8,32 +8,36 @@ import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.MobSpawnType import net.minecraft.world.entity.SpawnGroupData import net.minecraft.world.entity.monster.Phantom +import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.context.BlockPlaceContext import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.Level -import net.minecraft.world.level.LevelReader import net.minecraft.world.level.NaturalSpawner import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.level.block.state.properties.BlockStateProperties import net.minecraft.world.level.block.state.properties.DoubleBlockHalf import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor +import net.minecraft.world.level.material.PushReaction import net.minecraft.world.phys.AABB import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import net.minecraftforge.common.ForgeHooks +import net.minecraftforge.event.ForgeEventFactory +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.getShapeForEachState import ru.dbotthepony.mc.otm.core.get -import ru.dbotthepony.mc.otm.core.minus -import ru.dbotthepony.mc.otm.core.plus +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.once import ru.dbotthepony.mc.otm.registry.MBlocks import ru.dbotthepony.mc.otm.shapes.BlockShapes -class PhantomAttractorBlock : RotatableMatteryBlock(Properties.of(Material.METAL, MaterialColor.COLOR_BLUE).destroyTime(3f).explosionResistance(12f).randomTicks()) { +class PhantomAttractorBlock : RotatableMatteryBlock(Properties.of(Material.METAL, DyeColor.BLUE).sound(SoundType.METAL).destroyTime(3f).explosionResistance(12f).randomTicks()) { @Suppress("OVERRIDE_DEPRECATION") override fun randomTick( blockState: BlockState, @@ -72,6 +76,10 @@ class PhantomAttractorBlock : RotatableMatteryBlock(Properties.of(Material.METAL } } + override fun getPistonPushReaction(p_60584_: BlockState): PushReaction { + return PushReaction.BLOCK + } + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { super.createBlockStateDefinition(builder) builder.add(BlockStateProperties.DOUBLE_BLOCK_HALF) @@ -85,15 +93,13 @@ class PhantomAttractorBlock : RotatableMatteryBlock(Properties.of(Material.METAL } } - private val shapes by lazy { - getShapeForEachState { - val shape = when (it[BlockStateProperties.DOUBLE_BLOCK_HALF]!!) { - DoubleBlockHalf.UPPER -> BlockShapes.PHANTOM_ATTRACTOR_TOP.moveY(-1.0) - DoubleBlockHalf.LOWER -> BlockShapes.PHANTOM_ATTRACTOR_BOTTOM - } - - shape.rotate(it[FACING]!!).computeShape() + private val shapes = getShapeForEachState(listOf(rotationProperty, BlockStateProperties.DOUBLE_BLOCK_HALF)) { + val shape = when (it[BlockStateProperties.DOUBLE_BLOCK_HALF]!!) { + DoubleBlockHalf.UPPER -> BlockShapes.PHANTOM_ATTRACTOR_TOP.moveY(-1.0) + DoubleBlockHalf.LOWER -> BlockShapes.PHANTOM_ATTRACTOR_BOTTOM } + + shape.rotateFromNorth(it[rotationProperty]).computeShape() } override fun getShape( diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/PlatePressBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt similarity index 62% rename from src/main/kotlin/ru/dbotthepony/mc/otm/block/PlatePressBlock.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt index 910a7581a..8aed09a9a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/PlatePressBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PlatePressBlock.kt @@ -1,7 +1,6 @@ -package ru.dbotthepony.mc.otm.block +package ru.dbotthepony.mc.otm.block.tech import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag @@ -16,16 +15,18 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateDefinition import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity -import ru.dbotthepony.mc.otm.block.entity.PlatePressBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.capability.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.registry.MBlockEntities +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.shapes.BlockShapes -class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES) : RotatableMatteryBlock(properties), EntityBlock { +class PlatePressBlock(properties: Properties = DEFAULT_MACHINE_PROPERTIES, val isTwin: Boolean = false) : RotatableMatteryBlock(properties), EntityBlock { override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity { - return PlatePressBlockEntity(p_153215_, p_153216_) + return PlatePressBlockEntity(p_153215_, p_153216_, isTwin) } override fun getTicker( @@ -33,10 +34,10 @@ class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES) : RotatableMa p_153213_: BlockState, p_153214_: BlockEntityType ): BlockEntityTicker? { - if (p_153212_.isClientSide || p_153214_ !== MBlockEntities.PLATE_PRESS) + if (p_153212_.isClientSide) return null - return BlockEntityTicker { _, _, _, tile -> if (tile is PlatePressBlockEntity) tile.basicTicker() } + return BlockEntityTicker { _, _, _, tile -> if (tile is PlatePressBlockEntity) tile.tick() } } override fun createBlockStateDefinition(builder: StateDefinition.Builder) { @@ -54,29 +55,15 @@ class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES) : RotatableMa MatteryWorkerBlockEntity.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) } + private val shapes = getShapeForEachState(rotationProperty) { (if (isTwin) BlockShapes.TWIN_PLATE_PRESS_IDLE else BlockShapes.PLATE_PRESS_IDLE).rotateFromNorth(it[rotationProperty]).computeShape() } + + @Suppress("override_deprecation") override fun getShape( - p_60555_: BlockState, - p_60556_: BlockGetter, - p_60557_: BlockPos, - p_60558_: CollisionContext + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext ): VoxelShape { - return SHAPES[p_60555_.getValue(FACING).ordinal] - } - - companion object { - private val SHAPES: Array - - init { - val def = BlockShapes.PLATE_PRESS_IDLE.computeShape() - - SHAPES = arrayOf( - def, - def, - def, - BlockShapes.PLATE_PRESS_IDLE.rotate(Direction.NORTH).computeShape(), - BlockShapes.PLATE_PRESS_IDLE.rotate(Direction.WEST).computeShape(), - BlockShapes.PLATE_PRESS_IDLE.rotate(Direction.EAST).computeShape() - ) - } + return shapes[state]!! } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PoweredFurnaceBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PoweredFurnaceBlock.kt new file mode 100644 index 000000000..54eda35b1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/tech/PoweredFurnaceBlock.kt @@ -0,0 +1,64 @@ +package ru.dbotthepony.mc.otm.block.tech + +import net.minecraft.core.BlockPos +import net.minecraft.world.item.crafting.AbstractCookingRecipe +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.EntityBlock +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.BlockEntityTicker +import net.minecraft.world.level.block.entity.BlockEntityType +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.StateDefinition +import net.minecraft.world.phys.shapes.CollisionContext +import net.minecraft.world.phys.shapes.Shapes +import net.minecraft.world.phys.shapes.VoxelShape +import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock +import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.PoweredFurnaceBlockEntity +import ru.dbotthepony.mc.otm.block.getShapeForEachState +import ru.dbotthepony.mc.otm.config.WorkerBalanceValues +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe +import ru.dbotthepony.mc.otm.shapes.BlockShape + +class PoweredFurnaceBlock( + val type: () -> BlockEntityType, + val recipeType: RecipeType, + val secondaryRecipeType: (() -> RecipeType)?, + val config: WorkerBalanceValues, + shape: BlockShape? +) : RotatableMatteryBlock(DEFAULT_MACHINE_PROPERTIES), EntityBlock { + override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): PoweredFurnaceBlockEntity { + return PoweredFurnaceBlockEntity(type.invoke(), p_153215_, p_153216_, recipeType, secondaryRecipeType, config) + } + + override fun createBlockStateDefinition(builder: StateDefinition.Builder) { + super.createBlockStateDefinition(builder) + builder.add(WorkerState.WORKER_STATE) + } + + private val shapes = getShapeForEachState(rotationProperty) { + shape?.rotateFromNorth(it[rotationProperty])?.computeShape() ?: Shapes.block() + } + + @Suppress("override_deprecation") + override fun getShape( + state: BlockState, + blockGetter: BlockGetter, + pos: BlockPos, + context: CollisionContext + ): VoxelShape { + return shapes[state]!! + } + + override fun getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType): BlockEntityTicker? { + if (p_153212_.isClientSide) + return null + + return BlockEntityTicker { _, _, _, tile -> if (tile is PoweredFurnaceBlockEntity) tile.tick() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt new file mode 100644 index 000000000..f63328fb4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AbstractProfiledStorage.kt @@ -0,0 +1,217 @@ +package ru.dbotthepony.mc.otm.capability + +import com.google.common.collect.ImmutableList +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NumericTag +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.core.forValidRefs +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import java.lang.ref.WeakReference +import java.util.* +import kotlin.collections.ArrayList + +abstract class AbstractProfiledStorage(val parent: P) : INBTSerializable { + private val historyReceiveInternal = ArrayList(HISTORY_SIZE) + private val historyTransferInternal = ArrayList(HISTORY_SIZE) + + private var isTicking = false + + private fun startTicking() { + if (!isTicking) { + isTicking = true + storages.add(this) + } + } + + val historyReceive: List = Collections.unmodifiableList(historyReceiveInternal) + val historyTransfer: List = Collections.unmodifiableList(historyTransferInternal) + + val lastTickReceive + get() = historyReceiveInternal[tick] + + val lastTickTransfer + get() = historyTransferInternal[tick] + + private var thisTickReceive = Decimal.ZERO + private var thisTickTransfer = Decimal.ZERO + + var tick = 0 + private set + + init { + for (i in 0 until HISTORY_SIZE) { + historyReceiveInternal.add(Decimal.ZERO) + historyTransferInternal.add(Decimal.ZERO) + } + } + + protected fun recordTransfer(value: Decimal, simulate: Boolean): Decimal { + if (!simulate) { + thisTickTransfer += value + if (!value.isZero) startTicking() + } + + return value + } + + protected fun recordReceive(value: Decimal, simulate: Boolean): Decimal { + if (!simulate) { + thisTickReceive += value + if (!value.isZero) startTicking() + } + + return value + } + + private fun tick(): Boolean { + tick = (tick + 1) % HISTORY_SIZE + + historyReceiveInternal[tick] = thisTickReceive + historyTransferInternal[tick] = thisTickTransfer + isTicking = !thisTickReceive.isZero || !thisTickTransfer.isZero || historyReceiveInternal.any { !it.isZero } || historyTransferInternal.any { !it.isZero } + + thisTickReceive = Decimal.ZERO + thisTickTransfer = Decimal.ZERO + + return isTicking + } + + val savedata: INBTSerializable = object : INBTSerializable { + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { tag -> + tag["historyReceive"] = ListTag().also { + for (value in historyReceiveInternal) { + it.add(value.serializeNBT()) + } + } + + tag["historyTransfer"] = ListTag().also { + for (value in historyTransferInternal) { + it.add(value.serializeNBT()) + } + } + + tag["historyTick"] = tick + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + tick = 0 + thisTickReceive = Decimal.ZERO + thisTickTransfer = Decimal.ZERO + + for (i in 0 until HISTORY_SIZE) { + historyReceiveInternal[i] = Decimal.ZERO + historyTransferInternal[i] = Decimal.ZERO + } + + nbt ?: return + + nbt.map("historyTick") { it: NumericTag -> + tick = it.asInt + } + + nbt.map("historyReceive") { it: ListTag -> + for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) { + historyReceiveInternal[i] = Decimal.deserializeNBT(it[i]) + } + } + + nbt.map("historyTransfer") { it: ListTag -> + for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) { + historyTransferInternal[i] = Decimal.deserializeNBT(it[i]) + } + } + } + } + + override fun serializeNBT(): CompoundTag { + val tag: CompoundTag + + if (parent is INBTSerializable<*>) { + tag = (parent as INBTSerializable).serializeNBT() ?: CompoundTag() + } else { + tag = CompoundTag() + } + + val tag2 = savedata.serializeNBT()!! + + for (k in tag2.allKeys) { + tag[k] = tag2[k]!! + } + + return tag + } + + val weightedTransfer: Decimal get() { + return calcWeightedAverage(historyTransfer, tick) + } + + val weightedReceive: Decimal get() { + return calcWeightedAverage(historyReceive, tick) + } + + override fun deserializeNBT(nbt: CompoundTag?) { + if (parent is INBTSerializable<*>) { + (parent as INBTSerializable).deserializeNBT(nbt) + } + + savedata.deserializeNBT(nbt) + } + + companion object { + const val HISTORY_SIZE = 20 + private val storages = ObjectArrayList>() + + val HISTORY_WEIGHTERS: ImmutableList = ImmutableList.of( + Decimal("0.313335967"), + Decimal("0.146176397"), + Decimal("0.09357867"), + Decimal("0.068193701"), + Decimal("0.053351084"), + Decimal("0.043655993"), + Decimal("0.036847023"), + Decimal("0.031813486"), + Decimal("0.027947534"), + Decimal("0.024889161"), + Decimal("0.02241188"), + Decimal("0.020366241"), + Decimal("0.018649731"), + Decimal("0.017189744"), + Decimal("0.015933452"), + Decimal("0.014841516"), + Decimal("0.013884059"), + Decimal("0.013037985"), + Decimal("0.012285173"), + Decimal("0.011611204"), + ) + + fun calcWeightedAverage(inputs: List, pointer: Int): Decimal { + require(inputs.size == HISTORY_WEIGHTERS.size) { "Expected list of size ${HISTORY_WEIGHTERS.size}, got ${inputs.size}" } + require(pointer in 0 until HISTORY_WEIGHTERS.size) { "Invalid pointer position: $pointer" } + + var result = Decimal.ZERO + var i = 0 + + for (i2 in pointer downTo 0) + result += inputs[i2] * HISTORY_WEIGHTERS[i++] + + for (i2 in HISTORY_WEIGHTERS.size - 1 downTo pointer + 1) + result += inputs[i2] * HISTORY_WEIGHTERS[i++] + + return result + } + + // после раздумий, наиболее корректное место для обновления состояния профилированных + // хранилищ энергии и материи будет после всех остальных хуков на тики после тика сервера + // разумеется, такое решение может несколько снизить производительность сервера, + // но ничего страшного произойти не должно + internal fun onServerPostTick() { + storages.removeIf { !it.tick() } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AndroidPowerSource.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AndroidPowerSource.kt deleted file mode 100644 index c850a32e6..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/AndroidPowerSource.kt +++ /dev/null @@ -1,178 +0,0 @@ -package ru.dbotthepony.mc.otm.capability - -import net.minecraft.nbt.CompoundTag -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.getDecimal -import ru.dbotthepony.mc.otm.core.getItemStack -import ru.dbotthepony.mc.otm.core.ifPresentK -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import ru.dbotthepony.mc.otm.registry.StatNames -import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger - -class AndroidPowerSource( - private val ply: Player, - synchronizer: FieldSynchronizer, - initialCharge: Decimal, - maxCharge: Decimal -) : IMatteryEnergyStorage, INBTSerializable { - private var battery by synchronizer.fraction(initialCharge, name = "android battery") - private var maxBattery by synchronizer.fraction(maxCharge, name = "android max battery") - - var item by synchronizer.item(setter = setter@{ value, access, setByRemote -> - access.write(value) - - if (ply is ServerPlayer) { - AndroidBatteryTrigger.trigger(ply, value) - } - }, name = "android battery item") - - override fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["battery"] = battery.serializeNBT() - it["maxBattery"] = maxBattery.serializeNBT() - it["item"] = item.serializeNBT() - } - } - - override fun deserializeNBT(tag: CompoundTag) { - battery = tag.getDecimal("battery") - maxBattery = tag.getDecimal("maxBattery") - item = tag.getItemStack("item") - } - - fun tick() { - if (!item.isEmpty && battery < maxBattery) { - item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - battery += it.extractEnergyInner(maxBattery - battery, false) - } else { - battery += it.extractEnergy(maxBattery - battery, false) - } - } - } - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyOuter(howMuch, simulate) - } - - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return Decimal.ZERO - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - @Suppress("name_shadowing") - var howMuch = howMuch - var drained = Decimal.ZERO - - if (!item.isEmpty) { - item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - val extracted = it.extractEnergyOuter(howMuch, simulate) - drained += extracted - howMuch -= extracted - } else { - val extracted = it.extractEnergy(howMuch, simulate) - drained += extracted - howMuch -= extracted - } - } - - if (howMuch.isZero) { - if (!simulate && ply is ServerPlayer) { - ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10) - } - - return drained - } - } - - val new = (battery - howMuch).moreThanZero() - drained += battery - new - - if (!simulate) { - battery = new - - if (ply is ServerPlayer) { - ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10) - } - } - - return drained - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - @Suppress("name_shadowing") - var howMuch = howMuch - var received = Decimal.ZERO - - if (!item.isEmpty) { - item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - val extracted = it.receiveEnergyOuter(howMuch, simulate) - received += extracted - howMuch -= extracted - } else { - val extracted = it.receiveEnergy(howMuch, simulate) - received += extracted - howMuch -= extracted - } - } - - if (howMuch.isZero) { - return received - } - } - - val new = (battery + howMuch).coerceAtMost(maxBattery) - received += new - battery - - if (!simulate) { - battery = new - } - - return received - } - - override var batteryLevel: Decimal - get() { - if (!item.isEmpty) { - item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - return battery + it.batteryLevel - } else { - return battery + it.energyStored - } - } - } - - return battery - } - set(value) { - battery = value - } - - override var maxBatteryLevel: Decimal - get() { - if (item != ItemStack.EMPTY) { - item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { - if (it is IMatteryEnergyStorage) { - return maxBattery + it.maxBatteryLevel - } else { - return maxBattery + it.maxEnergyStored - } - } - } - - return maxBattery - } - set(value) { - maxBattery = value - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt deleted file mode 100644 index 5e96e9c81..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/EnergyStorageImpl.kt +++ /dev/null @@ -1,580 +0,0 @@ - -@file:Suppress("unused") - -package ru.dbotthepony.mc.otm.capability - -import net.minecraft.ChatFormatting -import net.minecraft.core.Direction -import net.minecraft.nbt.CompoundTag -import net.minecraft.network.chat.Component -import net.minecraft.world.item.BlockItem -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.TooltipFlag -import net.minecraft.world.level.BlockGetter -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.capabilities.ICapabilityProvider -import net.minecraftforge.common.util.INBTSerializable -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.energy.IEnergyStorage -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.VerboseBalanceValues -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity -import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.defineDecimal -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.mapIf -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.tagNotNull - -sealed interface IEnergyStorageImpl { - val maxInput: Decimal? - val maxOutput: Decimal? - val direction: FlowDirection -} - -private fun batteryLevel(it: IEnergyStorage, tooltips: MutableList) { - tooltips.add(TranslatableComponent( - "otm.item.power.storage", - it.energyStored.formatPower(), - it.maxEnergyStored.formatPower() - ).withStyle(ChatFormatting.GRAY)) -} - -private fun batteryLevel(it: IMatteryEnergyStorage, tooltips: MutableList) { - tooltips.add(TranslatableComponent( - "otm.item.power.storage", - it.batteryLevel.formatPower(), - it.maxBatteryLevel.formatPower() - ).withStyle(ChatFormatting.GRAY)) - - if (it is IEnergyStorageImpl) { - when (it.direction) { - FlowDirection.INPUT -> { - if (it.maxInput != null) { - tooltips.add(TranslatableComponent("otm.item.power.throughput_mono", it.maxInput!!.formatPower()).withStyle(ChatFormatting.GRAY)) - } else { - tooltips.add(TranslatableComponent("otm.item.power.throughput_mono", TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY)).withStyle(ChatFormatting.GRAY)) - } - } - - FlowDirection.OUTPUT -> { - if (it.maxOutput != null) { - tooltips.add(TranslatableComponent("otm.item.power.throughput_mono", it.maxOutput!!.formatPower()).withStyle(ChatFormatting.GRAY)) - } else { - tooltips.add(TranslatableComponent("otm.item.power.throughput_mono", TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY)).withStyle(ChatFormatting.GRAY)) - } - } - - FlowDirection.BI_DIRECTIONAL -> { - val maxInput = it.maxInput - val maxOutput = it.maxOutput - - if (maxInput != null && maxOutput != null) { - tooltips.add(TranslatableComponent("otm.item.power.throughput", maxInput.formatPower(), maxOutput.formatPower()).withStyle(ChatFormatting.GRAY)) - } else if (maxInput != null) { - tooltips.add(TranslatableComponent("otm.item.power.throughput", - maxInput.formatPower(), - TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY) - ).withStyle(ChatFormatting.GRAY)) - } else if (maxOutput != null) { - tooltips.add(TranslatableComponent("otm.item.power.throughput", - TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), - maxOutput.formatPower(), - ).withStyle(ChatFormatting.GRAY)) - } else { - tooltips.add(TranslatableComponent("otm.item.power.throughput", - TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), - TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), - ).withStyle(ChatFormatting.GRAY)) - } - } - } - } -} - -sealed class ItemEnergyStorageImpl( - final override val direction: FlowDirection, - protected val itemStack: ItemStack, - maxBatteryLevel: Decimal, - maxInput: Decimal?, - maxOutput: Decimal?, - val initialBatteryLevel: Decimal = Decimal.ZERO -) : IMatteryEnergyStorage, ICapabilityProvider, IEnergyStorageImpl { - final override var maxInput: Decimal? = maxInput - protected set - - final override var maxOutput: Decimal? = maxOutput - protected set - - private val resolver = LazyOptional.of { this } - private val resolverMekanism = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(this) } else null - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (cap == ForgeCapabilities.ENERGY || cap == MatteryCapability.ENERGY) { - return resolver.cast() - } else if (cap == MatteryCapability.MEKANISM_ENERGY) { - return resolverMekanism?.cast() ?: LazyOptional.empty() - } - - return LazyOptional.empty() - } - - override var maxBatteryLevel: Decimal = maxBatteryLevel - protected set - - override var batteryLevel: Decimal - get() = itemStack.tag?.map(ENERGY_KEY, Decimal::deserializeNBT) ?: initialBatteryLevel - protected set(value) { - itemStack.tagNotNull[ENERGY_KEY] = value.serializeNBT() - } - - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction == FlowDirection.INPUT) - return Decimal.ZERO - - return extractEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction == FlowDirection.OUTPUT) - return Decimal.ZERO - - return receiveEnergyInner(howMuch, simulate) - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive || itemStack.count != 1) - return Decimal.ZERO - - @Suppress("NAME_SHADOWING") - var howMuch = howMuch - val maxOutput = maxOutput - - if (maxOutput != null) { - howMuch = howMuch.coerceAtMost(maxOutput) - } - - val batteryLevel = batteryLevel - - if (!batteryLevel.isPositive) - return Decimal.ZERO - - val newLevel = (batteryLevel - howMuch).moreThanZero() - val diff = (batteryLevel - newLevel) - - if (!simulate && batteryLevel != newLevel) { - this.batteryLevel = newLevel - } - - return diff - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive || itemStack.count != 1) - return Decimal.ZERO - - @Suppress("NAME_SHADOWING") - var howMuch = howMuch - val maxInput = maxInput - - if (maxInput != null) { - howMuch = howMuch.coerceAtMost(maxInput) - } - - val batteryLevel = batteryLevel - - if (batteryLevel >= maxBatteryLevel) - return Decimal.ZERO - - val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel) - val diff = (newLevel - batteryLevel) - - if (!simulate && batteryLevel != newLevel) { - this.batteryLevel = newLevel - } - - return diff - } - - override fun canExtract(): Boolean { - return direction != FlowDirection.INPUT - } - - override fun canReceive(): Boolean { - return direction != FlowDirection.OUTPUT - } - - companion object { - const val ENERGY_KEY = "energy" - - fun appendHoverText(itemStack: ItemStack, level: Level?, tooltips: MutableList, flag: TooltipFlag) { - appendHoverText(itemStack, tooltips) - } - - fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { - val energy = itemStack.energy ?: return - - if (energy is IMatteryEnergyStorage) { - batteryLevel(energy, tooltips) - } else { - batteryLevel(energy, tooltips) - } - } - } -} - -open class EnergyConsumerItem( - stack: ItemStack, - maxBatteryLevel: Decimal, - maxInput: Decimal? = null, - maxOutput: Decimal? = maxInput, - initialBatteryLevel: Decimal = Decimal.ZERO -) : ItemEnergyStorageImpl(FlowDirection.INPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) - -open class EnergyProducerItem( - stack: ItemStack, - maxBatteryLevel: Decimal, - maxInput: Decimal? = null, - maxOutput: Decimal? = maxInput, - initialBatteryLevel: Decimal = Decimal.ZERO -) : ItemEnergyStorageImpl(FlowDirection.OUTPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) - -open class EnergyCapacitorItem( - stack: ItemStack, - maxBatteryLevel: Decimal, - maxInput: Decimal? = null, - maxOutput: Decimal? = maxInput, - initialBatteryLevel: Decimal = Decimal.ZERO -) : ItemEnergyStorageImpl(FlowDirection.BI_DIRECTIONAL, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) - -sealed class BlockEnergyStorageImpl( - protected val listener: () -> Unit, - final override val direction: FlowDirection, - private val maxBatteryLevelProvider: () -> Decimal, - private val maxInputProvider: () -> Decimal?, - private val maxOutputProvider: () -> Decimal?, -) : IMatteryEnergyStorage, INBTSerializable, IEnergyStorageImpl { - constructor( - listener: () -> Unit, - direction: FlowDirection, - maxBatteryLevel: Decimal, - maxInput: Decimal?, - maxOutput: Decimal?, - ) : this(listener, direction, { maxBatteryLevel }, { maxInput }, { maxOutput }) - - private var maxInputStorage: Decimal? = null - private var maxOutputStorage: Decimal? = null - private var maxBatteryLevelStorage: Decimal? = null - - final override var maxInput: Decimal? - get() = maxInputStorage ?: maxInputProvider.invoke() - protected set(value) { - if (value != maxInputStorage) { - maxInputStorage = value - listener.invoke() - } - } - - final override var maxOutput: Decimal? - get() = maxOutputStorage ?: maxOutputProvider.invoke() - protected set(value) { - if (value != maxOutputStorage) { - maxOutputStorage = value - listener.invoke() - } - } - - override var maxBatteryLevel: Decimal - get() = maxBatteryLevelStorage ?: maxBatteryLevelProvider.invoke() - protected set(value) { - if (value != maxBatteryLevelStorage) { - maxBatteryLevelStorage = value - listener.invoke() - } - } - - override var batteryLevel = Decimal.ZERO - protected set(value) { - if (value != field) { - field = value - listener.invoke() - } - } - - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction == FlowDirection.INPUT) - return Decimal.ZERO - - return extractEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction == FlowDirection.OUTPUT) - return Decimal.ZERO - - return receiveEnergyInner(howMuch, simulate) - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive) - return Decimal.ZERO - - @Suppress("NAME_SHADOWING") - var howMuch = howMuch - val maxOutput = maxOutput - - if (maxOutput != null) { - howMuch = howMuch.coerceAtMost(maxOutput) - } - - if (!batteryLevel.isPositive) - return Decimal.ZERO - - val newLevel = (batteryLevel - howMuch).moreThanZero() - val diff = (batteryLevel - newLevel) - - if (!simulate && batteryLevel != newLevel) { - batteryLevel = newLevel - } - - return diff - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive) - return Decimal.ZERO - - @Suppress("NAME_SHADOWING") - var howMuch = howMuch - val maxInput = maxInput - - if (maxInput != null) { - howMuch = howMuch.coerceAtMost(maxInput) - } - - if (batteryLevel >= maxBatteryLevel) - return Decimal.ZERO - - val newLevel = (batteryLevel + howMuch).coerceAtMost(maxBatteryLevel) - val diff = (newLevel - batteryLevel) - - if (!simulate && batteryLevel != newLevel) { - batteryLevel = newLevel - } - - return diff - } - - override fun canExtract(): Boolean { - return direction != FlowDirection.INPUT - } - - override fun canReceive(): Boolean { - return direction != FlowDirection.OUTPUT - } - - override fun serializeNBT(): CompoundTag { - val tag = CompoundTag() - tag[ENERGY_STORED_KEY] = batteryLevel.serializeNBT() - - maxBatteryLevelStorage?.let { tag[ENERGY_STORED_MAX_KEY] = it.serializeNBT() } - maxInputStorage?.let { tag[MAX_INPUT_KEY] = it.serializeNBT() } - maxOutputStorage?.let { tag[MAX_OUTPUT_KEY] = it.serializeNBT() } - - return tag - } - - override fun deserializeNBT(nbt: CompoundTag) { - batteryLevel = nbt.map(ENERGY_STORED_KEY, Decimal.Companion::deserializeNBT) ?: Decimal.ZERO - maxBatteryLevelStorage = nbt.mapIf(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT) - maxInputStorage = nbt.mapIf(MAX_INPUT_KEY, Decimal.Companion::deserializeNBT) - maxOutputStorage = nbt.mapIf(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT) - } - - var resolver: LazyOptional = LazyOptional.of { this } - private set - - fun invalidate() { - resolver.invalidate() - } - - fun revive() { - resolver = LazyOptional.of { this } - } - - companion object { - val DEFAULT_MAX_IO = Decimal(200) - val DEFAULT_MAX_CAPACITY = Decimal(60_000) - - const val ENERGY_STORED_KEY = "energy_stored" - const val ENERGY_STORED_MAX_KEY = "energy_stored_max" - const val MAX_INPUT_KEY = "max_input" - const val MAX_OUTPUT_KEY = "max_output" - - fun makeConfigEntry(builder: ForgeConfigSpec.Builder, name: String? = null, capacity: Decimal = DEFAULT_MAX_CAPACITY, throughput: Decimal = DEFAULT_MAX_IO): ConciseBalanceValues { - if (name != null) - builder.push(name) - - val obj = object : ConciseBalanceValues { - override val capacity: Decimal by builder.defineDecimal("capacity", capacity, Decimal.ONE) - override val throughput: Decimal by builder.defineDecimal("throughput", throughput, Decimal.ONE) - } - - if (name != null) - builder.pop() - - return obj - } - } -} - -open class WorkerEnergyStorage( - listener: () -> Unit, - maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, - maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, - maxOutput: () -> Decimal? = maxInput -) : BlockEnergyStorageImpl(listener, FlowDirection.INPUT, maxBatteryLevel, maxInput, maxOutput) { - constructor( - listener: () -> Unit, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput }) - - constructor( - listener: () -> Unit, - values: ConciseBalanceValues) : this(listener, values::capacity, values::throughput, values::throughput) - - constructor( - listener: BlockEntity, - values: ConciseBalanceValues) : this(listener::setChanged, values::capacity, values::throughput, values::throughput) - - constructor( - listener: () -> Unit, - values: VerboseBalanceValues) : this(listener, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - values: VerboseBalanceValues) : this(listener::setChanged, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput) - - companion object { - fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { - val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return - val cap = WorkerEnergyStorage({}, DEFAULT_MAX_CAPACITY) - cap.deserializeNBT(tag) - batteryLevel(cap, tooltips) - } - - fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { - return appendHoverText(itemStack, tooltips) - } - } -} - -open class GeneratorEnergyStorage( - listener: () -> Unit, - maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, - maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, - maxOutput: () -> Decimal? = maxInput -) : BlockEnergyStorageImpl(listener, FlowDirection.OUTPUT, maxBatteryLevel, maxInput, maxOutput) { - constructor( - listener: () -> Unit, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput }) - - constructor( - listener: () -> Unit, - values: ConciseBalanceValues) : this(listener, values::capacity, values::throughput, values::throughput) - - constructor( - listener: BlockEntity, - values: ConciseBalanceValues) : this(listener::setChanged, values::capacity, values::throughput, values::throughput) - - constructor( - listener: () -> Unit, - values: VerboseBalanceValues) : this(listener, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - values: VerboseBalanceValues) : this(listener::setChanged, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput) - - companion object { - fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { - val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return - val cap = GeneratorEnergyStorage({}, DEFAULT_MAX_CAPACITY) - cap.deserializeNBT(tag) - batteryLevel(cap, tooltips) - } - - fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { - return appendHoverText(itemStack, tooltips) - } - } -} - -open class CapacitorEnergyStorage( - listener: () -> Unit, - maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, - maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, - maxOutput: () -> Decimal? = maxInput -) : BlockEnergyStorageImpl(listener, FlowDirection.BI_DIRECTIONAL, maxBatteryLevel, maxInput, maxOutput) { - constructor( - listener: () -> Unit, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener, { maxBatteryLevel }, { maxInput }, { maxOutput }) - - constructor( - listener: () -> Unit, - values: ConciseBalanceValues) : this(listener, values::capacity, values::throughput, values::throughput) - - constructor( - listener: BlockEntity, - values: ConciseBalanceValues) : this(listener::setChanged, values::capacity, values::throughput, values::throughput) - - constructor( - listener: () -> Unit, - values: VerboseBalanceValues) : this(listener, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - values: VerboseBalanceValues) : this(listener::setChanged, values::capacity, values::receive, values::extract) - - constructor( - listener: BlockEntity, - maxBatteryLevel: Decimal = DEFAULT_MAX_CAPACITY, - maxInput: Decimal? = DEFAULT_MAX_IO, - maxOutput: Decimal? = maxInput) : this(listener::setChanged, maxBatteryLevel, maxInput, maxOutput) - - companion object { - fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { - val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return - val cap = CapacitorEnergyStorage({}, DEFAULT_MAX_CAPACITY) - cap.deserializeNBT(tag) - batteryLevel(cap, tooltips) - } - - fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { - return appendHoverText(itemStack, tooltips) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index c49fad79e..f7e7c73a4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -1,61 +1,92 @@ package ru.dbotthepony.mc.otm.capability import com.google.common.collect.Streams -import net.minecraft.core.Direction +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack import net.minecraftforge.common.capabilities.ForgeCapabilities import net.minecraftforge.common.capabilities.ICapabilityProvider -import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.energy.IEnergyStorage -import net.minecraftforge.fml.ModList +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import net.minecraftforge.items.IItemHandler +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.fluid.iterator +import ru.dbotthepony.mc.otm.capability.fluid.stream +import ru.dbotthepony.mc.otm.client.ShiftPressedCond import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorAwareStream import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorStream import ru.dbotthepony.mc.otm.compat.cos.isCosmeticArmorLoaded import ru.dbotthepony.mc.otm.compat.curios.curiosAwareStream import ru.dbotthepony.mc.otm.compat.curios.curiosStream import ru.dbotthepony.mc.otm.compat.curios.isCuriosLoaded -import ru.dbotthepony.mc.otm.compat.mekanism.getMekanismEnergySided -import ru.dbotthepony.mc.otm.compat.mekanism.mekanismEnergy -import ru.dbotthepony.mc.otm.container.awareStream -import ru.dbotthepony.mc.otm.container.stream -import ru.dbotthepony.mc.otm.core.AwareItemStack -import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.container.util.awareStream +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.AwareItemStack +import ru.dbotthepony.mc.otm.core.collect.ContainerItemStackEntry +import ru.dbotthepony.mc.otm.core.collect.concatIterators +import ru.dbotthepony.mc.otm.core.collect.emptyIterator +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.orNull -import java.util.IdentityHashMap -import java.util.LinkedList +import ru.dbotthepony.mc.otm.core.util.formatFluidLevel import java.util.stream.Stream +private val LOGGER = LogManager.getLogger() + val ICapabilityProvider.matteryPlayer: MatteryPlayerCapability? get() = getCapability(MatteryCapability.MATTERY_PLAYER).orNull() +/** + * Does a checked energy receive, calls [IMatteryEnergyStorage.receiveEnergyChecked] if possible + */ fun IEnergyStorage.receiveEnergy(amount: Decimal, simulate: Boolean): Decimal { if (this is IMatteryEnergyStorage) - return receiveEnergyOuter(amount, simulate) + return receiveEnergyChecked(amount, simulate) - if (!amount.isPositive) + if (amount.isNegative) + throw IllegalArgumentException("Negative energy: $amount") + + if (!canReceive()) return Decimal.ZERO - if (amount > Decimal.INT_MAX_VALUE) + if (amount >= Decimal.INT_MAX_VALUE) return Decimal.valueOf(receiveEnergy(Int.MAX_VALUE, simulate)) return Decimal.valueOf(receiveEnergy(amount.toInt(), simulate)) } +fun IEnergyStorage.transcieveEnergy(amount: Decimal, isReceiving: Boolean, simulate: Boolean): Decimal { + if (isReceiving) + return receiveEnergy(amount, simulate) + else + return extractEnergy(amount, simulate) +} + +/** + * Does a checked energy extraction, calls [IMatteryEnergyStorage.extractEnergyChecked] if possible + */ fun IEnergyStorage.extractEnergy(amount: Decimal, simulate: Boolean): Decimal { if (this is IMatteryEnergyStorage) - return extractEnergyOuter(amount, simulate) + return extractEnergyChecked(amount, simulate) - if (!amount.isPositive) + if (amount.isNegative) + throw IllegalArgumentException("Negative energy: $amount") + + if (!canExtract()) return Decimal.ZERO - if (amount > Decimal.INT_MAX_VALUE) + if (amount >= Decimal.INT_MAX_VALUE) return Decimal.valueOf(extractEnergy(Int.MAX_VALUE, simulate)) return Decimal.valueOf(extractEnergy(amount.toInt(), simulate)) } -val IEnergyStorage.maxEnergyStoredMattery: Decimal get() { +val IEnergyStorage.maxEnergyStoredMattery: Decimal + get() { if (this is IMatteryEnergyStorage) { return maxBatteryLevel } @@ -63,12 +94,29 @@ val IEnergyStorage.maxEnergyStoredMattery: Decimal get() { return Decimal.valueOf(maxEnergyStored) } -val IEnergyStorage.energyStoredMattery: Decimal get() { - if (this is IMatteryEnergyStorage) { - return batteryLevel +var IEnergyStorage.energyStoredMattery: Decimal + get() { + if (this is IMatteryEnergyStorage) { + return batteryLevel + } + + return Decimal.valueOf(energyStored) + } + set(value) { + if (this is IMatteryEnergyStorage) { + batteryLevel = value + return + } + + throw UnsupportedOperationException("Can't set stored energy on ${this::class.qualifiedName}") } - return Decimal.valueOf(energyStored) +val IEnergyStorage.canSetBatteryMattery: Boolean get() { + if (this is IMatteryEnergyStorage) { + return canSetBatteryLevel + } + + return false } val IEnergyStorage.chargeRatio: Float get() { @@ -79,8 +127,6 @@ val IEnergyStorage.chargeRatio: Float get() { return energyStored.toFloat() / maxEnergyStored.toFloat() } -val isMekanismLoaded by lazy { ModList.get().isLoaded("mekanism") } - /** * Shortcut for getting [IEnergyStorage], including wrappers for it */ @@ -91,125 +137,45 @@ val ICapabilityProvider.energy: IEnergyStorage? get() { return mattery.orNull() } - if (isMekanismLoaded) { - val mekanismEnergy = mekanismEnergy - - if (mekanismEnergy != null) { - return mekanismEnergy - } - } - return getCapability(ForgeCapabilities.ENERGY).orNull() } /** - * Shortcut for getting [IMatteryEnergyStorage], including wrappers for it + * Shortcut for getting sideless [IMatteryEnergyStorage], including wrappers for it */ val ICapabilityProvider.matteryEnergy: IMatteryEnergyStorage? get() { - val mattery = getCapability(MatteryCapability.ENERGY) - - if (mattery.isPresent) { - return mattery.orNull() - } - - if (isMekanismLoaded) { - val mekanismEnergy = mekanismEnergy - - if (mekanismEnergy != null) { - return mekanismEnergy - } - } - - return null + return getCapability(MatteryCapability.ENERGY).orNull() } -/** - * Shortcut for getting [LazyOptional] with [IEnergyStorage], including wrappers for it - */ -fun ICapabilityProvider.getEnergySided(side: Direction? = null): LazyOptional { - val mattery = getCapability(MatteryCapability.ENERGY, side) +fun Player.items(includeCosmetics: Boolean = true): Iterator { + val matteryPlayer = matteryPlayer ?: return emptyIterator() - if (mattery.isPresent) { - return mattery - } + val iterators = ArrayList>() + iterators.add(matteryPlayer.wrappedInventory.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) - if (isMekanismLoaded) { - val mekanismEnergy = getMekanismEnergySided(side) - - if (mekanismEnergy.isPresent) { - return mekanismEnergy - } - } - - return getCapability(ForgeCapabilities.ENERGY, side) -} - -/** - * Shortcut for getting [LazyOptional] with [IMatteryEnergyStorage], including wrappers for it - */ -fun ICapabilityProvider.getMatteryEnergySided(side: Direction? = null): LazyOptional { - val mattery = getCapability(MatteryCapability.ENERGY, side) - - if (mattery.isPresent) { - return mattery - } - - if (isMekanismLoaded) { - val mekanismEnergy = getMekanismEnergySided(side) - - if (mekanismEnergy.isPresent) { - return mekanismEnergy - } - } - - return LazyOptional.empty() -} - -/** - * DO NOT modify returned ItemStacks! - * - * Contains all items that player might carry - */ -fun Player.itemsStream(includeCosmetics: Boolean = true): Stream { - val streams = LinkedList>() - streams.add(inventory.stream()) - - matteryPlayer?.let { - if (it.hasExoPack) { - streams.add(it.exoPackContainer.stream()) - } + if (matteryPlayer.hasExopack) { + iterators.add(matteryPlayer.exopackContainer.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) + iterators.add(matteryPlayer.exopackEnergy.parent.iterator()) + iterators.add(matteryPlayer.exopackChargeSlots.iterator()) } if (isCuriosLoaded) { - streams.add(curiosStream(includeCosmetics)) + iterators.add(curiosStream(includeCosmetics)) } if (includeCosmetics && isCosmeticArmorLoaded) { - streams.add(cosmeticArmorStream()) + iterators.add(cosmeticArmorStream()) } - return Streams.concat(*streams.toTypedArray()) + return concatIterators(iterators) } -/** - * DO NOT modify returned ItemStacks! - * - * Contains all items that player might see/access ([itemsStream] + open container's items) - */ -fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream { +fun Player.trackedItems(includeCosmetics: Boolean = true): Iterator { if (containerMenu == inventoryMenu || containerMenu == matteryPlayer?.exoPackMenu) { - return itemsStream(includeCosmetics) + return items(includeCosmetics) } - val seen = IdentityHashMap(containerMenu.slots.size) - - for (slot in containerMenu.slots) { - seen[slot.item] = Unit - } - - return Streams.concat( - itemsStream(includeCosmetics).filter { it.isEmpty || it !in seen }, - containerMenu.slots.stream().map { it.item }) + return concatIterators(items(includeCosmetics), containerMenu.slots.iterator().map { it.item }.filter { it.isNotEmpty }) } /** @@ -218,12 +184,12 @@ fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream { - val streams = LinkedList>() + val streams = ArrayList>() streams.add(inventory.awareStream()) matteryPlayer?.let { - if (it.hasExoPack) { - streams.add(it.exoPackContainer.awareStream()) + if (it.hasExopack) { + streams.add(it.exopackContainer.awareStream()) } } @@ -235,7 +201,7 @@ fun Player.awareItemsStream(includeCosmetics: Boolean = false): Stream { + val getItem = source.extractItem(sourceSlot, Int.MAX_VALUE, true) + + if (getItem.isEmpty) { + return sourceSlot + 1 to destinationSlot + } else { + val leftover = destination.insertItem(destinationSlot, getItem, true) + + if (leftover.count == getItem.count) { + return sourceSlot to destinationSlot + 1 + } else { + val getItem2 = source.extractItem(sourceSlot, getItem.count - leftover.count, true) + + if (getItem2.isEmpty) { + return sourceSlot + 1 to destinationSlot + } else { + val leftover2 = destination.insertItem(destinationSlot, getItem2, true) + + if (leftover2.isEmpty) { + source.extractItem(sourceSlot, getItem2.count, false) + destination.insertItem(destinationSlot, getItem2, false) + + if (getItem2.count == getItem.count) { + return sourceSlot + 1 to destinationSlot + } else { + return sourceSlot to destinationSlot + } + } + } + } + } + + return sourceSlot to destinationSlot +} + +@Suppress("name_shadowing") +fun moveEnergy(source: IEnergyStorage, destination: IEnergyStorage, amount: Decimal = Decimal.LONG_MAX_VALUE.coerceAtLeast(source.energyStoredMattery), simulate: Boolean, ignoreFlowRestrictions: Boolean = false): Decimal { + val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(amount, true) else source.extractEnergy(amount, true) + + if (extracted.isPositive) { + val received = destination.receiveEnergy(extracted, true) + + if (received.isPositive) { + val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, true) else source.extractEnergy(received, true) + + if (extracted.isPositive) { + if (simulate) { + return extracted + } + + val extracted = if (ignoreFlowRestrictions && source is IMatteryEnergyStorage) source.extractEnergy(received, false) else source.extractEnergy(received, false) + return destination.receiveEnergy(extracted, false) + } + } + } + + return Decimal.ZERO +} + +internal fun IFluidHandler.fluidLevel(tooltips: MutableList) { + val fluid = getFluidInTank(0) + + if (fluid.isEmpty) { + tooltips.add(formatFluidLevel(0, getTankCapacity(0), formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY)) + } else { + tooltips.add(formatFluidLevel(fluid.amount, getTankCapacity(0), fluid.displayName, formatAsReadable = ShiftPressedCond).withStyle(ChatFormatting.GRAY)) + } +} + +private fun actuallyMoveFluid(drained: FluidStack, source: IFluidHandler, destination: IFluidHandler, limit: Int, actuallyDrain: Boolean, actuallyFill: Boolean): FluidStack { + if (drained.isEmpty) return FluidStack.EMPTY + + val filled = destination.fill(drained, IFluidHandler.FluidAction.SIMULATE) + if (filled == 0) return FluidStack.EMPTY + + val drained2 = source.drain(FluidStack(drained, filled), IFluidHandler.FluidAction.SIMULATE) + if (drained2.amount != filled) return FluidStack.EMPTY + + val filled2 = destination.fill(drained2, IFluidHandler.FluidAction.SIMULATE) + if (filled2 != drained2.amount) return FluidStack.EMPTY + + if (!actuallyDrain && !actuallyFill) return FluidStack(drained2, filled2) + + val drained3: FluidStack + + if (actuallyDrain) { + drained3 = source.drain(FluidStack(drained2, filled2), IFluidHandler.FluidAction.EXECUTE) + + if (drained3.amount != filled2) { + LOGGER.warn("Inconsistency of fluid extraction from $source between simulate and execute modes (simulated $drained2; extracted $drained3); This can lead to duping!!!") + } + } else { + drained3 = drained2 + } + + val filled3: Int + + if (actuallyFill) { + filled3 = destination.fill(drained3, IFluidHandler.FluidAction.EXECUTE) + + if (filled3 != drained3.amount) { + LOGGER.warn("Inconsistency of fluid insertion to $destination between simulate and execute modes (simulated $filled2; inserted $filled3); This can lead to duping!!!") + } + } else { + filled3 = filled2 + } + + return FluidStack(drained3, filled3) +} + +fun moveFluid(source: IFluidHandler, sourceTank: Int? = null, destination: IFluidHandler, limit: Int = Int.MAX_VALUE, actuallyDrain: Boolean = true, actuallyFill: Boolean = true): FluidStack { + if (sourceTank == null) { + for (drained in destination.iterator()) { + if (drained.isNotEmpty) { + val moved = actuallyMoveFluid(drained, source, destination, limit, actuallyDrain, actuallyFill) + if (moved.isNotEmpty) return moved + } + } + + for (drained in source.iterator()) { + if (drained.isNotEmpty) { + val moved = actuallyMoveFluid(drained, source, destination, limit, actuallyDrain, actuallyFill) + if (moved.isNotEmpty) return moved + } + } + + return FluidStack.EMPTY + } else { + return actuallyMoveFluid(source.drain(source.getFluidInTank(sourceTank), IFluidHandler.FluidAction.SIMULATE), source, destination, limit, actuallyDrain, actuallyFill) + } +} + +val IFluidHandler.isEmpty: Boolean get() = stream().allMatch { it.isEmpty } +val IFluidHandler.isNotEmpty: Boolean get() = stream().anyMatch { it.isNotEmpty } +val IFluidHandler.isNotFull: Boolean get() { + for ((i, fluid) in iterator().withIndex()) + if (fluid.isEmpty || fluid.amount < getTankCapacity(i)) + return true + + return false +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt index 67dc1ffa9..d11df0163 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt @@ -1,5 +1,156 @@ package ru.dbotthepony.mc.otm.capability -enum class FlowDirection { - INPUT, OUTPUT, BI_DIRECTIONAL +import com.google.common.collect.ImmutableSet +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import java.util.function.Predicate + +/** + * Represents possible flow direction, be it matter, energy, fluids, etc + * + * To dynamically get this enum by two booleans (by input and output states), use [FlowDirection.of] + * + * Also, this enum implements [Predicate], you can check incoming flow direction for "compatibility" with + * this flow direction, more precisely: + * [test] will return true and only true if [input] and [output] of incoming flow are assignable to this flow + * + * Example: + * * `INPUT.test(OUTPUT)` = `false` + * * `OUTPUT.test(INPUT)` = `false` + * * `INPUT.test(BI_DIRECTIONAL)` = `true` + * * `OUTPUT.test(BI_DIRECTIONAL)` = `true` + * * `BI_DIRECTIONAL.test(BI_DIRECTIONAL)` = `true` + * * `BI_DIRECTIONAL.test(OUTPUT)` = `false` + * * `BI_DIRECTIONAL.test(INPUT)` = `false` + */ +enum class FlowDirection(val input: Boolean, val output: Boolean, val translationKey: String) : Predicate { + /** + * Can only be inputted (consumer) + */ + INPUT(true, false, "otm.gui.side_mode.input"), + + /** + * Can only be outputted/transmitted (producer) + */ + OUTPUT(false, true, "otm.gui.side_mode.output"), + + /** + * Can both consume and produce (capacitor) + */ + BI_DIRECTIONAL(true, true, "otm.gui.side_mode.input_output"), + + /** + * No flow possible + */ + NONE(false, false, "otm.gui.side_mode.disabled"); + + /** + * All values that pass [isSupertype] + */ + val family: ImmutableSet by lazy { + ImmutableSet.Builder().also { + when (this) { + INPUT -> it.add(INPUT) + OUTPUT -> it.add(OUTPUT) + BI_DIRECTIONAL -> { + it.add(INPUT) + it.add(OUTPUT) + it.add(BI_DIRECTIONAL) + } + else -> {} + } + + it.add(NONE) + }.build() + } + + val title: Component + get() = TranslatableComponent(translationKey) + + /** + * Subtype test (returns true if we can assign [t] to this, for example if we can assign [BI_DIRECTIONAL] to [INPUT]) + */ + override fun test(t: FlowDirection): Boolean { + return t === this || (!input || t.input) && (!output || t.output) + } + + /** + * Subtype test (returns true if we can assign [value] to this, for example if we can assign [BI_DIRECTIONAL] to [INPUT]) + */ + fun isSubtype(value: FlowDirection) = test(value) + + /** + * Supertype test (e.g. [INPUT] is supertype to [BI_DIRECTIONAL], so if this is [BI_DIRECTIONAL] then calling [isSupertype] with [INPUT] will return `true`) + */ + fun isSupertype(value: FlowDirection): Boolean { + return value === this || value in family + } + + fun intersect(other: FlowDirection): FlowDirection { + return of(other.input && input, other.output && output) + } + + fun withInput(flag: Boolean): FlowDirection { + if (flag == input) + return this + + return of(flag, output) + } + + fun withOutput(flag: Boolean): FlowDirection { + if (flag == output) + return this + + return of(input, flag) + } + + companion object { + @JvmField + val WITHOUT_NONE: ImmutableSet = ImmutableSet.of(INPUT, OUTPUT, BI_DIRECTIONAL) + + @JvmStatic + fun of(input: Boolean, output: Boolean): FlowDirection { + if (input && output) { + return BI_DIRECTIONAL + } else if (input) { + return INPUT + } else if (output) { + return OUTPUT + } else { + return NONE + } + } + + /** + * @return [INPUT] if [flag] is true, [OUTPUT] otherwise + */ + @JvmStatic + fun input(flag: Boolean): FlowDirection { + return of(flag, !flag) + } + + /** + * @return [OUTPUT] if [flag] is true, [INPUT] otherwise + */ + @JvmStatic + fun output(flag: Boolean): FlowDirection { + return of(!flag, flag) + } + + /** + * @return [INPUT] if [flag] is true, [NONE] otherwise + */ + @JvmStatic + fun orInput(flag: Boolean): FlowDirection { + return of(flag, false) + } + + /** + * @return [OUTPUT] if [flag] is true, [NONE] otherwise + */ + @JvmStatic + fun orOutput(flag: Boolean): FlowDirection { + return of(false, flag) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryEnergyStorage.kt deleted file mode 100644 index f4c241e34..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IMatteryEnergyStorage.kt +++ /dev/null @@ -1,253 +0,0 @@ -package ru.dbotthepony.mc.otm.capability - -import net.minecraftforge.energy.IEnergyStorage -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.RGBAColor -import java.math.BigInteger -import kotlin.math.roundToInt - -// IEnergyStorage for direct compat with Forge Energy -interface IMatteryEnergyStorage : IEnergyStorage { - /** - * Energy extraction by external interacts - * - * @return energy extracted - */ - fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal - - /** - * Energy extraction by internal processes - * - * Nothing stops you from calling this directly, not on yours capability, - * but you really should think before doing this on not own capability. - * - * @return energy extracted - */ - fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal - - /** - * Energy insertion by external interacts - * - * @return energy accepted - */ - fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal - - /** - * Energy insertion by internal processes - * - * Nothing stops you from calling this directly, not on yours capability, - * but you really should think before doing this on not own capability. - * - * @return energy accepted - */ - fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal - - val batteryLevel: Decimal - val maxBatteryLevel: Decimal - val missingPower: Decimal - get() = (maxBatteryLevel - batteryLevel).moreThanZero() - - override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int { - val received = receiveEnergyOuter(maxReceive, true).toInt() - - // Receiving only a fraction - if (received == 0) - return 0 - - return receiveEnergyOuter(Decimal(received), simulate).toInt() - } - - override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { - val extracted = extractEnergyOuter(maxReceive, true).toInt() - - // Extracting only a fraction - if (extracted == 0) - return 0 - - return extractEnergyOuter(Decimal(extracted), simulate).toInt() - } - - override fun getEnergyStored(): Int { - return batteryLevel.toInt() - } - - override fun getMaxEnergyStored(): Int { - return maxBatteryLevel.toInt() - } - - override fun canExtract(): Boolean { - return extractEnergyOuter(Decimal.ONE, true) > Decimal.ZERO - } - - override fun canReceive(): Boolean { - return receiveEnergyOuter(Decimal.ONE, true) > Decimal.ZERO - } -} - -fun IMatteryEnergyStorage.receiveEnergyOuterExact(howMuch: Decimal, simulate: Boolean): Decimal { - val extracted = receiveEnergyOuter(howMuch, true) - - if (extracted != howMuch) { - return Decimal.ZERO - } - - if (!simulate) { - receiveEnergyOuter(howMuch, false) - } - - return extracted -} -/** - * All or nothing - * - * @return energy extracted - */ -fun IMatteryEnergyStorage.extractEnergyOuterExact(howMuch: Decimal, simulate: Boolean): Decimal { - val extracted = extractEnergyOuter(howMuch, true) - - if (extracted != howMuch) { - return Decimal.ZERO - } - - if (!simulate) { - extractEnergyOuter(howMuch, false) - } - - return extracted -} - -/** - * All or nothing - * - * @return energy accepted - */ -fun IMatteryEnergyStorage.receiveEnergyInnerExact(howMuch: Decimal, simulate: Boolean): Decimal { - val extracted = receiveEnergyInner(howMuch, true) - - if (extracted != howMuch) { - return Decimal.ZERO - } - - if (!simulate) { - receiveEnergyInner(howMuch, false) - } - - return extracted -} - -/** - * All or nothing - * - * @return energy extracted - */ -fun IMatteryEnergyStorage.extractEnergyInnerExact(howMuch: Decimal, simulate: Boolean): Decimal { - val extracted = extractEnergyInner(howMuch, true) - - if (extracted != howMuch) { - return Decimal.ZERO - } - - if (!simulate) { - extractEnergyInner(howMuch, false) - } - - return extracted -} - -fun IMatteryEnergyStorage.extractEnergyOuter(howMuch: Long, simulate: Boolean): Decimal { - return extractEnergyOuter(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.extractEnergyOuter(howMuch: Int, simulate: Boolean): Decimal { - return extractEnergyOuter(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.receiveEnergyOuter(howMuch: Long, simulate: Boolean): Decimal { - return receiveEnergyOuter(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.receiveEnergyOuter(howMuch: Int, simulate: Boolean): Decimal { - return receiveEnergyOuter(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.extractEnergyInner(howMuch: Long, simulate: Boolean): Decimal { - return extractEnergyInner(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.extractEnergyInner(howMuch: Int, simulate: Boolean): Decimal { - return extractEnergyInner(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.receiveEnergyInner(howMuch: Long, simulate: Boolean): Decimal { - return receiveEnergyInner(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.receiveEnergyInner(howMuch: Int, simulate: Boolean): Decimal { - return receiveEnergyInner(Decimal(howMuch), simulate) -} - -fun IMatteryEnergyStorage.transferInner(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal { - if (!amount.isPositive) - return Decimal.ZERO - - val extracted = extractEnergyInner(amount, true) - val received = other.receiveEnergyOuter(extracted, simulate) - - if (!simulate) - extractEnergyInner(received, false) - - return received -} - -fun IMatteryEnergyStorage.transferOuter(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal { - if (!amount.isPositive) - return Decimal.ZERO - - val extracted = extractEnergyOuter(amount, true) - val received = other.receiveEnergyOuter(extracted, simulate) - - if (!simulate) - extractEnergyInner(received, false) - - return received -} - -fun IMatteryEnergyStorage.extractStepInner(base: Decimal, multiplier: Int, simulate: Boolean): Int { - return (extractEnergyInner(base * multiplier, simulate) / base).toInt() -} - -fun IMatteryEnergyStorage.extractStepOuter(base: Decimal, multiplier: Int, simulate: Boolean): Int { - return (extractEnergyOuter(base * multiplier, simulate) / base).toInt() -} - -fun IMatteryEnergyStorage.extractStepInnerBi(base: Decimal, multiplier: Int, simulate: Boolean): BigInteger { - return (extractEnergyInner(base * multiplier, simulate) / base).whole -} - -fun IMatteryEnergyStorage.extractStepOuterBi(base: Decimal, multiplier: Int, simulate: Boolean): BigInteger { - return (extractEnergyOuter(base * multiplier, simulate) / base).whole -} - -fun IMatteryEnergyStorage.extractStepInner(base: Decimal, multiplier: BigInteger, simulate: Boolean): Int { - return (extractEnergyInner(base * multiplier, simulate) / base).toInt() -} - -fun IMatteryEnergyStorage.extractStepOuter(base: Decimal, multiplier: BigInteger, simulate: Boolean): Int { - return (extractEnergyOuter(base * multiplier, simulate) / base).toInt() -} - -fun IMatteryEnergyStorage.extractStepInnerBi(base: Decimal, multiplier: BigInteger, simulate: Boolean): BigInteger { - return (extractEnergyInner(base * multiplier, simulate) / base).whole -} - -fun IMatteryEnergyStorage.extractStepOuterBi(base: Decimal, multiplier: BigInteger, simulate: Boolean): BigInteger { - return (extractEnergyOuter(base * multiplier, simulate) / base).whole -} - -fun IMatteryEnergyStorage.getBarWidth(): Int { - return ((batteryLevel / maxBatteryLevel).toFloat().coerceAtLeast(0f).coerceAtMost(1f) * 13f).roundToInt() -} - -fun IMatteryEnergyStorage.getBarColor(): Int { - return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toInt() -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index fd891abaa..ba7ca02f3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -1,176 +1,433 @@ package ru.dbotthepony.mc.otm.capability +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntSet import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import net.minecraft.ChatFormatting +import net.minecraft.client.model.PlayerModel +import net.minecraft.client.player.AbstractClientPlayer +import net.minecraft.commands.Commands +import net.minecraft.commands.arguments.EntityArgument import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.IntTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.StringTag import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer import net.minecraft.tags.TagKey +import net.minecraft.world.Container import net.minecraft.world.Difficulty +import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.effect.MobEffect +import net.minecraft.world.effect.MobEffectInstance +import net.minecraft.world.effect.MobEffects import net.minecraft.world.entity.Entity -import net.minecraft.world.entity.MobSpawnType +import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.boss.wither.WitherBoss -import net.minecraft.world.entity.monster.Phantom import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import net.minecraft.world.item.ProjectileWeaponItem -import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse +import net.minecraft.world.item.crafting.RecipeManager +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.level.GameRules +import net.minecraft.world.level.Level +import net.minecraft.world.phys.Vec3 import net.minecraftforge.common.ForgeHooks +import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.event.AttachCapabilitiesEvent +import net.minecraftforge.event.RegisterCommandsEvent import net.minecraftforge.event.TickEvent import net.minecraftforge.event.TickEvent.PlayerTickEvent +import net.minecraftforge.event.entity.living.LivingAttackEvent import net.minecraftforge.event.entity.living.LivingDeathEvent import net.minecraftforge.event.entity.living.LivingHurtEvent -import net.minecraftforge.event.entity.living.LivingSpawnEvent import net.minecraftforge.event.entity.living.MobEffectEvent import net.minecraftforge.event.entity.player.PlayerEvent +import net.minecraftforge.eventbus.api.Cancelable import net.minecraftforge.eventbus.api.Event +import net.minecraftforge.network.PacketDistributor import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.server.command.EnumArgument import org.apache.logging.log4j.LogManager +import org.joml.Vector4f import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidFeatureType import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.JobContainer +import ru.dbotthepony.mc.otm.block.entity.JobStatus +import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop +import ru.dbotthepony.mc.otm.capability.energy.BatteryBackedEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.config.ExopackConfig +import ru.dbotthepony.mc.otm.container.CombinedContainer import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.stream +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.DynamicallyProxiedContainer +import ru.dbotthepony.mc.otm.container.IContainer +import ru.dbotthepony.mc.otm.container.IMatteryContainer +import ru.dbotthepony.mc.otm.container.util.slotIterator +import ru.dbotthepony.mc.otm.container.vanishCursedItems import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu +import ru.dbotthepony.mc.otm.core.collect.UUIDIntModifiersMap +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.nbt.getCompoundList +import ru.dbotthepony.mc.otm.core.nbt.getIntList +import ru.dbotthepony.mc.otm.core.nbt.getStringList +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.IntValueCodec +import ru.dbotthepony.mc.otm.core.util.ItemValueCodec +import ru.dbotthepony.mc.otm.core.util.RGBCodec +import ru.dbotthepony.mc.otm.core.util.Savetables +import ru.dbotthepony.mc.otm.core.util.TickList +import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec +import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec +import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import ru.dbotthepony.mc.otm.menu.IItemStackSortingSettings import ru.dbotthepony.mc.otm.network.* +import ru.dbotthepony.mc.otm.network.SmokeParticlesPacket.Companion.makeSmoke +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer import ru.dbotthepony.mc.otm.registry.AndroidFeatures +import ru.dbotthepony.mc.otm.registry.MDamageTypes +import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger -import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedCraftingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedEnderAccessTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger +import ru.dbotthepony.mc.otm.triggers.MatteryInventoryChangeTrigger import java.util.* import java.util.stream.Stream import kotlin.collections.ArrayDeque -import kotlin.collections.ArrayList -import kotlin.collections.HashMap +import kotlin.reflect.KMutableProperty1 + +private fun Player.dropContainer(container: Container, spill: Boolean = true, setThrower: Boolean = false) { + for (slot in container.slotIterator()) { + drop(slot.item, spill, setThrower) + slot.remove() + } +} @Suppress("unused") class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerializable { + /** + * This event is fired on main event bus before ticking logic takes place. + * + * Cancelling it is probably not a good idea, but you can do it anyway. + */ + @Cancelable + data class PreTick(val capability: MatteryPlayerCapability) : Event() { + val player get() = capability.ply + val level: Level get() = capability.ply.level + + override fun isCancelable(): Boolean { + return true + } + } + + /** + * This event is fired on main event bus after ticking logic took place. + */ + data class PostTick(val capability: MatteryPlayerCapability) : Event() { + val player get() = capability.ply + val level: Level get() = capability.ply.level + + override fun isCancelable(): Boolean { + return false + } + } + + /** + * This event is fired on main event bus when [Inventory.add] was called and entire [ItemStack] can not be stored + * (both in [Inventory] and [MatteryPlayerCapability] exopack due to inventory filters or no free space). + */ + data class ItemStackLeftoverEvent(val stack: ItemStack, val capability: MatteryPlayerCapability) : Event() { + val player get() = capability.ply + val level: Level get() = capability.ply.level + + override fun isCancelable(): Boolean { + return false + } + } + private inner class PlayerMatteryContainer(size: Int) : MatteryContainer(size) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - // TODO: Minecraft has hard-coded inventory triggers to Inventory class - // when nothing use Inventory class directly. + if (ply is ServerPlayer) { + val item = new.copy() - // Why? - // CriteriaTriggers.INVENTORY_CHANGED.trigger(this, this.getInventory(), p_143468_) + tickList.once { + MatteryInventoryChangeTrigger.trigger(ply, combinedInventory, item) + } + } } } /** * For fields that need to be synchronized only to owning player + * + * Please mind when you use this in your mod; + * any differences in field order/types/etc between client and server + * will break *everything* */ val synchronizer = FieldSynchronizer() /** * For fields that need to be synchronized to everyone + * + * Please mind when you use this in your mod; + * any differences in field order/types/etc between client and server + * will break *everything* */ val publicSynchronizer = FieldSynchronizer() - var hasExoPack by publicSynchronizer.bool(name = "hasExoPack") - var displayExoPack by publicSynchronizer.bool(true, name = "displayExoPack") + /** + * For data to be stored to and loaded from NBT automatically + */ + val savetables = Savetables() - private val exoPackSlotModifierMap: MutableMap by synchronizer.Map( + /** + * Whenever player has Exopack + */ + var hasExopack by publicSynchronizer.bool(setter = setter@{ value, access, _ -> + access.write(value) + _exoPackMenu = null + + if (value && ply is ServerPlayer) { + tickList.once { + ExopackObtainedTrigger.trigger(ply) + } + } + }).property + + /** + * Whenever to render Exopack on player + */ + var isExopackVisible by publicSynchronizer.bool(true).property + + /** + * Whenever to render Exopack glow in dark + */ + var exopackGlows by publicSynchronizer.bool(true).property + + var exopackColor by publicSynchronizer.Field(null, RGBCodec.nullable) + + /** + * Tick event schedulers + */ + val tickList = TickList() + + private val exopackSlotModifierMap: MutableMap by synchronizer.Map( keyCodec = UUIDValueCodec, valueCodec = IntValueCodec, backingMap = HashMap(), callback = { - this.exoPackSlotModifier.recompute() + this.exopackSlotModifier.recompute() }, - name = "exoPackSlotModifierMap" ) - val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{ - if (ply !is ServerPlayer) - return@observer - + /** + * Modifier map for Exopack slot counts + * + * If you want to properly extend Exopack suit capacity, add your value into this map + */ + val exopackSlotModifier = UUIDIntModifiersMap(observer = observer@{ if (it < 0) { - exoPackSlotCount = 0 + exopackContainer = PlayerMatteryContainer(0) } else { - exoPackSlotCount = it + exopackContainer = PlayerMatteryContainer(it) } - }, backingMap = this.exoPackSlotModifierMap) + }, backingMap = this.exopackSlotModifierMap) - var exoPackSlotCount by publicSynchronizer.int(setter = setter@{ value, access, _ -> - require(value >= 0) { "Invalid slot count $value" } + val regularSlotFilters = immutableList(Inventory.INVENTORY_SIZE) { + synchronizer.Field(null, ItemValueCodec.nullable) + } - if (value != access.read()) { - access.write(value) - _exoPackMenu = null - exoPackContainer = PlayerMatteryContainer(value) - } - }, name = "exoPackSlotCount") + val slotsChargeFlag by synchronizer.Set( + codec = VarIntValueCodec, + backingSet = IntAVLTreeSet(), + ) - var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0) + private fun slotChargeToDefault() { + // броня + slotsChargeFlag.add(36) + slotsChargeFlag.add(37) + slotsChargeFlag.add(38) + slotsChargeFlag.add(39) + } + + init { + slotChargeToDefault() + } + + /** + * Exopack container, which actually store items inside Exopack + */ + var exopackContainer: MatteryContainer = PlayerMatteryContainer(0) private set(value) { _exoPackMenu = null + field.removeFilterSynchronizer() @Suppress("SENSELESS_COMPARISON") // false positive - fields of player can easily be nulls, despite annotations saying otherwise if (ply.containerMenu != null && (ply !is ServerPlayer || ply.connection != null)) { ply.closeContainer() } - for (i in 0 until value.containerSize.coerceAtMost(field.containerSize)) { - if (!field[i].isEmpty) { - value[i] = field[i] - } - } - for (i in value.containerSize until field.containerSize) { - ply.spawnAtLocation(field[i]) + if (ply is ServerPlayer) + ply.spawnAtLocation(field[i]) + + field[i] = ItemStack.EMPTY } + value.deserializeNBT(field.serializeNBT()) + value.addFilterSynchronizer(synchronizer) field = value + + _combinedInventory = null + _combinedInventory2 = null + _combinedInventory3 = null } - var isExoPackCraftingUpgraded by publicSynchronizer.bool(setter = setter@{ value, access, _ -> - if (value != access.read()) { + private var _combinedInventory: CombinedContainer? = null + private var _combinedInventory2: CombinedContainer? = null + private var _combinedInventory3: CombinedContainer? = null + + val wrappedInventory: IMatteryContainer = object : IMatteryContainer, IContainer by DynamicallyProxiedContainer(ply::getInventory) { + override fun getSlotFilter(slot: Int): Item? { + return regularSlotFilters.getOrNull(slot)?.get() + } + + override fun setSlotFilter(slot: Int, filter: Item?): Boolean { + regularSlotFilters.getOrNull(slot)?.accept(filter) + return true + } + + override fun setChanged(slot: Int) { + ply.inventory.setChanged() + } + } + + val wrappedItemInventory: IMatteryContainer = object : IMatteryContainer by wrappedInventory { + override fun getContainerSize(): Int { + return 36 + } + } + + val combinedInventory: CombinedContainer + get() { + if (_combinedInventory == null) + _combinedInventory = CombinedContainer(wrappedInventory, exopackContainer) + + return _combinedInventory!! + } + + val inventoryAndExopack: CombinedContainer + get() { + if (_combinedInventory2 == null) + _combinedInventory2 = CombinedContainer(wrappedItemInventory, exopackContainer) + + return _combinedInventory2!! + } + + val inventoryAndExopackNoHotbar: CombinedContainer + get() { + if (_combinedInventory3 == null) + _combinedInventory3 = CombinedContainer.Builder() + .add(wrappedItemInventory, 9 .. 35) + .add(exopackContainer).build() + + return _combinedInventory3!! + } + + /** + * Whenever Exopack has 3x3 crafting grid upgrade installed + */ + var isExopackCraftingUpgraded by publicSynchronizer.bool(setter = setter@{ value, access, _ -> + if (value != access.readBoolean()) { access.write(value) _exoPackMenu = null - } - }, name = "isExoPackCraftingUpgraded") - private var _exoPackMenu: ExoPackInventoryMenu? = null + if (value && ply is ServerPlayer) { + tickList.once { + ExopackGainedCraftingTrigger.trigger(ply) + } + } + } + }).property + + var isExopackEnderAccessInstalled by publicSynchronizer.bool(setter = setter@{ value, access, _ -> + if (value != access.readBoolean()) { + access.write(value) + _exoPackMenu = null + + if (value && ply is ServerPlayer) { + tickList.once { + ExopackGainedEnderAccessTrigger.trigger(ply) + } + } + } + }).property + + private var _exoPackMenu: ExopackInventoryMenu? = null set(value) { if (field == ply.containerMenu) { ply.closeContainer() } + field?.actuallyRemoved() field = value } - val exoPackMenu: ExoPackInventoryMenu + /** + * [ExopackInventoryMenu] which player will see when pressing inventory key + */ + val exoPackMenu: ExopackInventoryMenu get() { if (_exoPackMenu == null) { - _exoPackMenu = ExoPackInventoryMenu(this) + _exoPackMenu = ExopackInventoryMenu(this) } return _exoPackMenu!! } + val sortingSettings = IItemStackSortingSettings.Impl() + val enderSortingSettings = IItemStackSortingSettings.Impl() + + fun recreateExoPackMenu() { + _exoPackMenu = ExopackInventoryMenu(this) + } + private var shouldSendIteration = false - var iteration = 0 - private set + private var iteration = 0 private var lastDeathTick = 0 - private val deathLog = ArrayDeque>() private val featureMap = IdentityHashMap, AndroidFeature>() @@ -180,22 +437,176 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial private var shouldPlaySound = false private val research = IdentityHashMap() + private var nextDischargeHurt = 20 + private var nextHealTick = 0 // players tracking us // stored separately because EntityTracker and ChunkMup, etc are buried deep and // getting them unburied will be a very work intense task private val trackingPlayers = Reference2ObjectOpenHashMap() + /** + * This returns if player is an Android or will become one on death/sleep/etc + */ val isEverAndroid: Boolean get() = isAndroid || willBecomeAndroid - var lastJumpTicks = 14 + internal var lastJumpTicks = 14 + /** + * In-game ticks this player exists (server play time, excluding time while "dead"), used by various things across the mod, such as + * "play time chance" loot condition + */ var ticksIExist = 0 private set - var willBecomeAndroid by publicSynchronizer.bool(name = "willBecomeAndroid") - var isAndroid by publicSynchronizer.bool(name = "isAndroid") + private var lastOutsideLiquid = Vec3(0.0, 0.0, 0.0) + private var wasInLiquid = false + private var lastDimension = ResourceLocation("overworld") - val androidEnergy = AndroidPowerSource(ply, synchronizer, ServerConfig.ANDROID_MAX_ENERGY, ServerConfig.ANDROID_MAX_ENERGY) + // clientside only flag for render hook + // TODO: actual code, currently particles spawn just behind player on ExopackSmokePacket + var spawnExopackSmoke = false + + /** + * Whenever player should become an Android once transformation conditions are met (e.g. player dies or sleeps in bed) + */ + var willBecomeAndroid by publicSynchronizer.bool().property + + /** + * Whenever player is an Android + * + * In OTM itself, Android players are generally treated as something in between alive and undead, such as: + * * They can't be healed using potions, can't receive regeneration buffs + * * But they can be harmed using harm potion + * * Can't be poisoned (with default datapack) + * * CAN be withered (with default datapack) + * * Ignored by the Wither but not by Wither Skeletons + * * Can't drown + * * etc + * + * Android-immune (de)buffs are specified in `data/overdrive_that_matters/tags/mob_effect/android_immune_effects.json` + */ + var isAndroid by publicSynchronizer.bool().property + + /** + * Whenever player has exosuit smelting upgrade + */ + var isExopackSmeltingInstalled by synchronizer.bool(setter = { value, access, _ -> + if (value != access.readBoolean()) { + access.write(value) + _exoPackMenu = null + + if (value && ply is ServerPlayer) { + tickList.once { + ExopackGainedSmeltingTrigger.trigger(ply) + } + } + } + }).property + + inner class SmelterBelt(index: Int) : MachineJobEventLoop(ItemJob.CODEC) { + override val energy: IMatteryEnergyStorage + get() = exopackEnergy + override val isBlockedByRedstone: Boolean + get() = false + + override val upgrades: IMatteryUpgrade? + get() = null + + override fun onJobFinish(status: JobStatus) { + if (output.fullyAddItem(status.job.itemStack)) { + exopackSmelterExperience += status.job.experience + } else { + status.noItem() + } + } + + private val cache = RecipeManager.createCheck(RecipeType.SMELTING) + + override fun computeNextJob(): JobContainer { + if (!exopackEnergy.batteryLevel.isPositive) return JobContainer.noEnergy() + val level = ply.level as? ServerLevel ?: return JobContainer.failure() + val recipe = cache.getRecipeFor(input, level) + + if (recipe.isEmpty) { + return JobContainer.noItem() + } else { + val actual = recipe.get() + val item = actual.value.assemble(input) + input[0].shrink(1) + input.setChanged(0) + return JobContainer.success(ItemJob(item, actual.value.cookingTime.toDouble(), ExopackConfig.FURNACE_POWER_CONSUMPTION, actual.value.experience)) + } + } + + override fun onJobTick(status: JobStatus) { + super.onJobTick(status) + + if (isExopackVisible && ply.level.random.nextFloat() <= 0.05f) { + MatteryPlayerNetworkChannel.send(PacketDistributor.TRACKING_ENTITY_AND_SELF.with { ply as ServerPlayer }, ExopackSmokePacket(ply.uuid)) + } + } + + val input = MatteryContainer({ notify(IdleReason.ITEM) }, 1) + val output = MatteryContainer({ notify(IdleReason.ITEM) }, 1) + + init { + savetables.stateful(::input, "exopack_smelter_input_$index") + savetables.stateful(::output, "exopack_smelter_output_$index") + savetables.stateful(this, "exopack_smelter_worker_$index") + } + } + + val smelter0 = SmelterBelt(0) + val smelter1 = SmelterBelt(1) + val smelter2 = SmelterBelt(2) + val smelters = listOf(smelter0, smelter1, smelter2) + + var exopackSmelterExperience = 0f + + /** + * [IMatteryEnergyStorage] instance, representing Android' battery charge + */ + val androidEnergy = BatteryBackedEnergyStorage(ply, synchronizer, AndroidConfig.ANDROID_MAX_ENERGY, AndroidConfig.ANDROID_MAX_ENERGY, true) + + /** + * [IMatteryEnergyStorage] instance, representing Exopack battery charge + */ + val exopackEnergy = ProfiledEnergyStorage(BatteryBackedEnergyStorage(ply, synchronizer, Decimal.ZERO, ExopackConfig.ENERGY_CAPACITY, false, onChange = { for (v in smelters) v.notify(MachineJobEventLoop.IdleReason.POWER) })) + + val exopackChargeSlots = MatteryContainer(4) + + init { + savetables.int(::ticksIExist) + savetables.int(::iteration) + + savetables.bool(::shouldSendIteration) + savetables.bool(::wasInLiquid) + savetables.bool(::isAndroid) + savetables.bool(::willBecomeAndroid) + savetables.bool(::hasExopack, "hasExoSuit") + savetables.bool(::isExopackVisible, "displayExoSuit") + savetables.bool(::isExopackCraftingUpgraded, "isExoSuitCraftingUpgraded") + savetables.bool(::isExopackEnderAccessInstalled, "isExopackEnderAccessUpgraded") + savetables.int(::nextDischargeHurt) + savetables.int(::nextHealTick) + + savetables.vector(::lastOutsideLiquid) + savetables.codec(::lastDimension, ResourceLocation.CODEC) + + savetables.stateful(::exopackSlotModifier, "exoSuitSlotCountModifiers") + savetables.stateful(::exopackContainer, "exoSuitContainer") + savetables.stateful(::androidEnergy) + savetables.stateful(::exopackEnergy, "exoPackEnergy") + savetables.stateful(::exopackChargeSlots, "exoPackChargeSlots") + savetables.float(::exopackSmelterExperience, "exoPackSmelterExperience") + savetables.bool(::isExopackSmeltingInstalled, "isExoPackSmeltingInstalled") + + savetables.codecNullable(::exopackColor, RGBAColor.CODECRGB) + savetables.bool(::exopackGlows) + + savetables.stateful(::sortingSettings) + savetables.stateful(::enderSortingSettings) + } fun invalidateNetworkState() { synchronizer.invalidate() @@ -212,14 +623,35 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial for (i in 0 until exoPackMenu.slots.size) { exoPackMenu.setRemoteSlotNoCopy(i, ItemStack.EMPTY) } + + tickList.timer(20) { + recreateExoPackMenu() + } } + /** + * Flags player to turn into Android + * + * This is used by Android Pill item in-game + * + * Does nothing if player is already an Android + */ fun becomeAndroidSoft() { if (isAndroid || willBecomeAndroid) return willBecomeAndroid = true (ply as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message").withStyle(ChatFormatting.GRAY), false) } + /** + * Unconditionally and instantly turns player into Android + * + * This is different than setting [isAndroid] because it setup some variables, + * such as battery charge + * + * Does not kill the player + * + * Does nothing if player is already an Android + */ fun becomeAndroid() { if (isAndroid) return @@ -228,21 +660,52 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial shouldPlaySound = false iteration = 0 deathLog.clear() - androidEnergy.batteryLevel = ServerConfig.ANDROID_MAX_ENERGY - androidEnergy.maxBatteryLevel = ServerConfig.ANDROID_MAX_ENERGY + androidEnergy.batteryLevel = AndroidConfig.ANDROID_MAX_ENERGY + androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY + + lastOutsideLiquid = ply.position() + wasInLiquid = false if (ply is ServerPlayer) { - BecomeAndroidTrigger.trigger(ply) + tickList.once { + BecomeAndroidTrigger.trigger(ply) + } } } + /** + * [becomeAndroid] plus instantly kills the player + * + * Does nothing if player is already an Android + */ fun becomeAndroidAndKill() { if (isAndroid) return becomeAndroid() - ply.hurt(MRegistry.DAMAGE_BECOME_ANDROID, ply.maxHealth * 2) + + if (!ply.abilities.invulnerable) + ply.hurt(MDamageTypes.BECOME_ANDROID, ply.maxHealth * 2) } + /** + * Unconditionally and instantly turns player into "human" + * + * This is different than setting [isAndroid] because it setup some variables, + * such as battery charge + * + * Does not kill the player + * + * Drops equipped battery BUT does NOT refund research, nor does remove features; + * Once player becomes Android again, they will retain all features and research. + * + * If you need to also remove research and features, look at [obliviate] + * + * However, this does reset [iteration] and death log + * + * Does nothing if player is not an Android + * + * Unsets [willBecomeAndroid] flag if it is true + */ fun becomeHumane() { if (willBecomeAndroid) willBecomeAndroid = false if (!isAndroid) return @@ -252,29 +715,52 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial iteration = 0 deathLog.clear() androidEnergy.batteryLevel = Decimal.ZERO - androidEnergy.maxBatteryLevel = ServerConfig.ANDROID_MAX_ENERGY + androidEnergy.maxBatteryLevel = AndroidConfig.ANDROID_MAX_ENERGY dropBattery() + lastOutsideLiquid = ply.position() + wasInLiquid = false + if (ply is ServerPlayer) { - BecomeHumaneTrigger.trigger(ply) + tickList.once { + BecomeHumaneTrigger.trigger(ply) + } + } + + for (feature in featureMap.values) { + feature.removeModifiers() } } + /** + * [becomeHumane] plus kills the player + * + * Does nothing if player is not an Android + * + * Unsets [willBecomeAndroid] flag if it is true + */ fun becomeHumaneAndKill() { if (willBecomeAndroid) willBecomeAndroid = false if (!isAndroid) return becomeHumane() - ply.hurt(MRegistry.DAMAGE_BECOME_HUMANE, ply.maxHealth * 2) + + if (!ply.abilities.invulnerable) + ply.hurt(MDamageTypes.BECOME_HUMANE, ply.maxHealth * 2) } + /** + * *Refunds* all research and removes all Android features + * + * Resets [iteration] and death log + */ fun obliviate(refund: Boolean = true) { for (instance in research.values) { if (instance.isResearched) { instance.unResearch() if (refund) { - instance.refund(simulate = false) + instance.refund() } } } @@ -289,13 +775,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial deathLog.clear() } + /** + * Returns [AndroidResearch] state for specified [AndroidResearchType] + */ fun getResearch(type: AndroidResearchType): AndroidResearch { return research.computeIfAbsent(type) { return@computeIfAbsent AndroidResearch(type, this) } } - fun reloadResearch() { + internal fun reloadResearch() { val old = ArrayList(research.size) old.addAll(research.values) research.clear() @@ -309,6 +798,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + /** + * Android features stream, use this to get list of all Android features this player has + */ val features: Stream get() = featureMap.values.stream() private fun addFeature(feature: AndroidFeature): Boolean { @@ -331,6 +823,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return true } + /** + * Adds or returns specified Android feature to this player + */ @Suppress("unchecked_cast") fun addFeature(feature: AndroidFeatureType): T { val get = featureMap[feature] @@ -340,7 +835,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial featureMap[feature] = factory - if (!ply.level.isClientSide) { + if (!ply.level.isClientSide && isAndroid) { queuedTicks.add(factory::applyModifiers) } @@ -356,14 +851,17 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return factory } + /** + * Removes specified Android feature from this player + * + * @return whenever feature was found and removed + */ fun removeFeature(feature: AndroidFeatureType<*>): Boolean { val removed = featureMap.remove(feature) if (removed != null) { - if (!ply.level.isClientSide) { - queuedTicks.add { - removed.removeModifiers() - } + if (!ply.level.isClientSide && isAndroid) { + queuedTicks.add(removed::removeModifiers) } if (ply is ServerPlayer) { @@ -374,21 +872,32 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return removed != null } + /** + * Whenever player has specified [feature] + */ fun hasFeature(feature: AndroidFeatureType<*>): Boolean { return featureMap.containsKey(feature) } + /** + * Whenever player has specified [feature] and its [AndroidFeature.level] is equal to or greater than [level] + */ fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean { val get = featureMap[feature] ?: return false return get.level >= level } + /** + * Raw get of Android feature of this player + * + * @return null if no such feature is present + */ @Suppress("unchecked_cast") fun getFeature(feature: AndroidFeatureType): T? { return featureMap[feature] as T? } - fun onHurt(event: LivingHurtEvent) { + private fun onHurt(event: LivingHurtEvent) { if (isAndroid) { for (feature in featureMap.values) { feature.onHurt(event) @@ -400,16 +909,22 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun computeIfAbsent(feature: AndroidFeatureType): T { + private fun onAttack(event: LivingAttackEvent) { + if (isAndroid) { + for (feature in featureMap.values) { + feature.onAttack(event) + } + } + } + + internal fun computeIfAbsent(feature: AndroidFeatureType): T { return getFeature(feature) ?: addFeature(feature) } - fun getFeatureO(feature: AndroidFeatureType): Optional { - val get = getFeature(feature) - return if (get != null) Optional.of(get) else Optional.empty() - } - - fun ifFeature(feature: AndroidFeatureType, consumer: (T) -> Unit) { + /** + * Lambda style of [getFeature] + */ + inline fun ifFeature(feature: AndroidFeatureType, consumer: (T) -> Unit) { val get = getFeature(feature) if (get != null) { @@ -418,14 +933,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } override fun serializeNBT(): CompoundTag { - val tag = CompoundTag() - - tag["ticksIExist"] = ticksIExist + val tag = savetables.serializeNBT() // iteration - tag["iteration"] = iteration - tag["shouldSendIteration"] = shouldSendIteration - tag["deathLog"] = ListTag().also { for ((ticks, component) in deathLog) { it.add(CompoundTag().also { @@ -435,47 +945,72 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - // exosuit - tag["hasExoSuit"] = hasExoPack - tag["displayExoSuit"] = displayExoPack - tag["exoSuitContainer"] = exoPackContainer.serializeNBT() - tag["isExoSuitCraftingUpgraded"] = isExoPackCraftingUpgraded - tag["exoSuitSlotCountModifiers"] = exoPackSlotModifier.serializeNBT() - - // android - tag["androidEnergy"] = androidEnergy.serializeNBT() - - tag["isAndroid"] = isAndroid - tag["willBecomeAndroid"] = willBecomeAndroid - - val featureList = ListTag() - val researchList = ListTag() - - for (feature in featureMap.values) { - featureList.add(feature.serializeNBT().also { - it["id"] = feature.type.registryName!!.toString() - }) + tag["features"] = ListTag().also { + for (feature in featureMap.values) { + it.add(feature.serializeNBT().also { + it["id"] = feature.type.registryName!!.toString() + }) + } } - for ((type, instance) in research) { - researchList.add(instance.serializeNBT().also { - it["id"] = type.id.toString() - }) + tag["research"] = ListTag().also { + for ((type, instance) in research) { + it.add(instance.serializeNBT().also { + it["id"] = type.id.toString() + }) + } } - tag["features"] = featureList - tag["research"] = researchList + tag["regularSlotFilters"] = ListTag().also { + for (filter in regularSlotFilters) { + it.add(StringTag.valueOf(filter.value?.registryName?.toString() ?: "")) + } + } + + tag["slotsChargeFlag"] = ListTag().also { + for (value in slotsChargeFlag) { + it.add(IntTag.valueOf(value)) + } + } return tag } override fun deserializeNBT(tag: CompoundTag) { - ticksIExist = tag.getInt("ticksIExist") + savetables.deserializeNBT(tag) + + if (MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON.uuid(ItemStack.EMPTY) in exopackSlotModifierMap) { + isExopackEnderAccessInstalled = true + } + + if (ply is ServerPlayer && hasExopack) + tickList.once { + ExopackSlotsExpandedTrigger.trigger(ply, 0, exopackContainer.containerSize) + } + + for (filter in regularSlotFilters) { + filter.value = null + } + + slotsChargeFlag.clear() + + val regularSlotFilters = tag.getStringList("regularSlotFilters") + + for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) { + val path = regularSlotFilters[i].asString + if (path == "") continue + this.regularSlotFilters[i].value = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(path) ?: continue) ?: Items.AIR + } + + if ("slotsChargeFlag" in tag) { + for (v in tag.getIntList("slotsChargeFlag")) { + this.slotsChargeFlag.add(v.asInt) + } + } else { + slotChargeToDefault() + } // iterations - iteration = tag.getInt("iteration") - shouldSendIteration = tag.getBoolean("shouldSendIteration") - deathLog.clear() for (value in tag.getCompoundList("deathLog")) { @@ -486,20 +1021,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - // exosuit - hasExoPack = tag.getBoolean("hasExoSuit") - displayExoPack = tag.getBoolean("displayExoSuit") - tag.map("exoSuitSlotCountModifiers", exoPackSlotModifier::deserializeNBT) - - exoPackContainer.deserializeNBT(tag["exoSuitContainer"]) - isExoPackCraftingUpgraded = tag.getBoolean("isExoSuitCraftingUpgraded") - - // android - isAndroid = tag.getBoolean("isAndroid") - willBecomeAndroid = tag.getBoolean("willBecomeAndroid") - - tag.map("androidEnergy", androidEnergy::deserializeNBT) - featureMap.clear() research.clear() @@ -527,7 +1048,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial this.research[research] = instance if (instance.isResearched && ply is ServerPlayer) { - onceServer(20) { + tickList.once { if (!ply.hasDisconnected()) AndroidResearchTrigger.trigger(ply, instance.type) } @@ -536,13 +1057,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } if (isAndroid && ply is ServerPlayer) { - onceServer(20) { + tickList.once { if (!ply.hasDisconnected()) BecomeAndroidTrigger.trigger(ply) } } } + /** + * Drops Android battery as in-world item + */ fun dropBattery() { if (androidEnergy.item.isEmpty) return ply.spawnAtLocation(androidEnergy.item) @@ -560,8 +1084,8 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } private fun tickInventory() { - if (hasExoPack) { - for ((i, stack) in exoPackContainer.withIndex()) { + if (hasExopack) { + for ((i, stack) in exopackContainer.withIndex()) { if (!stack.isEmpty) { stack.inventoryTick(ply.level, ply, i + 41, false) } @@ -569,7 +1093,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun tickClient() { + private fun tickClient() { queuedTicks.clear() if (!ply.isAlive) { @@ -585,7 +1109,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial tickInventory() } - fun preTick() { + private fun preTick() { if (!ply.isAlive) return if (isAndroid) { @@ -596,11 +1120,18 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun tick() { + private fun tick() { if (!ply.isAlive) return + PreTick(this).also { + MinecraftForge.EVENT_BUS.post(it) + if (it.isCanceled) return + } + ticksIExist++ + tickList.tick() + if (willBecomeAndroid) { if (ply.isSleeping && ply.sleepTimer > SLEEP_TICKS_LIMIT) { becomeAndroid() @@ -613,47 +1144,154 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - if (isAndroid) { - if (!ply.isSpectator && ply.airSupply < ply.maxAirSupply) - ply.airSupply = ply.maxAirSupply + if (hasExopack) { + exopackEnergy.parent.tick() - // TODO: Maybe passive drain? - // extractEnergyInner(BigDecimal.valueOf(new Random().nextDouble()), false); - if (!ply.isSpectator && ply.isSwimming && !hasFeature(AndroidFeatures.AIR_BAGS)) { - ply.isSwimming = false + if (!ply.isSpectator) { + if (isExopackSmeltingInstalled) + smelters.forEach { it.think() } + + if (exopackEnergy.batteryLevel.isPositive) { + var available = exopackEnergy.extractEnergy(exopackEnergy.batteryLevel, true) + + if (!exopackChargeSlots.isEmpty) { + for (item in exopackChargeSlots) { + if (item.isNotEmpty) { + item.energy?.let { + available -= exopackEnergy.extractEnergy(it.receiveEnergy(available, false), false) + } + + if (!available.isPositive) break + } + } + } + + if (available.isPositive) { + for (slot in slotsChargeFlag) { + if (slot in 0 until combinedInventory.containerSize) { + val item = combinedInventory[slot] + + if (item.isNotEmpty) { + item.energy?.let { + available -= exopackEnergy.extractEnergy(it.receiveEnergy(available, false), false) + } + + if (!available.isPositive) break + } + } + } + } + } } + } + if (isAndroid) { androidEnergy.tick() if (!ply.isSpectator) { - val stats = ply.foodData + if (ply.airSupply < ply.maxAirSupply) + ply.airSupply = ply.maxAirSupply - while (stats.foodLevel < 18 && androidEnergy.extractEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT, true) >= ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT) { - androidEnergy.extractEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT, false) - stats.foodLevel = stats.foodLevel + 1 + if (ply.isSwimming && !hasFeature(AndroidFeatures.AIR_BAGS)) + ply.isSwimming = false + + if (ply is ServerPlayer) { + if (ply.level.dimension().location() != lastDimension) { + lastDimension = ply.level.dimension().location() + wasInLiquid = false + lastOutsideLiquid = ply.position + } + + if (ply.isUnderWater && !ply.isCreative) { + if (!wasInLiquid) { + wasInLiquid = true + lastOutsideLiquid = ply.position + } + } else { + if (wasInLiquid) { + wasInLiquid = false + + if (!hasFeature(AndroidFeatures.AIR_BAGS)) + AndroidTravelUnderwater.trigger(ply, (lastOutsideLiquid - ply.position).length()) + } + + lastOutsideLiquid = ply.position + } } - // "block" quick regeneration - // also cause power to generate while in peaceful - if (ServerConfig.REGENERATE_ENERGY) { - while (stats.foodLevel > 18 && androidEnergy.receiveEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT, true) >= ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT) { - androidEnergy.receiveEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT, false) - stats.foodLevel = stats.foodLevel - 1 + val stats = ply.foodData + val fourTimesTheHunger = AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT * 4 + + // истощение + if (stats.exhaustionLevel > 0f) { + val extracted = androidEnergy.extractEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT * (stats.exhaustionLevel / 4f), false) + stats.setExhaustion(stats.exhaustionLevel - (extracted / AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat() * 4f) + } + + // Обычный голод + while ( + stats.foodLevel < 18 && + androidEnergy.batteryLevel >= fourTimesTheHunger && + androidEnergy.extractEnergyExact(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, false) + ) { + stats.foodLevel++ + } + + // "поглощение" излишек голода, как при мирном режиме, так и при поедании обычной еды + if (AndroidConfig.REGENERATE_ENERGY) { + while (stats.foodLevel > 18 && androidEnergy.receiveEnergyExact(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT / 2, false)) { + stats.foodLevel-- } - } else if (ply.level.server?.worldData?.difficulty != Difficulty.PEACEFUL) { + } else if (ply.level.difficulty != Difficulty.PEACEFUL) { stats.foodLevel = stats.foodLevel.coerceAtMost(18) } val foodLevel = stats.foodLevel.toFloat() - if (stats.saturationLevel < foodLevel) { - val extracted = androidEnergy.extractEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT * (foodLevel - stats.saturationLevel), false) - stats.setSaturation(stats.saturationLevel + (extracted / ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat()) + // насыщение + if (stats.saturationLevel < foodLevel && androidEnergy.batteryLevel >= fourTimesTheHunger) { + val extracted = androidEnergy.extractEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT * (foodLevel - stats.saturationLevel), false) + stats.setSaturation(stats.saturationLevel + (extracted / AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat()) } - if (stats.exhaustionLevel > 0f) { - val extracted = androidEnergy.extractEnergyInner(ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT * (stats.exhaustionLevel / 4f), false) - stats.setExhaustion(stats.exhaustionLevel - (extracted / ServerConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat() * 4f) + if (androidEnergy.batteryLevel <= Decimal.TEN && !ply.isCreative && ply.level.difficulty != Difficulty.PEACEFUL) { + if (stats.saturationLevel > 1f) { + if (androidEnergy.receiveEnergyExact(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, false)) { + stats.setSaturation(stats.saturationLevel - 1f) + } + } else if (stats.saturationLevel > 0f) { + val received = androidEnergy.receiveEnergy(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT * stats.saturationLevel, false) + stats.setSaturation(stats.saturationLevel - (received / AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT).toFloat()) + } else if (stats.foodLevel > 0) { + // так как голод не тикает для андроидов, "умереть с голоду" мы не можем + // но со стороны будет выглядеть как будто мы умираем с голода + if (androidEnergy.receiveEnergyExact(AndroidConfig.ANDROID_ENERGY_PER_HUNGER_POINT, false)) { + stats.foodLevel-- + } + } + + if (androidEnergy.batteryLevel <= Decimal.TEN) { + if (--nextDischargeHurt <= 0 && ply.hurt(MDamageTypes.ANDROID_DISCHARGE, 1f)) { + nextDischargeHurt = 20 + } + + val effect = ply.activeEffectsMap[MobEffects.MOVEMENT_SLOWDOWN] + + if (effect == null || effect.duration < 40 || effect.amplifier < 2) { + ply.addEffect(MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, effect?.duration?.coerceAtLeast(60) ?: 60, effect?.amplifier?.coerceAtLeast(2) ?: 2, false, false)) + } + } + } else { + nextDischargeHurt = 20 + + if (ply.isHurt && ply.level.gameRules.getBoolean(GameRules.RULE_NATURAL_REGENERATION)) { + if (--nextHealTick <= 0) { + nextHealTick = if (ply.level.difficulty == Difficulty.PEACEFUL) 10 else AndroidConfig.TIME_BETWEEN_NATURAL_REGENERATION + ply.heal(1f) + } + } else { + nextHealTick = if (ply.level.difficulty == Difficulty.PEACEFUL) 10 else AndroidConfig.TIME_BETWEEN_NATURAL_REGENERATION + } } } @@ -726,11 +1364,15 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial shouldSendIteration = false } - if (hasExoPack && ply.containerMenu == ply.inventoryMenu) { + if (hasExopack && ply.containerMenu == ply.inventoryMenu) { exoPackMenu.broadcastChanges() } tickInventory() + + PostTick(this).also { + MinecraftForge.EVENT_BUS.post(it) + } } private val resolver = LazyOptional.of { this } @@ -741,8 +1383,124 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } else LazyOptional.empty() } + /** + * This re-implement [Inventory.add] logic (original method is redirected to this) + */ + fun inventoryAddImpl(stack: ItemStack): Boolean { + if (hasExopack) { + combinedInventory.consumeItem(stack, false, slots = IntSet.of(ply.inventory.selected), onlyIntoExisting = true, popTime = 5) + combinedInventory.consumeItem(stack, false, slots = offhandSlotRange, onlyIntoExisting = true, popTime = 5) + inventoryAndExopack.consumeItem(stack, false, onlyIntoExisting = false, popTime = 5) + } else { + wrappedInventory.consumeItem(stack, false, slots = IntSet.of(ply.inventory.selected), onlyIntoExisting = true, popTime = 5) + wrappedInventory.consumeItem(stack, false, slots = offhandSlotRange, onlyIntoExisting = true, popTime = 5) + wrappedItemInventory.consumeItem(stack, false, onlyIntoExisting = false, popTime = 5) + } + + MinecraftForge.EVENT_BUS.post(ItemStackLeftoverEvent(stack, this)) + + if (ply.abilities.instabuild) { + stack.count = 0 + } + + return stack.isEmpty + } + + fun makeSmokeParticles(poseStack: PoseStack, model: PlayerModel) { + if (spawnExopackSmoke) { + spawnExopackSmoke = false + + // TODO: Proper positioning + + val pos = Vector4f(model.body.x / 16f, model.body.y / 16f, model.body.z / 16f, 1f) + val cam = minecraft.gameRenderer.mainCamera.position + + pos.mul(RenderSystem.getProjectionMatrix()) + pos.mul(poseStack.last().pose()) + makeSmoke(cam.x + pos.x, cam.y + pos.y, cam.z + pos.z, ply.level.random, ply.level) + } + } + + enum class UpgradeType(val prop: KMutableProperty1) { + CRAFTING(MatteryPlayerCapability::isExopackCraftingUpgraded), + ENDER_ACCESS(MatteryPlayerCapability::isExopackEnderAccessInstalled), + SMELTING(MatteryPlayerCapability::isExopackSmeltingInstalled); + } + @Suppress("unused") companion object { + private val offhandSlotRange = IntSet.of(40) + + private fun setExoPack(players: Collection, hasExoPack: Boolean): Int { + for (player in players) { + player.matteryPlayer?.hasExopack = hasExoPack + player.matteryPlayer?._exoPackMenu = null + } + + return players.size + } + + private fun makeAndroid(players: Collection): Int { + for (player in players) { + player.matteryPlayer?.becomeAndroid() + } + + return players.size + } + + private fun makeHuman(players: Collection): Int { + for (player in players) { + player.matteryPlayer?.becomeHumane() + } + + return players.size + } + + private fun setUpgrade(players: Collection, type: UpgradeType, state: Boolean): Int { + for (player in players) { + player.matteryPlayer?.let { type.prop.set(it, state) } + } + + return players.size + } + + fun addCommands(event: RegisterCommandsEvent) { + event.dispatcher.register( + Commands.literal("exopack") + .requires { it.hasPermission(Commands.LEVEL_GAMEMASTERS) } + .then(Commands.literal("add") + .executes { setExoPack(listOf(it.source.playerOrException), true) } + .then(Commands.argument("targets", EntityArgument.players()).executes { setExoPack(EntityArgument.getPlayers(it, "targets"), true) }) + ) + .then(Commands.literal("remove") + .executes { setExoPack(listOf(it.source.playerOrException), false) } + .then(Commands.argument("targets", EntityArgument.players()).executes { setExoPack(EntityArgument.getPlayers(it, "targets"), false) }) + ) + .then(Commands.literal("upgrade") + .then(Commands.argument("type", EnumArgument.enumArgument(UpgradeType::class.java)) + .then(Commands.literal("add") + .executes { setUpgrade(listOf(it.source.playerOrException), it.getArgument("type", UpgradeType::class.java), true) } + .then(Commands.argument("targets", EntityArgument.players()).executes { setUpgrade(EntityArgument.getPlayers(it, "targets"), it.getArgument("type", UpgradeType::class.java), true) })) + .then(Commands.literal("remove") + .executes { setUpgrade(listOf(it.source.playerOrException), it.getArgument("type", UpgradeType::class.java), false) } + .then(Commands.argument("targets", EntityArgument.players()).executes { setUpgrade(EntityArgument.getPlayers(it, "targets"), it.getArgument("type", UpgradeType::class.java), false) }))) + ) + ) + + event.dispatcher.register( + Commands.literal("android") + .requires { it.hasPermission(Commands.LEVEL_GAMEMASTERS) } + .then(Commands.literal("on") + .executes { makeAndroid(listOf(it.source.playerOrException)) } + .then(Commands.argument("targets", EntityArgument.players()).executes { makeAndroid(EntityArgument.getPlayers(it, "targets")) }) + ) + .then(Commands.literal("off") + .executes { makeHuman(listOf(it.source.playerOrException)) } + .then(Commands.argument("targets", EntityArgument.players()).executes { makeHuman(EntityArgument.getPlayers(it, "targets")) }) + ) + ) + } + init { WitherBoss.TARGETING_CONDITIONS.selector(WitherBoss.TARGETING_CONDITIONS.selector!!.and { it.matteryPlayer?.isAndroid != true }) WitherBoss.LIVING_ENTITY_SELECTOR = WitherBoss.LIVING_ENTITY_SELECTOR.and { it.matteryPlayer?.isAndroid != true } @@ -784,8 +1542,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { it.onHurt(event) } } - const val CAPABILITY_KEY = "otm_player" - val CAPABILITY_LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, CAPABILITY_KEY) + fun onAttackEvent(event: LivingAttackEvent) { + event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { it.onAttack(event) } + } + + val CAPABILITY_LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, "player") fun onAttachCapabilityEvent(event: AttachCapabilitiesEvent) { val ent = event.`object` @@ -796,8 +1557,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } fun onPlayerChangeDimensionEvent(event: PlayerEvent.PlayerChangedDimensionEvent) { - event.entity.getCapability(MatteryCapability.MATTERY_PLAYER) - .ifPresentK { it.invalidateNetworkState() } + onceServer { + event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { + it.invalidateNetworkState() + it.recreateExoPackMenu() + } + } } fun onPlayerDeath(event: LivingDeathEvent) { @@ -817,6 +1582,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial mattery.iteration++ mattery.shouldSendIteration = true mattery.deathLog.addLast(ply.tickCount to ply.combatTracker.deathMessage) + mattery.androidEnergy.batteryLevel = mattery.androidEnergy.batteryLevel.coerceAtLeast(AndroidConfig.ANDROID_MAX_ENERGY * Decimal("0.2")) // если смерть была от разряда батареи, то предотвращаем софтлок while (mattery.deathLog.size > 6) { mattery.deathLog.removeFirst() @@ -825,21 +1591,18 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial fun onPlayerCloneEvent(event: PlayerEvent.Clone) { val it = event.entity.matteryPlayer ?: return + var original = event.original.matteryPlayer - var resolver = event.original.getCapability(MatteryCapability.MATTERY_PLAYER) - - if (!resolver.isPresent || resolver.resolve().isEmpty) { + if (original == null) { event.original.reviveCaps() - resolver = event.original.getCapability(MatteryCapability.MATTERY_PLAYER) + original = event.original.matteryPlayer } - if (!resolver.isPresent || resolver.resolve().isEmpty) { + if (original == null) { event.original.invalidateCaps() return } - val original = resolver.resolve().get() - if (original.willBecomeAndroid && event.isWasDeath) { original.becomeAndroid() @@ -854,122 +1617,97 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial it.deserializeNBT(original.serializeNBT()) it.invalidateNetworkState() event.original.invalidateCaps() + + onceServer { + event.entity.getCapability(MatteryCapability.MATTERY_PLAYER).ifPresentK { + it.invalidateNetworkState() + it.recreateExoPackMenu() + } + } } const val SLEEP_TICKS_LIMIT = 80 private val LOGGER = LogManager.getLogger() - fun canEntitySpawn(event: LivingSpawnEvent.CheckSpawn) { - if (event.spawnReason == MobSpawnType.NATURAL && event.entity is Phantom) { - for (ply in event.entity.level.players()) { - if (!ply.isSpectator && ply.matteryPlayer?.isAndroid == true) { - val feature = (ply.matteryPlayer?.getFeature(AndroidFeatures.PHANTOM_ATTRACTOR) as AndroidSwitchableFeature?) + /** + * hooked into PhantomSpawner through coremod script + */ + @JvmStatic + fun phantomSpawnHook(iterator: Iterator): Iterator { + return iterator.filter { it.matteryPlayer?.isAndroid != true } + } - if (feature?.isActive != true) { - if ( - ply.position.y in event.y - 36.0 .. event.y - 19.0 && - ply.position.x in event.x - 12.0 .. event.x + 12.0 && - ply.position.z in event.z - 12.0 .. event.z + 12.0 - ) { - event.result = Event.Result.DENY + /** + * hooked into LivingEntity through coremod script + */ + @JvmStatic + fun addEatEffectHook(iterator: Iterator>, entity: LivingEntity): Iterator> { + if (entity !is Player) { + return iterator + } - if (feature == null && ply is ServerPlayer) { - PhantomSpawnDeniedTrigger.trigger(ply) - } + val mattery = entity.matteryPlayer ?: return iterator - return - } - } - } + if (mattery.isAndroid) { + return iterator.filter { + it.first.effect != MobEffects.HUNGER } } - } - /** - * this method is hooked through coremod transformer - */ - @JvmStatic - fun inventoryAddItemHook(inventory: Inventory, item: ItemStack) { - if (item.isEmpty) { - return - } - - val it = inventory.player.matteryPlayer ?: return - - if (!it.hasExoPack) { - return - } - - val leftover = it.exoPackContainer.addItem(item, false, popTime = 5) - item.count = leftover.count - } - - /** - * this method is hooked through coremod transformer - */ - @JvmStatic - fun inventoryAddItemHookPre(inventory: Inventory, item: ItemStack) { - if (item.isEmpty || !item.isStackable) { - return - } - - val it = inventory.player.matteryPlayer ?: return - - if (!it.hasExoPack) { - return - } - - val leftover = it.exoPackContainer.addItem(item, simulate = false, onlyIntoExisting = true, popTime = 5) - item.count = leftover.count - } - - /** - * this method is hooked through coremod transformer - */ - @JvmStatic - fun inventoryAddDamagedItemHook(determinedSlot: Int, inventory: Inventory, item: ItemStack) { - if (determinedSlot > 0) { - return - } - - inventoryAddItemHook(inventory, item) + return iterator } @JvmStatic fun inventoryDropAll(inventory: Inventory) { - if (inventory.player.matteryPlayer?.hasExoPack == false) { + val mattery = inventory.player.matteryPlayer ?: return + + if (!mattery.hasExopack) { return } - val iterator = inventory.player.matteryPlayer?.exoPackContainer?.iterator()?.nonEmpty() ?: return + inventory.player.dropContainer(mattery.exopackContainer) + inventory.player.dropContainer(mattery.exopackChargeSlots) + // inventory.player.dropContainer(mattery.exoPackEnergy.parent) - for (item in iterator) { - inventory.player.drop(item, true, false) - iterator.remove() + for (smelter in mattery.smelters) { + inventory.player.dropContainer(smelter.input) + inventory.player.dropContainer(smelter.output) + smelter.currentJob = null } } @JvmStatic fun inventoryClearContent(inventory: Inventory) { - if (inventory.player.matteryPlayer?.hasExoPack == false) { + val mattery = inventory.player.matteryPlayer ?: return + + if (!mattery.hasExopack) { return } - inventory.player.matteryPlayer?.exoPackContainer?.clearContent() + mattery.exopackContainer.clearContent() + mattery.exopackChargeSlots.clearContent() + // mattery.exoPackEnergy.parent.clearContent() + + for (smelter in mattery.smelters) { + smelter.input.clearContent() + smelter.output.clearContent() + smelter.currentJob = null + } } @JvmStatic fun playerDestroyVanishingCursedItems(player: Player) { - if (player.matteryPlayer?.hasExoPack == false) { - return - } + player.matteryPlayer?.let { + if (it.hasExopack) { + it.exopackContainer.vanishCursedItems() + it.exopackChargeSlots.vanishCursedItems() + // it.exoPackEnergy.parent.vanishCursedItems() - val iterator = player.matteryPlayer?.exoPackContainer?.iterator()?.nonEmpty() ?: return - - for (item in iterator) { - if (hasVanishingCurse(item)) { - iterator.remove() + for (smelter in it.smelters) { + smelter.input.vanishCursedItems() + smelter.output.vanishCursedItems() + } } } } @@ -984,12 +1722,12 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial val matteryPlayer = player.matteryPlayer ?: return - if (!matteryPlayer.hasExoPack) { + if (!matteryPlayer.hasExopack) { return } val targetSlot = player.inventory.suitableHotbarSlot - val itemSlot = matteryPlayer.exoPackContainer.indexOfFirst { !it.isEmpty && ItemStack.isSameItemSameTags(itemStack, it) } + val itemSlot = matteryPlayer.exopackContainer.indexOfFirst { !it.isEmpty && ItemStack.isSameItemSameTags(itemStack, it) } if (itemSlot == -1) { return @@ -1017,11 +1755,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial val weapon = weaponItem.item as? ProjectileWeaponItem ?: return null val matteryPlayer = player.matteryPlayer ?: return null - if (!matteryPlayer.hasExoPack) { + if (!matteryPlayer.hasExopack) { return null } - val item = matteryPlayer.exoPackContainer.stream().filter(weapon.allSupportedProjectiles).findFirst().orElse(null) ?: return null + val item = matteryPlayer.exopackContainer.stream().filter(weapon.allSupportedProjectiles).findFirst().orElse(null) ?: return null return ForgeHooks.getProjectile(player, weaponItem, item) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt new file mode 100644 index 000000000..2669485f4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt @@ -0,0 +1,260 @@ +package ru.dbotthepony.mc.otm.capability + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.collect.SupplierList +import ru.dbotthepony.mc.otm.core.immutableSet +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatMatter +import ru.dbotthepony.mc.otm.core.util.formatPower + +/** + * Upgrades merge by sum of their effects + * + * Values which are otherwise not stated to allow negatives or not, Do allow negative values. + */ +interface IMatteryUpgrade { + /** + * Type(s) this upgrade represent + */ + val upgradeTypes: Set get() = setOf() + + /** + * [speedBonus] of `1.0` means that machine doubles the speed (performs twice the amount of + * work ticks per work cycle). + */ + val speedBonus: Double get() = 0.0 + + /** + * **CAN NOT be negative.** + * + * [processingItems] of `4` means machine will accept at most 5 (1 + 4) of items per job. + */ + val processingItems: Int get() = 0 + + /** + * **CAM NOT be negative.** + * + * Added directly over regular energy storage capacity. + */ + val energyStorageFlat: Decimal get() = Decimal.ZERO + + /** + * **CAM NOT be negative.** + */ + val energyStorage: Decimal get() = Decimal.ZERO + + /** + * **CAM NOT be negative.** + * + * Added directly over regular matter storage capacity. + */ + val matterStorageFlat: Decimal get() = Decimal.ZERO + + /** + * **CAM NOT be negative.** + */ + val matterStorage: Decimal get() = Decimal.ZERO + + /** + * Value of `1` means power consumption is doubled, `2` is tripled, `-0.5` is halved. + */ + val energyConsumed: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * Added directly over regular throughput + */ + val energyThroughputFlat: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * Value of `1` means power throughput is doubled, `2` is tripled, and so on. + */ + val energyThroughput: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * **Merged by multiplication** + */ + val failureMultiplier: Double get() = 1.0 + + companion object : IMatteryUpgrade +} + +private val positiveBound = Decimal("0.01") +private val negativeBound = Decimal("-0.01") + +fun IMatteryUpgrade.addUpgradeTooltipLines(tooltips: MutableCollection) { + if (upgradeTypes.isNotEmpty() && ShiftPressedCond.asBoolean) { + tooltips.add(TranslatableComponent("otm.gui.upgrade_type.list").withStyle(ChatFormatting.GRAY)) + + for (upgrade in upgradeTypes) { + tooltips.add(upgrade.component.copy().withStyle(ChatFormatting.GRAY)) + } + + tooltips.add(TextComponent("")) + } + + if (speedBonus >= 0.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.speed", TextComponent("+" + (speedBonus.coerceIn(MachinesConfig.Upgrades.MIN_SPEED, MachinesConfig.Upgrades.MAX_SPEED) * 100.0).toInt().toString()).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } else if (speedBonus <= -0.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.speed", TextComponent((speedBonus.coerceIn(MachinesConfig.Upgrades.MIN_SPEED, MachinesConfig.Upgrades.MAX_SPEED) * 100.0).toInt().toString()).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } + + if (processingItems != 0) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.processing_items", TextComponent(processingItems.toString()).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyStorageFlat != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_storage_flat", energyStorageFlat.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyStorage != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_storage", TextComponent((energyStorage * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (matterStorageFlat != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.matter_storage_flat", matterStorageFlat.formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (matterStorage != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.matter_storage", TextComponent((matterStorage * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyConsumed >= positiveBound) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_consumed", TextComponent("+" + (energyConsumed.coerceIn(MachinesConfig.Upgrades.MIN_ENERGY, MachinesConfig.Upgrades.MAX_ENERGY) * 100).toString(0)).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } else if (energyConsumed <= negativeBound) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_consumed", TextComponent((energyConsumed.coerceIn(MachinesConfig.Upgrades.MIN_ENERGY, MachinesConfig.Upgrades.MAX_ENERGY) * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyThroughputFlat != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_throughput_flat", energyThroughputFlat.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyThroughput != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_throughput", TextComponent((energyThroughput * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (failureMultiplier >= 1.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.failsafe", TextComponent("+" + ((failureMultiplier - 1) * 100).toInt().toString()).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } else if (failureMultiplier <= 0.99) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.failsafe", TextComponent(((failureMultiplier.coerceAtLeast(0.0) - 1) * 100).toInt().toString()).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } +} + +fun IMatteryUpgrade.getUpgradeTooltipLines(): MutableList { + return ArrayList().also { addUpgradeTooltipLines(it) } +} + +interface ITieredUpgradeSet { + val BLANK: Item + + val SPEED: Item + val ENERGY_CONSUMPTION: Item + val FAILSAFE: Item + + val ENERGY_STORAGE: Item + val MATTER_STORAGE: Item + + val PROCESSING_ITEMS: Item + + val LIST: SupplierList + get() = SupplierList( + ::SPEED, + ::ENERGY_CONSUMPTION, + ::FAILSAFE, + ::ENERGY_STORAGE, + ::MATTER_STORAGE, + ::PROCESSING_ITEMS, + ) +} + +enum class UpgradeType { + SPEED, + PROCESSING, + ENERGY_STORAGE, + ENERGY_CONSUMPTION, + ENERGY_THROUGHPUT, + MATTER_STORAGE, + FAILSAFE; + + val localeId = "otm.gui.upgrade_type.${name.lowercase()}" + val component: Component get() = TranslatableComponent(localeId) + + val flag = 1 shl (ordinal + 1) + fun set() = sets[flag] + + companion object { + private val cached = values() + + private val sets = Array(2 shl cached.size) { + immutableSet { + for (u in values()) if (it.and(u.flag) != 0) accept(u) + } + } + + @JvmField + val ALL = set(*values()) + + @JvmField + val BASIC = set(SPEED, ENERGY_STORAGE, ENERGY_CONSUMPTION, ENERGY_THROUGHPUT) + + @JvmField + val BASIC_MATTER = set(SPEED, ENERGY_STORAGE, ENERGY_CONSUMPTION, ENERGY_THROUGHPUT, MATTER_STORAGE) + + @JvmField + val REPLICATOR = set(SPEED, ENERGY_STORAGE, ENERGY_CONSUMPTION, ENERGY_THROUGHPUT, MATTER_STORAGE, FAILSAFE) + + @JvmField + val BASIC_PROCESSING = set(SPEED, ENERGY_STORAGE, ENERGY_CONSUMPTION, ENERGY_THROUGHPUT, PROCESSING) + + fun set(vararg types: UpgradeType): Set { + var flags = 0 + for (v in types) flags = flags or v.flag + return sets[flags] + } + + fun set(): Set { + return sets[0] + } + + fun set( + type0: UpgradeType, + ): Set = sets[type0.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + ): Set = sets[type0.flag or type1.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + type3: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag or type3.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + type3: UpgradeType, + type4: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag or type3.flag or type4.flag] + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt index 2c31379ba..689056cb4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/API.kt @@ -4,27 +4,27 @@ import net.minecraft.nbt.CompoundTag import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.storage.IStorageComponent -import ru.dbotthepony.mc.otm.storage.IStorageStack import ru.dbotthepony.mc.otm.storage.IStorageTuple -import ru.dbotthepony.mc.otm.storage.ItemStackWrapper +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.storage.StorageStack import java.math.BigInteger import java.util.* -interface IItemMatteryDrive : IMatteryDrive { +interface IItemMatteryDrive : IMatteryDrive { /** * @param item * @return all items belonging to passed class */ - fun findItems(item: Item): Collection> + fun findItems(item: Item): Collection> /** * @param stack * @return [ItemStack] that match specified [stack] (item type, nbt tag) */ - fun findItems(stack: ItemStack): IStorageTuple? + fun findItems(stack: ItemStack): IStorageTuple? } -interface IMatteryDrive : IStorageComponent { +interface IMatteryDrive> : IStorageComponent { val uuid: UUID var isDirty: Boolean diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt index 1fbda2ec0..4d506a470 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt @@ -1,30 +1,37 @@ package ru.dbotthepony.mc.otm.capability.drive +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlin.jvm.JvmOverloads import java.util.UUID import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.BigInteger +import ru.dbotthepony.mc.otm.core.math.isPositive +import ru.dbotthepony.mc.otm.core.math.serializeNBT +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.storage.* import java.math.BigInteger import java.util.ArrayList import java.util.stream.Stream -abstract class AbstractMatteryDrive @JvmOverloads constructor( +abstract class AbstractMatteryDrive>( override var driveCapacity: BigInteger, override val uuid: UUID = UUID.randomUUID(), var maxDifferentStacks: Int = 0xFFFF ) : IMatteryDrive { - protected val tuples = HashMap>() - protected val tuplesByID: MutableMap> = HashMap() + protected val stack2tuples = Object2ObjectOpenCustomHashMap>(StorageStack.Companion) + protected val id2tuples = Object2ObjectOpenHashMap>() override var isDirty = false set(value) { - if (value != field && value && DrivePool.isLegalAccess()) { + if (value != field && value) { DrivePool.markDirty(uuid) } @@ -37,30 +44,43 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor override var storedCount: BigInteger = BigInteger.ZERO protected set - @Suppress("unchecked_cast") + protected val listeners = ObjectLinkedOpenHashSet>() + + override fun addListener(listener: IStorageEventConsumer): Boolean { + return listeners.add(listener) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return listeners.remove(listener) + } + + override fun equals(other: Any?): Boolean { + return other is AbstractMatteryDrive<*> && storageType == other.storageType && uuid == other.uuid + } + + override fun hashCode(): Int { + return (uuid.hashCode() * 31) xor storageType.hashCode() + } + override fun insertStack(stack: T, simulate: Boolean): T { val maxInsert = driveCapacity.minus(storedCount).coerceAtMost(stack.count) if (maxInsert <= BigInteger.ZERO) return stack - val key = stack.key() - val tuple = tuples[key] + val tuple = stack2tuples[stack] if (tuple != null) { if (!simulate) { - val oldCount = tuple.stack.count - tuple.stack.grow(maxInsert) + tuple.grow(maxInsert) storedCount += maxInsert for (listener in listeners) { - listener.changeStack(tuple.stack, tuple.id, oldCount) + listener.onStackChanged(tuple.stack, tuple.id) } isDirty = true } - val copy = stack.copy() as T - copy.shrink(maxInsert) - return copy + return stack.shrink(maxInsert) } if (storedDifferentStacks >= maxDifferentStacks) { @@ -71,28 +91,22 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor storedDifferentStacks++ storedCount = storedCount.plus(maxInsert) - val copy = stack.copy() as T - copy.count = maxInsert - - val state = StorageTuple(UUID.randomUUID(), copy) - tuples[key] = state - tuplesByID[state.id] = state + val state = IStorageTuple.M(UUID.randomUUID(), stack.copy(maxInsert)) + stack2tuples[stack] = state + id2tuples[state.id] = state for (listener in listeners) { - listener.addStack(state.stack, state.id, this) + listener.onStackAdded(state.stack, state.id, this) } isDirty = true } - val copy = stack.copy() as T - copy.shrink(maxInsert) - return copy + return stack.shrink(maxInsert) } - @Suppress("unchecked_cast") override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { - val get = tuplesByID[id] ?: return storageType.empty + val get = id2tuples[id] ?: return storageType.empty @Suppress("NAME_SHADOWING") var amount = amount @@ -105,28 +119,25 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor if (amount <= BigInteger.ZERO) return storageType.empty - val copy = get.stack.copy() as T - copy.count = amount + val copy = get.stack.copy(amount) if (!simulate) { if (amount == get.stack.count) { - val key = get.stack.key() - tuples.remove(key) ?: throw IllegalStateException("Can't find storage key for ${get.stack}") + stack2tuples.remove(get.stack) ?: throw IllegalStateException("Can't find storage key for ${get.stack}") storedDifferentStacks-- for (listener in listeners) { - listener.removeStack(get.stack, get.id) + listener.onStackRemoved(get.id) } } storedCount -= amount - val oldCount = get.stack.count - get.stack.shrink(amount) + get.shrink(amount) if (get.stack.count.isPositive) { for (listener in listeners) { - listener.changeStack(get.stack, get.id, oldCount) + listener.onStackChanged(get.stack, get.id) } } @@ -136,7 +147,7 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor return copy } - protected abstract fun serializeStack(item: IStorageTuple): CompoundTag? + protected abstract fun serializeStack(item: T): CompoundTag? protected abstract fun deserializeStack(tag: CompoundTag): T? override fun serializeNBT(): CompoundTag { @@ -148,8 +159,8 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor val list = ListTag() compound["items"] = list - for (stack in tuples.values) { - val serialized = serializeStack(stack) + for (tuple in stack2tuples.values) { + val serialized = serializeStack(tuple.stack) if (serialized != null) { list.add(serialized) @@ -161,57 +172,40 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor override fun deserializeNBT(nbt: CompoundTag) { for (listener in listeners) { - for (get in tuples.values) { - listener.removeStack(get.stack, get.id) + for (get in stack2tuples.values) { + listener.onStackRemoved(get.id) } } - tuples.clear() - tuplesByID.clear() + stack2tuples.clear() + id2tuples.clear() storedCount = BigInteger.ZERO storedDifferentStacks = 0 // nextID = 0L - nbt.ifHas("capacity") { - driveCapacity = BigInteger(it) - } - + driveCapacity = nbt.map("capacity", ::BigInteger) ?: driveCapacity maxDifferentStacks = nbt.getInt("max_different_stacks") for (entry in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) { if (entry is CompoundTag) { val stack = deserializeStack(entry) - if (stack != null) { + if (stack != null && stack.isNotEmpty) { storedCount += stack.count storedDifferentStacks++ - val tuple = StorageTuple(UUID.randomUUID(), stack) - tuples[tuple.stack.key()] = tuple - tuplesByID[tuple.id] = tuple + val tuple = IStorageTuple.M(UUID.randomUUID(), stack) + stack2tuples[tuple.stack] = tuple + id2tuples[tuple.id] = tuple } } } } override fun get(id: UUID): T { - return tuplesByID[id]?.stack ?: storageType.empty + return id2tuples[id]?.stack ?: storageType.empty } override val stacks: Stream> get() { - return ArrayList>(tuples.size).also { it.addAll(tuples.values) }.stream() + return ArrayList>(stack2tuples.size).also { it.addAll(stack2tuples.values) }.stream() } - - protected val listeners = ObjectArraySet>() - - override fun addListener(listener: IStorageEventConsumer): Boolean { - return listeners.add(listener) - } - - override fun removeListener(listener: IStorageEventConsumer): Boolean { - return listeners.remove(listener) - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt index 098f4d27d..9794484b9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt @@ -1,69 +1,96 @@ package ru.dbotthepony.mc.otm.capability.drive -import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import java.util.UUID import net.minecraft.ReportedException import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.CrashReport +import net.minecraft.Util +import net.minecraft.world.level.storage.LevelResource import java.util.concurrent.locks.LockSupport -import net.minecraftforge.event.TickEvent.ServerTickEvent -import net.minecraftforge.event.TickEvent import net.minecraftforge.event.server.ServerAboutToStartEvent import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.level.LevelEvent import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.SERVER_IS_LIVE +import ru.dbotthepony.mc.otm.isServerThread import java.io.File import java.lang.ref.WeakReference import java.util.ArrayList +import java.util.concurrent.ConcurrentLinkedQueue + +private class WeakDriveReference(drive: IMatteryDrive<*>) { + private var drive: IMatteryDrive<*>? = drive + private val weak = WeakReference(drive) + + val isValid: Boolean get() { + return drive != null || weak.get() != null + } + + fun drive(): IMatteryDrive<*>? { + return drive ?: weak.get() + } + + fun sync(): CompoundTag? { + val drive = drive() ?: return null + + if (!drive.isDirty) { + this.drive = null + return null + } + + val tag = drive.serializeNBT() + drive.isDirty = false + this.drive = null + + return tag + } + + fun access(): IMatteryDrive<*>? { + this.drive = weak.get() + return drive + } +} + +private data class BacklogLine(val uuid: UUID, val tag: CompoundTag) /** - * Let me answer everyone's questions about: - * - * Why? - * - * There are several reasons: - * 1. This data can get very large very quickly, even when playing singleplayer (and much quicker and bigger when hosting a dedicated server) - * 2. This data can not be stored inside ItemStack.ForgeCaps due to it's size - * 3. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with - * it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large" - * network kicks, often locking players out of server/singleplayer worlds - * 4. [net.minecraft.world.level.saveddata.SavedData] is for storing everything inside one dat file, which - * is performance tanking, because we have to write *entire* NBT on each save, not the data of Drives that are dirty - * 5. Mods which check items for being stack-able even with stack size of 1 gonna compare nbt tag, - * which will be performance tanking due to clause 1. + * Separate thread and separate files are way faster for enormous volumes of data (if mod is being run on public dedicated server) */ object DrivePool { + private val resource = LevelResource("otm_drives") private val LOGGER = LogManager.getLogger() private val pool = Object2ObjectOpenHashMap() - private var thread: Thread? = null - private var stopping = false - private var exception: ReportedException? = null - private const val DATA_PATH = "data/otm_drives" - @JvmStatic - operator fun > get( - id: UUID, - deserializer: (CompoundTag) -> T, - factory: () -> T - ): T { - if (!isLegalAccess()) + private val backlog = ConcurrentLinkedQueue() + private var knownBaseDirectory: File? = null + + private val baseDirectory: File? get() { + val server = NULLABLE_MINECRAFT_SERVER ?: return null + + val baseDirectory = server.storageSource.getLevelPath(resource).toFile() + + if (knownBaseDirectory != baseDirectory) { + baseDirectory.mkdirs() + knownBaseDirectory = baseDirectory + } + + return baseDirectory + } + + operator fun > get(id: UUID, deserializer: (CompoundTag) -> T, factory: () -> T): T { + if (!isServerThread()) throw IllegalAccessException("Can not access drive pool from outside of server thread.") if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") + throw IllegalStateException("Server is not running") - val get = pool[id] + val get = pool[id]?.access() if (get != null) { - val accessed = get.access() - - if (accessed != null) { - return accessed as T - } + return get as T } val file = File(baseDirectory, "$id.dat") @@ -85,113 +112,10 @@ object DrivePool { return factory().also { pool[id] = WeakDriveReference(it) } } - @JvmStatic - fun put(id: UUID, drive: IMatteryDrive<*>) { - if (!isLegalAccess()) - throw IllegalAccessException("Can not access drive pool from outside of server thread.") - - if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") - - pool[id] = WeakDriveReference(drive) - } - - @JvmStatic fun markDirty(id: UUID) { - if (!isLegalAccess()) - throw IllegalAccessException("Can not access drive pool from outside of server thread.") - - if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") - - pool[id]?.access() - } - - private var backlog = ArrayList() - private var knownBaseDirectory: File? = null - - private val baseDirectory: File? get() { - val server = NULLABLE_MINECRAFT_SERVER ?: return null - - val baseDirectory = File(server.storageSource.worldDir.toFile(), DATA_PATH) - - if (knownBaseDirectory != baseDirectory) { - baseDirectory.mkdirs() - knownBaseDirectory = baseDirectory + if (isServerThread()) { + pool[id]?.access() } - - return baseDirectory - } - - private var serverThread: Thread? = null - - private fun thread() { - while (true) { - LockSupport.park() - - if (stopping) { - return - } - - try { - // TODO: Syncing status with main thread, since server stop can occur during work. - sync() - } catch (error: ReportedException) { - exception = error - return - } - } - } - - fun onServerPostTick(event: ServerTickEvent) { - if (event.phase == TickEvent.Phase.END) { - if (exception != null) { - throw exception!! - } - } - } - - /** - * Returns whenever running on server thread. Calling [get], [put] or [markDirty] will throw an exception if this returns false. - * - * If you are making your own drive for your own storage stack, feed code outside of server thread (e.g. client code) dummy disk. - */ - @JvmStatic - fun isLegalAccess(): Boolean { - if (serverThread == null) - return false - - return Thread.currentThread() === serverThread - } - - fun serverStartEvent(event: ServerAboutToStartEvent) { - if (thread?.isAlive == true) { - LOGGER.error("FMLServerStartedEvent fired twice.") - LOGGER.error("Attempting to start another DrivePool I/O thread when already running one!") - return - } - - pool.clear() - serverThread = Thread.currentThread() - - stopping = false - thread = Thread(null, this::thread, "Overdrive That Matters DrivePool IO").also { it.start() } - } - - fun serverStopEvent(event: ServerStoppingEvent) { - val thread = thread - - if (thread != null && thread.isAlive) { - stopping = true - LockSupport.unpark(thread) - } - - if (exception == null) { - writeBacklog() - sync() - } - - pool.clear() } fun onWorldSave(event: LevelEvent.Save) { @@ -200,125 +124,51 @@ object DrivePool { private fun writeBacklog() { var needsSync = false - var removeKeys: ArrayList? = null + val removeKeys = ArrayList() for ((key, value) in pool) { - val tag = value.sync() + try { + val tag = value.sync() - if (tag != null) { - LOGGER.info("Serializing OTM Drive {}", key) - val index = backlog.indexOf(key) - - if (index != -1) { - backlog[index] = BacklogLine(key, tag, value.drive()!!) - } else { - backlog.add(BacklogLine(key, tag, value.drive()!!)) + if (tag != null) { + LOGGER.debug("Serializing OTM Drive {}", key) + backlog.add(BacklogLine(key, tag)) + needsSync = true + } else if (!value.isValid) { + removeKeys.add(key) } - - needsSync = true - } else if (!value.valid()) { - if (removeKeys == null) - removeKeys = ArrayList() - - removeKeys.add(key) + } catch (err: Throwable) { + LOGGER.error("Failed to serialize OTM Drive for persistent storage with ID $key", err) } } - if (removeKeys != null) { - for (key in removeKeys) { - pool.remove(key) - } + for (key in removeKeys) { + pool.remove(key) } if (needsSync) { - LockSupport.unpark(thread) + Util.backgroundExecutor().execute(::sync) } } private fun sync() { - val copy = backlog - backlog = ArrayList() + while (true) { + val next = backlog.poll() ?: return + val (uuid, tag) = next - for (entry in copy) { try { - LOGGER.info("Syncing OTM Drive {}", entry.file) + LOGGER.debug("Syncing OTM Drive {}", uuid) - val oldFile = File(baseDirectory, entry.file.toString() + ".dat_old") + val oldFile = File(baseDirectory, "$uuid.dat_old") + val newFile = File(baseDirectory, "$uuid.dat") - if (oldFile.exists()) { - check(oldFile.delete()) { "Unable to delete old dat file" } - } + if (oldFile.exists()) check(oldFile.delete()) { "Unable to delete old dat file" } + if (newFile.exists()) check(newFile.renameTo(oldFile)) { "Unable to move old dat file" } - val newFile = File(baseDirectory, entry.file.toString() + ".dat") - - if (newFile.exists()) { - check(newFile.renameTo(oldFile)) { "Unable to move old dat file" } - } - - NbtIo.writeCompressed(entry.tag, newFile) + NbtIo.writeCompressed(tag, newFile) } catch (error: Throwable) { - val report = CrashReport("Syncing OTM Drives state to disk", error) - val category = report.addCategory("Corrupt drive") - category.setDetail("UUID", entry.file.toString()) - category.setDetail("Stored item count", entry.drive.storedCount) - category.setDetail("Capacity", entry.drive.driveCapacity) - throw ReportedException(report) + LOGGER.error("Failed to sync OTM Drive to persistent storage with ID $uuid", error) } } } } - -private class WeakDriveReference(drive: IMatteryDrive<*>) { - private var drive: IMatteryDrive<*>? = drive - private val weak = WeakReference(drive) - - fun valid(): Boolean { - return drive != null || weak.get() != null - } - - fun drive(): IMatteryDrive<*>? { - return drive ?: weak.get() - } - - fun sync(): CompoundTag? { - var drive = drive - - if (drive == null) { - drive = weak.get() - - if (drive == null) - return null - } - - if (!drive.isDirty) { - this.drive = null - return null - } - - val tag = drive.serializeNBT() - drive.isDirty = false - this.drive = null - - return tag - } - - fun access(): IMatteryDrive<*>? { - this.drive = weak.get() - return drive - } -} - -@Suppress("EqualsOrHashCode") -private data class BacklogLine(val file: UUID, val tag: CompoundTag, val drive: IMatteryDrive<*>) { - override fun equals(other: Any?): Boolean { - if (other is BacklogLine) { - return other.file == file - } - - if (other is UUID) { - return other == file - } - - return false - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt index 6cd728abb..a5fb59159 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt @@ -1,70 +1,51 @@ package ru.dbotthepony.mc.otm.capability.drive import net.minecraft.nbt.CompoundTag -import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.BigInteger -import ru.dbotthepony.mc.otm.core.serializeNBT -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.math.BigInteger +import ru.dbotthepony.mc.otm.core.math.serializeNBT +import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.storage.IStorageTuple -import ru.dbotthepony.mc.otm.storage.ItemStackWrapper -import ru.dbotthepony.mc.otm.storage.StorageStackType -import ru.dbotthepony.mc.otm.storage.key +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.storage.StorageStack import java.math.BigInteger import java.util.* -import kotlin.collections.ArrayList -class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDrive { - constructor(capacity: BigInteger, max_different_stacks: Int) : super(capacity, maxDifferentStacks = max_different_stacks) - constructor(capacity: BigInteger, uuid: UUID, max_different_stacks: Int) : super(capacity, uuid, max_different_stacks) +class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDrive { + constructor(capacity: BigInteger, maxDifferentStacks: Int) : super(capacity, maxDifferentStacks = maxDifferentStacks) + constructor(capacity: BigInteger, uuid: UUID, maxDifferentStacks: Int) : super(capacity, uuid, maxDifferentStacks) constructor(capacity: BigInteger, uuid: UUID) : super(capacity, uuid) constructor(capacity: BigInteger) : super(capacity) - override val storageType: StorageStackType = OverdriveThatMatters.INSTANCE.ITEM_STORAGE() + override val storageType: StorageStack.Type = StorageStack.ITEMS fun insertStack(item: ItemStack, simulate: Boolean): ItemStack { - return insertStack(ItemStackWrapper(item), simulate).stack + return insertStack(ItemStorageStack(item), simulate).toItemStack() } - override fun serializeStack(item: IStorageTuple): CompoundTag? { + override fun serializeStack(item: ItemStorageStack): CompoundTag { val tag = CompoundTag() - val location = item.stack.registryName.toString() - - tag["item"] = location - tag["count"] = item.stack.count.serializeNBT() - - val itag = item.stack.item.tag - - if (itag != null) { - tag["data"] = itag - } - + tag["item"] = item.toItemStack(1).serializeNBT() + tag["count"] = item.count.serializeNBT() return tag } - override fun deserializeStack(tag: CompoundTag): ItemStackWrapper? { - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(tag.getString("item"))) - - if (item != null && item !== Items.AIR) { + override fun deserializeStack(tag: CompoundTag): ItemStorageStack? { + if ("item" in tag && "count" in tag) { + val item = tag["item"] as? CompoundTag ?: return null val count = BigInteger(tag["count"]) - val itemstack = ItemStack(item, 1) - itemstack.tag = tag["data"] as? CompoundTag - return ItemStackWrapper(itemstack, copy = false).also { it.count = count } + return ItemStorageStack.unsafe(ItemStack.of(item), count) } return null } - override fun findItems(item: Item): List> { - val list = ArrayList>() + override fun findItems(item: Item): List> { + val list = ArrayList>() - for ((key, value) in tuples.entries) { - if (key.item.item === item) { + for ((key, value) in stack2tuples.entries) { + if (key.item === item) { list.add(value) } } @@ -72,8 +53,8 @@ class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDri return list } - override fun findItems(stack: ItemStack): IStorageTuple? { - return tuples[ItemStackWrapper(stack).key()] + override fun findItems(stack: ItemStack): IStorageTuple? { + return stack2tuples[ItemStorageStack.unsafe(stack)] } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt new file mode 100644 index 000000000..38281241a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt @@ -0,0 +1,215 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraft.nbt.CompoundTag +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.extractEnergy +import ru.dbotthepony.mc.otm.capability.receiveEnergy +import ru.dbotthepony.mc.otm.container.IContainer +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.getItemStack +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import ru.dbotthepony.mc.otm.registry.StatNames +import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger + +class BatteryBackedEnergyStorage( + private val ply: Player, + synchronizer: FieldSynchronizer, + initialCharge: Decimal, + maxCharge: Decimal, + val isAndroid: Boolean, + val onChange: Runnable? = null +) : IMatteryEnergyStorage, INBTSerializable, IContainer { + override val energyFlow: FlowDirection + get() = FlowDirection.INPUT + + private var battery by synchronizer.decimal(initialCharge) + private var maxBattery by synchronizer.decimal(maxCharge) + + var item by synchronizer.item(setter = setter@{ value, access, _ -> + access.write(value) + + if (ply is ServerPlayer && isAndroid) { + AndroidBatteryTrigger.trigger(ply, value) + } else if (ply is ServerPlayer) { + ExopackBatterySlotTrigger.trigger(ply, value) + } + + onChange?.run() + }) + + override fun getItem(slot: Int): ItemStack { + require(slot == 0) { "Invalid slot $slot" } + return item + } + + override fun removeItem(slot: Int, count: Int): ItemStack { + require(slot == 0) { "Invalid slot $slot" } + return item.split(count) + } + + override fun setItem(slot: Int, stack: ItemStack) { + require(slot == 0) { "Invalid slot $slot" } + item = stack + } + + override fun clearContent() { + item = ItemStack.EMPTY + } + + override fun getContainerSize(): Int { + return 1 + } + + override fun isEmpty(): Boolean { + return item.isEmpty + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + TODO("Not yet implemented") + } + + override fun setChanged() { + + } + + override fun stillValid(p_18946_: Player): Boolean { + return true + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["battery"] = battery.serializeNBT() + it["maxBattery"] = maxBattery.serializeNBT() + it["item"] = item.serializeNBT() + } + } + + override fun deserializeNBT(tag: CompoundTag?) { + tag ?: return + battery = tag.getDecimal("battery") + maxBattery = tag.getDecimal("maxBattery") + item = tag.getItemStack("item") + } + + fun tick() { + if (!item.isEmpty && battery < maxBattery) { + item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + battery += it.extractEnergy(maxBattery - battery, false) + } + } + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + @Suppress("name_shadowing") + var howMuch = howMuch + var drained = Decimal.ZERO + + if (!item.isEmpty) { + item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + val extracted = it.extractEnergy(howMuch, simulate) + drained += extracted + howMuch -= extracted + + if (howMuch.isZero) { + if (!simulate && ply is ServerPlayer && isAndroid) { + ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10) + } + + onChange?.run() + return drained + } + } + } + + val new = (battery - howMuch).moreThanZero() + drained += battery - new + + if (!simulate) { + battery = new + + if (ply is ServerPlayer && isAndroid) { + ply.awardStat(StatNames.POWER_CONSUMED, drained.toInt() * 10) + } + + onChange?.run() + } + + return drained + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + @Suppress("name_shadowing") + var howMuch = howMuch + var received = Decimal.ZERO + + if (!item.isEmpty) { + item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + val extracted = it.receiveEnergy(howMuch, simulate) + received += extracted + howMuch -= extracted + } + + if (howMuch.isZero) { + onChange?.run() + return received + } + } + + val new = (battery + howMuch).coerceAtMost(maxBattery) + received += new - battery + + if (!simulate) { + battery = new + onChange?.run() + } + + return received + } + + override var batteryLevel: Decimal + get() { + if (!item.isEmpty) { + item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + if (it is IMatteryEnergyStorage) { + return battery + it.batteryLevel + } else { + return battery + it.energyStored + } + } + } + + return battery + } + set(value) { + battery = value + onChange?.run() + } + + override var maxBatteryLevel: Decimal + get() { + if (item != ItemStack.EMPTY) { + item.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + if (it is IMatteryEnergyStorage) { + return maxBattery + it.maxBatteryLevel + } else { + return maxBattery + it.maxEnergyStored + } + } + } + + return maxBattery + } + set(value) { + maxBattery = value + onChange?.run() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt new file mode 100644 index 000000000..0c9dd586a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt @@ -0,0 +1,234 @@ + +@file:Suppress("unused") + +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraftforge.common.ForgeConfigSpec +import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.common.util.LazyOptional +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues +import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal +import ru.dbotthepony.mc.otm.core.nbt.mapPresent +import ru.dbotthepony.mc.otm.core.nbt.set + +sealed class BlockEnergyStorageImpl( + protected val listener: () -> Unit, + final override val energyFlow: FlowDirection, + private val maxBatteryLevelProvider: () -> Decimal, + private val maxInputProvider: () -> Decimal?, + private val maxOutputProvider: () -> Decimal?, +) : IMatteryEnergyStorage, INBTSerializable, IEnergyStorageImpl { + private var maxInputStorage: Decimal? = null + private var maxOutputStorage: Decimal? = null + private var maxBatteryLevelStorage: Decimal? = null + + final override var maxInput: Decimal? + get() = maxInputStorage ?: maxInputProvider.invoke() + protected set(value) { + if (value != maxInputStorage) { + maxInputStorage = value + listener.invoke() + } + } + + final override var maxOutput: Decimal? + get() = maxOutputStorage ?: maxOutputProvider.invoke() + protected set(value) { + if (value != maxOutputStorage) { + maxOutputStorage = value + listener.invoke() + } + } + + override var maxBatteryLevel: Decimal + get() = maxBatteryLevelStorage ?: maxBatteryLevelProvider.invoke() + protected set(value) { + if (value != maxBatteryLevelStorage) { + maxBatteryLevelStorage = value + listener.invoke() + } + } + + override var batteryLevel = Decimal.ZERO + set(value) { + if (value != field) { + field = value + listener.invoke() + } + } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) + return Decimal.ZERO + + if (!batteryLevel.isPositive) + return Decimal.ZERO + + val newLevel = (batteryLevel - howMuch.coerceAtMost(maxOutput ?: Decimal.POSITIVE_INFINITY)).moreThanZero() + val diff = (batteryLevel - newLevel).coerceIn(Decimal.ZERO, howMuch) + + if (!simulate && batteryLevel != newLevel) { + batteryLevel = newLevel + } + + return diff + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) + return Decimal.ZERO + + if (batteryLevel >= maxBatteryLevel && !maxBatteryLevel.isInfinite) + return Decimal.ZERO + + val newLevel = (batteryLevel + howMuch.coerceAtMost(maxInput ?: Decimal.POSITIVE_INFINITY)).coerceAtMost(maxBatteryLevel) + val diff = (newLevel - batteryLevel).coerceIn(Decimal.ZERO, howMuch) + + if (!simulate && batteryLevel != newLevel) { + batteryLevel = newLevel + } + + return diff + } + + override fun serializeNBT(): CompoundTag { + val tag = CompoundTag() + tag[ENERGY_STORED_KEY] = batteryLevel.serializeNBT() + + maxBatteryLevelStorage?.let { tag[ENERGY_STORED_MAX_KEY] = it.serializeNBT() } + maxInputStorage?.let { tag[MAX_INPUT_KEY] = it.serializeNBT() } + maxOutputStorage?.let { tag[MAX_OUTPUT_KEY] = it.serializeNBT() } + + return tag + } + + override fun deserializeNBT(nbt: CompoundTag?) { + if (nbt == null) return + batteryLevel = nbt.mapPresent(ENERGY_STORED_KEY, Decimal.Companion::deserializeNBT) ?: Decimal.ZERO + maxBatteryLevelStorage = nbt.mapPresent(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT) + maxInputStorage = nbt.mapPresent(MAX_INPUT_KEY, Decimal.Companion::deserializeNBT) + maxOutputStorage = nbt.mapPresent(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT) + } + + var resolver: LazyOptional = LazyOptional.of { this } + private set + + fun invalidate() { + resolver.invalidate() + } + + fun revive() { + resolver = LazyOptional.of { this } + } + + companion object { + val DEFAULT_MAX_IO = Decimal(400) + val DEFAULT_MAX_CAPACITY = Decimal(40_000) + + const val ENERGY_STORED_KEY = "energy_stored" + const val ENERGY_STORED_MAX_KEY = "energy_stored_max" + const val MAX_INPUT_KEY = "max_input" + const val MAX_OUTPUT_KEY = "max_output" + } +} + +open class WorkerEnergyStorage( + listener: () -> Unit, + maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, + maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, + maxOutput: () -> Decimal? = maxInput +) : BlockEnergyStorageImpl(listener, FlowDirection.INPUT, maxBatteryLevel, maxInput, maxOutput) { + constructor( + listener: () -> Unit, + values: EnergyBalanceValues + ) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput) + + constructor( + listener: () -> Unit, + values: VerboseEnergyBalanceValues + ) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract) + + companion object { + fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { + val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return + val cap = WorkerEnergyStorage({}, { DEFAULT_MAX_CAPACITY }) + cap.deserializeNBT(tag) + batteryLevel(cap, tooltips, false) + } + + fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { + return appendHoverText(itemStack, tooltips) + } + } +} + +open class GeneratorEnergyStorage( + listener: () -> Unit, + maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, + maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, + maxOutput: () -> Decimal? = maxInput +) : BlockEnergyStorageImpl(listener, FlowDirection.OUTPUT, maxBatteryLevel, maxInput, maxOutput) { + constructor( + listener: () -> Unit, + values: EnergyBalanceValues + ) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput) + + constructor( + listener: () -> Unit, + values: VerboseEnergyBalanceValues + ) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract) + + companion object { + fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { + val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return + val cap = GeneratorEnergyStorage({}, { DEFAULT_MAX_CAPACITY }) + cap.deserializeNBT(tag) + batteryLevel(cap, tooltips) + } + + fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { + return appendHoverText(itemStack, tooltips) + } + } +} + +open class CapacitorEnergyStorage( + listener: () -> Unit, + maxBatteryLevel: () -> Decimal = { DEFAULT_MAX_CAPACITY }, + maxInput: () -> Decimal? = { DEFAULT_MAX_IO }, + maxOutput: () -> Decimal? = maxInput +) : BlockEnergyStorageImpl(listener, FlowDirection.BI_DIRECTIONAL, maxBatteryLevel, maxInput, maxOutput) { + constructor( + listener: () -> Unit, + values: EnergyBalanceValues + ) : this(listener, values::energyCapacity, values::energyThroughput, values::energyThroughput) + + constructor( + listener: () -> Unit, + values: VerboseEnergyBalanceValues + ) : this(listener, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract) + + companion object { + fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { + val tag = (itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag)?.get(MatteryBlockEntity.ENERGY_KEY) as? CompoundTag ?: return + val cap = CapacitorEnergyStorage({}, { DEFAULT_MAX_CAPACITY }) + cap.deserializeNBT(tag) + batteryLevel(cap, tooltips) + } + + fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { + return appendHoverText(itemStack, tooltips) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt new file mode 100644 index 000000000..9903b26db --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IEnergyStorageImpl.kt @@ -0,0 +1,111 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraftforge.energy.IEnergyStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.util.formatPower + +sealed interface IEnergyStorageImpl { + val maxInput: Decimal? + val maxOutput: Decimal? +} + +internal fun batteryLevel(it: IEnergyStorage, tooltips: MutableList) { + tooltips.add( + TranslatableComponent( + "otm.item.power.storage", + it.energyStored.formatPower(formatAsReadable = ShiftPressedCond), + it.maxEnergyStored.formatPower(formatAsReadable = ShiftPressedCond) + ).withStyle(ChatFormatting.GRAY)) +} + +internal fun batteryLevel(it: IMatteryEnergyStorage, tooltips: MutableList, displayMaxLevel: Boolean = true) { + if (displayMaxLevel) + tooltips.add( + TranslatableComponent( + "otm.item.power.storage", + it.batteryLevel.formatPower(formatAsReadable = ShiftPressedCond), + it.maxBatteryLevel.formatPower(formatAsReadable = ShiftPressedCond) + ).withStyle(ChatFormatting.GRAY)) + else + tooltips.add( + TranslatableComponent( + "otm.item.power.storage0", + it.batteryLevel.formatPower(formatAsReadable = ShiftPressedCond) + ).withStyle(ChatFormatting.GRAY)) + + if (it is IEnergyStorageImpl) { + when (it.energyFlow) { + FlowDirection.INPUT -> { + if (it.maxInput != null) { + tooltips.add( + TranslatableComponent("otm.item.power.throughput_mono", it.maxInput!!.formatPower(formatAsReadable = ShiftPressedCond)).withStyle( + ChatFormatting.GRAY + )) + } else { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput_mono", + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY) + ).withStyle(ChatFormatting.GRAY)) + } + } + + FlowDirection.OUTPUT -> { + if (it.maxOutput != null) { + tooltips.add( + TranslatableComponent("otm.item.power.throughput_mono", it.maxOutput!!.formatPower(formatAsReadable = ShiftPressedCond)).withStyle( + ChatFormatting.GRAY + )) + } else { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput_mono", + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY) + ).withStyle(ChatFormatting.GRAY)) + } + } + + FlowDirection.BI_DIRECTIONAL -> { + val maxInput = it.maxInput + val maxOutput = it.maxOutput + + if (maxInput != null && maxOutput != null) { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput", + maxInput.formatPower(formatAsReadable = ShiftPressedCond), + maxOutput.formatPower(formatAsReadable = ShiftPressedCond) + ).withStyle(ChatFormatting.GRAY)) + } else if (maxInput != null) { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput", + maxInput.formatPower(formatAsReadable = ShiftPressedCond), + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY) + ).withStyle(ChatFormatting.GRAY)) + } else if (maxOutput != null) { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput", + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), + maxOutput.formatPower(formatAsReadable = ShiftPressedCond), + ).withStyle(ChatFormatting.GRAY)) + } else { + tooltips.add( + TranslatableComponent( + "otm.item.power.throughput", + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), + TranslatableComponent("otm.item.power.infinity").withStyle(ChatFormatting.GRAY), + ).withStyle(ChatFormatting.GRAY)) + } + } + + FlowDirection.NONE -> {} + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt new file mode 100644 index 000000000..ffd4e221d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/IMatteryEnergyStorage.kt @@ -0,0 +1,376 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraftforge.energy.IEnergyStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import java.math.BigInteger +import kotlin.math.roundToInt + +/** + * Energy interface in Overdrive That Matters, which also implements backward compatible methods to implement [IEnergyStorage] + * + * Details of this energy system are as follows: + * * [extractEnergy] and [receiveEnergy] are *unchecked*, which means, they will generally work regardless of energy storage type ([energyFlow]), be it tool, battery or generator. + * What this means is that, despite making no sense to use tool as a battery in machine, [extractEnergy] will work regardless. + * To avoid such cases, [extractEnergyChecked] and [receiveEnergyChecked] are provided + * * Instead of [canReceive] and [canExtract] of [IEnergyStorage], this interface employs [energyFlow] enum property, which is cleaner and easier to use, + * see [FlowDirection] for details + * * This interface allows to set energy stored *directly*. With great power comes great responsibility though, since, sometimes it is technically + * cumbersome or straight up impossible to set stored energy value, therefore, setter of [batteryLevel] is free to throw [UnsupportedOperationException]. + * To avoid try/catch block, check [canSetBatteryLevel] property first + * * Due to limitations of setting [batteryLevel] directly, two methods are also provided: [fillBattery] and [drainBattery]. If you ever + * need to only fill or drain this energy storage, call these two methods and do not use [batteryLevel] directly. + * * Lastly, there is [missingPower]. It returns whatever it has in name. What it makes better than writing your own "[maxBatteryLevel] - [batteryLevel]" logic is that + * implementations can use different and more precise logic to determine actually missing power. **Please use [missingPower] instead of writing your own logic to determine + * whenever storage is missing energy, it will make players not experience silly bugs and make you avoid headaches**. + */ +interface IMatteryEnergyStorage : IEnergyStorage { + /** + * Extracts up to [howMuch] energy from this storage + * + * In general, this method *does not* obey [energyFlow] and will allow to extract energy + * even if [energyFlow] and [canExtract] say otherwise, unlike [IEnergyStorage.extractEnergy], which, in general, + * obeys return value of [IEnergyStorage.canExtract]. + * + * Therefore, if you want/need to obey [energyFlow], use [extractEnergyChecked] + * + * General contracts: + * * Negative values are not allowed, failing to comply with this contract will result in undefined behavior in worst case or nothing in best case + * * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code. + * Upstream code SHOULD NOT check for this contract. + * + * @return energy extracted + */ + fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal + + /** + * Extracts up to [howMuch] energy from this storage, while obeying [energyFlow]. See [extractEnergy]. + * + * Interface implementations generally do not need to override this. + * + * @return energy extracted + */ + fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (howMuch.isNegative || !energyFlow.output) + return Decimal.ZERO + + return extractEnergy(howMuch, simulate) + } + + /** + * Inserts up to [howMuch] energy into this storage + * + * In general, this method *does not* obey [energyFlow] and will allow to insert energy + * even if [energyFlow] and [canReceive] say otherwise, unlike [IEnergyStorage.receiveEnergy], which, in general, + * obeys return value of [IEnergyStorage.canReceive]. + * + * Therefore, if you want/need to obey [energyFlow], use [receiveEnergyChecked] + * + * General contracts: + * * Negative values are not allowed, failing to comply with this contract will result in undefined behavior in worst case or nothing in best case + * * Returned value CAN NOT be bigger than requested [howMuch], implementations failing to obey this contract will cause undefined behavior in upstream code. + * Upstream code SHOULD NOT check for this contract. + * + * @return energy extracted + */ + fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal + + /** + * Inserts up to [howMuch] energy into this storage, while obeying [energyFlow]. See [receiveEnergy]. + * + * Interface implementations generally do not need to override this. + * + * @return energy extracted + */ + fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (howMuch.isNegative || !energyFlow.input) + return Decimal.ZERO + + return receiveEnergy(howMuch, simulate) + } + + /** + * If this is false, then [batteryLevel] will throw [UnsupportedOperationException] when trying to set it + */ + val canSetBatteryLevel: Boolean get() = true + + /** + * How much energy (estimated) is stored in this energy storage. Why estimated? Because some objects can be bottomless. + * + * Implementations are free to throw [UnsupportedOperationException] if setting battery level is not supported + * due to technical complications (in this case, [canSetBatteryLevel] MUST be false) + * + * @throws [UnsupportedOperationException] + */ + var batteryLevel: Decimal + + /** + * How much energy (estimated) can this energy storage hold. Why estimated? Because some objects can be bottomless. + */ + val maxBatteryLevel: Decimal + + /** + * How much energy (estimated) is missing in this energy storage. Why estimated? Because some objects can be bottomless. + * + * Use this to determine whenever you need an estimate on how much energy this storage can accept, **and do not implement [maxBatteryLevel] - [batteryLevel] logic by yourself**. + */ + val missingPower: Decimal + get() = (maxBatteryLevel - batteryLevel).moreThanZero() + + /** + * Empties power of this energy storage + * + * @see batteryLevel + * @return whenever operation was successful + */ + fun drainBattery(): Boolean { + try { + batteryLevel = Decimal.ZERO + } catch(err: UnsupportedOperationException) { + return false + } + + return true + } + + /** + * Fully fills power in this energy storage + * + * @see batteryLevel + * @return whenever operation was successful + */ + fun fillBattery(): Boolean { + try { + batteryLevel = maxBatteryLevel + } catch(err: UnsupportedOperationException) { + return false + } + + return true + } + + /** + * Flow direction of energy possible for this energy storage, see [FlowDirection] + */ + val energyFlow: FlowDirection + + // -------- Forge Energy stuff + + override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int { + // follow contract of IEnergyStorage + if (!energyFlow.input) + return 0 + + val received = receiveEnergyChecked(Decimal(maxReceive), true).toInt() + + // Receiving only a fraction + if (received == 0) + return 0 + + return receiveEnergyChecked(Decimal(received), simulate).toInt() + } + + override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { + // follow contract of IEnergyStorage + if (!energyFlow.output) + return 0 + + val extracted = extractEnergyChecked(Decimal(maxReceive), true).toInt() + + // Extracting only a fraction + if (extracted == 0) + return 0 + + return extractEnergyChecked(Decimal(extracted), simulate).toInt() + } + + override fun getEnergyStored(): Int { + return batteryLevel.toInt() + } + + override fun getMaxEnergyStored(): Int { + return maxBatteryLevel.toInt() + } + + override fun canExtract(): Boolean { + return energyFlow.output + } + + override fun canReceive(): Boolean { + return energyFlow.input + } + + /** + * Companion object of [IMatteryEnergyStorage] represents empty energy storage + */ + companion object : IMatteryEnergyStorage { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return Decimal.ZERO + } + + override var batteryLevel: Decimal + get() = Decimal.ZERO + set(value) {} + override val maxBatteryLevel: Decimal + get() = Decimal.ZERO + override val energyFlow: FlowDirection + get() = FlowDirection.NONE + + override val canSetBatteryLevel: Boolean + get() = false + + override val missingPower: Decimal + get() = Decimal.ZERO + + override fun drainBattery(): Boolean { + return false + } + + override fun fillBattery(): Boolean { + return false + } + + override fun receiveEnergy(maxReceive: Int, simulate: Boolean): Int { + return 0 + } + + override fun extractEnergy(maxReceive: Int, simulate: Boolean): Int { + return 0 + } + } +} + +/** + * Performs *unchecked* extraction of energy + * + * @return whenever can (or we did) extract exactly [howMuch] energy from this storage + */ +fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, simulate: Boolean): Boolean { + if (extractEnergy(howMuch, true) == howMuch) { + if (!simulate) { + return extractEnergy(howMuch, false) == howMuch + } + + return true + } + + return false +} + +/** + * Performs *unchecked* insertion of energy + * + * @return whenever can (or we did) receive exactly [howMuch] energy into this storage + */ +fun IMatteryEnergyStorage.receiveEnergyExact(howMuch: Decimal, simulate: Boolean): Boolean { + if (receiveEnergy(howMuch, true) == howMuch) { + if (!simulate) { + return receiveEnergy(howMuch, false) == howMuch + } + + return true + } + + return false +} + +/** + * Performs *unchecked* extraction of energy + * + * @return how much times can (or we did) extract exactly [howMuch] * [times] energy from this storage + */ +fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, times: Int, simulate: Boolean): Int { + if (times == 0) + return 0 + + require(times >= 0) { "times $times >= 0" } + require(howMuch >= Decimal.ZERO) { "howMuch $howMuch >= 0" } + + @Suppress("name_shadowing") + val times = (extractEnergy(howMuch * times, true) / times).toInt() + + if (simulate) + return times + + return (extractEnergy(howMuch * times, false) / times).toInt() +} + +/** + * Performs *unchecked* extraction of energy + * + * @return how much times can (or we did) extract exactly [howMuch] * [times] energy from this storage + */ +fun IMatteryEnergyStorage.extractEnergyExact(howMuch: Decimal, multiplier: BigInteger, simulate: Boolean): BigInteger { + return (extractEnergy(howMuch * multiplier, simulate) / howMuch).whole +} + +/** + * @see [IMatteryEnergyStorage.extractEnergy] + */ +fun IMatteryEnergyStorage.extractEnergy(howMuch: Long, simulate: Boolean): Decimal { + return extractEnergy(Decimal(howMuch), simulate) +} + +/** + * @see [IMatteryEnergyStorage.receiveEnergy] + */ +fun IMatteryEnergyStorage.receiveEnergy(howMuch: Long, simulate: Boolean): Decimal { + return receiveEnergy(Decimal(howMuch), simulate) +} + +/** + * Performs *unchecked* energy transfer from this storage to [other] + * + * @see IMatteryEnergyStorage.extractEnergy + * @see IMatteryEnergyStorage.receiveEnergy + */ +fun IMatteryEnergyStorage.transfer(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal { + if (!amount.isPositive) + return Decimal.ZERO + + val extracted = extractEnergy(amount, true) + val received = other.receiveEnergy(extracted, simulate) + + if (!simulate) + return extractEnergy(received, false) + + return received +} + +/** + * Performs checked energy transfer from this storage to [other] + * + * @see IMatteryEnergyStorage.extractEnergy + * @see IMatteryEnergyStorage.receiveEnergy + */ +fun IMatteryEnergyStorage.transferChecked(other: IMatteryEnergyStorage, amount: Decimal, simulate: Boolean): Decimal { + if (!amount.isPositive) + return Decimal.ZERO + + val extracted = extractEnergy(amount, true) + val received = other.receiveEnergyChecked(extracted, simulate) + + if (!simulate) + return extractEnergy(received, false) + + return received +} + +fun IMatteryEnergyStorage.getBarWidth(): Int { + if (!maxBatteryLevel.isPositive) + return 0 + + return ((batteryLevel / maxBatteryLevel).toFloat().coerceIn(0f, 1f) * 13f).roundToInt() +} + +fun IMatteryEnergyStorage.getBarColor(): Int { + if (!maxBatteryLevel.isPositive) + return 0 + + return RGBAColor.LOW_POWER.linearInterpolation((batteryLevel / maxBatteryLevel).toFloat(), RGBAColor.FULL_POWER).toRGB() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt new file mode 100644 index 000000000..86b0916eb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ItemEnergyStorageImpl.kt @@ -0,0 +1,134 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import net.minecraft.core.Direction +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import net.minecraftforge.common.capabilities.Capability +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.common.util.LazyOptional +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull + +abstract class ItemEnergyStorageImpl(val itemStack: ItemStack) : IMatteryEnergyStorage, ICapabilityProvider, IEnergyStorageImpl { + private val resolver = LazyOptional.of { this } + + override fun getCapability(cap: Capability, side: Direction?): LazyOptional { + if (cap === ForgeCapabilities.ENERGY || cap === MatteryCapability.ENERGY) { + return resolver.cast() + } + + return LazyOptional.empty() + } + + abstract val initialBatteryLevel: Decimal + + override var batteryLevel: Decimal + get() = itemStack.tag?.map(ENERGY_KEY, Decimal.Companion::deserializeNBT) ?: initialBatteryLevel + set(value) { itemStack.tagNotNull[ENERGY_KEY] = value.serializeNBT() } + + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive || itemStack.count != 1) + return Decimal.ZERO + + val batteryLevel = batteryLevel + + if (!batteryLevel.isPositive) + return Decimal.ZERO + + val newLevel = (batteryLevel - howMuch.coerceAtMost(maxOutput ?: Decimal.POSITIVE_INFINITY)).moreThanZero() + val diff = (batteryLevel - newLevel).coerceIn(Decimal.ZERO, howMuch) + + if (!simulate && batteryLevel != newLevel) { + this.batteryLevel = newLevel + } + + return diff + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive || itemStack.count != 1) + return Decimal.ZERO + + val batteryLevel = batteryLevel + if (batteryLevel >= maxBatteryLevel && !maxBatteryLevel.isInfinite) + return Decimal.ZERO + + val newLevel = (batteryLevel + howMuch.coerceAtMost(maxInput ?: Decimal.POSITIVE_INFINITY)).coerceAtMost(maxBatteryLevel) + val diff = (newLevel - batteryLevel).coerceIn(Decimal.ZERO, howMuch) + + if (!simulate && batteryLevel != newLevel) { + this.batteryLevel = newLevel + } + + return diff + } + + companion object { + const val ENERGY_KEY = "energy" + + fun appendHoverText(itemStack: ItemStack, level: Level?, tooltips: MutableList, flag: TooltipFlag) { + appendHoverText(itemStack, tooltips) + } + + fun appendHoverText(itemStack: ItemStack, tooltips: MutableList) { + val energy = itemStack.energy ?: return + + if (energy is IMatteryEnergyStorage) { + batteryLevel(energy, tooltips) + } else { + batteryLevel(energy, tooltips) + } + } + } +} + +abstract class SimpleEnergyItem( + override val energyFlow: FlowDirection, + stack: ItemStack, + maxBatteryLevel: Decimal, + maxInput: Decimal?, + maxOutput: Decimal?, + override val initialBatteryLevel: Decimal +) : ItemEnergyStorageImpl(stack) { + override var maxInput: Decimal? = maxInput + protected set + + override var maxOutput: Decimal? = maxOutput + protected set + + override var maxBatteryLevel: Decimal = maxBatteryLevel + protected set +} + +open class EnergyConsumerItem( + stack: ItemStack, + maxBatteryLevel: Decimal, + maxInput: Decimal? = null, + maxOutput: Decimal? = maxInput, + initialBatteryLevel: Decimal = Decimal.ZERO +) : SimpleEnergyItem(FlowDirection.INPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) + +open class EnergyProducerItem( + stack: ItemStack, + maxBatteryLevel: Decimal, + maxInput: Decimal? = null, + maxOutput: Decimal? = maxInput, + initialBatteryLevel: Decimal = Decimal.ZERO +) : SimpleEnergyItem(FlowDirection.OUTPUT, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) + +open class EnergyCapacitorItem( + stack: ItemStack, + maxBatteryLevel: Decimal, + maxInput: Decimal? = null, + maxOutput: Decimal? = maxInput, + initialBatteryLevel: Decimal = Decimal.ZERO +) : SimpleEnergyItem(FlowDirection.BI_DIRECTIONAL, stack, maxBatteryLevel, maxInput, maxOutput, initialBatteryLevel) + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt new file mode 100644 index 000000000..3cb14c888 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProfiledEnergyStorage.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal + +class ProfiledEnergyStorage(parent: E) : AbstractProfiledStorage(parent), IMatteryEnergyStorage { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractEnergy(howMuch, simulate), simulate) + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveEnergy(howMuch, simulate), simulate) + } + + override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractEnergyChecked(howMuch, simulate), simulate) + } + + override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveEnergyChecked(howMuch, simulate), simulate) + } + + override val canSetBatteryLevel: Boolean get() = parent.canSetBatteryLevel + override val missingPower: Decimal get() = parent.missingPower + + override fun drainBattery(): Boolean { + return parent.drainBattery() + } + + override fun fillBattery(): Boolean { + return parent.fillBattery() + } + + override var batteryLevel: Decimal + get() = parent.batteryLevel + set(value) { parent.batteryLevel = value } + + override val maxBatteryLevel: Decimal get() = parent.maxBatteryLevel + override val energyFlow: FlowDirection get() = parent.energyFlow +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt new file mode 100644 index 000000000..4aff62fc1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/ProxiedEnergyStorage.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.mc.otm.capability.energy + +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal + +class ProxiedEnergyStorage(var parent: T? = null) : IMatteryEnergyStorage { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return parent?.extractEnergy(howMuch, simulate) ?: Decimal.ZERO + } + + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + return parent?.receiveEnergy(howMuch, simulate) ?: Decimal.ZERO + } + + override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return parent?.extractEnergyChecked(howMuch, simulate) ?: Decimal.ZERO + } + + override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return parent?.receiveEnergyChecked(howMuch, simulate) ?: Decimal.ZERO + } + + override var batteryLevel: Decimal + get() = parent?.batteryLevel ?: Decimal.ZERO + set(value) { parent?.batteryLevel = value } + override val maxBatteryLevel: Decimal + get() = parent?.maxBatteryLevel ?: Decimal.ZERO + override val energyFlow: FlowDirection + get() = parent?.energyFlow ?: FlowDirection.NONE + + override val canSetBatteryLevel: Boolean + get() = parent?.canSetBatteryLevel ?: false + override val missingPower: Decimal + get() = parent?.missingPower ?: Decimal.ZERO + + override fun drainBattery(): Boolean { + return parent?.drainBattery() ?: false + } + + override fun fillBattery(): Boolean { + return parent?.fillBattery() ?: false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt new file mode 100644 index 000000000..4b55a6bf7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/AbstractMatteryFluidHandler.kt @@ -0,0 +1,102 @@ +package ru.dbotthepony.mc.otm.capability.fluid + +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.isNotEmpty + +abstract class AbstractMatteryFluidHandler : IFluidHandler { + abstract var fluid: FluidStack + abstract val capacity: Int + + val isEmpty: Boolean + get() = fluid.isEmpty + + val isNotEmpty: Boolean + get() = fluid.isNotEmpty + + val isNotFull: Boolean + get() = fluid.isEmpty || fluid.amount < capacity + + open val direction: FlowDirection + get() = FlowDirection.BI_DIRECTIONAL + + final override fun getTanks() = 1 + + final override fun getFluidInTank(tank: Int): FluidStack { + require(tank == 0) { "Invalid tank: $tank" } + return fluid.copy() + } + + final override fun getTankCapacity(tank: Int): Int { + require(tank == 0) { "Invalid tank: $tank" } + return capacity + } + + protected open fun isFluidValid(stack: FluidStack): Boolean { + return true + } + + final override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + require(tank == 0) { "Invalid tank: $tank" } + return isFluidValid(stack) + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (resource.isEmpty || !isFluidValid(resource) || !direction.input) { + return 0 + } + + val fluid = fluid + + if (fluid.isEmpty || fluid.isFluidEqual(resource)) { + val new = (fluid.amount + resource.amount).coerceAtMost(capacity) + if (new <= fluid.amount) return 0 + + if (action.execute()) { + this.fluid = FluidStack(resource, new) + } + + return new - fluid.amount + } else { + return 0 + } + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (resource.isEmpty || !direction.output) { + return FluidStack.EMPTY + } + + val fluid = fluid + + if (!fluid.isEmpty && fluid.isFluidEqual(resource)) { + return drain(resource.amount, action) + } else { + return FluidStack.EMPTY + } + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + require(maxDrain >= 0) { "Invalid amount to drain: $maxDrain" } + if (maxDrain == 0 || !direction.output) return FluidStack.EMPTY + + val fluid = fluid + + if (fluid.isEmpty) { + return FluidStack.EMPTY + } else { + val new = (fluid.amount - maxDrain).coerceAtLeast(0) + + if (action.execute()) { + if (new == 0) { + this.fluid = FluidStack.EMPTY + } else { + this.fluid = FluidStack(fluid, new) + } + } + + return FluidStack(fluid, fluid.amount - new) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt new file mode 100644 index 000000000..ac675dac6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/BlockMatteryFluidHandler.kt @@ -0,0 +1,75 @@ +package ru.dbotthepony.mc.otm.capability.fluid + +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.fluids.FluidStack +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import java.util.function.IntSupplier + +/** + * Fluid handler for blocks + */ +class BlockMatteryFluidHandler(val onChanged: (new: FluidStack, old: FluidStack) -> Unit, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), INBTSerializable { + override var fluid: FluidStack = FluidStack.EMPTY + set(value) { + val old = field + field = value + onChanged(value, old) + } + + override val capacity: Int + get() = _capacity.asInt + + override fun serializeNBT(): CompoundTag { + return fluid.writeToNBT(CompoundTag()) + } + + override fun deserializeNBT(nbt: CompoundTag?) { + fluid = FluidStack.loadFluidStackFromNBT(nbt) + } + + /** + * Fluid handler for items representing block with [BlockMatteryFluidHandler] + */ + open class Item(itemStack: ItemStack, capacity: IntSupplier, private val nbtName: String) : ItemMatteryFluidHandler(itemStack, capacity) { + override var fluid: FluidStack + get() { + val sub = itemStack.tag?.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag ?: return FluidStack.EMPTY + return FluidStack.loadFluidStackFromNBT(sub[nbtName] as? CompoundTag ?: return FluidStack.EMPTY) + } + set(value) { + if (value.isEmpty) { + val tag = itemStack.tag ?: return + val subTag = tag.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag + + if (subTag == null) { + if (tag.isEmpty) { + itemStack.tag = null + } + } else { + subTag.remove(nbtName) + + if (subTag.isEmpty) { + tag.remove(BlockItem.BLOCK_ENTITY_TAG) + + if (tag.isEmpty) { + itemStack.tag = null + } + } + } + } else { + var sub = itemStack.tagNotNull.get(BlockItem.BLOCK_ENTITY_TAG) as? CompoundTag + + if (sub == null) { + sub = CompoundTag() + itemStack.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] = sub + } + + sub[nbtName] = value.writeToNBT(CompoundTag()) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt new file mode 100644 index 000000000..6e81f7cfb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerIterator.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.mc.otm.capability.fluid + +import it.unimi.dsi.fastutil.objects.ObjectIterators.AbstractIndexBasedIterator +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.container.get + +class FluidHandlerIterator(private val handler: IFluidHandler, initialPosition: Int = 0) : AbstractIndexBasedIterator(0, initialPosition) { + init { + require(initialPosition in 0 until handler.tanks) { "Invalid initial position: $initialPosition" } + } + + override fun remove(location: Int) { + throw UnsupportedOperationException() + } + + override fun get(location: Int): FluidStack { + return handler[location] + } + + override fun getMaxPos(): Int { + return handler.tanks + } +} + +fun IFluidHandler.iterator(position: Int = 0) = FluidHandlerIterator(this, position) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt new file mode 100644 index 000000000..4d7f874d8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/FluidHandlerSpliterator.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.capability.fluid + +import it.unimi.dsi.fastutil.objects.ObjectSpliterator +import it.unimi.dsi.fastutil.objects.ObjectSpliterators.AbstractIndexBasedSpliterator +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.container.get +import java.util.Spliterator +import java.util.stream.Stream +import java.util.stream.StreamSupport + +class FluidHandlerSpliterator(private val handler: IFluidHandler, offset: Int = 0, private val maxPos: Int = handler.tanks) : AbstractIndexBasedSpliterator(offset) { + init { + require(offset in 0 until handler.tanks) { "Invalid offset $offset" } + require(offset <= maxPos) { "offset <= maxPos: $offset > $maxPos!" } + require(maxPos >= offset && maxPos in 0 .. handler.tanks) { "Invalid spliterator configuration: maxPos $maxPos offset $offset max tanks ${handler.tanks}" } + } + + override fun get(location: Int): FluidStack { + return handler[location] + } + + override fun getMaxPos(): Int { + return maxPos + } + + override fun makeForSplit(pos: Int, maxPos: Int): ObjectSpliterator { + return FluidHandlerSpliterator(handler, pos, maxPos) + } +} + +fun IFluidHandler.spliterator(offset: Int = 0): Spliterator = FluidHandlerSpliterator(this, offset) +fun IFluidHandler.stream(offset: Int = 0): Stream = StreamSupport.stream(spliterator(offset), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt new file mode 100644 index 000000000..fcde61b2b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/fluid/ItemMatteryFluidHandler.kt @@ -0,0 +1,77 @@ +package ru.dbotthepony.mc.otm.capability.fluid + +import net.minecraft.core.Direction +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.capabilities.Capability +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.common.util.LazyOptional +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import net.minecraftforge.fluids.capability.IFluidHandlerItem +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import java.util.function.IntSupplier + +/** + * Fluid handler for standalone items + */ +open class ItemMatteryFluidHandler(val itemStack: ItemStack, private val _capacity: IntSupplier) : AbstractMatteryFluidHandler(), IFluidHandlerItem, ICapabilityProvider { + private val resolver = LazyOptional.of { this } + + override var fluid: FluidStack + get() { return FluidStack.loadFluidStackFromNBT(itemStack.tag?.get("fluid") as? CompoundTag ?: return FluidStack.EMPTY) } + set(value) { + if (value.isEmpty) { + val tag = itemStack.tag + + if (tag != null) { + tag.remove("fluid") + + if (tag.isEmpty) { + itemStack.tag = null + } + } + } else { + itemStack.tagNotNull["fluid"] = value.writeToNBT(CompoundTag()) + } + } + + final override val capacity: Int + get() = _capacity.asInt + + final override fun getContainer(): ItemStack { + return itemStack + } + + override fun getCapability(cap: Capability, side: Direction?): LazyOptional { + if (cap === ForgeCapabilities.FLUID_HANDLER_ITEM || cap === ForgeCapabilities.FLUID_HANDLER) { + return resolver.cast() + } + + return LazyOptional.empty() + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (itemStack.count != 1) + return 0 + + return super.fill(resource, action) + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + if (itemStack.count != 1) + return FluidStack.EMPTY + + return super.drain(resource, action) + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + if (itemStack.count != 1) + return FluidStack.EMPTY + + return super.drain(maxDrain, action) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt new file mode 100644 index 000000000..518d43e3e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt @@ -0,0 +1,89 @@ +package ru.dbotthepony.mc.otm.capability.item + +import com.google.common.collect.ImmutableList +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler +import ru.dbotthepony.mc.otm.container.ContainerHandler +import java.util.stream.Stream + +class CombinedItemHandler(val handlers: ImmutableList) : IItemHandler { + constructor(handlers: Stream) : this(handlers.collect(ImmutableList.toImmutableList())) + constructor(handlers: Collection) : this(ImmutableList.copyOf(handlers)) + constructor(vararg handlers: IItemHandler) : this(ImmutableList.copyOf(handlers)) + + private val needsChecking = handlers.any { it !is ContainerHandler } + private val lastSizes = IntArray(this.handlers.size) + private var totalSize = 0 + private val mappings = ArrayList() + private data class Mapping(val handler: IItemHandler, val slot: Int) + + init { + check(true) + } + + private fun check(force: Boolean = false) { + if (!needsChecking && !force) + return + + for ((i, handler) in handlers.withIndex()) { + var oldSize = lastSizes[i] + + if (oldSize != handler.slots) { + totalSize += handler.slots - lastSizes[i] + var edge = 0 + + for (i2 in 0 .. i) { + edge += lastSizes[i2] + } + + edge-- + + while (oldSize > handler.slots) { + mappings.removeAt(edge--) + oldSize-- + } + + while (oldSize < handler.slots) { + mappings.add(++edge, Mapping(handler, oldSize++)) + } + + lastSizes[i] = handler.slots + } + } + } + + override fun getSlots(): Int { + check() + return totalSize + } + + override fun getStackInSlot(slot: Int): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return ItemStack.EMPTY + return mapping.handler.getStackInSlot(mapping.slot) + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return stack + return mapping.handler.insertItem(mapping.slot, stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + check() + val mapping = mappings.getOrNull(slot) ?: return ItemStack.EMPTY + return mapping.handler.extractItem(mapping.slot, amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + check() + val mapping = mappings.getOrNull(slot) ?: return 0 + return mapping.handler.getSlotLimit(mapping.slot) + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + check() + val mapping = mappings.getOrNull(slot) ?: return false + return mapping.handler.isItemValid(mapping.slot, stack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt new file mode 100644 index 000000000..5fb9e1ff3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/EmptyItemHandler.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.capability.item + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +object EmptyItemHandler : IItemHandler { + override fun getSlots(): Int { + return 0 + } + + override fun getStackInSlot(slot: Int): ItemStack { + return ItemStack.EMPTY + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return stack + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return ItemStack.EMPTY + } + + override fun getSlotLimit(slot: Int): Int { + return 0 + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt new file mode 100644 index 000000000..81533ebd0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/ProxiedItemHandler.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.capability.item + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +class ProxiedItemHandler(var parent: T? = null) : IItemHandler { + override fun getSlots(): Int { + return parent?.slots ?: 0 + } + + override fun getStackInSlot(slot: Int): ItemStack { + return parent?.getStackInSlot(slot) ?: ItemStack.EMPTY + } + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return parent?.insertItem(slot, stack, simulate) ?: stack + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return parent?.extractItem(slot, amount, simulate) ?: ItemStack.EMPTY + } + + override fun getSlotLimit(slot: Int): Int { + return parent?.getSlotLimit(slot) ?: 0 + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return parent?.isItemValid(slot, stack) ?: false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt new file mode 100644 index 000000000..7895a417b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/UnmodifiableItemHandler.kt @@ -0,0 +1,14 @@ +package ru.dbotthepony.mc.otm.capability.item + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +class UnmodifiableItemHandler(private val parent: IItemHandler) : IItemHandler by parent { + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return ItemStack.EMPTY + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/Data.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/Data.kt index 78df561cc..8a52f25e0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/Data.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/Data.kt @@ -8,8 +8,8 @@ private enum class PatternInsertResult { sealed class PatternInsertStatus( private val status: PatternInsertResult, - val newState: IPatternState?, - val oldState: IPatternState? + val newState: PatternState?, + val oldState: PatternState? ) { val isFailed get() = status == PatternInsertResult.FAIL val isUpdated get() = status == PatternInsertResult.UPDATED @@ -17,8 +17,8 @@ sealed class PatternInsertStatus( } object PatternInsertFailure : PatternInsertStatus(PatternInsertResult.FAIL, null, null) -class PatternInsertUpdated(new: IPatternState, old: IPatternState) : PatternInsertStatus(PatternInsertResult.UPDATED, new, old) -class PatternInsertInserted(new: IPatternState) : PatternInsertStatus(PatternInsertResult.INSERTED, new, null) +class PatternInsertUpdated(new: PatternState, old: PatternState) : PatternInsertStatus(PatternInsertResult.UPDATED, new, old) +class PatternInsertInserted(new: PatternState) : PatternInsertStatus(PatternInsertResult.INSERTED, new, null) @JvmRecord -data class ReplicationTaskAllocation(val task: IReplicationTask<*>, val pattern: IPatternState?) +data class ReplicationTaskAllocation(val task: ReplicationTask, val pattern: PatternState?) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterHandler.kt deleted file mode 100644 index a661ef67a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterHandler.kt +++ /dev/null @@ -1,38 +0,0 @@ -package ru.dbotthepony.mc.otm.capability.matter - -import net.minecraftforge.common.capabilities.ICapabilityProvider -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.core.orNull -import kotlin.math.roundToInt - -interface IMatterHandler { - val storedMatter: Decimal - val maxStoredMatter: Decimal - - fun receiveMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal - fun receiveMatterInner(howMuch: Decimal, simulate: Boolean): Decimal - fun extractMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal - fun extractMatterInner(howMuch: Decimal, simulate: Boolean): Decimal - - val direction: MatterDirection - val missingMatter: Decimal - get() = maxStoredMatter.minus(storedMatter).moreThanZero() - - val allowsExtract: Boolean - get() = direction != MatterDirection.RECEIVE - - val allowsReceive: Boolean - get() = direction != MatterDirection.EXTRACT -} - -fun IMatterHandler.getBarWidth(): Int { - return ((storedMatter / maxStoredMatter).toFloat().coerceAtLeast(0f).coerceAtMost(1f) * 13f).roundToInt() -} - -fun IMatterHandler.getBarColor(): Int { - return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toInt() -} - -val ICapabilityProvider.matter: IMatterHandler? get() = getCapability(MatteryCapability.MATTER).orNull() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt new file mode 100644 index 000000000..196389911 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IMatterStorage.kt @@ -0,0 +1,129 @@ +package ru.dbotthepony.mc.otm.capability.matter + +import net.minecraftforge.common.capabilities.ICapabilityProvider +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import kotlin.math.roundToInt + +/** + * Matter interface in Overdrive That Matters + * + * Contracts of this interface follow the ones of [IMatteryEnergyStorage] + */ +interface IMatterStorage { + /** + * If this is false, then [storedMatter] will throw [UnsupportedOperationException] when trying to set it + */ + val canSetMatterLevel: Boolean get() = true + + /** + * How much matter (estimated) is stored in this matter storage. Why estimated? Because some objects can be bottomless. + * + * Implementations are free to throw [UnsupportedOperationException] if setting battery level is not supported + * due to technical complications (in this case, [canSetMatterLevel] MUST be false) + * + * If you need to fully fill matter value, DO call [fillMatter], or, in case if you want to fully drain, call [drainMatter] + * + * @throws [UnsupportedOperationException] + */ + var storedMatter: Decimal + + /** + * How much matter (estimated) can this matter storage hold. Why estimated? Because some objects can be bottomless. + * + * **DO NOT use this to determine "how much stuff is missing", use [missingMatter] instead!** + */ + val maxStoredMatter: Decimal + + /** + * Empties matter stored of this matter storage, if possible + * + * @throws [UnsupportedOperationException] + * @see storedMatter + */ + fun drainMatter() { + storedMatter = Decimal.ZERO + } + + /** + * Fully fills matter stored, if possible + * + * @throws [UnsupportedOperationException] + * @see storedMatter + */ + fun fillMatter() { + storedMatter = maxStoredMatter + } + + /** + * Fill matter into this object, ignoring [matterFlow] + * + * @return matter accepted + */ + fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal + + /** + * Extract matter from this matter storage, ignoring [matterFlow] + * + * @return matter extracted + */ + fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal + + /** + * Fill matter into this object, accounting for [matterFlow] + * + * @return matter accepted + */ + fun receiveMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (matterFlow.input) { + return receiveMatter(howMuch, simulate) + } + + return Decimal.ZERO + } + + /** + * Extract matter from this matter storage, accounting for [matterFlow] + * + * @return matter extracted + */ + fun extractMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + if (matterFlow.output) { + return extractMatter(howMuch, simulate) + } + + return Decimal.ZERO + } + + /** + * How much matter (estimated) is missing in this matter storage. Why estimated? Because some objects can be bottomless. + * + * Use this to determine whenever you need an estimate on how much matter this storage can accept, **and do not implement [maxStoredMatter] - [storedMatter] logic by yourself**. + */ + val missingMatter: Decimal + get() = maxStoredMatter.minus(storedMatter).moreThanZero() + + /** + * Which direction does matter flows + */ + val matterFlow: FlowDirection +} + +inline val IMatterStorage.canExtractMatter: Boolean + get() = matterFlow.output +inline val IMatterStorage.canReceiveMatter: Boolean + get() = matterFlow.input + +fun IMatterStorage.getBarWidth(): Int { + return ((storedMatter / maxStoredMatter).toFloat().coerceAtLeast(0f).coerceAtMost(1f) * 13f).roundToInt() +} + +fun IMatterStorage.getBarColor(): Int { + return RGBAColor.LOW_MATTER.linearInterpolation((storedMatter / maxStoredMatter).toFloat(), RGBAColor.FULL_MATTER).toRGB() +} + +val ICapabilityProvider.matter: IMatterStorage? get() = getCapability(MatteryCapability.MATTER).orNull() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt index 4429e70e9..0eecae475 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IPatternStorage.kt @@ -1,10 +1,9 @@ package ru.dbotthepony.mc.otm.capability.matter -import net.minecraft.util.Mth import net.minecraft.world.item.Item import net.minecraftforge.common.capabilities.ICapabilityProvider import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.core.orNull import java.util.* import java.util.function.Predicate @@ -14,23 +13,23 @@ import kotlin.math.roundToInt interface IPatternStorage { /** - * It must return new stream each time + * Patterns stored in this pattern storage */ - val patterns: Stream + val patterns: Stream - fun findPatterns(item: Item): Collection { + fun findPatterns(item: Item): Collection { return findPatterns { item == it.item } } - fun findPatterns(predicate: Predicate): Collection { + fun findPatterns(predicate: Predicate): Collection { return patterns.filter(predicate).collect(Collectors.toList()) } - fun findPattern(item: Item): IPatternState? { + fun findPattern(item: Item): PatternState? { return patterns.filter { it.item == item }.findAny().orElse(null) } - fun getPattern(id: UUID?): IPatternState? { + fun getPattern(id: UUID?): PatternState? { return patterns.filter { it.id == id }.findAny().orElse(null) } @@ -38,7 +37,7 @@ interface IPatternStorage { return patterns.filter { it.item == item }.findAny().isPresent } - fun hasPattern(state: IPatternState): Boolean { + fun hasPattern(state: PatternState): Boolean { return hasPattern(state.id) } @@ -58,13 +57,13 @@ interface IPatternStorage { * @param simulate whenever to affect state * @return record of status of the operation (at status() FAIL, UPDATED, INSERTED) as well as new_state and old_state */ - fun insertPattern(pattern: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus + fun insertPattern(pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus - fun insertPattern(pattern: IPatternState, simulate: Boolean): PatternInsertStatus { + fun insertPattern(pattern: PatternState, simulate: Boolean): PatternInsertStatus { return insertPattern(pattern, false, simulate) } - fun updatePattern(pattern: IPatternState, simulate: Boolean): PatternInsertStatus { + fun updatePattern(pattern: PatternState, simulate: Boolean): PatternInsertStatus { return insertPattern(pattern, true, simulate) } } @@ -74,7 +73,7 @@ fun IPatternStorage.getBarWidth(): Int { } fun IPatternStorage.getBarColor(): Int { - return RGBAColor.LOW_PATTERNS.linearInterpolation((storedPatterns / patternCapacity).toFloat(), RGBAColor.FULL_PATTERNS).toInt() + return RGBAColor.LOW_PATTERNS.linearInterpolation((storedPatterns / patternCapacity).toFloat(), RGBAColor.FULL_PATTERNS).toRGB() } val ICapabilityProvider.patterns: IPatternStorage? get() = getCapability(MatteryCapability.PATTERN).orNull() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IReplicationTaskProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IReplicationTaskProvider.kt index 2fc50bb25..500a9a5e2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IReplicationTaskProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/IReplicationTaskProvider.kt @@ -7,19 +7,19 @@ interface IReplicationTaskProvider { /** * It must return new stream each time */ - val replicationTasks: Stream> + val replicationTasks: Stream /** * It must return new stream each time */ - val allReplicationTasks: Stream> + val allReplicationTasks: Stream /** * Allocates (marks as work-in-progress) a task - * by incrementing it's [IReplicationTask.inProgress] by 1 - * and shrinking [IReplicationTask.required] by 1 + * by incrementing it's [ReplicationTask.inProgress] by 1 + * and shrinking [ReplicationTask.required] by 1 * - * If [IReplicationTask.required] == 0, it should not be returned by this method + * If [ReplicationTask.required] == 0, it should not be returned by this method * @param simulate whenever to change internal state * @return MatterTaskAllocation(task, pattern) that should be performed, or null if no work is available */ @@ -38,7 +38,7 @@ interface IReplicationTaskProvider { * @param id uuid of task * @return MatterTask that this capability holds with this id, or null */ - fun getTask(id: UUID): IReplicationTask<*>? { + fun getTask(id: UUID): ReplicationTask? { return allReplicationTasks.filter { it.id == id }.findAny().orElse(null) } @@ -46,4 +46,4 @@ interface IReplicationTaskProvider { * Destroys all tasks this capability contains */ fun dropAllTasks() -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterDirection.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterDirection.kt deleted file mode 100644 index e03c7bc4e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterDirection.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dbotthepony.mc.otm.capability.matter - -enum class MatterDirection { RECEIVE, EXTRACT, BIDIRECTIONAL } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt similarity index 61% rename from src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt index b50bc0bb5..f15c6780c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterHandlerImpl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/MatterStorageImpl.kt @@ -2,22 +2,22 @@ package ru.dbotthepony.mc.otm.capability.matter import net.minecraft.nbt.CompoundTag import net.minecraftforge.common.util.INBTSerializable -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.VerboseBalanceValues -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues +import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nbt.set -open class MatterHandlerImpl @JvmOverloads constructor( +open class MatterStorageImpl @JvmOverloads constructor( protected val listener: Runnable?, - override val direction: MatterDirection, + override val matterFlow: FlowDirection, protected val maxStoredMatterSupplier: () -> Decimal, protected val maxReceiveSupplier: () -> Decimal? = { null }, protected val maxExtractSupplier: () -> Decimal? = maxReceiveSupplier -) : IMatterHandler, INBTSerializable { +) : IMatterStorage, INBTSerializable { constructor( listener: Runnable?, - direction: MatterDirection, + direction: FlowDirection, maxStoredMatter: Decimal, maxReceive: Decimal? = null, maxExtract: Decimal? = null, @@ -25,15 +25,15 @@ open class MatterHandlerImpl @JvmOverloads constructor( constructor( listener: Runnable?, - direction: MatterDirection, - values: ConciseBalanceValues - ) : this(listener, direction, values::capacity, values::throughput, values::throughput) + direction: FlowDirection, + values: EnergyBalanceValues + ) : this(listener, direction, values::energyCapacity, values::energyThroughput, values::energyThroughput) constructor( listener: Runnable?, - direction: MatterDirection, - values: VerboseBalanceValues - ) : this(listener, direction, values::capacity, values::receive, values::extract) + direction: FlowDirection, + values: VerboseEnergyBalanceValues + ) : this(listener, direction, values::energyCapacity, values::maxEnergyReceive, values::maxEnergyExtract) val maxReceive get() = maxReceiveSupplier.invoke() val maxExtract get() = maxExtractSupplier.invoke() @@ -42,34 +42,15 @@ open class MatterHandlerImpl @JvmOverloads constructor( get() = maxStoredMatterSupplier.invoke() override var storedMatter = Decimal.ZERO - protected set - - private var handler = LazyOptional.of { this } - - fun invalidate() { - handler.invalidate() - } - - fun revive() { - handler = LazyOptional.of { this } - } - - fun get(): LazyOptional { - return handler - } open fun canReceiveAll(value: Decimal): Boolean { return maxStoredMatter >= value && storedMatter + value <= maxStoredMatter } - override fun receiveMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction === MatterDirection.EXTRACT) + override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) return Decimal.ZERO - return receiveMatterInner(howMuch, simulate) - } - - override fun receiveMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { val new: Decimal if (maxReceive == null) { @@ -88,14 +69,10 @@ open class MatterHandlerImpl @JvmOverloads constructor( return diff } - override fun extractMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - if (direction === MatterDirection.RECEIVE) + override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) return Decimal.ZERO - return extractMatterInner(howMuch, simulate) - } - - override fun extractMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { val new: Decimal if (maxExtract == null) { @@ -123,7 +100,8 @@ open class MatterHandlerImpl @JvmOverloads constructor( } } - override fun deserializeNBT(tag: CompoundTag) { + override fun deserializeNBT(tag: CompoundTag?) { + if (tag == null) return storedMatter = Decimal.deserializeNBT(tag["stored"]) //maxStoredMatter = ImpreciseFraction.deserializeNBT(tag["max_storage"]) //maxReceive = if (tag.contains("max_receive")) ImpreciseFraction.deserializeNBT(tag["max_receive"]) else null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt index c53bcc134..d3ae04379 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt @@ -1,146 +1,59 @@ package ru.dbotthepony.mc.otm.capability.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodec +import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec +import ru.dbotthepony.mc.otm.data.UUIDCodec import java.util.* -sealed interface IPatternState { - val id: UUID - val item: Item - val researchPercent: Double - - fun asMutable(): MutablePatternState - fun asImmutable() : PatternState - - fun matchId(other: IPatternState): Boolean { +data class PatternState( + val id: UUID, + val item: Item, + val researchPercent: Double, +) { + fun matchId(other: PatternState): Boolean { return other.id == id } - fun copyAsMutable( - id: UUID = this.id, - item: Item = this.item, - researchPercent: Double = this.researchPercent, - ): MutablePatternState { - return MutablePatternState(id, item, researchPercent) - } - - fun copyAsImmutable( - id: UUID = this.id, - item: Item = this.item, - researchPercent: Double = this.researchPercent, - ) : PatternState { - return PatternState(id, item, researchPercent) - } - fun stack(count: Int = 1): ItemStack { return ItemStack(item, count) } - fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["id"] = id - it["item"] = item.registryName!!.toString() - it["researchPercent"] = researchPercent - } - } - fun write(buff: FriendlyByteBuf) { - buff.writeUUID(id) - buff.writeItemType(item) - buff.writeDouble(researchPercent) - } -} - -private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IPatternState? { - if (nbt !is CompoundTag) - return null - - if (!nbt.contains("id", "researchPercent", "item")) - return null - - val id = nbt.getUUID("id") - val researchPercent = nbt.getDouble("researchPercent") - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(nbt.getString("item"))) ?: return null - - if (item == Items.AIR) - return null - - if (mutable) { - return MutablePatternState(id, item, researchPercent) - } else { - return PatternState(id, item, researchPercent) - } -} - -private fun read(buff: FriendlyByteBuf, mutable: Boolean): IPatternState? { - val id = buff.readUUID() - val item = buff.readItemType() - val researchPercent = buff.readDouble() - - item ?: return null - - if (mutable) { - return MutablePatternState(id, item, researchPercent) - } else { - return PatternState(id, item, researchPercent) - } -} - -data class PatternState( - override val id: UUID, - override val item: Item, - override val researchPercent: Double, -) : IPatternState { - override fun asMutable(): MutablePatternState { - return MutablePatternState(id, item, researchPercent) + buff.writeBinaryJsonWithCodec(CODEC, this) } - override fun asImmutable(): PatternState { - return this + fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get() + .map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize PatternState: ${it.message()}") }) } companion object { fun deserializeNBT(tag: Tag?): PatternState? { - return deserializeNBT(tag, false) as PatternState? + tag ?: return null + return CODEC.decode(NbtOps.INSTANCE, tag).result().map { it.first }.orElse(null) } - fun read(buff: FriendlyByteBuf): PatternState? { - return read(buff, false) as PatternState? + fun read(buff: FriendlyByteBuf): PatternState { + return buff.readBinaryJsonWithCodec(CODEC) + } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + UUIDCodec.fieldOf("id").forGetter(PatternState::id), + ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(PatternState::item), + Codec.doubleRange(0.0, 1.0).fieldOf("researchPercent").forGetter(PatternState::researchPercent) + ).apply(it, ::PatternState) + } } } } - -data class MutablePatternState( - override val id: UUID, - override val item: Item, - override var researchPercent: Double, -) : IPatternState { - override fun asMutable(): MutablePatternState { - return this - } - - override fun asImmutable(): PatternState { - return PatternState(id, item, researchPercent) - } - - companion object { - fun deserializeNBT(tag: Tag?): MutablePatternState? { - return deserializeNBT(tag, true) as MutablePatternState? - } - - fun read(buff: FriendlyByteBuf): MutablePatternState? { - return read(buff, true) as MutablePatternState? - } - } -} - -fun FriendlyByteBuf.writePatternState(state: IPatternState) = state.write(this) -fun FriendlyByteBuf.readPatternState() = PatternState.read(this) -fun FriendlyByteBuf.readMutablePatternState() = MutablePatternState.read(this) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt new file mode 100644 index 000000000..b3ebe61f0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ProfiledMatterStorage.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.capability.matter + +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.math.Decimal + +class ProfiledMatterStorage(parent: M) : AbstractProfiledStorage(parent), IMatterStorage { + override var storedMatter by parent::storedMatter + + override val maxStoredMatter: Decimal + get() = parent.maxStoredMatter + + override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveMatter(howMuch, simulate), simulate) + } + + override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractMatter(howMuch, simulate), simulate) + } + + override fun receiveMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordReceive(parent.receiveMatterChecked(howMuch, simulate), simulate) + } + + override fun extractMatterChecked(howMuch: Decimal, simulate: Boolean): Decimal { + return recordTransfer(parent.extractMatterChecked(howMuch, simulate), simulate) + } + + override val matterFlow: FlowDirection + get() = parent.matterFlow + + override val canSetMatterLevel: Boolean + get() = parent.canSetMatterLevel + + override fun drainMatter() { + parent.drainMatter() + } + + override fun fillMatter() { + parent.fillMatter() + } + + override val missingMatter: Decimal + get() = parent.missingMatter +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt index 0dd3991d9..feb080e75 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt @@ -1,55 +1,37 @@ package ru.dbotthepony.mc.otm.capability.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.core.readItemType -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.writeItemType +import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodec +import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec +import ru.dbotthepony.mc.otm.data.UUIDCodec +import java.util.Optional import java.util.UUID -sealed interface IReplicationTask> { - val id: UUID - val patternId: UUID? - val item: Item - val inProgress: Int - val finished: Int +data class ReplicationTask( + val id: UUID, + val patternId: Optional, + val item: Item, + val inProgress: Int, + val finished: Int, val required: Int - - fun asMutable(): MutableReplicationTask - fun asImmutable(): ReplicationTask +) { + init { + require(inProgress >= 0) { "Invalid inProgress value: $inProgress"} + require(finished >= 0) { "Invalid finished value: $finished"} + require(required >= 0) { "Invalid required value: $required"} + } val total get() = inProgress + finished + required - fun copyAsMutable( - id: UUID = this.id, - patternId: UUID? = this.patternId, - item: Item = this.item, - inProgress: Int = this.inProgress, - finished: Int = this.finished, - required: Int = this.required, - ): MutableReplicationTask { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) - } - - fun copyAsImmutable( - id: UUID = this.id, - patternId: UUID? = this.patternId, - item: Item = this.item, - inProgress: Int = this.inProgress, - finished: Int = this.finished, - required: Int = this.required, - ): ReplicationTask { - return ReplicationTask(id, patternId, item, inProgress, finished, required) - } - - fun matchId(other: IReplicationTask<*>): Boolean { + fun matchId(other: ReplicationTask): Boolean { return other.id == id } @@ -57,154 +39,42 @@ sealed interface IReplicationTask> { return ItemStack(item, count) } - fun allocate(amount: Int = 1): S - fun finish(amount: Int = 1): S - - fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["id"] = id - - if (patternId != null) - it["patternId"] = patternId!! - - it["item"] = item.registryName!!.toString() - it["inProgress"] = inProgress - it["finished"] = finished - it["required"] = required - } - } - - fun write(buff: FriendlyByteBuf) { - buff.writeUUID(id) - - buff.writeBoolean(patternId != null) - patternId?.let(buff::writeUUID) - - buff.writeItemType(item) - - buff.writeInt(inProgress) - buff.writeInt(finished) - buff.writeInt(required) - } -} - -private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IReplicationTask<*>? { - if (nbt !is CompoundTag) - return null - - if (!nbt.contains("id") || !nbt.contains("inProgress") || !nbt.contains("finished") || !nbt.contains("required") || !nbt.contains("item")) - return null - - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(nbt.getString("item"))) ?: return null - - if (item == Items.AIR) - return null - - val id = nbt.getUUID("id") - val patternId = if (nbt.contains("patternId")) nbt.getUUID("patternId") else null - - val inProgress = nbt.getInt("inProgress") - val finished = nbt.getInt("finished") - val required = nbt.getInt("required") - - if (mutable) { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) - } else { - return ReplicationTask(id, patternId, item, inProgress, finished, required) - } -} - -private fun read(buff: FriendlyByteBuf, mutable: Boolean): IReplicationTask<*>? { - val id = buff.readUUID() - val patternId: UUID? = if (buff.readBoolean()) buff.readUUID() else null - val item = buff.readItemType() - val inProgress = buff.readInt() - val finished = buff.readInt() - val required = buff.readInt() - - item ?: return null - - if (mutable) { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) - } else { - return ReplicationTask(id, patternId, item, inProgress, finished, required) - } -} - -data class ReplicationTask( - override val id: UUID, - override val patternId: UUID?, - override val item: Item, - override val inProgress: Int, - override val finished: Int, - override val required: Int -) : IReplicationTask { - override fun asMutable(): MutableReplicationTask { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) - } - - override fun asImmutable(): ReplicationTask { - return this - } - - override fun allocate(amount: Int): ReplicationTask { + fun allocate(amount: Int = 1): ReplicationTask { return ReplicationTask(id, patternId, item, inProgress + amount, finished, required - amount) } - override fun finish(amount: Int): ReplicationTask { + fun finish(amount: Int = 1): ReplicationTask { return ReplicationTask(id, patternId, item, inProgress - amount, finished + amount, required) } + fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize ReplicationTask: ${it.message()}") }) + } + + fun write(buff: FriendlyByteBuf) { + buff.writeBinaryJsonWithCodec(CODEC, this) + } + companion object { fun deserializeNBT(nbt: Tag?): ReplicationTask? { - return deserializeNBT(nbt, false) as ReplicationTask? + return CODEC.decode(NbtOps.INSTANCE, nbt ?: return null).result().orElse(null)?.first } - fun read(buff: FriendlyByteBuf): ReplicationTask? { - return read(buff, false) as ReplicationTask? + fun read(buff: FriendlyByteBuf): ReplicationTask { + return buff.readBinaryJsonWithCodec(CODEC) + } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + UUIDCodec.fieldOf("id").forGetter(ReplicationTask::id), + UUIDCodec.optionalFieldOf("patternId").forGetter(ReplicationTask::patternId), + ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(ReplicationTask::item), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("inProgress").forGetter(ReplicationTask::inProgress), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("finished").forGetter(ReplicationTask::finished), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("required").forGetter(ReplicationTask::required), + ).apply(it, ::ReplicationTask) + } } } } - -data class MutableReplicationTask( - override val id: UUID, - override val patternId: UUID?, - override val item: Item, - override var inProgress: Int, - override var finished: Int, - override var required: Int -) : IReplicationTask { - override fun asMutable(): MutableReplicationTask { - return this - } - - override fun asImmutable(): ReplicationTask { - return ReplicationTask(id, patternId, item, inProgress, finished, required) - } - - override fun allocate(amount: Int): MutableReplicationTask { - inProgress += amount - finished -= amount - return this - } - - override fun finish(amount: Int): MutableReplicationTask { - finished += amount - inProgress -= amount - return this - } - - companion object { - fun deserializeNBT(nbt: Tag?): MutableReplicationTask? { - return deserializeNBT(nbt, true) as MutableReplicationTask? - } - - fun read(buff: FriendlyByteBuf): MutableReplicationTask? { - return read(buff, true) as MutableReplicationTask? - } - } -} - -fun FriendlyByteBuf.writeReplicationTask(task: IReplicationTask<*>) = task.write(this) -fun FriendlyByteBuf.readReplicationTask() = ReplicationTask.read(this) -fun FriendlyByteBuf.readMutableReplicationTask() = MutableReplicationTask.read(this) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt index d3ae061e9..bbaa708ee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidAbilityKeyMapping.kt @@ -1,8 +1,6 @@ package ru.dbotthepony.mc.otm.client import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.KeyMapping import net.minecraftforge.client.event.RegisterKeyMappingsEvent import net.minecraftforge.client.event.RenderGuiEvent @@ -11,10 +9,10 @@ import net.minecraftforge.client.settings.KeyConflictContext import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidActiveFeature import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.render.drawRect import ru.dbotthepony.mc.otm.client.render.is3DContext -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.network.ActivateAndroidFeaturePacket import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import kotlin.math.roundToInt @@ -26,7 +24,7 @@ object AndroidAbilityKeyMapping : KeyMapping("key.otm.android_ability", KeyConfl val old = this.isDown super.setDown(isDown) - if (old != isDown) { + if (old != isDown && minecraft.player?.isSpectator == false) { if (isDown) { val capability = minecraft.player?.matteryPlayer ?: return @@ -58,27 +56,19 @@ object AndroidAbilityKeyMapping : KeyMapping("key.otm.android_ability", KeyConfl val x = minecraft.window.guiScaledWidth.toFloat() * .5f + iconSize / 2f val y = minecraft.window.guiScaledHeight.toFloat() * .5f - iconSize / 2f + val wrap = MGUIGraphics(event.poseStack) - RGBAColor.WHITE.setSystemColor() - - feature.renderIcon(event.poseStack, x, y, iconSize, iconSize) + feature.renderIcon(wrap, x, y, iconSize, iconSize) if (feature.isOnCooldown) { - RGBAColor.WHITE.setSystemColor() - val cooldownPct = feature.cooldownPercent if (cooldownPct > 0.0f) { - RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f) - val nodrawpixels = (iconSize * (1 - cooldownPct)).roundToInt().toFloat() - drawRect(event.poseStack, x, y + nodrawpixels, iconSize, iconSize - nodrawpixels) - - RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + wrap.renderRect(x, y + nodrawpixels, iconSize, iconSize - nodrawpixels, color = RGBAColor.HALF_TRANSPARENT) } - - Widgets18.COOLDOWN.render(event.poseStack, x, y + iconSize, iconSize, iconSize) + Widgets18.COOLDOWN.render(wrap, x, y + iconSize, iconSize, iconSize) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt index 7476fed2b..8c59ac6b3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/AndroidMenuKeyMapping.kt @@ -13,10 +13,13 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidSwitchableFeature import ru.dbotthepony.mc.otm.capability.matteryPlayer -import ru.dbotthepony.mc.otm.client.render.TextAlign -import ru.dbotthepony.mc.otm.client.render.drawAligned +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity import ru.dbotthepony.mc.otm.client.render.drawArc -import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.angleDifference +import ru.dbotthepony.mc.otm.core.math.normalizeAngle +import ru.dbotthepony.mc.otm.core.util.formatTickDuration import ru.dbotthepony.mc.otm.milliTimeD import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import ru.dbotthepony.mc.otm.network.SwitchAndroidFeaturePacket @@ -42,6 +45,20 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon private var clickOnce = false + private fun resetState() { + selectedFeature = null + lastSelectedFeature = null + lastSelectedDegree = null + lastSelectProgress.clear() + clickOnce = false + holdIt = null + } + + private fun ohNoIHaveHitSomethingNastyInMinecraftCode() { + grabbedInput = false + resetState() + } + override fun setDown(isDown: Boolean) { val old = this.isDown super.setDown(isDown) @@ -74,17 +91,24 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon } } - selectedFeature = null - lastSelectedFeature = null - lastSelectedDegree = null - lastSelectProgress.clear() - clickOnce = false - holdIt = null + resetState() } } fun onMouseClick(event: InputEvent.MouseButton.Pre) { + // fix binding wheel menu to mouse button not calling back setIsDown(false) + if (isDown && event.action == 0 && key.type == InputConstants.Type.MOUSE && InputConstants.Type.MOUSE.getOrCreate(event.button) == key) { + isDown = false + return + } + if (grabbedInput) { + if (minecraft.screen != null) { + // whoopsie + ohNoIHaveHitSomethingNastyInMinecraftCode() + return + } + clickOnce = true event.isCanceled = true @@ -131,9 +155,9 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon RenderSystem.setShaderColor(0f, 0f, 0f, 0.6f) val size = minecraft.window.guiScaledHeight.coerceAtMost(minecraft.window.guiScaledWidth).toFloat() * 0.35f + val wrap = MGUIGraphics(event.poseStack) - drawArc( - event.poseStack, + wrap.drawArc( minecraft.window.guiScaledWidth / 2f, minecraft.window.guiScaledHeight / 2f, size, @@ -202,12 +226,11 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon if (lastSelectedDegree != null && lastSelectedFeature != null) { RenderSystem.setShaderColor(85 / 255f, 197 / 255f, 255 / 255f, 0.3f * lastSelectProgressGlobal) - drawArc( - event.poseStack, + wrap.drawArc( minecraft.window.guiScaledWidth / 2f, minecraft.window.guiScaledHeight / 2f, - linearInterpolation(lastSelectProgressGlobal, size, size * 1.2f), - linearInterpolation(lastSelectProgressGlobal, size * 0.3f, size * 0.4f), + ru.dbotthepony.mc.otm.core.math.linearInterpolation(lastSelectProgressGlobal, size, size * 1.2f), + ru.dbotthepony.mc.otm.core.math.linearInterpolation(lastSelectProgressGlobal, size * 0.3f, size * 0.4f), startDegree = lastSelectedDegree!!, endDegree = lastSelectedDegree!! + degreePerSlice @@ -218,8 +241,9 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon val iconSize = size * 0.25f - event.poseStack.pushPose() - event.poseStack.translate(minecraft.window.guiScaledWidth.toDouble() / 2f, minecraft.window.guiScaledHeight.toDouble() / 2f, 0.0) + val poseStack = event.poseStack + poseStack.pushPose() + poseStack.translate(minecraft.window.guiScaledWidth.toDouble() / 2f, minecraft.window.guiScaledHeight.toDouble() / 2f, 0.0) for ((index, feature) in features.withIndex()) { var sin = sin((index + 0.5) * degreePerSlice).toFloat() @@ -231,20 +255,21 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon } val shift = size * 0.6f - feature.renderIcon(event.poseStack, -iconSize / 2f + shift * cos, -shift * sin - iconSize / 2f, iconSize, iconSize) - minecraft.font.drawAligned(event.poseStack, feature.type.displayName, TextAlign.CENTER_CENTER, shift * cos + 1f, -shift * sin - iconSize / 1.5f + 1f, 0) - minecraft.font.drawAligned(event.poseStack, feature.type.displayName, TextAlign.CENTER_CENTER, shift * cos, -shift * sin - iconSize / 1.5f, if (feature.isActive) RGBAColor.DARK_GREEN else RGBAColor.DARK_RED) + feature.renderIcon(wrap, -iconSize / 2f + shift * cos, -shift * sin - iconSize / 2f, iconSize, iconSize) + + wrap.draw(feature.type.displayName, shift * cos + 1f, -shift * sin - iconSize / 1.5f + 1f, color = RGBAColor.BLACK, gravity = RenderGravity.CENTER_CENTER) + wrap.draw(feature.type.displayName, shift * cos, -shift * sin - iconSize / 1.5f, color = if (feature.isActive) RGBAColor.DARK_GREEN else RGBAColor.DARK_RED, gravity = RenderGravity.CENTER_CENTER) if (feature.isOnCooldown && feature.cooldownPercent > 0.0f) { RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f) - drawArc(event.poseStack, shift * cos, -shift * sin, iconSize / 2f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * feature.cooldownPercent.toDouble()) + wrap.drawArc(shift * cos, -shift * sin, iconSize / 2f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * feature.cooldownPercent.toDouble()) RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - minecraft.font.drawAligned(event.poseStack, formatTickDuration(feature.cooldown), TextAlign.CENTER_CENTER, shift * cos, -shift * sin + iconSize / 1.5f, RGBAColor.WHITE) + wrap.draw(formatTickDuration(feature.cooldown), shift * cos, -shift * sin + iconSize / 1.5f, color = RGBAColor.WHITE, gravity = RenderGravity.CENTER_CENTER) } } - event.poseStack.popPose() + poseStack.popPose() } private fun renderRegular(event: RenderGuiEvent.Post) { @@ -269,15 +294,16 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon val y = minecraft.window.guiScaledHeight * 0.2f var x = minecraft.window.guiScaledWidth * 0.5f - (features.size.toFloat() * COOLDOWN_ICON_SIZE / 2f + (features.size - 1).toFloat() * (COOLDOWN_ICON_MARGIN / 2f)) + val wrap = MGUIGraphics(event.poseStack) for (feature in features) { - feature.renderIcon(event.poseStack, x, y, COOLDOWN_ICON_SIZE, COOLDOWN_ICON_SIZE) + feature.renderIcon(wrap, x, y, COOLDOWN_ICON_SIZE, COOLDOWN_ICON_SIZE) RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f) - drawArc(event.poseStack, x, y, COOLDOWN_ICON_SIZE / 2f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * feature.cooldownPercent, alignAtCenter = false) + wrap.drawArc(x, y, COOLDOWN_ICON_SIZE / 2f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * feature.cooldownPercent, alignAtCenter = false) RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - font.drawAligned(event.poseStack, formatTickDuration(feature.cooldown), TextAlign.TOP_CENTER, x + COOLDOWN_ICON_SIZE / 2f, y + COOLDOWN_ICON_SIZE + 1f, RGBAColor.WHITE) + wrap.draw(formatTickDuration(feature.cooldown), x + COOLDOWN_ICON_SIZE / 2f, y + COOLDOWN_ICON_SIZE + 1f, color = RGBAColor.WHITE, gravity = RenderGravity.TOP_CENTER) x += COOLDOWN_ICON_SIZE + COOLDOWN_ICON_MARGIN } @@ -295,7 +321,7 @@ object AndroidMenuKeyMapping : KeyMapping("key.otm.android_menu", KeyConflictCon renderWheel(event) if (minecraft.screen != null) { - isDown = false + ohNoIHaveHitSomethingNastyInMinecraftCode() } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt index bde02c326..d85321aa3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt @@ -2,32 +2,39 @@ package ru.dbotthepony.mc.otm.client import com.mojang.blaze3d.platform.InputConstants import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap +import net.minecraft.ChatFormatting import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.screens.Screen import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraft.tags.TagKey import net.minecraft.world.inventory.InventoryMenu import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.Item +import net.minecraft.world.level.block.Block import net.minecraftforge.client.event.MovementInputUpdateEvent import net.minecraftforge.client.event.ScreenEvent import net.minecraftforge.client.event.ScreenEvent.MouseDragged import net.minecraftforge.client.event.ScreenEvent.MouseScrolled -import ru.dbotthepony.mc.otm.ClientConfig +import net.minecraftforge.event.entity.player.ItemTooltipEvent +import ru.dbotthepony.mc.otm.config.ClientConfig import ru.dbotthepony.mc.otm.android.feature.JumpBoostFeature import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.screen.ExoPackInventoryScreen +import ru.dbotthepony.mc.otm.client.screen.ExopackInventoryScreen import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.client.screen.panels.AbstractSlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.DiscreteScrollBarPanel -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.LargeRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.Panel2Widget -import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket +import ru.dbotthepony.mc.otm.compat.vanilla.InventoryScrollPacket import ru.dbotthepony.mc.otm.compat.cos.isCosmeticArmorScreen import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.integerDivisionUp +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.math.integerDivisionUp import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.MenuNetworkChannel import ru.dbotthepony.mc.otm.registry.AndroidFeatures @@ -77,7 +84,7 @@ var inventoryScroll = 0 private fun inventoryLogic(event: ScreenEvent.Init.Post) { val player = minecraft.player?.matteryPlayer ?: return - if (!player.hasExoPack) { + if (!player.hasExopack) { return } @@ -85,20 +92,20 @@ private fun inventoryLogic(event: ScreenEvent.Init.Post) { val screen = if (eventScreen is AbstractContainerScreen<*> && (eventScreen.menu is InventoryMenu || eventScreen.isCosmeticArmorScreen)) eventScreen else return val widget = Panel2Widget(LargeRectangleButtonPanel(screen, null, - x = screen.guiLeft + screen.xSize - 2f - LargeRectangleButtonPanel.SIZE, - y = screen.guiTop.toFloat() - LargeRectangleButtonPanel.SIZE - 2, - skinElement = Widgets18.RETURN_ARROW_LEFT, - skinElementWinding = UVWindingOrder.FLOP, - onPress = { + x = screen.guiLeft + screen.xSize - 2f - LargeRectangleButtonPanel.SIZE, + y = screen.guiTop.toFloat() - LargeRectangleButtonPanel.SIZE - 2, + icon = Widgets18.RETURN_ARROW_LEFT, + iconWinding = UVWindingOrder.FLOP, + onPress = { shouldOpenVanillaInventory = false val mouseX = minecraft.mouseHandler.xpos() val mouseY = minecraft.mouseHandler.ypos() event.screen.onClose() - minecraft.setScreen(ExoPackInventoryScreen(player.exoPackMenu)) + minecraft.setScreen(ExopackInventoryScreen(player.exoPackMenu)) InputConstants.grabOrReleaseMouse(minecraft.window.window, 212993, mouseX, mouseY) - }).also { it.tooltip = TranslatableComponent("otm.gui.exosuit.go_in") }) + }).also { it.tooltips.add(TranslatableComponent("otm.gui.exopack.go_in")) }) event.addListener(widget) @@ -109,7 +116,7 @@ private fun inventoryLogic(event: ScreenEvent.Init.Post) { } private class InventoryScrollbarPanel(screen: S, matteryPlayer: MatteryPlayerCapability) : DiscreteScrollBarPanel( - screen, null, { integerDivisionUp(matteryPlayer.exoPackContainer.containerSize, 9) }, { _, _, newScroll -> + screen, null, { integerDivisionUp(matteryPlayer.exopackContainer.containerSize, 9) }, { _, _, newScroll -> inventoryScroll = newScroll MenuNetworkChannel.sendToServer(InventoryScrollPacket(newScroll).also { it.play(matteryPlayer.ply) }) }, isSlim = true @@ -122,7 +129,7 @@ private fun exosuitInventoryLogic(screen: Screen, addListener: (GuiEventListener val player = minecraft.player ?: return val matteryPlayer = player.matteryPlayer ?: return - if (!matteryPlayer.hasExoPack || matteryPlayer.exoPackContainer.containerSize == 0) { + if (!matteryPlayer.hasExopack || matteryPlayer.exopackContainer.containerSize == 0) { return } @@ -222,10 +229,10 @@ fun onMouseScrolled(event: MouseScrolled.Pre) { return } - if (ClientConfig.EXOPACK_FREE_SCROLL && widget.panel is InventoryScrollbarPanel) { + if (ClientConfig.GUI.EXOPACK_FREE_SCROLL && widget.panel is InventoryScrollbarPanel) { val slot = screen.slotUnderMouse - if (slot != null && (slot.container == minecraft.player?.inventory && slot.containerSlot in 9 .. 35 || slot.container == minecraft.player?.matteryPlayer?.exoPackContainer)) { + if (slot != null && (slot.container == minecraft.player?.inventory && slot.containerSlot in 9 .. 35 || slot.container == minecraft.player?.matteryPlayer?.exopackContainer)) { widget.panel.mouseScrolledInner(event.mouseX, event.mouseY, event.scrollDelta) event.isCanceled = true return @@ -249,7 +256,46 @@ fun onScreenOpen(event: ScreenEvent.Opening) { val player = minecraft.player?.matteryPlayer ?: return - if (player.hasExoPack && event.newScreen is InventoryScreen) { - event.newScreen = ExoPackInventoryScreen(player.exoPackMenu) + if (player.hasExopack && event.newScreen is InventoryScreen) { + event.newScreen = ExopackInventoryScreen(player.exoPackMenu) + } +} + +private val TOOLTIP_TAG_DISPLAY_HELP = TranslatableComponent("otm.gui.debug.tags.help").withStyle(ChatFormatting.GRAY) +private val TOOLTIP_TAG_DISPLAY_ITEM_TITLE = TranslatableComponent("otm.gui.debug.tags.item.title").withStyle(ChatFormatting.DARK_GRAY) +private val TOOLTIP_TAG_DISPLAY_BLOCK_TITLE = TranslatableComponent("otm.gui.debug.tags.block.title").withStyle(ChatFormatting.DARK_GRAY) + +fun tooltipEvent(event: ItemTooltipEvent) { + if (event.flags.isAdvanced && ClientConfig.Tooltip.DISPLAY_TAGS && !event.itemStack.isEmpty) { + val itemTags = ArrayList>() + if (event.itemStack.tags.count() > 0) { + itemTags.addAll(event.itemStack.tags) + } + + val blockTags = ArrayList>() + if (event.itemStack.item is BlockItem) { + blockTags.addAll((event.itemStack.item as BlockItem).block.defaultBlockState().tags) + } + + if (itemTags.isNotEmpty() || blockTags.isNotEmpty()) { + if (!minecraft.window.isShiftDown) { + event.toolTip.add(TOOLTIP_TAG_DISPLAY_HELP) + return + } + } + + if (itemTags.isNotEmpty()) { + event.toolTip.add(TOOLTIP_TAG_DISPLAY_ITEM_TITLE) + for (tag in itemTags) { + event.toolTip.add(TranslatableComponent("otm.gui.debug.tags.tag.entry", tag.location).withStyle(ChatFormatting.DARK_GRAY)) + } + } + + if (blockTags.isNotEmpty()) { + event.toolTip.add(TOOLTIP_TAG_DISPLAY_BLOCK_TITLE) + for (tag in blockTags) { + event.toolTip.add(TranslatableComponent("otm.gui.debug.tags.tag.entry", tag.location).withStyle(ChatFormatting.DARK_GRAY)) + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientExtensions.kt similarity index 83% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientExtensions.kt index 8c0eb85e6..bb55d7bee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientExtensions.kt @@ -1,18 +1,47 @@ package ru.dbotthepony.mc.otm.client +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.platform.Window import net.minecraft.client.Minecraft import net.minecraft.client.gui.Font import net.minecraft.client.resources.sounds.SimpleSoundInstance import net.minecraft.sounds.SoundEvents import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW.GLFW_CURSOR +import ru.dbotthepony.mc.otm.isClient import java.nio.ByteBuffer import java.nio.ByteOrder -import java.nio.DoubleBuffer +import java.util.function.BooleanSupplier inline val minecraft: Minecraft get() = Minecraft.getInstance() inline val font: Font get() = minecraft.font +fun Window.isKeyDown(key: Int) = InputConstants.isKeyDown(window, key) + +val Window.isShiftDown get() = isKeyDown(InputConstants.KEY_LSHIFT) || isKeyDown(InputConstants.KEY_RSHIFT) +val Window.isCtrlDown get() = isKeyDown(InputConstants.KEY_RCONTROL) || isKeyDown(InputConstants.KEY_LCONTROL) +val Window.isAltDown get() = isKeyDown(InputConstants.KEY_RALT) || isKeyDown(InputConstants.KEY_LALT) + +object ShiftPressedCond : BooleanSupplier { + override fun getAsBoolean(): Boolean { + if (isClient) { + return impl() + } + + return false + } + + private fun impl(): Boolean { + return minecraft.window.isShiftDown + } +} + +object ShiftDepressedCond : BooleanSupplier { + override fun getAsBoolean(): Boolean { + return !ShiftPressedCond.asBoolean + } +} + fun playGuiClickSound() { minecraft.soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f)) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt index 52b8581d2..0d812203e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientTickHandler.kt @@ -2,38 +2,35 @@ package ru.dbotthepony.mc.otm.client import net.minecraftforge.client.event.ClientPlayerNetworkEvent import net.minecraftforge.event.TickEvent -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.core.IConditionalTickable -import ru.dbotthepony.mc.otm.core.ITickable -import ru.dbotthepony.mc.otm.core.TickList -import ru.dbotthepony.mc.otm.core.TimerQueue +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable +import ru.dbotthepony.mc.otm.core.util.TickList import ru.dbotthepony.mc.otm.isClient private val preTickList = TickList() private val postTickList = TickList() -private val preTimerList = TimerQueue() -private val postTimerList = TimerQueue() + var LOGGED_IN = false private set fun onceClient(ticker: ITickable) { check(isClient) { "Illegal side" } - postTickList.add(ticker, LOGGED_IN, "Not logged in") + postTickList.once(ticker, LOGGED_IN, "Not logged in") } fun onceClientPre(ticker: ITickable) { check(isClient) { "Illegal side" } - preTickList.add(ticker, LOGGED_IN, "Not logged in") + preTickList.once(ticker, LOGGED_IN, "Not logged in") } fun onceClient(inTicks: Int, ticker: Runnable) { check(isClient) { "Illegal side" } - postTimerList.add(inTicks, ticker, LOGGED_IN, "Not logged in") + postTickList.timer(inTicks, ticker, LOGGED_IN, "Not logged in") } fun onceClientPre(inTicks: Int, ticker: Runnable) { check(isClient) { "Illegal side" } - preTimerList.add(inTicks, ticker, LOGGED_IN, "Not logged in") + preTickList.timer(inTicks, ticker, LOGGED_IN, "Not logged in") } fun tickClient(ticker: IConditionalTickable) { @@ -64,10 +61,8 @@ fun tickWhileClientPre(condition: () -> Boolean, ticker: () -> Unit) { fun onClientTick(event: TickEvent.ClientTickEvent) { if (event.phase == TickEvent.Phase.START) { - preTimerList.tick() preTickList.tick() } else { - postTimerList.tick() postTickList.tick() } } @@ -75,17 +70,13 @@ fun onClientTick(event: TickEvent.ClientTickEvent) { fun onClientDisconnected(event: ClientPlayerNetworkEvent.LoggingOut) { LOGGED_IN = false - preTimerList.clear() preTickList.clear() - postTimerList.clear() postTickList.clear() } fun onClientConnected(event: ClientPlayerNetworkEvent.LoggingIn) { LOGGED_IN = true - preTimerList.clear() preTickList.clear() - postTimerList.clear() postTickList.clear() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt index bdca96c1e..f4569b45d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/MatteryGUI.kt @@ -1,6 +1,9 @@ package ru.dbotthepony.mc.otm.client import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.gui.Font + import net.minecraft.client.gui.components.Button import net.minecraft.client.gui.screens.DeathScreen import net.minecraft.client.gui.screens.InBedChatScreen @@ -10,32 +13,51 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.effect.MobEffects import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.ShieldItem import net.minecraftforge.client.event.RenderGuiEvent import net.minecraftforge.client.event.RenderGuiOverlayEvent -import net.minecraftforge.client.event.RenderLevelLastEvent -import net.minecraftforge.client.event.RenderLevelStageEvent import net.minecraftforge.client.event.ScreenEvent import net.minecraftforge.client.gui.overlay.ForgeGui import net.minecraftforge.client.gui.overlay.GuiOverlayManager +import net.minecraftforge.common.ToolActions import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.render.* -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.core.formatPower +import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas +import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite +import ru.dbotthepony.mc.otm.config.ClientConfig +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.registry.AndroidFeatures import java.util.* +import kotlin.math.PI import kotlin.math.ceil object MatteryGUI { - private val BARS = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "player_bars"), 80f, 36f) - val CHARGE = BARS.subElement(height = 9f) - val CHARGE_BG = BARS.subElement(y = 9f, height = 9f) + private val BARS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/player_bars.png"), 81f, 36f) + val CHARGE = BARS.sprite(height = 9f) + val CHARGE_BG = BARS.sprite(y = 9f, height = 9f) - val CHARGE_HUNGER = BARS.subElement(y = 18f, height = 9f) - val CHARGE_HUNGER_BG = BARS.subElement(y = 27f, height = 9f) + val CHARGE_HUNGER = BARS.sprite(y = 18f, height = 9f) + val CHARGE_HUNGER_BG = BARS.sprite(y = 27f, height = 9f) + + private val BARS_HP = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/player_bars_health.png"), 81f, 63f) + + val HEALTH = BARS_HP.sprite(height = 9f) + val HEALTH_BG = BARS_HP.sprite(y = 9f, height = 9f) + val HEALTH_BG_NANOBOTS = BARS_HP.sprite(y = 18f, height = 9f) + + val HEALTH_POISON = BARS_HP.sprite(y = 27f, height = 9f) + val HEALTH_WITHER = BARS_HP.sprite(y = 36f, height = 9f) + val HEALTH_ABSORB = BARS_HP.sprite(y = 45f, height = 9f) + val HEALTH_FROZEN = BARS_HP.sprite(y = 54f, height = 9f) private var originalBedButtonX = -1 private var originalBedButtonY = -1 @@ -99,6 +121,10 @@ object MatteryGUI { GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "air_level")) } + private val PLAYER_HEALTH_ELEMENT by lazy { + GuiOverlayManager.findOverlay(ResourceLocation("minecraft", "player_health")) + } + var iteration = 0 var showIterationUntil = 0L var showIterationUntilFade = 0L @@ -116,7 +142,8 @@ object MatteryGUI { return } - val stack = event.poseStack + val guiGraphics = MGUIGraphics(event.poseStack) + val stack = guiGraphics.pose val window = event.window stack.pushPose() @@ -139,13 +166,17 @@ object MatteryGUI { pushScissorRect(0, (scissorBase + scissorHeight * (1f - progress)).toInt(), window.width, (scissorHeight * progress * 2f).toInt()) - setDrawColor(RGBAColor(1f, 1f, 1f, 0.4f)) - drawRect(stack, 0f, y - 12f, window.guiScaledWidth.toFloat(), 24f + (deathLog.size - 2f).coerceAtLeast(0f) * minecraft.font.lineHeight * modifyScale * 0.175f) + guiGraphics.renderRect( + 0f, + y - 12f, + window.guiScaledWidth.toFloat(), + 24f + (deathLog.size - 2f).coerceAtLeast(0f) * minecraft.font.lineHeight * modifyScale * 0.175f, + color = RGBAColor(1f, 1f, 1f, 0.4f)) val text = TranslatableComponent("otm.iteration", iteration) - minecraft.font.drawAligned(stack, text, TextAlign.CENTER_CENTER, x + 1f, y + 1f, RGBAColor.BLACK) - minecraft.font.drawAligned(stack, text, TextAlign.CENTER_CENTER, x, y, RGBAColor.WHITE) + guiGraphics.draw(text, x + 1f, y + 1f, color = RGBAColor.BLACK, gravity = RenderGravity.CENTER_CENTER) + guiGraphics.draw(text, x, y, color = RGBAColor.WHITE, gravity = RenderGravity.CENTER_CENTER) stack.scale(0.35f, 0.35f, 0.35f) @@ -161,8 +192,8 @@ object MatteryGUI { for (i in deathLog.indices.reversed()) { val component = deathLog[i] - minecraft.font.drawAligned(stack, component.second, TextAlign.CENTER_CENTER, x + 1f, y + 1f, RGBAColor.BLACK) - minecraft.font.drawAligned(stack, component.second, TextAlign.CENTER_CENTER, x, y, RGBAColor(color, color, color)) + guiGraphics.draw(component.second, x + 1f, y + 1f, color = RGBAColor.BLACK, gravity = RenderGravity.CENTER_CENTER) + guiGraphics.draw(component.second, x, y, color = RGBAColor(color, color, color), gravity = RenderGravity.CENTER_CENTER) y += minecraft.font.lineHeight color = (color - 0x20).coerceAtLeast(0x0) @@ -177,18 +208,10 @@ object MatteryGUI { showIteration(event) } - fun onLayerRenderEvent(event: RenderGuiOverlayEvent.Pre) { - if (event.overlay != FOOD_LEVEL_ELEMENT && event.overlay != AIR_LEVEL_ELEMENT) { - return - } - + private fun renderFoodAndAir(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) { val ply: LocalPlayer = minecraft.player ?: return - if (ply.vehicle is LivingEntity || - minecraft.options.hideGui || - minecraft.gameMode?.canHurtPlayer() == false || - minecraft.getCameraEntity() !is Player - ) { + if (ply.vehicle is LivingEntity) { return } @@ -196,24 +219,25 @@ object MatteryGUI { if (!ply.isAlive && mattery == null) { mattery = lastState + } else if (ply.isAlive && mattery != null) { + lastState = mattery } - val gui = minecraft.gui as? ForgeGui ?: return - if (mattery != null && mattery.isAndroid) { event.isCanceled = true - lastState = mattery if (event.overlay === AIR_LEVEL_ELEMENT) { return } + if (!gui.shouldDrawSurvivalElements()) return + var level: Float if (mattery.androidEnergy.maxBatteryLevel.isZero) { level = 0f } else { - level = mattery.androidEnergy.batteryLevel.div(mattery.androidEnergy.maxBatteryLevel).toFloat() + level = mattery.androidEnergy.batteryLevel.percentage(mattery.androidEnergy.maxBatteryLevel) if (level >= 0.98f) level = 1f @@ -228,25 +252,130 @@ object MatteryGUI { val top: Int = height - gui.rightHeight gui.rightHeight += 10 - val leftPadding = ceil(level * 79f - 0.5f) + val leftPadding = ceil(level * 80f - 0.5f) + + val guiGraphics = MGUIGraphics(event.poseStack) if (ply.hasEffect(MobEffects.HUNGER)) { - CHARGE_HUNGER_BG.render(event.poseStack, left.toFloat(), top.toFloat()) - CHARGE_HUNGER.renderPartial(event.poseStack, left.toFloat() - leftPadding + 79f, top.toFloat(), width = leftPadding) + CHARGE_HUNGER_BG.render(guiGraphics, left.toFloat(), top.toFloat()) + CHARGE_HUNGER.renderPartial(guiGraphics, left.toFloat() - leftPadding + 80f, top.toFloat(), width = leftPadding) } else { - CHARGE_BG.render(event.poseStack, left.toFloat(), top.toFloat()) - CHARGE.renderPartial(event.poseStack, left.toFloat() - leftPadding + 79f, top.toFloat(), width = leftPadding) + CHARGE_BG.render(guiGraphics, left.toFloat(), top.toFloat()) + CHARGE.renderPartial(guiGraphics, left.toFloat() - leftPadding + 80f, top.toFloat(), width = leftPadding) } val formattedPower = mattery.androidEnergy.batteryLevel.formatPower() - event.poseStack.pushPose() - event.poseStack.scale(0.5f, 0.5f, 0.5f) - - minecraft.font.drawAligned(event.poseStack, formattedPower, TextAlign.CENTER_LEFT, (left + 83f) * 2f, (top + 5.5f) * 2f, RGBAColor.BLACK.toInt()) - minecraft.font.drawAligned(event.poseStack, formattedPower, TextAlign.CENTER_LEFT, (left + 82f) * 2f, (top + 4.5f) * 2f, RGBAColor.YELLOW.toInt()) - - event.poseStack.popPose() + val scale = ClientConfig.HUD.BAR_TEXT_SCALE.toFloat() + guiGraphics.draw(formattedPower, left + CHARGE_BG.width + 2f + scale, top + CHARGE_BG.height / 2f + scale, font = gui.font, scale = scale, gravity = RenderGravity.CENTER_LEFT, color = RGBAColor.YELLOW, drawOutline = true) } } + + private fun getSpriteForPlayer(player: Player): MatterySprite { + if (player.hasEffect(MobEffects.POISON)) { + return HEALTH_POISON + } else if (player.hasEffect(MobEffects.WITHER)) { + return HEALTH_WITHER + } else if (player.isFullyFrozen) { + return HEALTH_FROZEN + } + + return HEALTH + } + + private fun getHealthColorForPlayer(player: Player): RGBAColor { + if (player.hasEffect(MobEffects.POISON)) { + return RGBAColor.DARK_GREEN + } else if (player.hasEffect(MobEffects.WITHER)) { + return RGBAColor.WHITE + } else if (player.isFullyFrozen) { + return RGBAColor.AQUA + } + + return RGBAColor.RED + } // можно вынести в конфиг, но для этого нужен селектор цвета + + private fun renderPlayerHealth(event: RenderGuiOverlayEvent.Pre, gui: ForgeGui) { + if (!ClientConfig.HUD.ANDROID_HEALTH_BAR) return + + val ply: LocalPlayer = minecraft.player ?: return + var mattery = ply.matteryPlayer + + if (!ply.isAlive && mattery == null) { + mattery = lastState + } else if (ply.isAlive && mattery != null) { + lastState = mattery + } + + if (mattery != null && mattery.isAndroid) { + event.isCanceled = true + + if (!gui.shouldDrawSurvivalElements()) return + + val level: Float = (ply.health / ply.maxHealth).coerceIn(0.0f, 1.0f) + val levelAbsorb: Float = (ply.absorptionAmount / ply.maxHealth).coerceIn(0.0f, 1.0f) + + gui.setupOverlayRenderState(true, false) + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f) + + val width = event.window.guiScaledWidth + val height = event.window.guiScaledHeight + val left = width / 2 - 10 - 81 + val top: Int = height - gui.leftHeight + gui.leftHeight += 10 + + val guiGraphics = MGUIGraphics(event.poseStack) + + HEALTH_BG.render(guiGraphics, left.toFloat(), top.toFloat()) + + if (mattery.hasFeature(AndroidFeatures.NANOBOTS_ARMOR)) { + val featArmor = mattery.getFeature(AndroidFeatures.NANOBOTS_ARMOR) as NanobotsArmorFeature + val levelArmor: Float = (featArmor.layers.toFloat() / (featArmor.strength + 1).toFloat()).coerceIn(0.0f, 1.0f) + + HEALTH_BG_NANOBOTS.renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(levelArmor * 81f)) + } + + getSpriteForPlayer(ply).renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(level * 80f - 0.5f)) + if (levelAbsorb > 0) { + HEALTH_ABSORB.renderPartial(guiGraphics, left.toFloat(), top.toFloat(), width = ceil(levelAbsorb * 80f - 0.5f)) + } + + var formattedHealth = TextComponent("%d/%d".format(ply.health.toInt(), ply.maxHealth.toInt())) + + if (ply.absorptionAmount > 0) + formattedHealth = TextComponent("%d+%d/%d".format(ply.health.toInt(), ply.absorptionAmount.toInt(), ply.maxHealth.toInt())) + + val scale = ClientConfig.HUD.BAR_TEXT_SCALE.toFloat() + guiGraphics.draw(formattedHealth, left - 2f, top + HEALTH_BG.height / 2f + 1f * scale, scale = scale, gravity = RenderGravity.CENTER_RIGHT, color = getHealthColorForPlayer(ply), drawOutline = true) + } + } + + fun onLayerRenderEvent(event: RenderGuiOverlayEvent.Pre) { + val gui = minecraft.gui as? ForgeGui ?: return + if (minecraft.options.hideGui || !gui.shouldDrawSurvivalElements()) return + + if (event.overlay == FOOD_LEVEL_ELEMENT || event.overlay == AIR_LEVEL_ELEMENT) { + renderFoodAndAir(event, gui) + } else if (event.overlay == PLAYER_HEALTH_ELEMENT) { + renderPlayerHealth(event, gui) + } + } + + fun renderShieldCooldownOverlay(pose: PoseStack, font: Font, stack: ItemStack, x: Int, y: Int): Boolean { + if (!stack.isEmpty && stack.item is ShieldItem) { + if (!stack.canPerformAction(ToolActions.SHIELD_BLOCK)) return false + + val ply = minecraft.player ?: return false + if (!ply.isUsingItem || stack != ply.useItem || ply.isBlocking) return false + + val percent = ((stack.item.getUseDuration(stack) - ply.useItemRemainingTicks + minecraft.partialTick) / 5f).coerceIn(0f, 1f) + RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f) + drawArc(pose, x + 8f, y + 8f, 8f, 0f, PI / 2.0, PI / 2.0 + PI * 2.0 * percent, alignAtCenter = true) + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + + return true + } + + return false + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AbstractSkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AbstractSkinElement.kt deleted file mode 100644 index 2805c30d2..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AbstractSkinElement.kt +++ /dev/null @@ -1,352 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import com.google.gson.JsonObject -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack -import com.mojang.blaze3d.vertex.VertexConsumer -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.core.linearInterpolation -import java.lang.ref.WeakReference - -sealed class AbstractSkinElement { - /** - * Expected image width in pixels, used in calculations - * and as default width argument in render methods - */ - abstract val width: Float - - /** - * Expected image height in pixels, used in calculations - * and as default height argument in render methods - */ - abstract val height: Float - - abstract val u0: Float - abstract val v0: Float - abstract val u1: Float - abstract val v1: Float - - internal open fun addListener(listener: AbstractSkinElement) { - // no-op by default - } - - internal open fun parentChanges(parent: AbstractSkinElement) { - - } - - fun partialU(offset: Float): Float { - return u0 + (offset / width) * (u1 - u0) - } - - fun partialV(offset: Float): Float { - return v0 + (offset / height) * (v1 - v0) - } - - abstract val type: SkinElementType - - open val winding: UVWindingOrder get() = UVWindingOrder.NORMAL - - abstract val texture: ResourceLocation - - /** - * See [ru.dbotthepony.mc.otm.client.render.clearDepth] - */ - fun clearDepth( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - ) = ru.dbotthepony.mc.otm.client.render.clearDepth(stack, x, y, width, height) - - @JvmOverloads - fun render( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - winding: UVWindingOrder = this.winding - ) { - RenderSystem.setShaderTexture(0, texture) - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - - renderRaw(stack, x, y, width, height, winding) - } - - @JvmOverloads - fun render( - stack: PoseStack, - x: Double, - y: Double = 0.0, - width: Double = this.width.toDouble(), - height: Double = this.height.toDouble(), - winding: UVWindingOrder = this.winding - ) = render(stack, x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), winding) - - @JvmOverloads - fun renderPartial( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - winding: UVWindingOrder = this.winding - ) { - RenderSystem.setShaderTexture(0, texture) - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - - renderRawPartial(stack, x, y, width, height, winding) - } - - @JvmOverloads - fun renderPartial( - stack: PoseStack, - x: Double, - y: Double = 0.0, - width: Double = this.width.toDouble(), - height: Double = this.height.toDouble(), - winding: UVWindingOrder = this.winding - ) = renderPartial(stack, x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), winding) - - @JvmOverloads - fun renderWidth( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - winding: UVWindingOrder = this.winding - ) { - render(stack, x, y, width = width, winding = winding) - } - - @JvmOverloads - fun renderWidth( - stack: PoseStack, - x: Double, - y: Double = 0.0, - width: Double = this.width.toDouble(), - winding: UVWindingOrder = this.winding - ) = renderWidth(stack, x.toFloat(), y.toFloat(), width.toFloat(), winding) - - @JvmOverloads - fun renderHeight( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - height: Float = this.height, - winding: UVWindingOrder = this.winding - ) { - render(stack, x, y, height = height, winding = winding) - } - - @JvmOverloads - fun renderHeight( - stack: PoseStack, - x: Double, - y: Double = 0.0, - height: Double = this.height.toDouble(), - winding: UVWindingOrder = this.winding - ) = renderHeight(stack, x.toFloat(), y.toFloat(), height.toFloat(), winding) - - - @JvmOverloads - fun renderRaw( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - winding: UVWindingOrder = this.winding - ) { - val winded = winding.translate(u0, v0, u1, v1) - - drawTexturedRect( - stack, - x, - y, - width, - height, - winded, - ) - } - - @JvmOverloads - fun renderRaw( - stack: PoseStack, - x: Double, - y: Double = 0.0, - width: Double = this.width.toDouble(), - height: Double = this.height.toDouble(), - winding: UVWindingOrder = this.winding - ) = renderRaw(stack, x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), winding) - - @JvmOverloads - fun renderRawPartial( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - winding: UVWindingOrder = this.winding - ) { - val u1 = (u0 + linearInterpolation(width / this.width, 0f, u1 - u0)).coerceAtLeast(0f).coerceAtMost(1f) - val v1 = (v0 + linearInterpolation(height / this.height, 0f, v1 - v0)).coerceAtLeast(0f).coerceAtMost(1f) - - val winded = winding.translate(u0, v0, u1, v1) - - drawTexturedRect( - stack, - x, - y, - width, - height, - winded, - ) - } - - @JvmOverloads - fun renderRawPartial( - stack: PoseStack, - x: Double, - y: Double = 0.0, - width: Double = this.width.toDouble(), - height: Double = this.height.toDouble(), - winding: UVWindingOrder = this.winding - ) = renderRawPartial(stack, x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), winding) - - protected fun uploadOnto( - pose: PoseStack, - builder: VertexConsumer, - x: Float, - y: Float, - width: Float, - height: Float, - z: Float, - color: RGBAColor?, - u0: Float, - v0: Float, - u1: Float, - v1: Float, - ) { - val intColor = color?.toARGB() ?: 0 - val matrix = pose.last().pose() - builder.vertex(matrix, x, y + height, z).also { if (color != null) it.color(intColor) }.uv(u0, v1).endVertex() - builder.vertex(matrix, x + width, y + height, z).also { if (color != null) it.color(intColor) }.uv(u1, v1).endVertex() - builder.vertex(matrix, x + width, y, z).also { if (color != null) it.color(intColor) }.uv(u1, v0).endVertex() - builder.vertex(matrix, x, y, z).also { if (color != null) it.color(intColor) }.uv(u0, v0).endVertex() - } - - @JvmOverloads - fun uploadOnto( - pose: PoseStack, - builder: VertexConsumer, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - z: Float = 0f, - color: RGBAColor? = null, - winding: UVWindingOrder = this.winding, - ) { - val (u0, v0, u1, v1) = winding.translate(u0, v0, u1, v1) - uploadOnto(pose, builder, x, y, width, height, z, color, u0, v0, u1, v1) - } - - @JvmOverloads - fun uploadOntoPartial( - pose: PoseStack, - builder: VertexConsumer, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - z: Float = 0f, - color: RGBAColor? = null, - winding: UVWindingOrder = this.winding, - ) { - val u1 = (u0 + linearInterpolation(width / this.width, 0f, u1 - u0)).coerceAtLeast(0f).coerceAtMost(1f) - val v1 = (v0 + linearInterpolation(height / this.height, 0f, v1 - v0)).coerceAtLeast(0f).coerceAtMost(1f) - val (u0_, v0_, u1_, v1_) = winding.translate(u0, v0, u1, v1) - - uploadOnto(pose, builder, x, y, width, height, z, color, u0_, v0_, u1_, v1_) - } - - @JvmOverloads - fun uploadOntoPartialColor( - pose: PoseStack, - builder: VertexConsumer, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - z: Float = 0f, - color: RGBAColor = RGBAColor.WHITE, - winding: UVWindingOrder = this.winding, - ) { - uploadOntoPartial(pose, builder, x, y, width, height, z, color, winding) - } - - @JvmOverloads - fun uploadOntoColor( - pose: PoseStack, - builder: VertexConsumer, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, - z: Float = 0f, - color: RGBAColor = RGBAColor.WHITE, - winding: UVWindingOrder = this.winding, - ) { - uploadOnto(pose, builder, x, y, width, height, z, color, winding) - } - - fun toJson(): JsonObject { - return type.toActualJson(this) - } - - fun toNetwork(buff: FriendlyByteBuf) { - type.toActualNetwork(this, buff) - } - - abstract class Mutable : AbstractSkinElement() { - private val listeners = ArrayList>(0) - - override fun addListener(listener: AbstractSkinElement) { - synchronized(listeners) { - val iterator = listeners.listIterator() - - for (ref in iterator) { - if (ref.get() == null) { - iterator.remove() - } else if (ref.get() === listener) { - return - } - } - - listeners.add(WeakReference(listener)) - } - } - - protected fun notifyListeners() { - synchronized(listeners) { - val iterator = listeners.listIterator() - - for (ref in iterator) { - val value = ref.get() - - if (value == null) { - iterator.remove() - } else { - value.parentChanges(this) - } - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AtlasSkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AtlasSkinElement.kt deleted file mode 100644 index be2bbc42b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/AtlasSkinElement.kt +++ /dev/null @@ -1,215 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.DefaultVertexFormat -import com.mojang.blaze3d.vertex.VertexFormat -import net.minecraft.client.renderer.GameRenderer -import net.minecraft.client.renderer.RenderStateShard -import net.minecraft.client.renderer.RenderStateShard.TextureStateShard -import net.minecraft.client.renderer.RenderType -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.resources.ResourceLocation -import net.minecraft.util.Mth -import org.lwjgl.opengl.GL11.GL_ALWAYS -import ru.dbotthepony.mc.otm.isClient -import java.lang.ref.WeakReference -import java.util.stream.Stream - -class AtlasSkinElement( - val location: ResourceLocation, - override val width: Float, - override val height: Float, - val spriteWidth: Float = Mth.smallestEncompassingPowerOfTwo(width.toInt()).toFloat(), - val spriteHeight: Float = Mth.smallestEncompassingPowerOfTwo(height.toInt()).toFloat(), - override val winding: UVWindingOrder = UVWindingOrder.NORMAL, -) : AbstractSkinElement.Mutable() { - init { - synchronized(keys) { - if (keys.add(location)) { - if (isWidgetAtlasAvailable) { - queueRebuild() - } - } - } - - // optimistic: we should not create a lot of instances of this class - synchronized(atlasListeners) { - atlasListeners.add(WeakReference(this)) - val iterator = atlasListeners.listIterator() - - for (ref in iterator) { - val value = ref.get() - - if (value == null) { - iterator.remove() - } - } - } - - if (isWidgetAtlasAvailable) { - earlyBindingImpl() - } - } - - private fun earlyBindingImpl() { - if (WidgetAtlasHolder.INSTANCE.once) { - textureAtlasSpriteImpl - } - } - - private var changeset = -1 - private var _textureAtlasSprite: TextureAtlasSprite? = null - - private val textureAtlasSpriteImpl: TextureAtlasSprite get(): TextureAtlasSprite { - check(isClient) { "Invalid realm" } - val _textureAtlasSprite = _textureAtlasSprite - - if ( - _textureAtlasSprite == null || - _textureAtlasSprite.name.let { it.namespace == "minecraft" && it.path == "missingno" } || - changeset != WidgetAtlasHolder.INSTANCE.changeset - ) { - val get = WidgetAtlasHolder.INSTANCE.getSprite(location) - this._textureAtlasSprite = get - changeset = WidgetAtlasHolder.INSTANCE.changeset - - u0 = get.u0 - v0 = get.v0 - u1 = get.u1 - (get.u1 - get.u0) * (1f - width / spriteWidth) - v1 = get.v1 - (get.v1 - get.v0) * (1f - height / spriteHeight) - - notifyListeners() - - return get - } - - return _textureAtlasSprite - } - - val textureAtlasSprite: TextureAtlasSprite get(): TextureAtlasSprite { - check(isWidgetAtlasAvailable) { "Atlas is not available; either we are not a client, or we are running datagen" } - return textureAtlasSpriteImpl - } - - override var u0 = 0f - private set - override var v0 = 0f - private set - override var u1 = 0f - private set - override var v1 = 0f - private set - - override val texture: ResourceLocation get() = WidgetAtlasHolder.LOCATION - - override fun equals(other: Any?): Boolean { - if (other is AtlasSkinElement) - return location == other.location - - return super.equals(other) - } - - override fun hashCode(): Int { - return WidgetAtlasHolder.INSTANCE.hashCode() xor location.hashCode() - } - - override fun toString(): String { - return "AtlasSkinElement[$location]" - } - - override val type: SkinElementType - get() = SkinElementType.ATLAS - - companion object { - private val keys = HashSet() - private val atlasListeners = ArrayList>() - val keysStream: Stream get() = keys.stream() - - fun notifyListeners() { - synchronized(atlasListeners) { - val iterator = atlasListeners.listIterator() - - for (ref in iterator) { - val value = ref.get() - - if (value == null) { - iterator.remove() - } else { - value.textureAtlasSpriteImpl - } - } - } - } - - private fun queueRebuild() { - WidgetAtlasHolder.INSTANCE.queueRebuild() - } - - val renderTypeNoDepth by lazy { - val builder = RenderType.CompositeState.builder() - - builder.setTextureState(TextureStateShard(WidgetAtlasHolder.LOCATION, false, false)) - builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) - builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_ALWAYS)) - builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - }, { - RenderSystem.disableBlend() - })) - - @Suppress("INACCESSIBLE_TYPE") - RenderType.create("otm_gui_element_no_depth", - DefaultVertexFormat.POSITION_COLOR_TEX, - VertexFormat.Mode.QUADS, - 2048, - false, - false, - builder.createCompositeState(false)) as RenderType - } - - val renderTypeDepth by lazy { - val builder = RenderType.CompositeState.builder() - - builder.setTextureState(TextureStateShard(WidgetAtlasHolder.LOCATION, false, false)) - builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) - builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - }, { - RenderSystem.disableBlend() - })) - - @Suppress("INACCESSIBLE_TYPE") - RenderType.create("otm_gui_element_depth", - DefaultVertexFormat.POSITION_COLOR_TEX, - VertexFormat.Mode.QUADS, - 2048, - false, - false, - builder.createCompositeState(false)) as RenderType - } - - val renderTypeWorld by lazy { - val builder = RenderType.CompositeState.builder() - - builder.setTextureState(TextureStateShard(WidgetAtlasHolder.LOCATION, false, false)) - builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) - builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - }, { - RenderSystem.disableBlend() - })) - - @Suppress("INACCESSIBLE_TYPE") - RenderType.create("otm_gui_element_world", - DefaultVertexFormat.POSITION_COLOR_TEX, - VertexFormat.Mode.QUADS, - 8192, - false, - false, - builder.createCompositeState(false)) as RenderType - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt index 2e54a7fee..53c1f89e7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/DynamicBufferSource.kt @@ -3,11 +3,13 @@ package ru.dbotthepony.mc.otm.client.render import com.google.common.collect.ImmutableList import com.mojang.blaze3d.vertex.BufferBuilder import com.mojang.blaze3d.vertex.VertexConsumer +import it.unimi.dsi.fastutil.ints.IntArrays import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ReferenceArraySet import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.Sheets +import org.joml.Vector3f private fun equals(existing: ImmutableList?, types: Array): Boolean { if (types.isEmpty()) { @@ -62,7 +64,8 @@ private fun equals(existing: ImmutableList?, types: ImmutableList= 0) { "Invalid minimal buffer size $minimalInitialBufferSize" } require(maximalInitialBufferSize >= minimalInitialBufferSize) { "Maximal buffer size $maximalInitialBufferSize must be greater or equal to minimal buffer size $minimalInitialBufferSize" } @@ -142,10 +145,6 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit it.add(next) next = State(RenderType.waterMask(), ImmutableList.of(next.type), true, false) it.add(next) - - it.add(State(AtlasSkinElement.renderTypeDepth, ImmutableList.of(RenderType.waterMask()), true)) - it.add(State(AtlasSkinElement.renderTypeNoDepth, ImmutableList.of(RenderType.waterMask()), true)) - it.add(State(AtlasSkinElement.renderTypeWorld, ImmutableList.of(Sheets.signSheet()), true)) } private fun determineHeight(input: State, seen: MutableSet) { @@ -353,7 +352,7 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit for (state in bufferList) { if (state.dirty) { state.dirty = false - state.type.end(state.builder, 0, 0, 0) + state.type.end(state.builder, sortX, sortY, sortZ) } } } @@ -363,7 +362,7 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit if (state.dirty) { state.dirty = false - type.end(state.builder, 0, 0, 0) + type.end(state.builder, sortX, sortY, sortZ) } } @@ -372,7 +371,7 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit if (state.dirty) { state.dirty = false - type.end(state.builder, 0, 0, 0) + type.end(state.builder, sortX, sortY, sortZ) } for (ustate in state.dependants) { @@ -383,7 +382,7 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit private fun endBatchChain(state: State) { if (state.dirty) { state.dirty = false - state.type.end(state.builder, 0, 0, 0) + state.type.end(state.builder, sortX, sortY, sortZ) } for (ustate in state.dependants) { @@ -392,7 +391,9 @@ class DynamicBufferSource(val minimalInitialBufferSize: Int = 0, val maximalInit } companion object { + @JvmField val GUI = DynamicBufferSource(maximalInitialBufferSize = 2 shl 8) + @JvmField val WORLD = DynamicBufferSource(minimalInitialBufferSize = 2 shl 12) /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt deleted file mode 100644 index 5f25a168d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Ext.kt +++ /dev/null @@ -1,266 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import com.mojang.blaze3d.vertex.PoseStack -import com.mojang.blaze3d.vertex.Tesselator -import com.mojang.blaze3d.vertex.VertexConsumer -import com.mojang.math.Matrix4f -import com.mojang.math.Vector3f -import net.minecraft.client.gui.Font -import net.minecraft.client.renderer.MultiBufferSource -import net.minecraft.core.Vec3i -import net.minecraft.network.chat.Component -import net.minecraft.util.FormattedCharSequence -import ru.dbotthepony.mc.otm.core.* -import kotlin.math.roundToInt - -val tesselator: Tesselator get() = Tesselator.getInstance() - -fun VertexConsumer.normal(vector: Vector): VertexConsumer = normal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) -fun VertexConsumer.vertex(matrix4f: Matrix4f, vector: Vector): VertexConsumer = vertex(matrix4f, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) -fun VertexConsumer.color(color: RGBAColor): VertexConsumer = color(color.red, color.green, color.blue, color.alpha) - -fun PoseStack.translate(vector: Vector) = translate(vector.x, vector.y, vector.z) -fun PoseStack.translate(vector: Vec3i) = translate(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble()) -fun PoseStack.translate(vector: Vector3f) = last().pose().multiplyWithTranslation(vector.x(), vector.y(), vector.z()) - -fun PoseStack.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { - val last = last() - last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) - last.normal().mul(axis.rotateAroundThis(rotation, isDegrees)) -} - -fun PoseStack.rotateAroundPoint(point: Vector, rotation: IAngle) { - val last = last() - last.pose().rotateAroundPoint(point, rotation) - // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) -} - -fun PoseStack.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { - val last = last() - last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) - last.normal().mul(axis.rotateAroundThis(rotation, isDegrees)) -} - -fun PoseStack.rotateAroundPoint(point: Vector3f, rotation: IAngle) { - val last = last() - last.pose().rotateAroundPoint(point, rotation) - // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) -} - -fun PoseStack.translation(): Vector3f { - return last().pose().translation -} - -inline val PoseStack.last: PoseStack.Pose get() = last() -inline val PoseStack.Pose.pose: Matrix4f get() = pose() - -enum class TextAlign { - TOP_LEFT, - TOP_CENTER, - TOP_RIGHT, - - CENTER_LEFT, - CENTER_CENTER, - CENTER_RIGHT, - - BOTTOM_LEFT, - BOTTOM_CENTER, - BOTTOM_RIGHT, -} - -private fun Font.drawScaledDuckTyped(poseStack: PoseStack, text: Any, scale: Float, x: Float, y: Float, color: Int): Int { - val translation = poseStack.translation() - - poseStack.pushPose() - poseStack.translate(-translation) - poseStack.scale(scale, scale, scale) - val inv = 1f / scale - poseStack.translate(-translation * inv) - val size = drawDuckTyped(poseStack, text, x * inv, y * inv, color) - poseStack.popPose() - - return size -} - -private fun Font.drawScaledDuckTyped(poseStack: PoseStack, buffer: MultiBufferSource, text: Any, scale: Float, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0): Int { - val translation = poseStack.translation() - - poseStack.pushPose() - poseStack.translate(-translation) - poseStack.scale(scale, scale, scale) - val inv = 1f / scale - poseStack.translate(-translation * inv) - val size = drawDuckTyped(poseStack, buffer, text, x * inv, y * inv, color, drawShadow, seeThrough, packedLightCoords, effectColor) - poseStack.popPose() - - return size -} - -private fun Font.drawDuckTyped(poseStack: PoseStack, text: Any, x: Float, y: Float, color: Int): Int { - return when (text) { - is Component -> draw(poseStack, text, x, y, color) - is String -> draw(poseStack, text, x, y, color) - is FormattedCharSequence -> draw(poseStack, text, x, y, color) - else -> throw ClassCastException(text::class.qualifiedName) - } -} - -private fun Font.drawDuckTyped( - poseStack: PoseStack, - buffer: MultiBufferSource, - text: Any, - x: Float, - y: Float, - color: Int, - drawShadow: Boolean = false, - seeThrough: Boolean = false, - packedLightCoords: Int = 15728880, - effectColor: Int = 0 -): Int { - if (drawShadow) { - poseStack.pushPose() - } - - val result = when (text) { - is Component -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, seeThrough, effectColor, packedLightCoords) - is String -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, seeThrough, effectColor, packedLightCoords) - is FormattedCharSequence -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, seeThrough, effectColor, packedLightCoords) - else -> throw ClassCastException(text::class.qualifiedName) - } - - if (drawShadow) { - poseStack.popPose() - } - - return result -} - -private fun Font.widthDuckTyped(text: Any): Int { - return when (text) { - is Component -> width(text) - is String -> width(text) - is FormattedCharSequence -> width(text) - else -> throw ClassCastException(text::class.qualifiedName) - } -} - -private fun Font.drawAlignedDuckTyped(poseStack: PoseStack, text: Any, align: TextAlign, x: Float, y: Float, color: Int): Int { - return when (align) { - TextAlign.TOP_LEFT -> drawDuckTyped(poseStack, text, x, y, color) - TextAlign.TOP_CENTER -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), y, color) - TextAlign.TOP_RIGHT -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), y, color) - - TextAlign.CENTER_LEFT -> drawDuckTyped(poseStack, text, x, (y - lineHeight / 2f).roundToInt().toFloat(), color) - TextAlign.CENTER_CENTER -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), (y - lineHeight / 2f).roundToInt().toFloat(), color) - TextAlign.CENTER_RIGHT -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), (y - lineHeight / 2f).roundToInt().toFloat(), color) - - TextAlign.BOTTOM_LEFT -> drawDuckTyped(poseStack, text, x, (y - lineHeight).roundToInt().toFloat(), color) - TextAlign.BOTTOM_CENTER -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), (y - lineHeight).roundToInt().toFloat(), color) - TextAlign.BOTTOM_RIGHT -> drawDuckTyped(poseStack, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), (y - lineHeight).roundToInt().toFloat(), color) - } -} - -private fun Font.drawAlignedDuckTyped( - poseStack: PoseStack, - buffer: MultiBufferSource, - text: Any, - align: TextAlign, - x: Float, - y: Float, - color: Int, - drawShadow: Boolean = false, - seeThrough: Boolean = false, - packedLightCoords: Int = 15728880, - effectColor: Int = 0 -): Int { - return when (align) { - TextAlign.TOP_LEFT -> drawDuckTyped(poseStack, buffer, text, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.TOP_CENTER -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.TOP_RIGHT -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - - TextAlign.CENTER_LEFT -> drawDuckTyped(poseStack, buffer, text, x, (y - lineHeight / 2f).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.CENTER_CENTER -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), (y - lineHeight / 2f).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.CENTER_RIGHT -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), (y - lineHeight / 2f).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - - TextAlign.BOTTOM_LEFT -> drawDuckTyped(poseStack, buffer, text, x, (y - lineHeight).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.BOTTOM_CENTER -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text) / 2f).roundToInt().toFloat(), (y - lineHeight).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.BOTTOM_RIGHT -> drawDuckTyped(poseStack, buffer, text, (x - widthDuckTyped(text)).roundToInt().toFloat(), (y - lineHeight).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - } -} - -private fun Font.drawScaledAlignedDuckTyped(poseStack: PoseStack, text: Any, scale: Float, align: TextAlign, x: Float, y: Float, color: Int): Int { - return when (align) { - TextAlign.TOP_LEFT -> drawScaledDuckTyped(poseStack, text, scale, x, y, color) - TextAlign.TOP_CENTER -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), y, color) - TextAlign.TOP_RIGHT -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), y, color) - - TextAlign.CENTER_LEFT -> drawScaledDuckTyped(poseStack, text, scale, x, (y - lineHeight / 2f * scale).roundToInt().toFloat(), color) - TextAlign.CENTER_CENTER -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), (y - lineHeight * scale / 2f).roundToInt().toFloat(), color) - TextAlign.CENTER_RIGHT -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), (y - lineHeight * scale / 2f).roundToInt().toFloat(), color) - - TextAlign.BOTTOM_LEFT -> drawScaledDuckTyped(poseStack, text, scale, x, (y - lineHeight * scale).roundToInt().toFloat(), color) - TextAlign.BOTTOM_CENTER -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), (y - lineHeight * scale).roundToInt().toFloat(), color) - TextAlign.BOTTOM_RIGHT -> drawScaledDuckTyped(poseStack, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), (y - lineHeight * scale).roundToInt().toFloat(), color) - } -} - -private fun Font.drawScaledAlignedDuckTyped( - poseStack: PoseStack, - buffer: MultiBufferSource, - text: Any, - scale: Float, - align: TextAlign, - x: Float, - y: Float, - color: Int, - drawShadow: Boolean = false, - seeThrough: Boolean = false, - packedLightCoords: Int = 15728880, - effectColor: Int = 0 -): Int { - return when (align) { - TextAlign.TOP_LEFT -> drawScaledDuckTyped(poseStack, buffer, text, scale, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.TOP_CENTER -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.TOP_RIGHT -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - - TextAlign.CENTER_LEFT -> drawScaledDuckTyped(poseStack, buffer, text, scale, x, (y - lineHeight / 2f * scale).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.CENTER_CENTER -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), (y - lineHeight * scale / 2f).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.CENTER_RIGHT -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), (y - lineHeight * scale / 2f).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - - TextAlign.BOTTOM_LEFT -> drawScaledDuckTyped(poseStack, buffer, text, scale, x, (y - lineHeight * scale).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.BOTTOM_CENTER -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale / 2f).roundToInt().toFloat(), (y - lineHeight * scale).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - TextAlign.BOTTOM_RIGHT -> drawScaledDuckTyped(poseStack, buffer, text, scale, (x - widthDuckTyped(text) * scale).roundToInt().toFloat(), (y - lineHeight * scale).roundToInt().toFloat(), color, drawShadow, seeThrough, packedLightCoords, effectColor) - } -} - -fun Font.drawAligned(poseStack: PoseStack, text: String, align: TextAlign, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color) -fun Font.drawAligned(poseStack: PoseStack, text: Component, align: TextAlign, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color) -fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color) - -fun Font.drawAligned(poseStack: PoseStack, text: String, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toInt()) -fun Font.drawAligned(poseStack: PoseStack, text: Component, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toInt()) -fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toInt()) - -fun Font.drawScaledAligned(poseStack: PoseStack, text: String, scale: Float, align: TextAlign, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color) -fun Font.drawScaledAligned(poseStack: PoseStack, text: Component, scale: Float, align: TextAlign, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color) -fun Font.drawScaledAligned(poseStack: PoseStack, text: FormattedCharSequence, scale: Float, align: TextAlign, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color) - -fun Font.drawScaledAligned(poseStack: PoseStack, text: String, scale: Float, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toInt()) -fun Font.drawScaledAligned(poseStack: PoseStack, text: Component, scale: Float, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toInt()) -fun Font.drawScaledAligned(poseStack: PoseStack, text: FormattedCharSequence, scale: Float, align: TextAlign, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toInt()) - -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, align: TextAlign, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, align: TextAlign, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, align: TextAlign, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) - -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, scale: Float, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, scale: Float, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, scale: Float, align: TextAlign, x: Float, y: Float, color: Int, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, seeThrough, packedLightCoords, effectColor) - -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, scale: Float, align: TextAlign, x: Float, y: Float, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, scale: Float, align: TextAlign, x: Float, y: Float, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) -fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, scale: Float, align: TextAlign, x: Float, y: Float, drawShadow: Boolean = false, seeThrough: Boolean = false, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toInt(), drawShadow, seeThrough, packedLightCoords, effectColor) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/FontRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/FontRenderer.kt new file mode 100644 index 000000000..edcd8de6f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/FontRenderer.kt @@ -0,0 +1,374 @@ +package ru.dbotthepony.mc.otm.client.render + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.gui.Font +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.network.chat.Component +import net.minecraft.util.FormattedCharSequence +import org.joml.Matrix4f +import ru.dbotthepony.mc.otm.core.FloatSupplier +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 + +private val buffer = DynamicBufferSource() +private fun buffer() = buffer + +private fun Font.drawInBatch( + text: Any, x: Float, y: Float, color: Int, drawShadow: Boolean, + matrix4f: Matrix4f, buffer: MultiBufferSource, displayMode: Boolean, + effectColor: Int, packedLightCoords: Int, gravity: RenderGravity, + rounding: GravityRounding, width: FloatSupplier +): Int { + if (text is String) + return drawInBatch( + text, + gravity.x(x, width, rounding = rounding), + gravity.y(y, lineHeight.toFloat(), rounding = rounding), + color, + drawShadow, + matrix4f, + buffer, + displayMode, + effectColor, + packedLightCoords) + else if (text is Component) + return drawInBatch( + text, + gravity.x(x, width, rounding = rounding), + gravity.y(y, lineHeight.toFloat(), rounding = rounding), + color, + drawShadow, + matrix4f, + buffer, + displayMode, + effectColor, + packedLightCoords) + else if (text is FormattedCharSequence) + return drawInBatch( + text, + gravity.x(x, width, rounding = rounding), + gravity.y(y, lineHeight.toFloat(), rounding = rounding), + color, + drawShadow, + matrix4f, + buffer, + displayMode, + effectColor, + packedLightCoords) + else + throw IllegalArgumentException(text::class.qualifiedName) +} + +private val outlinePositions = listOf( + 1 to 0, + -1 to 0, + 0 to 1, + 0 to -1, +) + +private class CachedTextWidth0(val font: Font, val text: Component) : FloatSupplier { + private var computed = false + private var width = 0f + + override fun getAsFloat(): Float { + if (!computed) { + width = font.width(text).toFloat() + computed = true + } + + return width + } +} + +private class CachedTextWidth1(val font: Font, val text: String) : FloatSupplier { + private var computed = false + private var width = 0f + + override fun getAsFloat(): Float { + if (!computed) { + width = font.width(text).toFloat() + computed = true + } + + return width + } +} + +private class CachedTextWidth2(val font: Font, val text: FormattedCharSequence) : FloatSupplier { + private var computed = false + private var width = 0f + + override fun getAsFloat(): Float { + if (!computed) { + width = font.width(text).toFloat() + computed = true + } + + return width + } +} + +private fun Font.drawInternal( + poseStack: PoseStack, + text: Any, + x: Float, + y: Float, + buffer: MultiBufferSource, + gravity: RenderGravity, + scale: Float, + color: RGBAColor, + drawShadow: Boolean, + displayMode: Boolean, + packedLightCoords: Int, + effectColor: Int, + shadowColor: RGBAColor, + shadowX: Float, + shadowY: Float, + shadowZ: Float, + customShadow: Boolean, + rounding: GravityRounding, + drawOutline: Boolean, + outlineColor: RGBAColor, + outlineZ: Float, +): Float { + val inv: Float + + if (scale != 1f) { + poseStack.pushPose() + + val (_x, _y, _z) = poseStack.translation() + + poseStack.translate(-_x, -_y, -_z) + poseStack.scale(scale, scale, scale) + inv = 1f / scale + poseStack.translate(_x * inv, _y * inv, _z * inv) + } else { + inv = 1f + } + + val cache = if (text is Component) + CachedTextWidth0(this, text) + else if (text is String) + CachedTextWidth1(this, text) + else if (text is FormattedCharSequence) + CachedTextWidth2(this, text) + else + throw IllegalArgumentException(text::class.qualifiedName) + + if (customShadow) { + if (shadowZ != 0f) { + poseStack.translate(0f, 0f, shadowZ) + } + + drawInBatch( + text, + (x + shadowX) * inv, + (y + shadowY) * inv, + shadowColor.toRGB(), + false, + poseStack.last().pose(), + buffer, + displayMode, + effectColor, + packedLightCoords, + gravity, rounding, cache) + + if (shadowZ != 0f) { + poseStack.translate(0f, 0f, -shadowZ) + } + } + + if (drawOutline) { + if (outlineZ != 0f) { + poseStack.translate(0f, 0f, outlineZ) + } + + for ((_x, _y) in outlinePositions) { + drawInBatch( + text, + x * inv + _x, + y * inv + _y, + outlineColor.toRGB(), + drawShadow, + poseStack.last().pose(), + buffer, + displayMode, + effectColor, + packedLightCoords, + gravity, rounding, cache) + } + + if (outlineZ != 0f) { + poseStack.translate(0f, 0f, -outlineZ) + } + } + + val width = drawInBatch( + text, + x * inv, + y * inv, + color.toRGB(), + drawShadow, + poseStack.last().pose(), + buffer, + displayMode, + effectColor, + packedLightCoords, + gravity, rounding, cache) + + if (scale != 1f) { + poseStack.popPose() + } + + if (buffer === buffer()) { + buffer.endBatch() + } + + return width * scale +} + +fun Font.draw( + poseStack: PoseStack, + text: Component, + x: Float = 0f, + y: Float = 0f, + buffer: MultiBufferSource = buffer(), + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = 0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = 0.1f, +): Float { + return drawInternal( + poseStack = poseStack, + text = text, + x = x, + y = y, + buffer = buffer, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) +} + +fun Font.draw( + poseStack: PoseStack, + text: String, + x: Float = 0f, + y: Float = 0f, + buffer: MultiBufferSource = buffer(), + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = 0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = 0.1f, +): Float { + return drawInternal( + poseStack = poseStack, + text = text, + x = x, + y = y, + buffer = buffer, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) +} + +fun Font.draw( + poseStack: PoseStack, + text: FormattedCharSequence, + x: Float = 0f, + y: Float = 0f, + buffer: MultiBufferSource = buffer(), + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = 0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = 0.1f, +): Float { + return drawInternal( + poseStack = poseStack, + text = text, + x = x, + y = y, + buffer = buffer, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt index a85eec9da..34c5747a1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/GlitchRenderer.kt @@ -1,85 +1,26 @@ package ru.dbotthepony.mc.otm.client.render -import com.google.common.collect.ImmutableList import com.mojang.blaze3d.pipeline.MainTarget import com.mojang.blaze3d.platform.GlConst import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.BufferBuilder -import com.mojang.blaze3d.vertex.BufferUploader -import com.mojang.blaze3d.vertex.DefaultVertexFormat -import com.mojang.blaze3d.vertex.Tesselator -import com.mojang.blaze3d.vertex.VertexBuffer -import com.mojang.blaze3d.vertex.VertexFormat -import com.mojang.math.Matrix4f -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap -import it.unimi.dsi.fastutil.ints.Int2ObjectFunction +import com.mojang.blaze3d.vertex.* import net.minecraft.client.Minecraft import net.minecraft.client.renderer.FogRenderer import net.minecraft.client.renderer.GameRenderer -import net.minecraft.core.Vec3i import net.minecraft.world.level.levelgen.XoroshiroRandomSource import net.minecraft.world.level.material.FogType -import org.lwjgl.opengl.GL14 -import ru.dbotthepony.mc.otm.ClientConfig +import org.joml.Matrix4f import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 -import ru.dbotthepony.mc.otm.core.linearInterpolation +import ru.dbotthepony.mc.otm.core.math.linearInterpolation import ru.dbotthepony.mc.otm.milliTime import java.lang.ref.WeakReference -import java.util.stream.Collectors import kotlin.math.absoluteValue -import kotlin.math.ceil -import kotlin.math.pow @Suppress("SameParameterValue") object GlitchRenderer { - private abstract class VideoGlitchType { - abstract fun upload(builder: BufferBuilder, x: Double, y: Double, width: Double, height: Double, u0: Float, v0: Float, u1: Float, v1: Float) - - protected fun uploadVertices(faces: Int, builder: BufferBuilder, x: Double, y: Double, width: Double, height: Double, u0: Float, v0: Float, u1: Float, v1: Float, red: Int, green: Int, blue: Int, alpha: Int) { - if (faces and BOTTOM_LEFT != 0) { - builder.vertex(x, y + height, 0.0).uv(u0, v1).color(red, green, blue, alpha).endVertex() - } - - if (faces and BOTTOM_RIGHT != 0) { - builder.vertex(x + width, y + height, 0.0).uv(u1, v1).color(red, green, blue, alpha).endVertex() - } - - if (faces and TOP_RIGHT != 0) { - builder.vertex(x + width, y, 0.0).uv(u1, v0).color(red, green, blue, alpha).endVertex() - } - - if (faces and TOP_LEFT != 0) { - builder.vertex(x, y, 0.0).uv(u0, v0).color(red, green, blue, alpha).endVertex() - } - } - - protected fun uploadQuad(builder: BufferBuilder, x: Double, y: Double, width: Double, height: Double, u0: Float, v0: Float, u1: Float, v1: Float, red: Int, green: Int, blue: Int, alpha: Int) { - uploadVertices(TOP_LEFT or TOP_RIGHT or BOTTOM_RIGHT, builder, x, y, width, height, u0, v0, u1, v1, red, green, blue, alpha) - uploadVertices(TOP_LEFT or BOTTOM_RIGHT or BOTTOM_LEFT, builder, x, y, width, height, u0, v0, u1, v1, red, green, blue, alpha) - } - - companion object { - const val TOP_LEFT = 1 - const val TOP_RIGHT = 2 - const val BOTTOM_LEFT = 4 - const val BOTTOM_RIGHT = 8 - - val vertices = intArrayOf( - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT, - ) - } - } - private val random = XoroshiroRandomSource(System.nanoTime(), System.currentTimeMillis()) var redShiftX = 0.0 @@ -101,14 +42,6 @@ object GlitchRenderer { var nextGlitch = 0L private set - var lastEncodingGlitch = System.nanoTime() - private set - - var nextEncodingGlitch = 0L - private set - - - private val glitchBuffer by lazy(LazyThreadSafetyMode.NONE) { MainTarget(minecraft.window.width, minecraft.window.height) } @@ -215,7 +148,7 @@ object GlitchRenderer { val glitchBuffer = glitchBuffer val projection = RenderSystem.getProjectionMatrix() - RenderSystem.setProjectionMatrix(Matrix4f().also { it.setIdentity() }) + RenderSystem.setProjectionMatrix(Matrix4f()) RenderSystem.getModelViewStack().also { it.pushPose() @@ -227,7 +160,6 @@ object GlitchRenderer { RenderSystem.disableCull() RenderSystem.disableDepthTest() RenderSystem.enableBlend() - RenderSystem.enableTexture() if (glitchBuffer.width != minecraft.window.width || glitchBuffer.height != minecraft.window.height) { glitchBuffer.resize(minecraft.window.width, minecraft.window.height, Minecraft.ON_OSX) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/IGUIRenderable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/IGUIRenderable.kt new file mode 100644 index 000000000..e73213175 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/IGUIRenderable.kt @@ -0,0 +1,143 @@ +package ru.dbotthepony.mc.otm.client.render + +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.math.RGBAColor + +interface IGUIRenderable { + /** + * Expected width of this gui element, in pixels + */ + val width: Float + + /** + * Expected height of this gui element, in pixels + */ + val height: Float + + /** + * Utilized only for purpose of default argument + */ + val winding: UVWindingOrder get() = UVWindingOrder.NORMAL + + fun render( + guiGraphics: MGUIGraphics, + x: Float = 0f, + y: Float = 0f, + gravity: RenderGravity = RenderGravity.TOP_LEFT, + winding: UVWindingOrder = this.winding, + color: RGBAColor = RGBAColor.WHITE, + ) { + render(guiGraphics, gravity.x(x, width), gravity.y(y, height), width, height, winding, color) + } + + /** + * Render at specified position [x], [y] with size of [width] x [height], optionally with UV [winding], if we are rendering flat texture/sprite + */ + fun render( + guiGraphics: MGUIGraphics, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding, + color: RGBAColor = RGBAColor.WHITE + ) + + fun composeBefore(other: IGUIRenderable): IGUIRenderable { + return object : IGUIRenderable { + override val width: Float + get() = this@IGUIRenderable.width.coerceAtLeast(other.width) + override val height: Float + get() = this@IGUIRenderable.height.coerceAtLeast(other.height) + + override fun render(guiGraphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, winding: UVWindingOrder, color: RGBAColor) { + this@IGUIRenderable.render(guiGraphics, x, y, width, height, winding, color) + other.render(guiGraphics, x, y, width, height, winding, color) + } + } + } + + fun composeAfter(other: IGUIRenderable): IGUIRenderable { + return other.composeBefore(this) + } + + fun padded(left: Float, top: Float, right: Float, bottom: Float): IGUIRenderable { + return object : IGUIRenderable { + override val width: Float + get() = this@IGUIRenderable.width + override val height: Float + get() = this@IGUIRenderable.height + + override fun render(guiGraphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, winding: UVWindingOrder, color: RGBAColor) { + this@IGUIRenderable.render(guiGraphics, x + left, y + top, width + right + left, height + bottom + top, winding, color) + } + } + } + + /** + * Locks argument values to default ones and aligns render position to center of render rectangle (or specified [gravity]) + * + * e.g. for example, if we want [ItemStackIcon] to always render as 16x16 pixels icon, even if required render + * dimensions are bigger (e.g. 18x18), after calling [fix] on [ItemStackIcon] it will always render as 16x16 icon, + * positioned on center of render canvas (e.g. rendering on +0+0 with 18x18 size will put icon at +1+1 and render as 16x16; + * rendering on +0+0 with 32x32 canvas will put icon at +8+8 and render as 16x16) + */ + fun fixed(fixedWidth: Boolean = true, fixedHeight: Boolean = true, fixedWinding: Boolean = true, gravity: RenderGravity = RenderGravity.CENTER_CENTER): IGUIRenderable { + if (!fixedHeight && !fixedWidth && !fixedWinding) return this + + return object : IGUIRenderable { + override val width: Float + get() = this@IGUIRenderable.width + override val height: Float + get() = this@IGUIRenderable.height + + override fun render(guiGraphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, winding: UVWindingOrder, color: RGBAColor) { + var realX = x + var realY = y + + if (fixedWidth && width > this.width) { + realX += gravity.repositionX(width, this.width) + } + + if (fixedHeight && height > this.height) { + realY += gravity.repositionY(height, this.height) + } + + this@IGUIRenderable.render(guiGraphics, realX, realY, if (fixedWidth) this.width else width, if (fixedHeight) this.height else height, if (fixedWinding) this.winding else winding, color) + } + } + } + + companion object { + fun of(value: ItemStack, width: Float = 16f, height: Float = 16f): IGUIRenderable { + return ItemStackIcon(value, width, height) + } + + fun of(value: IGUIRenderable): IGUIRenderable { + return value + } + } +} + +data class ItemStackIcon(private val itemStack: ItemStack, override val width: Float = 16f, override val height: Float = 16f) : IGUIRenderable { + override fun render(guiGraphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, winding: UVWindingOrder, color: RGBAColor) { + val pose = guiGraphics.pose + + pose.pushPose() + pose.translate(x, y, 0f) + + pose.scale(width / 16f, height / 16f, 1f) + + guiGraphics.setColor(color.red, color.green, color.blue, color.alpha) + guiGraphics.renderFakeItem(itemStack, 0, 0) + pose.popPose() + + clearDepth(pose, x, y, width, height) + } +} + +data class FlatRectangleIcon(override val width: Float = 1f, override val height: Float = 1f, val color: RGBAColor) : IGUIRenderable { + override fun render(guiGraphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, winding: UVWindingOrder, color: RGBAColor) { + guiGraphics.renderRect(x, y, width, height, color = this.color) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MGUIGraphics.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MGUIGraphics.kt new file mode 100644 index 000000000..f76f3e46a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/MGUIGraphics.kt @@ -0,0 +1,337 @@ +package ru.dbotthepony.mc.otm.client.render + +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.BufferUploader +import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.blaze3d.vertex.Tesselator +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.gui.Font +import net.minecraft.client.gui.GuiComponent +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.FormattedCharSequence +import net.minecraft.world.item.ItemStack +import net.minecraftforge.client.ForgeHooksClient +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import kotlin.math.PI +import kotlin.math.roundToInt + +// polyfill class for 1.19.4 and older +class MGUIGraphics(val pose: PoseStack) { + val bufferSource: MultiBufferSource.BufferSource get() = minecraft.renderBuffers().crumblingBufferSource() + val width get() = minecraft.window.guiScaledWidth + val height get() = minecraft.window.guiScaledHeight + val font: Font get() = minecraft.font + + fun setColor(red: Float, green: Float, blue: Float, alpha: Float) { + RenderSystem.setShaderColor(red, green, blue, alpha) + } + + fun renderFakeItem(itemStack: ItemStack, x: Int, y: Int) { + minecraft.itemRenderer.renderGuiItem(itemStack, x, y) + } + + fun drawLine( + startX: Float, + startY: Float, + endX: Float, + endY: Float, + width: Float, + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE + ) { + drawLine(pose.last().pose(), startX, startY, endX, endY, width, z, color) + } + + fun renderRect( + x: Float, + y: Float, + width: Float, + height: Float, + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE + ) { + renderRect(pose.last().pose(), x, y, width, height, z, color) + } + + fun renderTexturedRect( + x: Float, + y: Float, + width: Float, + height: Float, + z: Float = 0f, + uv: IUVCoords = defaultUV, + uvWinding: UVWindingOrder = UVWindingOrder.NORMAL, + color: RGBAColor = RGBAColor.WHITE, + texture: ResourceLocation? = null + ) { + renderTexturedRect(pose.last().pose(), x, y, width, height, z, uv, uvWinding, color, texture) + } + + fun renderCheckerboard( + x: Float, + y: Float, + width: Float, + height: Float, + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE + ) { + renderCheckerboard(pose.last().pose(), x, y, width, height, z, color) + } + + fun renderComponentTooltip(font: Font, lines: MutableList, x: Int, y: Int, itemStack: ItemStack = ItemStack.EMPTY) { + if (lines.isNotEmpty()) { + val mapped = lines.map { ClientTooltipComponent.create(it.visualOrderText) } + val preEvent = ForgeHooksClient.onRenderTooltipPre(itemStack, pose, x, y, width, height, mapped, font, font) + + if (preEvent.isCanceled) return + var totalWidth = 0 + var totalHeight = if (lines.size == 1) -2 else 0 + + for (line in mapped) { + val k = line.getWidth(preEvent.font) + if (k > totalWidth) totalWidth = k + totalHeight += line.height + } + + @Suppress("NAME_SHADOWING") + var x = x + 12 + @Suppress("NAME_SHADOWING") + var y = y - 12 + + if (x + totalWidth >= minecraft.window.guiScaledWidth) + x = (x - 24 - totalWidth).coerceAtLeast(4) + + if (y + totalHeight + 3 >= minecraft.window.guiScaledHeight) + y = minecraft.window.guiScaledHeight - totalHeight - 3 + + pose.pushPose() + val tesselator = Tesselator.getInstance() + val bufferbuilder = tesselator.builder + RenderSystem.setShader { GameRenderer.getPositionColorShader() } + bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) + val matrix4f = pose.last().pose() + TooltipRenderUtil.renderTooltipBackground(GuiComponent::fillGradient, matrix4f, bufferbuilder, x, y, totalWidth, totalHeight, 400) + RenderSystem.enableDepthTest() + RenderSystem.disableTexture() + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + BufferUploader.drawWithShader(bufferbuilder.end()) + RenderSystem.disableBlend() + RenderSystem.enableTexture() + + pose.translate(0.0f, 0.0f, 400.0f) + var yCurrent = y + + for (i in mapped.indices) { + val line = mapped[i] + line.renderText(preEvent.font, x, yCurrent, matrix4f, bufferSource) + yCurrent += line.height + if (i == 0) 2 else 0 + } + + bufferSource.endBatch() + + yCurrent = y + + for (i in mapped.indices) { + val line = mapped[i] + line.renderImage(preEvent.font, x, yCurrent, pose, minecraft.itemRenderer, 0 /* blit offset, i love you, go commit self-murder */) + /* because your existence servers ZERO FUCKING PURPOSE */ + /* Z-BUFFER IN MY GUI PIECE OF SHIT */ + yCurrent += line.height + if (i == 0) 2 else 0 + } + + pose.popPose() + } + } + + fun flush() { + bufferSource.endBatch() + } + + fun draw( + text: String, + x: Float = 0f, + y: Float = 0f, + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = -0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = -0.1f, + font: Font = this.font, + ): Float { + val width = font.draw( + poseStack = pose, + text = text, + x = x, + y = y, + buffer = bufferSource, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) + + flush() + return width + } + + fun draw( + text: Component, + x: Float = 0f, + y: Float = 0f, + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = -0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = -0.1f, + font: Font = this.font, + ): Float { + val width = font.draw( + poseStack = pose, + text = text, + x = x, + y = y, + buffer = bufferSource, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) + + flush() + return width + } + + fun draw( + text: FormattedCharSequence, + x: Float = 0f, + y: Float = 0f, + gravity: RenderGravity = RenderGravity.TOP_LEFT, + scale: Float = 1f, + color: RGBAColor = RGBAColor.WHITE, + drawShadow: Boolean = false, + displayMode: Boolean = false, + packedLightCoords: Int = 15728880, + effectColor: Int = 0, + shadowColor: RGBAColor = RGBAColor.BLACK, + shadowX: Float = 1f, + shadowY: Float = 1f, + shadowZ: Float = -0.1f, + customShadow: Boolean = false, + rounding: GravityRounding = if (scale == 1f) GravityRounding.DEFAULT else GravityRounding.NO, + drawOutline: Boolean = false, + outlineColor: RGBAColor = RGBAColor.BLACK, + outlineZ: Float = -0.1f, + font: Font = this.font, + ): Float { + val width = font.draw( + poseStack = pose, + text = text, + x = x, + y = y, + buffer = bufferSource, + gravity = gravity, + scale = scale, + color = color, + drawShadow = drawShadow, + displayMode = displayMode, + packedLightCoords = packedLightCoords, + effectColor = effectColor, + shadowColor = shadowColor, + shadowX = shadowX, + shadowY = shadowY, + shadowZ = shadowZ, + customShadow = customShadow, + rounding = rounding, + drawOutline = drawOutline, + outlineColor = outlineColor, + outlineZ = outlineZ, + ) + + flush() + return width + } + + fun renderSprite( + sprite: TextureAtlasSprite, x: Float, y: Float, + width: Float = sprite.contents().width().toFloat(), + height: Float = sprite.contents().height().toFloat(), + color: RGBAColor = RGBAColor.WHITE + ) { + renderTexturedRect( + x, y, width, height, + uv = UVCoords(sprite.u0, sprite.v0, sprite.u1, sprite.v1), + texture = sprite.atlasLocation(), + color = color + ) + } + + fun drawArc( + x: Float, + y: Float, + outerRadius: Float, + innerRadius: Float = 0f, + startDegree: Double = 0.0, + endDegree: Double = PI * 2.0, + steps: Int = (outerRadius * (endDegree - startDegree) * 4.0).roundToInt().coerceAtLeast(12), + alignAtCenter: Boolean = true + ) = drawArc(pose, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter) + + companion object { + private val defaultUV = UVCoords(0f, 0f, 1f, 1f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt new file mode 100644 index 000000000..432d88021 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderExtensions.kt @@ -0,0 +1,67 @@ +package ru.dbotthepony.mc.otm.client.render + +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.blaze3d.vertex.Tesselator +import com.mojang.blaze3d.vertex.VertexConsumer +import net.minecraft.core.Vec3i +import org.joml.Matrix4f +import org.joml.Quaternionf +import org.joml.Vector3f +import ru.dbotthepony.mc.otm.core.math.IAngle +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.rotateAroundPoint +import ru.dbotthepony.mc.otm.core.math.rotateAroundThis +import ru.dbotthepony.mc.otm.core.math.translation + +val tesselator: Tesselator get() = Tesselator.getInstance() + +fun VertexConsumer.normal(vector: Vector): VertexConsumer = normal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) +fun VertexConsumer.vertex(matrix4f: Matrix4f, vector: Vector): VertexConsumer = vertex(matrix4f, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat()) +fun VertexConsumer.color(color: RGBAColor?): VertexConsumer { + if (color != null) + color(color.redInt, color.greenInt, color.blueInt, color.alphaInt) + + return this +} + +fun PoseStack.translate(vector: Vector) = translate(vector.x, vector.y, vector.z) +fun PoseStack.translate(vector: Vec3i) = translate(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble()) +fun PoseStack.translate(vector: Vector3f) = translate(vector.x(), vector.y(), vector.z()) + +fun PoseStack.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val last = last() + last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) + last.normal().rotate(axis.rotateAroundThis(rotation, isDegrees)) +} + +fun PoseStack.rotateAroundPoint(point: Vector, rotation: IAngle) { + val last = last() + last.pose().rotateAroundPoint(point, rotation) + // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) +} + +fun PoseStack.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val last = last() + last.pose().rotateAroundPoint(point, axis, rotation, isDegrees) + last.normal().rotate(axis.rotateAroundThis(rotation, isDegrees)) +} + +fun PoseStack.rotateAroundPoint(point: Vector3f, rotation: IAngle) { + val last = last() + last.pose().rotateAroundPoint(point, rotation) + // last.normal().mul(rotation.forward().rotateAroundThis(rotation)) +} + +fun PoseStack.rotateAround(rotationMatrix: Quaternionf, x: Float, y: Float, z: Float) { + val pose = last + pose.pose.rotateAround(rotationMatrix, x, y, z) + pose.normal().rotate(rotationMatrix) +} + +fun PoseStack.translation(): Vector3f { + return last().pose().translation +} + +inline val PoseStack.last: PoseStack.Pose get() = last() +inline val PoseStack.Pose.pose: Matrix4f get() = pose() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderGravity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderGravity.kt new file mode 100644 index 000000000..33a72244a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderGravity.kt @@ -0,0 +1,251 @@ +package ru.dbotthepony.mc.otm.client.render + +import net.minecraft.client.gui.Font +import net.minecraft.network.chat.Component +import net.minecraft.util.FormattedCharSequence +import ru.dbotthepony.mc.otm.core.FloatSupplier +import kotlin.math.roundToInt + +private operator fun FloatSupplier.div(other: Float): Float { + return getAsFloat() / other +} + +private operator fun FloatSupplier.times(other: Float): Float { + return getAsFloat() * other +} + +private operator fun Float.minus(other: FloatSupplier): Float { + return this - other.getAsFloat() +} + +enum class GravityRounding { + /** + * Round aligned element only if base coordinate is whole + */ + DEFAULT { + override fun round(base: Float, subtraction: Float): Float { + if (base % 1f == 0f) { + return (base - subtraction).roundToInt().toFloat() + } else { + return base - subtraction + } + } + + override fun round(base: Float): Float { + return base + } + }, + + /** + * Always round final position + */ + YES { + override fun round(base: Float, subtraction: Float): Float { + return (base - subtraction).roundToInt().toFloat() + } + + override fun round(base: Float): Float { + return base.roundToInt().toFloat() + } + }, + + /** + * Never round final position + */ + NO { + override fun round(base: Float, subtraction: Float): Float { + return base - subtraction + } + + override fun round(base: Float): Float { + return base + } + }; + + abstract fun round(base: Float, subtraction: Float): Float + abstract fun round(base: Float): Float +} + +interface IXGravity { + fun x(x: Float, width: FloatSupplier, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + fun x(x: Float, width: Float, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + + fun x(x: Float, width: FloatSupplier): Float = x(x, width, 1f) + fun x(x: Float, width: Float): Float = x(x, width, 1f) + + fun repositionX(outer: Float, inner: Float, rounding: GravityRounding = GravityRounding.YES): Float + + fun x(x: Float, font: Font, text: Component): Float = x(x, font, text, 1f) + fun x(x: Float, font: Font, text: Component, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + + fun x(x: Float, font: Font, text: String): Float = x(x, font, text, 1f) + fun x(x: Float, font: Font, text: String, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + + fun x(x: Float, font: Font, text: FormattedCharSequence): Float = x(x, font, text, 1f) + fun x(x: Float, font: Font, text: FormattedCharSequence, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + + fun x(x: Float, width: Int, scale: Float = 1f): Float { + return x(x, width.toFloat() * scale).roundToInt().toFloat() + } +} + +interface IYGravity { + fun y(y: Float, height: FloatSupplier, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + fun y(y: Float, height: FloatSupplier): Float = y(y, height, 1f) + fun y(y: Float, height: Float, scale: Float = 1f, rounding: GravityRounding = GravityRounding.DEFAULT): Float + fun y(y: Float, height: Float): Float = y(y, height, 1f) + + fun repositionY(outer: Float, inner: Float, rounding: GravityRounding = GravityRounding.YES): Float + + fun y(y: Float, height: Int, scale: Float = 1f): Float { + return y(y, height.toFloat(), scale).roundToInt().toFloat() + } +} + +enum class XGravity : IXGravity { + LEFT { + override fun x(x: Float, width: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return x + } + + override fun x(x: Float, width: Float, scale: Float, rounding: GravityRounding): Float { + return x + } + + override fun x(x: Float, font: Font, text: Component, scale: Float, rounding: GravityRounding): Float { + return x + } + + override fun x(x: Float, font: Font, text: String, scale: Float, rounding: GravityRounding): Float { + return x + } + + override fun x(x: Float, font: Font, text: FormattedCharSequence, scale: Float, rounding: GravityRounding): Float { + return x + } + + override fun repositionX(outer: Float, inner: Float, rounding: GravityRounding): Float { + return 0f + } + }, + CENTER { + override fun x(x: Float, width: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, width / 2f * scale) + } + + override fun x(x: Float, width: Float, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, width / 2f * scale) + } + + override fun x(x: Float, font: Font, text: Component, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) / 2f * scale) + } + + override fun x(x: Float, font: Font, text: String, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) / 2f * scale) + } + + override fun x(x: Float, font: Font, text: FormattedCharSequence, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) / 2f * scale) + } + + override fun repositionX(outer: Float, inner: Float, rounding: GravityRounding): Float { + if (outer <= inner) + return 0f + else + return rounding.round((outer - inner) / 2f, 0f) + } + }, + RIGHT { + override fun x(x: Float, width: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, width * scale) + } + + override fun x(x: Float, width: Float, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, width * scale) + } + + override fun x(x: Float, font: Font, text: Component, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) * scale) + } + + override fun x(x: Float, font: Font, text: String, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) * scale) + } + + override fun x(x: Float, font: Font, text: FormattedCharSequence, scale: Float, rounding: GravityRounding): Float { + return rounding.round(x, font.width(text) * scale) + } + + override fun repositionX(outer: Float, inner: Float, rounding: GravityRounding): Float { + if (outer <= inner) + return 0f + else + return rounding.round(outer - inner, 0f) + } + }; +} + +enum class YGravity : IYGravity { + TOP { + override fun y(y: Float, height: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return y + } + + override fun y(y: Float, height: Float, scale: Float, rounding: GravityRounding): Float { + return y + } + + override fun repositionY(outer: Float, inner: Float, rounding: GravityRounding): Float { + return 0f + } + }, + + CENTER { + override fun y(y: Float, height: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return rounding.round(y, height / 2f * scale) + } + + override fun y(y: Float, height: Float, scale: Float, rounding: GravityRounding): Float { + return rounding.round(y, height / 2f * scale) + } + + override fun repositionY(outer: Float, inner: Float, rounding: GravityRounding): Float { + if (outer <= inner) + return 0f + else + return rounding.round((outer - inner) / 2f, 0f) + } + }, + + BOTTOM { + override fun y(y: Float, height: FloatSupplier, scale: Float, rounding: GravityRounding): Float { + return rounding.round(y, height * scale) + } + + override fun y(y: Float, height: Float, scale: Float, rounding: GravityRounding): Float { + return rounding.round(y, height * scale) + } + + override fun repositionY(outer: Float, inner: Float, rounding: GravityRounding): Float { + if (outer <= inner) + return 0f + else + return rounding.round(outer - inner, 0f) + } + }; +} + +enum class RenderGravity(x: IXGravity, y: IYGravity) : IXGravity by x, IYGravity by y { + TOP_LEFT(XGravity.LEFT, YGravity.TOP), + TOP_CENTER(XGravity.CENTER, YGravity.TOP), + TOP_RIGHT(XGravity.RIGHT, YGravity.TOP), + + CENTER_LEFT(XGravity.LEFT, YGravity.CENTER), + CENTER_CENTER(XGravity.CENTER, YGravity.CENTER), + CENTER_RIGHT(XGravity.RIGHT, YGravity.CENTER), + + BOTTOM_LEFT(XGravity.LEFT, YGravity.BOTTOM), + BOTTOM_CENTER(XGravity.CENTER, YGravity.BOTTOM), + BOTTOM_RIGHT(XGravity.RIGHT, YGravity.BOTTOM); +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt index ffc77bb79..74e51efc4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/RenderHelper.kt @@ -3,16 +3,20 @@ package ru.dbotthepony.mc.otm.client.render import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.* -import com.mojang.math.Matrix4f import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.renderer.RenderStateShard import net.minecraft.client.renderer.RenderStateShard.LineStateShard import net.minecraft.client.renderer.RenderType -import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.ShaderInstance +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.packs.resources.ResourceProvider +import org.apache.logging.log4j.LogManager +import org.joml.Matrix4f import org.lwjgl.opengl.GL11.GL_ALWAYS import org.lwjgl.opengl.GL11.GL_LESS +import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor import java.util.* import kotlin.collections.ArrayDeque import kotlin.math.PI @@ -21,11 +25,8 @@ import kotlin.math.cos import kotlin.math.pow import kotlin.math.roundToInt import kotlin.math.sin +import kotlin.properties.Delegates -private val identity = Matrix4f().also { it.setIdentity() } - -var zLevel = 0f -var drawColor = RGBAColor(255, 255, 255, 255) var is3DContext = false /** @@ -35,25 +36,128 @@ var is3DContext = false */ var lockBlendFunc = false -@JvmName("setDrawColor\$JVM") -fun setDrawColor(color: RGBAColor) { - drawColor = color +private val defaultUV = UVCoords(0f, 0f, 1f, 1f) + +private val LOGGER = LogManager.getLogger() +var customTexColorShader: ShaderInstance by Delegates.notNull() + private set + +fun reloadShaders(provider: ResourceProvider) { + try { + customTexColorShader = ShaderInstance(provider, ResourceLocation(OverdriveThatMatters.MOD_ID, "position_tex_color"), DefaultVertexFormat.POSITION_TEX_COLOR) + } catch (err: Throwable) { + LOGGER.error("Failed to load custom tex color shader, this gonna result in uninitialized property exception later", err) + } } -// Regular functions -fun drawTexturedRect( +fun renderRect( matrix: Matrix4f, x: Float, y: Float, width: Float, height: Float, - u0: Float = 0f, - v0: Float = 0f, - u1: Float = 1f, - v1: Float = 1f + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE ) { - RenderSystem.setShader(GameRenderer::getPositionTexShader) - RenderSystem.enableTexture() + if (color.isWhite) + RenderSystem.setShader(GameRenderer::getPositionShader) + else + RenderSystem.setShader(GameRenderer::getPositionColorShader) + + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + + if (!is3DContext) + RenderSystem.depthFunc(GL_ALWAYS) + + val tess = tesselator + val builder = tess.builder + + if (color.isWhite) { + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION) + + builder.vertex(matrix, x, y + height, z).endVertex() + builder.vertex(matrix, x + width, y + height, z).endVertex() + builder.vertex(matrix, x + width, y, z).endVertex() + builder.vertex(matrix, x, y, z).endVertex() + } else { + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) + + builder.vertex(matrix, x, y + height, z).color(color).endVertex() + builder.vertex(matrix, x + width, y + height, z).color(color).endVertex() + builder.vertex(matrix, x + width, y, z).color(color).endVertex() + builder.vertex(matrix, x, y, z).color(color).endVertex() + } + + tess.end() +} + +@Suppress("NAME_SHADOWING") +fun renderCheckerboard( + matrix: Matrix4f, + x: Float, + y: Float, + width: Float, + height: Float, + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE +) { + RenderSystem.setShader(GameRenderer::getPositionColorShader) + + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + + if (!is3DContext) + RenderSystem.depthFunc(GL_ALWAYS) + + val tess = tesselator + val builder = tess.builder + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) + + for (i in 0 ..< width.toInt()) { + val x = x + i.toFloat() + + for (j in 0 ..< height.toInt()) { + val y = y + j.toFloat() + + if ((i + j) % 2 == 0) { + builder.vertex(matrix, x, y + 1f, z).color(color).endVertex() + builder.vertex(matrix, x + 1f, y + 1f, z).color(color).endVertex() + builder.vertex(matrix, x + 1f, y, z).color(color).endVertex() + builder.vertex(matrix, x, y, z).color(color).endVertex() + } + } + } + + tess.end() +} + +fun renderTexturedRect( + matrix: Matrix4f, + x: Float, + y: Float, + width: Float, + height: Float, + z: Float = 0f, + uv: IUVCoords = defaultUV, + uvWinding: UVWindingOrder = UVWindingOrder.NORMAL, + color: RGBAColor = RGBAColor.WHITE, + texture: ResourceLocation? = null +) { + val u0 = uvWinding.u0(uv) + val v0 = uvWinding.v0(uv) + val u1 = uvWinding.u1(uv) + val v1 = uvWinding.v1(uv) + + if (color.isWhite) + RenderSystem.setShader(GameRenderer::getPositionTexShader) + else + RenderSystem.setShader { customTexColorShader } + + if (texture != null) + RenderSystem.setShaderTexture(0, texture) + RenderSystem.enableBlend() RenderSystem.defaultBlendFunc() @@ -62,65 +166,26 @@ fun drawTexturedRect( val builder = tesselator.builder - builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX) - builder.vertex(matrix, x, y + height, zLevel).uv(u0, v1).endVertex() - builder.vertex(matrix, x + width, y + height, zLevel).uv(u1, v1).endVertex() - builder.vertex(matrix, x + width, y, zLevel).uv(u1, v0).endVertex() - builder.vertex(matrix, x, y, zLevel).uv(u0, v0).endVertex() + if (color.isWhite) { + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX) + + builder.vertex(matrix, x, y + height, z).uv(u0, v1).endVertex() + builder.vertex(matrix, x + width, y + height, z).uv(u1, v1).endVertex() + builder.vertex(matrix, x + width, y, z).uv(u1, v0).endVertex() + builder.vertex(matrix, x, y, z).uv(u0, v0).endVertex() + } else { + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR) + + builder.vertex(matrix, x, y + height, z).uv(u0, v1).color(color).endVertex() + builder.vertex(matrix, x + width, y + height, z).uv(u1, v1).color(color).endVertex() + builder.vertex(matrix, x + width, y, z).uv(u1, v0).color(color).endVertex() + builder.vertex(matrix, x, y, z).uv(u0, v0).color(color).endVertex() + } BufferUploader.drawWithShader(builder.end()) } -fun drawTexturedRect( - matrix: Matrix4f, - x: Float, - y: Float, - width: Float, - height: Float, - uv: IUVCoords -) = drawTexturedRect(matrix, x, y, width, height, uv.u0, uv.v0, uv.u1, uv.v1) - -fun drawTexturedRect( - stack: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float, - uv: IUVCoords -) = drawTexturedRect(stack.last().pose(), x, y, width, height, uv.u0, uv.v0, uv.u1, uv.v1) - -fun drawTexturedRect( - stack: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float, - u0: Float, - v0: Float, - u1: Float, - v1: Float -) = drawTexturedRect(stack.last().pose(), x, y, width, height, u0, v0, u1, v1) - -fun drawTexturedRect( - x: Float, - y: Float, - width: Float, - height: Float, - uv: IUVCoords -) = drawTexturedRect(identity, x, y, width, height, uv.u0, uv.v0, uv.u1, uv.v1) - -fun drawTexturedRect( - x: Float, - y: Float, - width: Float, - height: Float, - u0: Float, - v0: Float, - u1: Float, - v1: Float -) = drawTexturedRect(identity, x, y, width, height, u0, v0, u1, v1) - -fun colorSphere(matrix: Matrix4f, radius: Float) { +fun renderColoredSphere(pose: PoseStack, radius: Float, color: RGBAColor = RGBAColor.WHITE) { val fragments = 32 RenderSystem.enableBlend() RenderSystem.defaultBlendFunc() @@ -145,20 +210,20 @@ fun colorSphere(matrix: Matrix4f, radius: Float) { val yPre = (sin(tiltPre) + 0.5).toFloat() * radius val yPost = (sin(tiltPost) + 0.5).toFloat() * radius - builder.vertex(matrix, xPre * cos(tiltPost).toFloat(), yPost, zPre * cos(tiltPost).toFloat()) - .color(drawColor) + builder.vertex(pose.last().pose(), xPre * cos(tiltPost).toFloat(), yPost, zPre * cos(tiltPost).toFloat()) + .color(color) .endVertex() - builder.vertex(matrix, xPost * cos(tiltPost).toFloat(), yPost, zPost * cos(tiltPost).toFloat()) - .color(drawColor) + builder.vertex(pose.last().pose(), xPost * cos(tiltPost).toFloat(), yPost, zPost * cos(tiltPost).toFloat()) + .color(color) .endVertex() - builder.vertex(matrix, xPost * cos(tiltPre).toFloat(), yPre, zPost * cos(tiltPre).toFloat()) - .color(drawColor) + builder.vertex(pose.last().pose(), xPost * cos(tiltPre).toFloat(), yPre, zPost * cos(tiltPre).toFloat()) + .color(color) .endVertex() - builder.vertex(matrix, xPre * cos(tiltPre).toFloat(), yPre, zPre * cos(tiltPre).toFloat()) - .color(drawColor) + builder.vertex(pose.last().pose(), xPre * cos(tiltPre).toFloat(), yPre, zPre * cos(tiltPre).toFloat()) + .color(color) .endVertex() } } @@ -166,202 +231,19 @@ fun colorSphere(matrix: Matrix4f, radius: Float) { BufferUploader.drawWithShader(builder.end()) } -fun colorSphere(pose: PoseStack, radius: Float) = colorSphere(pose.last().pose(), radius) - -fun drawTexturedRectAuto( - stack: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float -) = drawTexturedRect( - stack, - x, - y, - width, - height, - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height -) - -fun drawTexturedRectAuto( - stack: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float, - order: UVWindingOrder -) = drawTexturedRect( - stack, - x, - y, - width, - height, - order.translate( - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height - ) -) - -fun drawTexturedRectAuto( - matrix: Matrix4f, - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float -) = drawTexturedRect( - matrix, - x, - y, - width, - height, - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height -) - -fun drawTexturedRectAuto( - matrix: Matrix4f, - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float, - order: UVWindingOrder -) = drawTexturedRect( - matrix, - x, - y, - width, - height, - order.translate( - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height - ) -) - - -fun drawTexturedRectAuto( - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float -) = drawTexturedRect( - x, - y, - width, - height, - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height -) - -fun drawTexturedRectAuto( - x: Float, - y: Float, - width: Float, - height: Float, - image_x: Float, - image_y: Float, - mapped_width: Float, - mapped_height: Float, - order: UVWindingOrder -) = drawTexturedRect( - x, - y, - width, - height, - order.translate( - image_x / mapped_width, - image_y / mapped_height, - (image_x + width) / mapped_width, - (image_y + height) / mapped_height - ) -) - -fun drawRect( - matrix: Matrix4f, - x: Float, - y: Float, - width: Float, - height: Float -) { - RenderSystem.disableTexture() - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - RenderSystem.setShader(GameRenderer::getPositionColorShader) - - if (!is3DContext) - RenderSystem.depthFunc(GL_ALWAYS) - - val tess = tesselator - val builder = tess.builder - - builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR) - - builder.vertex(matrix, x, y + height, zLevel).color(drawColor).endVertex() - builder.vertex(matrix, x + width, y + height, zLevel).color(drawColor).endVertex() - builder.vertex(matrix, x + width, y, zLevel).color(drawColor).endVertex() - builder.vertex(matrix, x, y, zLevel).color(drawColor).endVertex() - - tess.end() - RenderSystem.enableTexture() -} - -fun drawRect( - pose: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float -) = drawRect(pose.last().pose(), x, y, width, height) - -fun drawRect( - x: Float, - y: Float, - width: Float, - height: Float -) = drawRect(identity, x, y, width, height) - fun drawLine( matrix: Matrix4f, startX: Float, startY: Float, endX: Float, endY: Float, - width: Float + width: Float, + z: Float = 0f, + color: RGBAColor = RGBAColor.WHITE ) { - RenderSystem.disableTexture() + RenderSystem.setShader(GameRenderer::getPositionColorShader) RenderSystem.enableBlend() RenderSystem.defaultBlendFunc() - RenderSystem.setShader(GameRenderer::getPositionColorShader) if (!is3DContext) RenderSystem.depthFunc(GL_ALWAYS) @@ -390,45 +272,27 @@ fun drawLine( builder.vertex(matrix, startX - y0 * sin, startY + y0 * cos, - zLevel).color(drawColor).endVertex() + z).color(color).endVertex() builder.vertex(matrix, startX - y1 * sin, startY + y1 * cos, - zLevel).color(drawColor).endVertex() + z).color(color).endVertex() builder.vertex(matrix, startX + x2 * cos - y2 * sin, startY + x2 * sin + y2 * cos, - zLevel).color(drawColor).endVertex() + z).color(color).endVertex() builder.vertex(matrix, startX + x3 * cos - y3 * sin, startY + x3 * sin + y3 * cos, - zLevel).color(drawColor).endVertex() + z).color(color).endVertex() tess.end() - RenderSystem.enableTexture() } -fun drawLine( - pose: PoseStack, - startX: Float, - startY: Float, - endX: Float, - endY: Float, - width: Float -) = drawLine(pose.last().pose(), startX, startY, endX, endY, width) - -fun drawLine( - startX: Float, - startY: Float, - endX: Float, - endY: Float, - width: Float -) = drawLine(identity, startX, startY, endX, endY, width) - -data class ScissorRect(val x: Int, val y: Int, val width: Int, val height: Int) { +data class ScissorRect(val x: Int, val y: Int, val width: Int, val height: Int, val lock: Boolean = false) { fun withinBounds(x: Int, y: Int): Boolean { return (x in this.x .. this.x + width) && (y in this.y .. this.y + height) } @@ -468,7 +332,8 @@ data class ScissorRect(val x: Int, val y: Int, val width: Int, val height: Int) private val scissorStack = ArrayDeque() @Suppress("NAME_SHADOWING") -fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) { +@JvmOverloads +fun pushScissorRect(x: Int, y: Int, width: Int, height: Int, lock: Boolean = false) { var x = x var y = y var width = width @@ -477,6 +342,11 @@ fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) { val peek = scissorStack.lastOrNull() if (peek != null) { + if (peek.lock) { + scissorStack.add(peek) + return + } + x = x.coerceAtLeast(peek.x) y = y.coerceAtLeast(peek.y) width = width.coerceAtMost(peek.width) @@ -488,7 +358,7 @@ fun pushScissorRect(x: Int, y: Int, width: Int, height: Int) { } } - scissorStack.add(ScissorRect(x, y, width, height)) + scissorStack.add(ScissorRect(x, y, width, height, lock)) y = minecraft.window.height - y - height RenderSystem.enableScissor(x, y, width, height) } @@ -509,23 +379,6 @@ fun popScissorRect() { val currentScissorRect get() = scissorStack.lastOrNull() -fun TextureAtlasSprite.render( - stack: PoseStack, - x: Float = 0f, - y: Float = 0f, - width: Float = this.width.toFloat(), - height: Float = this.height.toFloat(), - winding: UVWindingOrder = UVWindingOrder.NORMAL -) { - RenderSystem.setShaderTexture(0, atlas().location()) - - if (winding.isIdentity) { - drawTexturedRect(stack.last().pose(), x, y, width, height, u0, v0, u1, v1) - } else { - drawTexturedRect(stack.last().pose(), x, y, width, height, winding.translate(u0, v0, u1, v1)) - } -} - fun determineTooltipPosition(x: Float, y: Float, width: Float, height: Float): Pair { val windowWidth = minecraft.window.guiScaledWidth.toFloat() val windowHeight = minecraft.window.guiScaledHeight.toFloat() @@ -583,6 +436,9 @@ fun clearDepth(stack: PoseStack, x: Float, y: Float, width: Float, height: Float } } +@JvmOverloads +fun clearDepth(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, depth: Float = -500f) = clearDepth(graphics.pose, x, y, width, height, depth) + fun drawArc( stack: PoseStack, x: Float, @@ -595,7 +451,6 @@ fun drawArc( alignAtCenter: Boolean = true ) = drawArc(stack.last().pose(), x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter) - fun uploadArc( matrix: Matrix4f, builder: VertexConsumer, @@ -607,6 +462,7 @@ fun uploadArc( endDegree: Double = PI * 2.0, steps: Int = (outerRadius * (endDegree - startDegree) * 4.0).roundToInt().coerceAtLeast(12), alignAtCenter: Boolean = true, + z: Float = 0f, triangleFan: Boolean ) { require(startDegree < endDegree) { "Invalid arc degree range: $startDegree - $endDegree" } @@ -636,13 +492,13 @@ fun uploadArc( if (triangleFan) { val singleStep = (endDegree - startDegree) / steps - builder.vertex(matrix, x, y, zLevel).endVertex() + builder.vertex(matrix, x, y, z).endVertex() for (i in 0 .. steps) { val sin = sin(startDegree + i * singleStep).toFloat() val cos = cos(startDegree + i * singleStep).toFloat() - builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, zLevel).endVertex() + builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z).endVertex() } } else { val singleStep = (endDegree - startDegree) / (steps + 1) @@ -654,10 +510,10 @@ fun uploadArc( val sin2 = sin(startDegree + (i + 1) * singleStep).toFloat() val cos2 = cos(startDegree + (i + 1) * singleStep).toFloat() - builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, zLevel).endVertex() - builder.vertex(matrix, x + outerRadius * sin2, y + cos2 * outerRadius, zLevel).endVertex() - builder.vertex(matrix, x + innerRadius * sin2, y + cos2 * innerRadius, zLevel).endVertex() - builder.vertex(matrix, x + innerRadius * sin, y + cos * innerRadius, zLevel).endVertex() + builder.vertex(matrix, x + outerRadius * sin, y + cos * outerRadius, z).endVertex() + builder.vertex(matrix, x + outerRadius * sin2, y + cos2 * outerRadius, z).endVertex() + builder.vertex(matrix, x + innerRadius * sin2, y + cos2 * innerRadius, z).endVertex() + builder.vertex(matrix, x + innerRadius * sin, y + cos * innerRadius, z).endVertex() } } } @@ -671,7 +527,8 @@ fun drawArc( startDegree: Double = 0.0, endDegree: Double = PI * 2.0, steps: Int = (outerRadius * (endDegree - startDegree) * 4.0).roundToInt().coerceAtLeast(12), - alignAtCenter: Boolean = true + alignAtCenter: Boolean = true, + z: Float = 0f ) { require(startDegree < endDegree) { "Invalid arc degree range: $startDegree - $endDegree" } require(steps >= 0) { "Invalid amount of arc steps: $steps" } @@ -687,12 +544,12 @@ fun drawArc( if (innerRadius == 0f) { if (steps >= 1) { builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION) - uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = true) + uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = true, z = z) BufferUploader.drawWithShader(builder.end()) } } else { builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION) - uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = false) + uploadArc(matrix, builder, x, y, outerRadius, innerRadius, startDegree, endDegree, steps, alignAtCenter, triangleFan = false, z = z) BufferUploader.drawWithShader(builder.end()) } } @@ -739,20 +596,3 @@ val linesIgnoreZRenderType by lazy { .createCompositeState(false) ) as RenderType } - -fun VertexConsumer.quad( - pose: PoseStack, - x: Float, - y: Float, - width: Float, - height: Float, - z: Float, - color: RGBAColor? = null, -) { - val intColor = color?.toARGB() ?: 0 - val matrix = pose.last().pose() - vertex(matrix, x, y + height, z).also { if (color != null) it.color(intColor) }.endVertex() - vertex(matrix, x + width, y + height, z).also { if (color != null) it.color(intColor) }.endVertex() - vertex(matrix, x + width, y, z).also { if (color != null) it.color(intColor) }.endVertex() - vertex(matrix, x, y, z).also { if (color != null) it.color(intColor) }.endVertex() -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt index 02abfc9ee..1124d674f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ResearchIcons.kt @@ -2,39 +2,42 @@ package ru.dbotthepony.mc.otm.client.render import net.minecraft.resources.ResourceLocation import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.client.render.sprites.GridAtlas +import ru.dbotthepony.mc.otm.client.render.sprites.sprite object ResearchIcons { - val ICON_TRANSFER: AbstractSkinElement - val ICON_ATTACK_BOOST: AbstractSkinElement - val ICON_PLASMA_SHIELD_BOOST: AbstractSkinElement - val ICON_CLOAK: AbstractSkinElement - val ICON_GRAVITATIONAL_STABILIZER: AbstractSkinElement - val ICON_AIR_BAGS: AbstractSkinElement - val ICON_JUMP_BOOST: AbstractSkinElement + val ICON_TRANSFER: AbstractMatterySprite + val ICON_ATTACK_BOOST: AbstractMatterySprite + val ICON_PLASMA_SHIELD_BOOST: AbstractMatterySprite + val ICON_CLOAK: AbstractMatterySprite + val ICON_GRAVITATIONAL_STABILIZER: AbstractMatterySprite + val ICON_AIR_BAGS: AbstractMatterySprite + val ICON_JUMP_BOOST: AbstractMatterySprite - val ICON_FEATHER_FALLING: AbstractSkinElement - val ICON_ITEM_MAGNET: AbstractSkinElement - val ICON_ARROW: AbstractSkinElement - val ICON_ARMOR: AbstractSkinElement - val ICON_NANOBOTS: AbstractSkinElement - val ICON_NIGHT_VISION: AbstractSkinElement - val ICON_OXYGEN_SUPPLY: AbstractSkinElement + val ICON_FEATHER_FALLING: AbstractMatterySprite + val ICON_ITEM_MAGNET: AbstractMatterySprite + val ICON_ARROW: AbstractMatterySprite + val ICON_ARMOR: AbstractMatterySprite + val ICON_NANOBOTS: AbstractMatterySprite + val ICON_NIGHT_VISION: AbstractMatterySprite + val ICON_OXYGEN_SUPPLY: AbstractMatterySprite - val ICON_PLASMA_SHIELD: AbstractSkinElement - val ICON_SHOCKWAVE: AbstractSkinElement - val ICON_LIMB_OVERCLOCKING: AbstractSkinElement - val ICON_STEP_ASSIST: AbstractSkinElement - val ICON_ENDER_TELEPORT: AbstractSkinElement - val ICON_WIRELESS_CHARGING: AbstractSkinElement - val ICON_UNKNOWN: AbstractSkinElement + val ICON_PLASMA_SHIELD: AbstractMatterySprite + val ICON_SHOCKWAVE: AbstractMatterySprite + val ICON_LIMB_OVERCLOCKING: AbstractMatterySprite + val ICON_STEP_ASSIST: AbstractMatterySprite + val ICON_ENDER_TELEPORT: AbstractMatterySprite + val ICON_WIRELESS_CHARGING: AbstractMatterySprite + val ICON_UNKNOWN: AbstractMatterySprite - val ICON_EXTENDED_REACH: AbstractSkinElement - val ICON_PHANTOM_ATTRACTOR: AbstractSkinElement + val ICON_EXTENDED_REACH: AbstractMatterySprite + val ICON_PHANTOM_ATTRACTOR: AbstractMatterySprite - val KOT = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/block/ph_kitty.png").element(0f, 0f, 32f, 32f, 32f, 32f) + val KOT = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/block/ph_kitty.png").sprite(0f, 0f, 32f, 32f, 32f, 32f) init { - val grid = SubSkinGrid(AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_upgrades"), 126f, 126f), 18f, 18f, 7, 7) + val grid = GridAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/android_upgrades.png"), 18f, 18f, 7, 7) ICON_TRANSFER = grid.next() ICON_ATTACK_BOOST = grid.next() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt index a1b418fef..c408b8245 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/ShockwaveRenderer.kt @@ -4,16 +4,17 @@ import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.BufferUploader import com.mojang.blaze3d.vertex.DefaultVertexFormat import com.mojang.blaze3d.vertex.VertexFormat -import com.mojang.math.Vector3f import net.minecraft.client.renderer.GameRenderer import net.minecraftforge.client.event.RenderLevelStageEvent import org.lwjgl.opengl.GL11.GL_LESS -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 -import ru.dbotthepony.mc.otm.core.linearInterpolation +import ru.dbotthepony.mc.otm.config.AndroidConfig +import ru.dbotthepony.mc.otm.config.ServerConfig +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.linearInterpolation +import ru.dbotthepony.mc.otm.core.math.rotateX import ru.dbotthepony.mc.otm.network.ShockwaveEffectPacket import ru.dbotthepony.mc.otm.secondTimeD import kotlin.math.PI @@ -52,7 +53,7 @@ object ShockwaveRenderer { event.poseStack.pushPose() val (x, y, z) = event.camera.position event.poseStack.translate(pos.x - x, pos.y - y + 0.1f, pos.z - z) // render slightly above landed position - event.poseStack.mulPose(Vector3f.XP.rotation(PI.toFloat() / 2f)) + event.poseStack.rotateX(PI.toFloat() / 2f) uploadArc(event.poseStack.last.pose, builder, x = 0f, y = 0f, innerRadius = (radius - 1f).coerceAtLeast(0f), outerRadius = radius, triangleFan = false) event.poseStack.popPose() @@ -82,6 +83,6 @@ object ShockwaveRenderer { } fun handle(packet: ShockwaveEffectPacket) { - State(packet.pos, ServerConfig.Shockwave.RADIUS_HORIZONTAL.toFloat()) + State(packet.pos, AndroidConfig.Shockwave.RADIUS_HORIZONTAL.toFloat()) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt deleted file mode 100644 index b590d5b72..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt +++ /dev/null @@ -1,79 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import com.google.gson.JsonSyntaxException -import com.google.gson.TypeAdapter -import com.google.gson.internal.bind.TypeAdapters -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.core.set -import java.lang.reflect.Type - -fun ResourceLocation.element( - x: Float, - y: Float, - width: Float, - height: Float, - textureWidth: Float = 256f, - textureHeight: Float = 256f -) = SkinElement(this, x, y, width, height, textureWidth, textureHeight) - -fun ResourceLocation.pixel( - x: Float, - y: Float, - textureWidth: Float = 256f, - textureHeight: Float = 256f -) = SkinElement(this, x, y, 1f, 1f, textureWidth, textureHeight) - -fun ResourceLocation.hLine( - x: Float, - y: Float, - width: Float, - textureWidth: Float = 256f, - textureHeight: Float = 256f -) = SkinElement(this, x, y, width, 1f, textureWidth, textureHeight) - -fun ResourceLocation.vLine( - x: Float, - y: Float, - height: Float, - textureWidth: Float = 256f, - textureHeight: Float = 256f -) = SkinElement(this, x, y, 1f, height, textureWidth, textureHeight) - -@Suppress("unused") -data class SkinElement @JvmOverloads constructor( - override val texture: ResourceLocation, - val x: Float, - val y: Float, - override val width: Float, - override val height: Float, - val imageWidth: Float = 256f, - val imageHeight: Float = 256f, - override val winding: UVWindingOrder = UVWindingOrder.NORMAL, -) : AbstractSkinElement() { - init { - require(x >= 0f) { "Invalid x $x" } - require(y >= 0f) { "Invalid y $y" } - require(width > 0f) { "Invalid width $width" } - require(height > 0f) { "Invalid height $height" } - require(imageWidth > 0f) { "Invalid image width $imageWidth" } - require(imageHeight > 0f) { "Invalid image height $imageHeight" } - } - - override val u0 = this.x / imageWidth - override val v0 = this.y / imageHeight - override val u1 = (this.x + width) / imageWidth - override val v1 = (this.y + height) / imageHeight - - override val type: SkinElementType - get() = SkinElementType.SINGLE -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElementType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElementType.kt deleted file mode 100644 index e8af0c1a5..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElementType.kt +++ /dev/null @@ -1,172 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSyntaxException -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.core.set - -enum class SkinElementType { - SINGLE { - override fun toJson(value: AbstractSkinElement): JsonObject { - require(value is SkinElement) { "Invalid skin element provided, expected SkinElement, got ${value::class.qualifiedName}" } - - return JsonObject().also { - it["texture"] = JsonPrimitive(value.texture.toString()) - it["x"] = JsonPrimitive(value.x) - it["y"] = JsonPrimitive(value.y) - it["width"] = JsonPrimitive(value.width) - it["height"] = JsonPrimitive(value.height) - it["imageWidth"] = JsonPrimitive(value.imageWidth) - it["imageHeight"] = JsonPrimitive(value.imageHeight) - it["winding"] = JsonPrimitive(value.winding.name) - } - } - - override fun toNetwork(value: AbstractSkinElement, buff: FriendlyByteBuf) { - require(value is SkinElement) { "Invalid skin element provided, expected SkinElement, got ${value::class.qualifiedName}" } - - buff.writeUtf(value.texture.toString()) - buff.writeFloat(value.x) - buff.writeFloat(value.y) - buff.writeFloat(value.width) - buff.writeFloat(value.height) - buff.writeFloat(value.imageWidth) - buff.writeFloat(value.imageHeight) - buff.writeEnum(value.winding) - } - - override fun fromNetwork(buff: FriendlyByteBuf): AbstractSkinElement { - val texture = ResourceLocation(buff.readUtf()) - val x = buff.readFloat() - val y = buff.readFloat() - val width = buff.readFloat() - val height = buff.readFloat() - val imageWidth = buff.readFloat() - val imageHeight = buff.readFloat() - val winding = buff.readEnum(UVWindingOrder::class.java) - - return SkinElement(texture, x, y, width, height, imageWidth, imageHeight, winding) - } - - override fun fromJson(element: JsonObject): AbstractSkinElement { - val texture = element["texture"]?.asString ?: throw JsonSyntaxException("Missing texture element") - val x = element["x"]?.asFloat ?: throw JsonSyntaxException("Missing x element") - val y = element["y"]?.asFloat ?: throw JsonSyntaxException("Missing y element") - val width = element["width"]?.asFloat ?: throw JsonSyntaxException("Missing width element") - val height = element["height"]?.asFloat ?: throw JsonSyntaxException("Missing height element") - val imageWidth = element["imageWidth"]?.asFloat ?: throw JsonSyntaxException("Missing imageWidth element") - val imageHeight = element["imageHeight"]?.asFloat ?: throw JsonSyntaxException("Missing imageHeight element") - val winding = element["winding"]?.asString ?: throw JsonSyntaxException("Missing winding element") - - return SkinElement(ResourceLocation.tryParse(texture) ?: throw JsonSyntaxException("Invalid resource location: $texture"), x, y, width, height, imageWidth, imageHeight, UVWindingOrder.valueOf(winding)) - } - }, - ATLAS { - override fun toJson(value: AbstractSkinElement): JsonObject { - require(value is AtlasSkinElement) { "Invalid skin element provided, expected AtlasSkinElement, got ${value::class.qualifiedName}" } - - return JsonObject().also { - it["location"] = JsonPrimitive(value.location.toString()) - it["width"] = JsonPrimitive(value.width) - it["height"] = JsonPrimitive(value.height) - } - } - - override fun toNetwork(value: AbstractSkinElement, buff: FriendlyByteBuf) { - require(value is AtlasSkinElement) { "Invalid skin element provided, expected AtlasSkinElement, got ${value::class.qualifiedName}" } - - buff.writeResourceLocation(value.location) - buff.writeFloat(value.width) - buff.writeFloat(value.height) - } - - override fun fromNetwork(buff: FriendlyByteBuf): AbstractSkinElement { - val location = ResourceLocation(buff.readUtf()) - val width = buff.readFloat() - val height = buff.readFloat() - - return AtlasSkinElement(location, width, height) - } - - override fun fromJson(element: JsonObject): AbstractSkinElement { - val location = element["location"]?.asString ?: throw JsonSyntaxException("Missing location element") - val width = element["width"]?.asFloat ?: throw JsonSyntaxException("Missing width element") - val height = element["height"]?.asFloat ?: throw JsonSyntaxException("Missing height element") - - return AtlasSkinElement(ResourceLocation.tryParse(location) ?: throw JsonSyntaxException("Invalid resource location: $location"), width, height) - } - }, - SUBELEMENT { - override fun toJson(value: AbstractSkinElement): JsonObject { - require(value is SubSkinElement) { "Invalid skin element provided, expected SubSkinElement, got ${value::class.qualifiedName}" } - - return JsonObject().also { - it["parent"] = value.parent.toJson() - it["xOffset"] = JsonPrimitive(value.xOffset) - it["yOffset"] = JsonPrimitive(value.yOffset) - it["subWidth"] = JsonPrimitive(value.width) - it["subHeight"] = JsonPrimitive(value.height) - } - } - - override fun toNetwork(value: AbstractSkinElement, buff: FriendlyByteBuf) { - require(value is SubSkinElement) { "Invalid skin element provided, expected SubSkinElement, got ${value::class.qualifiedName}" } - - value.parent.toNetwork(buff) - buff.writeFloat(value.xOffset) - buff.writeFloat(value.yOffset) - buff.writeFloat(value.width) - buff.writeFloat(value.height) - } - - override fun fromNetwork(buff: FriendlyByteBuf): AbstractSkinElement { - val parent = Companion.fromNetwork(buff) - val xOffset = buff.readFloat() - val yOffset = buff.readFloat() - val subWidth = buff.readFloat() - val subHeight = buff.readFloat() - - return SubSkinElement(parent, xOffset, yOffset, subWidth, subHeight) - } - - override fun fromJson(element: JsonObject): AbstractSkinElement { - val parent = element["parent"] as? JsonObject ?: throw JsonSyntaxException("Invalid parent") - val xOffset = element["xOffset"].asFloat - val yOffset = element["yOffset"].asFloat - val subWidth = element["subWidth"].asFloat - val subHeight = element["subHeight"].asFloat - - return SubSkinElement(Companion.fromJson(parent), xOffset, yOffset, subWidth, subHeight) - } - }; - - protected abstract fun toJson(value: AbstractSkinElement): JsonObject - protected abstract fun toNetwork(value: AbstractSkinElement, buff: FriendlyByteBuf) - protected abstract fun fromNetwork(buff: FriendlyByteBuf): AbstractSkinElement - protected abstract fun fromJson(element: JsonObject): AbstractSkinElement - - fun toActualJson(value: AbstractSkinElement): JsonObject { - return toJson(value).also { - it["type"] = JsonPrimitive(name) - } - } - - fun toActualNetwork(value: AbstractSkinElement, buff: FriendlyByteBuf) { - buff.writeEnum(this) - toNetwork(value, buff) - } - - companion object { - fun fromNetwork(buff: FriendlyByteBuf): AbstractSkinElement { - val type = buff.readEnum(SkinElementType::class.java) - return type.fromNetwork(buff) - } - - fun fromJson(element: JsonObject): AbstractSkinElement { - val type = SkinElementType.valueOf(element["type"].asString) - return type.fromJson(element) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinElement.kt deleted file mode 100644 index 61dc54c57..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinElement.kt +++ /dev/null @@ -1,64 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import net.minecraft.resources.ResourceLocation - -class SubSkinElement( - val parent: AbstractSkinElement, - val xOffset: Float = 0f, - val yOffset: Float = 0f, - override val width: Float = parent.width, - override val height: Float = parent.height, - private val overrideWinding: UVWindingOrder? = null -) : AbstractSkinElement.Mutable() { - override var u0: Float = 0f - private set - override var v0: Float = 0f - private set - override var u1: Float = 0f - private set - override var v1: Float = 0f - private set - - private fun calculateUVs() { - u0 = parent.u0 + (xOffset / parent.width) * (parent.u1 - parent.u0) - v0 = parent.v0 + (yOffset / parent.height) * (parent.v1 - parent.v0) - u1 = parent.u0 + ((xOffset + width) / parent.width) * (parent.u1 - parent.u0) - v1 = parent.v0 + ((yOffset + height) / parent.height) * (parent.v1 - parent.v0) - - notifyListeners() - } - - override fun parentChanges(parent: AbstractSkinElement) = calculateUVs() - - init { - parent.addListener(this) - } - - init { - calculateUVs() - } - - override val texture: ResourceLocation - get() = parent.texture - - override val type: SkinElementType - get() = SkinElementType.SUBELEMENT - - override val winding: UVWindingOrder - get() = overrideWinding ?: parent.winding - - fun copy( - xOffset: Float = this.xOffset, - yOffset: Float = this.yOffset, - width: Float = this.width, - height: Float = this.height, - overrideWinding: UVWindingOrder? = this.overrideWinding - ) = SubSkinElement(parent, xOffset, yOffset, width, height, overrideWinding) -} - -fun AbstractSkinElement.subElement( - x: Float = 0f, - y: Float = 0f, - width: Float = this.width, - height: Float = this.height, -) = SubSkinElement(this, x, y, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/UVStuff.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/UVStuff.kt index 406e8fec9..b71b9d1c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/UVStuff.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/UVStuff.kt @@ -1,34 +1,47 @@ package ru.dbotthepony.mc.otm.client.render -import com.google.common.collect.ImmutableList - -sealed interface IUVCoords { +interface IUVCoords { val u0: Float val v0: Float val u1: Float val v1: Float - operator fun get(index: Int): Float + + operator fun component1() = u0 + operator fun component2() = v0 + operator fun component3() = u1 + operator fun component4() = v1 + + fun getUV(index: Int): Float { + return when (index) { + 0 -> u0 + 1 -> v0 + 2 -> u1 + 3 -> v1 + else -> throw IndexOutOfBoundsException("$index") + } + } } -private val mapImmutable = ArrayList<(input: UVCoords) -> Float>() +private fun interface FlatGetter { + operator fun get(u0: Float, v0: Float, u1: Float, v1: Float): Float +} + +private fun interface RegularGetter { + operator fun get(input: IUVCoords): Float +} + +private val getters = ArrayList() .also { it.add { it.u0 } } .also { it.add { it.v0 } } .also { it.add { it.u1 } } .also { it.add { it.v1 } } .toTypedArray() -private val mapMutable = ArrayList<(input: MutableUVCoords) -> Float>() - .also { it.add { it.u0 } } - .also { it.add { it.v0 } } - .also { it.add { it.u1 } } - .also { it.add { it.v1 } } - .toTypedArray() - -private val mapSetMutable = ArrayList<(input: MutableUVCoords, value: Float) -> Unit>() - .also { it.add { input, value -> input.u0 = value } } - .also { it.add { input, value -> input.v0 = value } } - .also { it.add { input, value -> input.u1 = value } } - .also { it.add { input, value -> input.v1 = value } } +private val flatGetters = ArrayList() + .also { it.add { u0, _, _, _ -> u0 } } + .also { it.add { _, v0, _, _ -> v0 } } + .also { it.add { _, _, u1, _ -> u1 } } + .also { it.add { _, _, _, v1 -> v1 } } .toTypedArray() data class UVCoords( @@ -36,43 +49,16 @@ data class UVCoords( override val v0: Float, override val u1: Float, override val v1: Float, -) : IUVCoords { - override fun get(index: Int): Float { - return mapImmutable[index].invoke(this) - } -} +) : IUVCoords data class MutableUVCoords( override var u0: Float, override var v0: Float, override var u1: Float, override var v1: Float, -) : IUVCoords { - override fun get(index: Int): Float { - return mapMutable[index].invoke(this) - } +) : IUVCoords - operator fun set(index: Int, value: Float) { - mapSetMutable[index].invoke(this, value) - } -} - -private val pickers = ArrayList<(u0: Float, - v0: Float, - u1: Float, - v1: Float) -> Float>() - .also { it.add { u0, _, _, _ -> u0 } } - .also { it.add { _, v0, _, _ -> v0 } } - .also { it.add { _, _, u1, _ -> u1 } } - .also { it.add { _, _, _, v1 -> v1 } } - .toTypedArray() - -enum class UVWindingOrder( - val u0: Int, - val v0: Int, - val u1: Int, - val v1: Int, -) { +enum class UVWindingOrder(val u0: Int, val v0: Int, val u1: Int, val v1: Int) { NORMAL(0, 1, 2, 3), // normal operation U0_V0_U1_V1(0, 1, 2, 3), // normal operation U0_V1_U1_V0(0, 3, 2, 1), // mirror y @@ -82,10 +68,6 @@ enum class UVWindingOrder( U1_V1_U0_V0(2, 3, 0, 1), // mirror both FLIP_FLOP(2, 3, 0, 1); // mirror both - companion object { - val distinct: List = ImmutableList.of(NORMAL, FLIP, FLOP, FLIP_FLOP) - } - val isIdentity: Boolean = u0 == 0 && v0 == 1 && u1 == 2 && v1 == 3 operator fun component1() = u0 @@ -93,57 +75,55 @@ enum class UVWindingOrder( operator fun component3() = u1 operator fun component4() = v1 - val u0Picker = pickers[u0] - val v0Picker = pickers[v0] - val u1Picker = pickers[u1] - val v1Picker = pickers[v1] + private val u0FlatGetter = flatGetters[u0] + private val v0FlatGetter = flatGetters[v0] + private val u1FlatGetter = flatGetters[u1] + private val v1FlatGetter = flatGetters[v1] - private val buffer = FloatArray(4) + private val u0Getter = getters[u0] + private val v0Getter = getters[v0] + private val u1Getter = getters[u1] + private val v1Getter = getters[v1] - fun translate( - u0: Float, - v0: Float, - u1: Float, - v1: Float, - ): UVCoords { - synchronized(buffer) { - buffer[0] = u0 - buffer[1] = v0 - buffer[2] = u1 - buffer[3] = v1 + fun u0(u0: Float, v0: Float, u1: Float, v1: Float): Float = u0FlatGetter[u0, v0, u1, v1] + fun v0(u0: Float, v0: Float, u1: Float, v1: Float): Float = v0FlatGetter[u0, v0, u1, v1] + fun u1(u0: Float, v0: Float, u1: Float, v1: Float): Float = u1FlatGetter[u0, v0, u1, v1] + fun v1(u0: Float, v0: Float, u1: Float, v1: Float): Float = v1FlatGetter[u0, v0, u1, v1] - return UVCoords(buffer[this.u0], buffer[this.v0], buffer[this.u1], buffer[this.v1]) - } + fun u0(input: IUVCoords): Float = u0Getter[input] + fun v0(input: IUVCoords): Float = v0Getter[input] + fun u1(input: IUVCoords): Float = u1Getter[input] + fun v1(input: IUVCoords): Float = v1Getter[input] + + fun translate(u0: Float, v0: Float, u1: Float, v1: Float): IUVCoords { + return UVCoords( + u0FlatGetter[u0, v0, u1, v1], + v0FlatGetter[u0, v0, u1, v1], + u1FlatGetter[u0, v0, u1, v1], + v1FlatGetter[u0, v0, u1, v1] + ) } - fun translate( - input: UVCoords - ): UVCoords { - synchronized(buffer) { - buffer[0] = input.u0 - buffer[1] = input.v0 - buffer[2] = input.u1 - buffer[3] = input.v1 - - return UVCoords(buffer[this.u0], buffer[this.v0], buffer[this.u1], buffer[this.v1]) - } + fun translate(input: IUVCoords): IUVCoords { + return UVCoords( + u0Getter[input], + v0Getter[input], + u1Getter[input], + v1Getter[input], + ) } - fun translate( - input: MutableUVCoords - ): MutableUVCoords { - synchronized(buffer) { - buffer[0] = input.u0 - buffer[1] = input.v0 - buffer[2] = input.u1 - buffer[3] = input.v1 + fun translate(input: MutableUVCoords): MutableUVCoords { + val u0 = u0(input) + val v0 = v0(input) + val u1 = u1(input) + val v1 = v1(input) - input[0] = buffer[this.u0] - input[1] = buffer[this.v0] - input[2] = buffer[this.u1] - input[3] = buffer[this.v1] + input.u0 = u0 + input.v0 = v0 + input.u1 = u1 + input.v1 = v1 - return input - } + return input } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetAtlasHolder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetAtlasHolder.kt deleted file mode 100644 index 30dd20860..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetAtlasHolder.kt +++ /dev/null @@ -1,96 +0,0 @@ -package ru.dbotthepony.mc.otm.client.render - -import net.minecraft.client.renderer.texture.TextureAtlas -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.client.renderer.texture.TextureManager -import net.minecraft.client.resources.TextureAtlasHolder -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.packs.resources.ResourceManager -import net.minecraft.util.profiling.ProfilerFiller -import net.minecraftforge.client.event.RegisterClientReloadListenersEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.isClientThread -import ru.dbotthepony.mc.otm.core.WriteOnce -import java.util.stream.Stream -import kotlin.properties.Delegates - -var isWidgetAtlasAvailable: Boolean = false - private set - -class WidgetAtlasHolder private constructor(manager: TextureManager) : TextureAtlasHolder(manager, LOCATION, "gui") { - var changeset = 0 - private set - - var once = false - private set - var demandsRebuild = false - private set - - private var resourceManager by Delegates.notNull() - private var profileManager by Delegates.notNull() - - override fun prepare(p_118891_: ResourceManager, p_118892_: ProfilerFiller): TextureAtlas.Preparations { - resourceManager = p_118891_ - profileManager = p_118892_ - changeset++ - return super.prepare(p_118891_, p_118892_) - } - - override fun apply(p_118894_: TextureAtlas.Preparations, p_118895_: ResourceManager, p_118896_: ProfilerFiller) { - once = true - resourceManager = p_118895_ - profileManager = p_118896_ - changeset++ - super.apply(p_118894_, p_118895_, p_118896_) - AtlasSkinElement.notifyListeners() - } - - fun rebuildIfRequired(): Boolean { - if (once && demandsRebuild && isClientThread()) { - apply(prepare(resourceManager, profileManager), resourceManager, profileManager) - } - - return demandsRebuild - } - - public override fun getSprite(p_118902_: ResourceLocation): TextureAtlasSprite { - if (!once) { - throw IllegalStateException("Trying to get sprite too early") - } - - return super.getSprite(p_118902_) - } - - fun queueRebuild() { - demandsRebuild = true - } - - override fun getResourcesToLoad(): Stream { - demandsRebuild = false - return AtlasSkinElement.keysStream - } - - companion object { - @JvmStatic - val LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/atlas/gui.png") - - @JvmStatic - var INSTANCE: WidgetAtlasHolder by WriteOnce() - private set - - @JvmStatic - fun register(event: RegisterClientReloadListenersEvent) { - INSTANCE = WidgetAtlasHolder(minecraft.textureManager) - event.registerReloadListener(INSTANCE) - isWidgetAtlasAvailable = true - } - - @JvmStatic - fun renderGameHook() { - if (isWidgetAtlasAvailable) { - INSTANCE.rebuildIfRequired() - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt index 85b3a3bb3..7929fc86c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt @@ -2,15 +2,22 @@ package ru.dbotthepony.mc.otm.client.render import net.minecraft.resources.ResourceLocation import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas object WidgetLocation { - val WIDGETS_18 = ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets_18.png") + val LARGE_BUTTON = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/large_button.png"), 72f, 18f) + val STORAGE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/storage_controls.png"), 90f, 72f) + val MISC_18 = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/misc18.png"), 72f, 72f) + val SLOT_BACKGROUNDS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/slot_backgrounds.png"), 72f, 72f) - val MISC = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/misc"), 64f, 64f) - val PATTERN_PANEL_TABS = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/pattern_panel_tabs"), 64f, 32f) + val MISC = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/misc.png"), 64f, 64f) + val TABS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/tabs.png"), 224f, 64f) + val PATTERN_PANEL_TABS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/pattern_panel_tabs.png"), 60f, 23f) - val CHECKBOX = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/checkbox"), 32f, 64f) - val PROGRESS_ARROWS = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/progress_arrows"), 32f, 32f) - val HORIZONTAL_GAUGES = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/horizontal_gauges"), 128f, 64f) - val VERTICAL_GAUGES = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/vertical_gauges"), 128f, 48f) + val CHECKBOX = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/checkbox.png"), 30f, 60f) + val PROGRESS_ARROWS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/progress_arrows.png"), 22f, 31f) + val HORIZONTAL_GAUGES = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/horizontal_gauges.png"), 96f, 54f) + val VERTICAL_GAUGES = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/vertical_gauges.png"), 90f, 48f) + val REDSTONE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/redstone.png"), 54f, 18f) + val SIDE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/side_controls.png"), 144f, 72f) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets.kt index 0921111a3..5248b2d71 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets.kt @@ -1,15 +1,15 @@ package ru.dbotthepony.mc.otm.client.render object Widgets { - val SLOT = WidgetLocation.MISC.subElement(0f, 0f, 18f, 18f) + val SLOT = WidgetLocation.MISC.sprite(0f, 0f, 18f, 18f) - val ARROW_UP_BUTTON_IDLE = WidgetLocation.MISC.subElement(0f, 18f, 18f, 6f) - val ARROW_UP_BUTTON_HOVERED = WidgetLocation.MISC.subElement(0f, 18f + 6f, 18f, 6f) - val ARROW_UP_BUTTON_PRESSED = WidgetLocation.MISC.subElement(0f, 18f + 6f * 2f, 18f, 6f) - val ARROW_UP_BUTTON_DISABLED = WidgetLocation.MISC.subElement(0f, 18f + 6f * 3f, 18f, 6f) + val ARROW_UP_BUTTON_IDLE = WidgetLocation.MISC.sprite(0f, 18f, 18f, 6f) + val ARROW_UP_BUTTON_HOVERED = WidgetLocation.MISC.sprite(0f, 18f + 6f, 18f, 6f) + val ARROW_UP_BUTTON_PRESSED = WidgetLocation.MISC.sprite(0f, 18f + 6f * 2f, 18f, 6f) + val ARROW_UP_BUTTON_DISABLED = WidgetLocation.MISC.sprite(0f, 18f + 6f * 3f, 18f, 6f) - val ARROW_DOWN_BUTTON_IDLE = ARROW_UP_BUTTON_IDLE.copy(overrideWinding = UVWindingOrder.FLIP) - val ARROW_DOWN_BUTTON_HOVERED = ARROW_UP_BUTTON_HOVERED.copy(overrideWinding = UVWindingOrder.FLIP) - val ARROW_DOWN_BUTTON_PRESSED = ARROW_UP_BUTTON_PRESSED.copy(overrideWinding = UVWindingOrder.FLIP) - val ARROW_DOWN_BUTTON_DISABLED = ARROW_UP_BUTTON_DISABLED.copy(overrideWinding = UVWindingOrder.FLIP) + val ARROW_DOWN_BUTTON_IDLE = ARROW_UP_BUTTON_IDLE.copy(winding = UVWindingOrder.FLIP) + val ARROW_DOWN_BUTTON_HOVERED = ARROW_UP_BUTTON_HOVERED.copy(winding = UVWindingOrder.FLIP) + val ARROW_DOWN_BUTTON_PRESSED = ARROW_UP_BUTTON_PRESSED.copy(winding = UVWindingOrder.FLIP) + val ARROW_DOWN_BUTTON_DISABLED = ARROW_UP_BUTTON_DISABLED.copy(winding = UVWindingOrder.FLIP) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt index 34a812e8d..8999e8f91 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt @@ -1,49 +1,177 @@ package ru.dbotthepony.mc.otm.client.render -private fun makeButton(grid: SkinGrid): StretchingRectangleElement { +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.client.render.sprites.GridAtlas +import ru.dbotthepony.mc.otm.client.render.sprites.StretchingRectangleElement +import ru.dbotthepony.mc.otm.client.render.sprites.sprite +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide + +private fun makeButton(grid: GridAtlas): StretchingRectangleElement { val x = grid.currentX val y = grid.currentY return StretchingRectangleElement( - topLeft = WidgetLocation.WIDGETS_18.element(x, y, 2f, 2f, grid.imageWidth, grid.imageHeight), - topRight = WidgetLocation.WIDGETS_18.element(x + 16f, y, 2f, 2f, grid.imageWidth, grid.imageHeight), - bottomLeft = WidgetLocation.WIDGETS_18.element(x, y + 16f, 2f, 2f, grid.imageWidth, grid.imageHeight), - bottomRight = WidgetLocation.WIDGETS_18.element(x + 16f, y + 16f, 2f, 2f, grid.imageWidth, grid.imageHeight), - middle = WidgetLocation.WIDGETS_18.element(x + 2f, y + 2f, 14f, 14f, grid.imageWidth, grid.imageHeight), - top = WidgetLocation.WIDGETS_18.element(x + 2f, y, 14f, 2f, grid.imageWidth, grid.imageHeight), - left = WidgetLocation.WIDGETS_18.element(x, y + 2f, 2f, 14f, grid.imageWidth, grid.imageHeight), - right = WidgetLocation.WIDGETS_18.element(x + 16f, y + 2f, 2f, 14f, grid.imageWidth, grid.imageHeight), - bottom = WidgetLocation.WIDGETS_18.element(x + 2f, y + 16f, 14f, 2f, grid.imageWidth, grid.imageHeight), + topLeft = grid.texture.sprite(x, y, 2f, 2f, grid.atlasWidth, grid.atlasHeight), + topRight = grid.texture.sprite(x + 16f, y, 2f, 2f, grid.atlasWidth, grid.atlasHeight), + bottomLeft = grid.texture.sprite(x, y + 16f, 2f, 2f, grid.atlasWidth, grid.atlasHeight), + bottomRight = grid.texture.sprite(x + 16f, y + 16f, 2f, 2f, grid.atlasWidth, grid.atlasHeight), + middle = grid.texture.sprite(x + 2f, y + 2f, 14f, 14f, grid.atlasWidth, grid.atlasHeight), + top = grid.texture.sprite(x + 2f, y, 14f, 2f, grid.atlasWidth, grid.atlasHeight), + left = grid.texture.sprite(x, y + 2f, 2f, 14f, grid.atlasWidth, grid.atlasHeight), + right = grid.texture.sprite(x + 16f, y + 2f, 2f, 14f, grid.atlasWidth, grid.atlasHeight), + bottom = grid.texture.sprite(x + 2f, y + 16f, 14f, 2f, grid.atlasWidth, grid.atlasHeight), ) } object Widgets18 { - val GRID = SkinGrid(WidgetLocation.WIDGETS_18, 18f, 18f) + private val buttonGrids = WidgetLocation.LARGE_BUTTON.grid(rows = 1, columns = 4) - val BUTTON_IDLE_STRETCHABLE = makeButton(GRID) - val BUTTON_IDLE = GRID.next() - val BUTTON_HOVERED_STRETCHABLE = makeButton(GRID) - val BUTTON_HOVERED = GRID.next() - val BUTTON_PRESSED_STRETCHABLE = makeButton(GRID) - val BUTTON_PRESSED = GRID.next() - val ARROW_DOWN = GRID.next() - val AZ = GRID.next() - val COUNT = GRID.next() - val COLON = GRID.next() - val C = GRID.next() - val CROSS = GRID.next() - val FORWARD_SLASH = GRID.next() - val BATTERY_SLOT_BACKGROUND = GRID.next() - val PATTERN_SLOT_BACKGROUND = GRID.next() - val EQUIPMENT_BATTERY_SLOT_BACKGROUND = GRID.next() - val MATTER_CAPACITOR_SLOT_BACKGROUND = GRID.next() - val RETURN_ARROW_LEFT = GRID.next() + val BUTTON_IDLE_STRETCHABLE = makeButton(buttonGrids) + val BUTTON_IDLE = buttonGrids.next() + val BUTTON_HOVERED_STRETCHABLE = makeButton(buttonGrids) + val BUTTON_HOVERED = buttonGrids.next() + val BUTTON_PRESSED_STRETCHABLE = makeButton(buttonGrids) + val BUTTON_PRESSED = buttonGrids.next() + val BUTTON_DISABLED_STRETCHABLE = makeButton(buttonGrids) + val BUTTON_DISABLED = buttonGrids.next() - init { - GRID.jump() + private val storageGrid = WidgetLocation.STORAGE_CONTROLS.grid(rows = 4, columns = 5) + val SORT_DESCENDING = storageGrid.next() + val SORT_ASCENDING = storageGrid.next() + val SORT_DEFAULT = storageGrid.next() + val SORT_ALPHABET = storageGrid.next() + val SORT_COUNT = storageGrid.next() + val SORT_MODID = storageGrid.next() + val SORT_ID = storageGrid.next() + val SORT_MATTER_VALUE = storageGrid.next() + val SORT_MATTER_COMPLEXITY = storageGrid.next() + val ONLY_STORE = storageGrid.next() + val ONLY_EXTRACT = storageGrid.next() + val STORE_EXTRACT = storageGrid.next() + val PAUSE = storageGrid.next() + val PLAY = storageGrid.next() + val STOP = storageGrid.next() + val SORT_NOW = storageGrid.next() + + private val miscGrid = WidgetLocation.MISC_18.grid(4, 4) + + val COOLDOWN = miscGrid.next() + val CROSS = miscGrid.next() + val FORWARD_SLASH = miscGrid.next() + val RETURN_ARROW_LEFT = miscGrid.next() + val CURIOS_INVENTORY = miscGrid.next() + + private val slotBgGrid = WidgetLocation.SLOT_BACKGROUNDS.grid(4, 4) + + val BATTERY_SLOT_BACKGROUND = slotBgGrid.next() + val EQUIPMENT_BATTERY_SLOT_BACKGROUND = slotBgGrid.next() + val PATTERN_SLOT_BACKGROUND = slotBgGrid.next() + val MATTER_CAPACITOR_SLOT_BACKGROUND = slotBgGrid.next() + val CHARGE_SLOT_BACKGROUND = slotBgGrid.next() + + private val controlsGrid = WidgetLocation.SIDE_CONTROLS.grid(rows = 8, columns = 9) + + val BATTERY_ONLY = controlsGrid.next() + val ITEMS_CONFIGURATION = controlsGrid.next() + val ENERGY_CONFIGURATION = controlsGrid.next() + val FLUID_CONFIGURATION = controlsGrid.next() + + val REDSTONE_IGNORED = controlsGrid.next() + val REDSTONE_LOW = controlsGrid.next() + val REDSTONE_HIGH = controlsGrid.next() + + val BALANCING_DISABLED = controlsGrid.next() + val BALANCING_ENABLED = controlsGrid.next() + + class SideControls { + val disabled = controlsGrid.next() + val input = controlsGrid.next() + val pull = controlsGrid.next() + val output = controlsGrid.next() + val push = controlsGrid.next() + val inputOutput = controlsGrid.next() + val pullOutput = controlsGrid.next() + val inputPush = controlsGrid.next() + val pullPush = controlsGrid.next() + + val flow = immutableMap { + put(FlowDirection.NONE, disabled) + put(FlowDirection.INPUT, input) + put(FlowDirection.OUTPUT, output) + put(FlowDirection.BI_DIRECTIONAL, inputOutput) + } + + val flowPush = immutableMap { + put(FlowDirection.NONE, disabled) + put(FlowDirection.INPUT, input) + put(FlowDirection.OUTPUT, push) + put(FlowDirection.BI_DIRECTIONAL, inputPush) + } + + val flowPull = immutableMap { + put(FlowDirection.NONE, disabled) + put(FlowDirection.INPUT, pull) + put(FlowDirection.OUTPUT, output) + put(FlowDirection.BI_DIRECTIONAL, pullOutput) + } + + val flowPullPush = immutableMap { + put(FlowDirection.NONE, disabled) + put(FlowDirection.INPUT, pull) + put(FlowDirection.OUTPUT, push) + put(FlowDirection.BI_DIRECTIONAL, pullPush) + } + + val items = immutableMap { + put(MatteryDeviceBlockEntity.ItemHandlerMode.DISABLED, disabled) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT, input) + put(MatteryDeviceBlockEntity.ItemHandlerMode.OUTPUT, output) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT_OUTPUT, inputOutput) + put(MatteryDeviceBlockEntity.ItemHandlerMode.BATTERY, BATTERY_ONLY) + } + + val itemsPush = immutableMap { + put(MatteryDeviceBlockEntity.ItemHandlerMode.DISABLED, disabled) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT, input) + put(MatteryDeviceBlockEntity.ItemHandlerMode.OUTPUT, push) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT_OUTPUT, inputPush) + put(MatteryDeviceBlockEntity.ItemHandlerMode.BATTERY, BATTERY_ONLY) + } + + val itemsPull = immutableMap { + put(MatteryDeviceBlockEntity.ItemHandlerMode.DISABLED, disabled) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT, pull) + put(MatteryDeviceBlockEntity.ItemHandlerMode.OUTPUT, output) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT_OUTPUT, pullOutput) + put(MatteryDeviceBlockEntity.ItemHandlerMode.BATTERY, BATTERY_ONLY) + } + + val itemsPullPush = immutableMap { + put(MatteryDeviceBlockEntity.ItemHandlerMode.DISABLED, disabled) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT, pull) + put(MatteryDeviceBlockEntity.ItemHandlerMode.OUTPUT, push) + put(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT_OUTPUT, pullPush) + put(MatteryDeviceBlockEntity.ItemHandlerMode.BATTERY, BATTERY_ONLY) + } } - val BUTTON_DISABLED_STRETCHABLE = makeButton(GRID) - val BUTTON_DISABLED = GRID.next() - val COOLDOWN = GRID.next() + val LEFT_CONTROLS = SideControls() + val RIGHT_CONTROLS = SideControls() + val TOP_CONTROLS = SideControls() + val BOTTOM_CONTROLS = SideControls() + val FRONT_CONTROLS = SideControls() + val BACK_CONTROLS = SideControls() + + val CONTROLS = immutableMap { + put(RelativeSide.BOTTOM, BOTTOM_CONTROLS) + put(RelativeSide.TOP, TOP_CONTROLS) + put(RelativeSide.LEFT, LEFT_CONTROLS) + put(RelativeSide.RIGHT, RIGHT_CONTROLS) + put(RelativeSide.FRONT, FRONT_CONTROLS) + put(RelativeSide.BACK, BACK_CONTROLS) + } + + val UPGRADES = controlsGrid.next() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt index 4b39a9136..44af60e19 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets8.kt @@ -2,9 +2,10 @@ package ru.dbotthepony.mc.otm.client.render import net.minecraft.resources.ResourceLocation import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.sprites.GridAtlas object Widgets8 { - val GRID = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets_8"), 64f, 32f).subGrid(8f, 8f, 64 / 8, 32 / 8) + val GRID = GridAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets_8.png"), 8f, 8f, columns = 64 / 8, rows = 32 / 8) val BUTTON_IDLE = GRID[0, 0] val BUTTON_HOVERED = GRID[0, 1] @@ -21,6 +22,7 @@ object Widgets8 { val ARROW_PAINTED_UP = GRID[1, 2] val MINUS = GRID[1, 3] - val EXOSUIT_SHOWN = GRID[2, 1] - val EXOSUIT_HIDDEN = GRID[3, 1] + val EXOPACK_SHOWN = GRID[2, 1] + val EXOPACK_HIDDEN = GRID[3, 1] + val EXOPACK_COLOR = GRID[4, 1] } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BankRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BankRenderer.kt index 312172f87..07969b2b0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BankRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BankRenderer.kt @@ -1,26 +1,41 @@ package ru.dbotthepony.mc.otm.client.render.blockentity import com.mojang.blaze3d.vertex.PoseStack -import com.mojang.math.Vector3f import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import net.minecraft.client.resources.model.BakedModel import net.minecraft.core.Direction -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.block.entity.BatteryBankBlockEntity -import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.level.levelgen.XoroshiroRandomSource +import net.minecraftforge.client.event.ModelEvent +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.matter.MatterCapacitorBankBlockEntity -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.AtlasSkinElement +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.rotate +import ru.dbotthepony.mc.otm.core.math.rotateY +import ru.dbotthepony.mc.otm.nanoTime +import java.util.function.BooleanSupplier import kotlin.math.PI -abstract class BankRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { +abstract class BankRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { protected abstract fun gaugeLevel(entity: T): Float - protected abstract val texture: AbstractSkinElement + protected abstract val texture: AbstractMatterySprite + protected abstract val models: List + protected abstract fun status(entity: T): List + + private val random = XoroshiroRandomSource(nanoTime) override fun render( blockEntity: T, @@ -30,6 +45,26 @@ abstract class BankRenderer(private val context: BlockEn p_112311_: Int, p_112312_: Int ) { + val status = status(blockEntity) + + stack.pushPose() + stack.translate(0.5f, 0.5f, 0.5f) + stack.rotate(blockEntity.blockState[BlockRotationFreedom.HORIZONTAL.property].front) + stack.translate(-0.5f, -0.5f, -0.5f) + + for ((i, model) in models.withIndex()) { + if (!status[i].asBoolean) { + continue + } + + val buffer = DynamicBufferSource.WORLD.getBuffer(RenderType.solid()) + + for (quad in model.getQuads(null, null, random)) + buffer.putBulkData(stack.last(), quad, 1f, 1f, 1f, p_112311_, p_112312_) + } + + stack.popPose() + val plainLevel = gaugeLevel(blockEntity) if (plainLevel <= 0f) { @@ -38,11 +73,11 @@ abstract class BankRenderer(private val context: BlockEn stack.pushPose() - val facing = blockEntity.blockState[RotatableMatteryBlock.FACING] + val facing = blockEntity.blockState[BlockRotationFreedom.HORIZONTAL].front val rotateFacing = facing == Direction.NORTH || facing == Direction.SOUTH if (rotateFacing) { - stack.mulPose(Vector3f.YP.rotation(PI.toFloat() / 2f)) + stack.rotateY(PI.toFloat() / 2f) } stack.scale(0.02f, 0.01f, 0.01f) @@ -53,27 +88,29 @@ abstract class BankRenderer(private val context: BlockEn val width = 9f val heightMax = 39.36f - val buffer = DynamicBufferSource.WORLD.getBuffer(AtlasSkinElement.renderTypeWorld) + val buffer = DynamicBufferSource.WORLD.getBuffer(texture.renderTypeWorld) - texture.uploadOntoPartialColor( + texture.uploadOntoPartial( stack, buffer, x = 25f - width / 2f, y = 30f, width = width, - height = heightMax * gaugeLevel(blockEntity) + height = heightMax * gaugeLevel(blockEntity), + color = RGBAColor.WHITE ) - stack.mulPose(Vector3f.YP.rotation(PI.toFloat())) + stack.rotateY(PI.toFloat()) stack.translate(-50.0, 0.0, -101.0) - texture.uploadOntoPartialColor( + texture.uploadOntoPartial( stack, buffer, x = 25f - width / 2f, y = 30f, width = width, - height = heightMax * gaugeLevel(blockEntity) + height = heightMax * gaugeLevel(blockEntity), + color = RGBAColor.WHITE ) stack.popPose() @@ -81,20 +118,56 @@ abstract class BankRenderer(private val context: BlockEn } class BatteryBankRenderer(context: BlockEntityRendererProvider.Context) : BankRenderer(context) { + override val models: List by lazy { + immutableList(12) { + minecraft.modelManager.modelBakery.bakedTopLevelModels[ResourceLocation(OverdriveThatMatters.MOD_ID, "block/battery/battery$it")]!! + } + } + + override fun status(entity: BatteryBankBlockEntity): List { + return entity.batteryStatus + } + override fun gaugeLevel(entity: BatteryBankBlockEntity): Float { return entity.gaugeLevel } - override val texture: AbstractSkinElement + override val texture: AbstractMatterySprite get() = PowerGaugePanel.GAUGE_FOREGROUND + + companion object { + fun onRegisterAdditionalModels(event: ModelEvent.RegisterAdditional) { + for (i in 0 .. 11) { + event.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "block/battery/battery$i")) + } + } + } } class MatterBatteryBankRenderer(context: BlockEntityRendererProvider.Context) : BankRenderer(context) { + override val models: List by lazy { + immutableList(12) { + minecraft.modelManager.modelBakery.bakedTopLevelModels[ResourceLocation(OverdriveThatMatters.MOD_ID, "block/battery/matter_capacitor$it")]!! + } + } + + override fun status(entity: MatterCapacitorBankBlockEntity): List { + return entity.capacitorStatus + } + override fun gaugeLevel(entity: MatterCapacitorBankBlockEntity): Float { return entity.gaugeLevel } - override val texture: AbstractSkinElement + override val texture: AbstractMatterySprite get() = MatterGaugePanel.GAUGE_FOREGROUND + + companion object { + fun onRegisterAdditionalModels(event: ModelEvent.RegisterAdditional) { + for (i in 0 .. 11) { + event.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "block/battery/matter_capacitor$i")) + } + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt index 40d9458ed..f47dd49e4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/BlackHoleRenderer.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.client.render.blockentity import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.* -import com.mojang.math.Matrix4f import net.minecraft.client.Minecraft import net.minecraft.client.renderer.GameRenderer import net.minecraft.client.renderer.MultiBufferSource @@ -10,14 +9,26 @@ import net.minecraft.client.renderer.blockentity.BeaconRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider import net.minecraft.world.phys.Vec3 +import org.joml.Matrix4f import org.lwjgl.opengl.GL30 import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.entity.GravitationStabilizerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.ShiftPressedCond import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.* -import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.VECTOR_FORWARD +import ru.dbotthepony.mc.otm.core.math.VECTOR_RIGHT +import ru.dbotthepony.mc.otm.core.math.VECTOR_UP +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.asMutableAngle +import ru.dbotthepony.mc.otm.core.math.asVector +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.rotateAroundAxis +import ru.dbotthepony.mc.otm.core.math.times +import ru.dbotthepony.mc.otm.core.util.formatMatter import ru.dbotthepony.mc.otm.registry.MItems import kotlin.math.PI import kotlin.math.pow @@ -94,7 +105,6 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context i: Int, i1: Int ) { - setDrawColor(RGBAColor.BLACK) RenderSystem.setShader(GameRenderer::getPositionColorShader) RenderSystem.depthFunc(GL30.GL_LESS) @@ -105,10 +115,9 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context poseStack.pushPose() val size = tile.gravitationStrength.pow(0.5) poseStack.translate(0.5, -size / 2.0 + 0.5, 0.5) - colorSphere(poseStack, size.toFloat()) + renderColoredSphere(poseStack, size.toFloat(), color = RGBAColor.BLACK) RenderSystem.enableCull() - RenderSystem.enableTexture() poseStack.popPose() @@ -163,23 +172,20 @@ class BlackHoleRenderer(private val context: BlockEntityRendererProvider.Context poseStack.pushPose() poseStack.translate(0.5, 0.75, 0.5) - poseStack.translate(ang.forward() * tile.gravitationStrength * 0.6) + poseStack.translate(ang.forward() * tile.gravitationStrength.pow(0.5)) poseStack.rotateAroundPoint(poseStack.translation(), ang) val scale = (distance.coerceAtLeast(8.0) / 168.0).toFloat() poseStack.scale(scale, scale, scale) val font = Minecraft.getInstance().font - val text1 = TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", tile.mass.formatMatter()) + val text1 = TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", tile.mass.formatMatter(formatAsReadable = ShiftPressedCond)) val text2 = TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(tile.gravitationStrength)) val sorse = DynamicBufferSource.WORLD - font.drawAligned(poseStack, sorse, text1, TextAlign.TOP_LEFT, 0.8f, 0.8f - font.lineHeight.toFloat() / 2f, 0x0) - font.drawAligned(poseStack, sorse, text2, TextAlign.TOP_LEFT, 0.8f, 0.8f + font.lineHeight.toFloat() / 2f, 0x0) - poseStack.translate(0.0, 0.0, -1.0) - font.drawAligned(poseStack, sorse, text1, TextAlign.TOP_LEFT, 0f, -font.lineHeight.toFloat() / 2f, 0xFFFFFF) - font.drawAligned(poseStack, sorse, text2, TextAlign.TOP_LEFT, 0f, font.lineHeight.toFloat() / 2f, 0xFFFFFF) + font.draw(poseStack, text1, 0.8f, 0.8f - font.lineHeight.toFloat() / 2f, gravity = RenderGravity.TOP_LEFT, color = RGBAColor.WHITE, buffer = sorse, drawOutline = true) + font.draw(poseStack, text2, 0.8f, 0.8f + font.lineHeight.toFloat() / 2f, gravity = RenderGravity.TOP_LEFT, color = RGBAColor.WHITE, buffer = sorse, drawOutline = true) poseStack.popPose() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt index 95d5fdd75..e67110f66 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/EnergyCounterRenderer.kt @@ -1,20 +1,20 @@ package ru.dbotthepony.mc.otm.client.render.blockentity import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.Minecraft import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider import net.minecraft.core.Direction import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.EnergyCounterBlock -import ru.dbotthepony.mc.otm.block.entity.EnergyCounterBlockEntity +import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.* -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.core.asAngle -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.asAngle +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.core.math.times import kotlin.math.PI class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { @@ -49,21 +49,21 @@ class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Con var y = -16f - val finalX = font.drawAligned(poseStack, sorse, "00000000", TextAlign.CENTER_CENTER, -4f, y, RGBAColor.GRAY) - font.drawAligned(poseStack, sorse, "00000000", TextAlign.CENTER_CENTER, -4f, y + font.lineHeight, RGBAColor.GRAY) - font.drawAligned(poseStack, sorse, "/t", TextAlign.CENTER_LEFT, finalX.toFloat(), y, RGBAColor.GRAY) - font.drawAligned(poseStack, sorse, "/s", TextAlign.CENTER_LEFT, finalX.toFloat(), y + font.lineHeight, RGBAColor.GRAY) + val finalX = font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y, color = RGBAColor.GRAY) + font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y + font.lineHeight, color = RGBAColor.GRAY) + font.draw(poseStack, buffer = sorse, text = "/t", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y, color = RGBAColor.GRAY) + font.draw(poseStack, buffer = sorse, text = "/s", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y + font.lineHeight, color = RGBAColor.GRAY) poseStack.pushPose() poseStack.translate(-0.1, -0.1, -0.1) - font.drawAligned(poseStack, sorse, tile.lastTick.toString(0), TextAlign.CENTER_RIGHT, finalX.toFloat(), y, RGBAColor.WHITE) - font.drawAligned(poseStack, sorse, tile.sumHistory(20).toString(0), TextAlign.CENTER_RIGHT, finalX.toFloat(), y + font.lineHeight, RGBAColor.WHITE) + font.draw(poseStack, buffer = sorse, text = tile.lastTick.toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y, color = RGBAColor.WHITE) + font.draw(poseStack, buffer = sorse, text = tile.sumHistory(20).toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y + font.lineHeight, color = RGBAColor.WHITE) poseStack.popPose() y += font.lineHeight * 3 - font.drawAligned(poseStack, sorse, TOTAL, TextAlign.CENTER_CENTER, 0f, y, RGBAColor.WHITE) - font.drawAligned(poseStack, sorse, tile.passed.formatPower(), TextAlign.CENTER_CENTER, 0f, y + font.lineHeight, RGBAColor.WHITE) + font.draw(poseStack, buffer = sorse, text = TOTAL, gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y, color = RGBAColor.WHITE) + font.draw(poseStack, buffer = sorse,text = tile.passed.formatPower(), gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y + font.lineHeight, color = RGBAColor.WHITE) poseStack.popPose() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt new file mode 100644 index 000000000..c36fb08dd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/FluidTankRenderer.kt @@ -0,0 +1,142 @@ +package ru.dbotthepony.mc.otm.client.render.blockentity + +import com.mojang.blaze3d.vertex.* +import com.mojang.math.Axis +import net.minecraft.client.renderer.* +import net.minecraft.client.renderer.block.model.ItemTransforms +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import net.minecraft.client.renderer.entity.ItemRenderer +import net.minecraft.world.inventory.InventoryMenu +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.block.HalfTransparentBlock +import net.minecraft.world.level.block.StainedGlassPaneBlock +import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.rotateAround +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.linearInterpolation +import ru.dbotthepony.mc.otm.registry.MBlocks + +class FluidTankRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + override fun render( + tile: FluidTankBlockEntity, + partialTick: Float, + poseStack: PoseStack, + bufferSource: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + renderFluidInTank(tile.fluid, tile.synchronizedFluid, poseStack, bufferSource, packedLight, packedOverlay) + } + + object FluidTankItemRenderer : BlockEntityWithoutLevelRenderer(minecraft.blockEntityRenderDispatcher, minecraft.entityModels) { + override fun renderByItem( + stack: ItemStack, + displayContext: ItemTransforms.TransformType, + poseStack: PoseStack, + bufferSource: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + val model = minecraft.modelManager.blockModelShaper.getBlockModel(MBlocks.FLUID_TANK.defaultBlockState()) + + stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + renderFluidInTank(it, it.getFluidInTank(0), poseStack, bufferSource, packedLight, packedOverlay) + } + + val hasFoil = stack.hasFoil() + val fabulous = displayContext == ItemTransforms.TransformType.GUI + || displayContext.firstPerson() + || (stack.item is BlockItem && ((stack.item as BlockItem).block is HalfTransparentBlock || (stack.item as BlockItem).block is StainedGlassPaneBlock)) + + for (renderPass in model.getRenderPasses(stack, fabulous)) { + for (renderType in renderPass.getRenderTypes(stack, fabulous)) { + val buffer = if (fabulous) ItemRenderer.getFoilBufferDirect(bufferSource, renderType, true, hasFoil) else ItemRenderer.getFoilBuffer(bufferSource, renderType, true, hasFoil) + minecraft.itemRenderer.renderModelLists(renderPass, stack, packedLight, packedOverlay, poseStack, buffer) + } + } + } + } + + private companion object { + val fluidTop = 13f / 16f + val fluidBottom = 3f / 16f + val fluidHeight = 10f / 16f + val fluidWidth = 15.8f / 16f + + val fluidPadding = (1f - fluidWidth) * .5f + + fun renderFluidInTank( + fluidHandler: IFluidHandler, + fluid: FluidStack, + poseStack: PoseStack, + bufferSource: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + if (fluid.isEmpty) + return + + val fluidLevel = fluid.amount.toFloat() / fluidHandler.getTankCapacity(0).toFloat() + + if (fluidLevel < 0.01f) + return + + val data = IClientFluidTypeExtensions.of(fluid.fluid) + val texture = data.stillTexture + val sprite = minecraft.getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(texture) + val tint = RGBAColor.argb(data.getTintColor(fluid)) + val gas = fluid.fluid.fluidType.isLighterThanAir + + val interp = linearInterpolation(fluidLevel, sprite.v1, sprite.v0) + val v1 = linearInterpolation(fluidBottom, sprite.v1, sprite.v0) + + val builder = bufferSource.getBuffer(Sheets.translucentCullBlockSheet()) + + poseStack.pushPose() + poseStack.translate(0f, fluidBottom + (if (gas) 1f - fluidLevel else 0f) * fluidHeight, 0f) + + for (i in 0 .. 3) { + poseStack.pushPose() + + poseStack.translate(0.5, 0.0, 0.5) + poseStack.rotateAround(Axis.YP.rotationDegrees(i * 90.0f), 0.0f, 1.0f, 0.0f) + poseStack.translate(-0.5f, 0f, fluidWidth * .5f) + + val matrix = poseStack.last().pose() + + builder.vertex(matrix, fluidPadding, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, 0f, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, interp).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, 0f).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, interp).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + + poseStack.popPose() + } + + val matrix = poseStack.last().pose() + + if (!gas && fluidLevel < 1f) { // top + builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding, fluidLevel * fluidHeight, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + } + + if (gas && fluidLevel < 1f) { // bottom + builder.vertex(matrix, fluidPadding, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v0).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding + fluidWidth, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u1, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + builder.vertex(matrix, fluidPadding, 0f, fluidPadding + fluidWidth).color(tint.red, tint.green, tint.blue, tint.alpha).uv(sprite.u0, sprite.v1).overlayCoords(packedOverlay).uv2(packedLight).normal(0.0f, 1.0f, 0.0f).endVertex() + } + + poseStack.popPose() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt index f5960e948..60e1b97f4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/GravitationStabilizerRenderer.kt @@ -2,8 +2,6 @@ package ru.dbotthepony.mc.otm.client.render.blockentity import com.mojang.blaze3d.vertex.* -import com.mojang.math.Matrix4f -import net.minecraft.client.Minecraft import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.blockentity.BeaconRenderer @@ -11,18 +9,30 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider import net.minecraft.client.renderer.texture.OverlayTexture import net.minecraft.core.Direction +import org.joml.Matrix4f import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.BlackHoleBlock -import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock -import ru.dbotthepony.mc.otm.block.entity.GravitationStabilizerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.client.ShiftPressedCond import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.VECTOR_DOWN +import ru.dbotthepony.mc.otm.core.math.VECTOR_FORWARD +import ru.dbotthepony.mc.otm.core.math.VECTOR_RIGHT +import ru.dbotthepony.mc.otm.core.math.VECTOR_UP +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.asAngle +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.rotateAroundAxis +import ru.dbotthepony.mc.otm.core.math.times +import ru.dbotthepony.mc.otm.core.util.formatMatter import kotlin.math.PI -@Suppress("PrivatePropertyName") class GravitationStabilizerRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { override fun render( tile: GravitationStabilizerBlockEntity, @@ -37,7 +47,7 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv var len = 64.0 var lenI = 64 - val normal = tile.blockState.getValue(RotatableMatteryBlock.FACING_FULL).normal + val normal = tile.blockState[BlockRotationFreedom.DIRECTIONAL].normal val level = tile.level var bhTile: BlackHoleBlockEntity? = null @@ -58,7 +68,7 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv poseStack.pushPose() poseStack.translate(0.5, 0.5, 0.5) - val facing: Direction = tile.blockState.getValue(RotatableMatteryBlock.FACING_FULL) + val facing = tile.blockState[BlockRotationFreedom.DIRECTIONAL].front val rotateZ: Double val rotateY: Double @@ -145,12 +155,20 @@ class GravitationStabilizerRenderer(private val context: BlockEntityRendererProv val sorse = DynamicBufferSource.WORLD val font = minecraft.font - font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.formatMatter()), TextAlign.TOP_CENTER, 1f, -font.lineHeight.toFloat() / 2f + 1f, 0) - font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), TextAlign.TOP_CENTER, 1f, font.lineHeight.toFloat() / 2f + 1f, 0) - poseStack.translate(facing.opposite.normal * 0.02) - font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.formatMatter()), TextAlign.TOP_CENTER, 0f, -font.lineHeight.toFloat() / 2f, 0xFFFFFF) - font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), TextAlign.TOP_CENTER, 0f, font.lineHeight.toFloat() / 2f, 0xFFFFFF) + font.draw( + poseStack, + TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.formatMatter(formatAsReadable = ShiftPressedCond)), + 1f, -font.lineHeight.toFloat() / 2f + 1f, + color = RGBAColor.WHITE, buffer = sorse, gravity = RenderGravity.TOP_CENTER, + drawOutline = true) + + font.draw( + poseStack, + TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), + 1f, font.lineHeight.toFloat() / 2f + 1f, + color = RGBAColor.WHITE, buffer = sorse, gravity = RenderGravity.TOP_CENTER, + drawOutline = true) poseStack.popPose() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt new file mode 100644 index 000000000..4e1be4a88 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/HoloSignRenderer.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.client.render.blockentity + +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity +import ru.dbotthepony.mc.otm.client.font +import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.draw +import ru.dbotthepony.mc.otm.core.get +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.rotateWithBlockFacing + +class HoloSignRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + override fun render( + tile: HoloSignBlockEntity, + partialTick: Float, + poseStack: PoseStack, + badSorse: MultiBufferSource, + p_112311_: Int, + p_112312_: Int + ) { + if (tile.redstoneControl.isBlockedByRedstone) + return + + poseStack.pushPose() + poseStack.rotateWithBlockFacing(tile.blockState[BlockRotationFreedom.DIRECTIONAL_WITH_ROTATION]) + poseStack.translate(0.5f, 0.5f, 0.5f) + poseStack.scale(0.01f, 0.01f, 0.01f) + + val sorse = DynamicBufferSource.WORLD + + val lines = tile.signText.split('\n') + val totalHeight = lines.size * font.lineHeight + (lines.size - 1) * 2f + var y = -totalHeight / 2f + + for (line in lines) { + font.draw(poseStack = poseStack, buffer = sorse, text = line, gravity = RenderGravity.TOP_CENTER, y = y, color = RGBAColor.YELLOW) + y += font.lineHeight + 2f + } + + poseStack.popPose() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReconstructorRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReconstructorRenderer.kt new file mode 100644 index 000000000..df2c300b3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReconstructorRenderer.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.mc.otm.client.render.blockentity + +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.math.Axis +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.block.model.ItemTransforms +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource + +class MatterReconstructorRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + override fun render( + tile: MatterReconstructorBlockEntity, + partialTick: Float, + pose: PoseStack, + bufferSource: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + val item = tile.visualItemStack + + if (item.isEmpty) { + return + } + + pose.pushPose() + + pose.translate(0.5, 0.6, 0.5) + pose.scale(0.5f, 0.5f, 0.5f) + + pose.mulPose(Axis.XP.rotationDegrees(90f)) + pose.mulPose(Axis.ZP.rotationDegrees(tile.blockRotation.back.toYRot())) + + context.itemRenderer.renderStatic(item, ItemTransforms.TransformType.FIXED, packedLight, packedOverlay, pose, DynamicBufferSource.WORLD, tile.blockPos.asLong().toInt()) + + pose.popPose() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReplicatorRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReplicatorRenderer.kt index 42205bffa..73bc49d0d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReplicatorRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterReplicatorRenderer.kt @@ -1,37 +1,27 @@ package ru.dbotthepony.mc.otm.client.render.blockentity -import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.platform.GlStateManager.DestFactor import com.mojang.blaze3d.platform.GlStateManager.SourceFactor import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack -import com.mojang.math.Vector3f import net.minecraft.client.renderer.MultiBufferSource import net.minecraft.client.renderer.block.model.ItemTransforms import net.minecraft.client.renderer.blockentity.BlockEntityRenderer import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider -import net.minecraft.client.server.IntegratedServer import net.minecraft.core.particles.DustParticleOptions import net.minecraft.world.level.levelgen.XoroshiroRandomSource -import org.lwjgl.opengl.GL14 -import org.lwjgl.opengl.GL14.GL_ADD -import org.lwjgl.opengl.GL14.GL_FUNC_ADD -import org.lwjgl.opengl.GL14.GL_MIN +import org.joml.Vector3f import org.lwjgl.opengl.GL14.glBlendColor -import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity -import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource import ru.dbotthepony.mc.otm.client.render.lockBlendFunc -import ru.dbotthepony.mc.otm.client.render.popScissorRect -import ru.dbotthepony.mc.otm.client.render.pushScissorRect -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.core.component1 -import ru.dbotthepony.mc.otm.core.component2 -import ru.dbotthepony.mc.otm.core.component3 +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 import ru.dbotthepony.mc.otm.core.get -import ru.dbotthepony.mc.otm.core.normalizeAngleDeg +import ru.dbotthepony.mc.otm.core.math.normalizeAngleDeg +import ru.dbotthepony.mc.otm.core.math.rotateYDegrees import ru.dbotthepony.mc.otm.isPaused class MatterReplicatorRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { @@ -78,7 +68,7 @@ class MatterReplicatorRenderer(private val context: BlockEntityRendererProvider. if (!isPaused) tile.renderRotation = normalizeAngleDeg(tile.renderRotation + diff / 10_000_000f) - pose.mulPose(Vector3f.YP.rotationDegrees(tile.renderRotation)) + pose.rotateYDegrees(tile.renderRotation) val model = context.itemRenderer.getModel( item, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterScannerRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterScannerRenderer.kt new file mode 100644 index 000000000..f61b83232 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/blockentity/MatterScannerRenderer.kt @@ -0,0 +1,70 @@ +package ru.dbotthepony.mc.otm.client.render.blockentity + +import com.mojang.blaze3d.platform.GlStateManager +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.math.Axis +import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.block.model.ItemTransforms +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider +import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity +import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource +import ru.dbotthepony.mc.otm.client.render.lockBlendFunc + +class MatterScannerRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer { + companion object { + private val source = DynamicBufferSource(maximalInitialBufferSize = 2 shl 10) + } + + override fun render( + tile: MatterScannerBlockEntity, + partialTick: Float, + pose: PoseStack, + bufferSource: MultiBufferSource, + packedLight: Int, + packedOverlay: Int + ) { + val item = tile.visualItemStack + + if (item.isEmpty) { + return + } + + pose.pushPose() + + pose.translate(0.5, 0.5, 0.5) + pose.scale(0.5f, 0.5f, 0.5f) + + pose.mulPose(Axis.XP.rotationDegrees(90f)) + pose.mulPose(Axis.ZP.rotationDegrees(tile.blockRotation.back.toYRot())) + + context.itemRenderer.renderStatic( + item, + ItemTransforms.TransformType.FIXED, + packedLight, + packedOverlay, + pose, + source, + tile.blockPos.asLong().toInt() + ) + + RenderSystem.enableBlend() + RenderSystem.setShaderColor(1f, 1f, 1f, 1f - tile.visualProgress) + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA) + + lockBlendFunc = true + + try { + source.endBatch() + } finally { + lockBlendFunc = false + } + + RenderSystem.disableBlend() + RenderSystem.defaultBlendFunc() + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + + pose.popPose() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt index 34c8917fd..926a04f65 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/entity/PlasmaProjectileRenderer.kt @@ -1,37 +1,50 @@ package ru.dbotthepony.mc.otm.client.render.entity import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.math.Axis import net.minecraft.client.renderer.MultiBufferSource +import net.minecraft.client.renderer.RenderType import net.minecraft.client.renderer.entity.EntityRenderer import net.minecraft.client.renderer.entity.EntityRendererProvider +import net.minecraft.client.renderer.texture.OverlayTexture import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.client.render.TextAlign -import ru.dbotthepony.mc.otm.client.render.drawAligned -import ru.dbotthepony.mc.otm.client.render.rotateAroundPoint -import ru.dbotthepony.mc.otm.client.render.translation -import ru.dbotthepony.mc.otm.core.Angle +import ru.dbotthepony.mc.otm.OverdriveThatMatters.loc import ru.dbotthepony.mc.otm.entity.PlasmaProjectile -import kotlin.math.PI class PlasmaProjectileRenderer(context: EntityRendererProvider.Context) : EntityRenderer(context) { - override fun getTextureLocation(p_114482_: PlasmaProjectile): ResourceLocation { - throw UnsupportedOperationException() - } + override fun getTextureLocation(p_114482_: PlasmaProjectile): ResourceLocation = PLASMA_PROJECTILE_LOCATION override fun render( entity: PlasmaProjectile, - p_114486_: Float, - p_114487_: Float, - pose: PoseStack, + entityYaw: Float, + partialTick: Float, + poseStack: PoseStack, buffer: MultiBufferSource, - p_114490_: Int + packedLight: Int ) { - super.render(entity, p_114486_, p_114487_, pose, buffer, p_114490_) + val consumer = buffer.getBuffer(RENDER_TYPE) - pose.pushPose() - pose.scale(0.03f, 0.03f, 0.03f) - pose.rotateAroundPoint(pose.translation(), Angle.deg(roll = entity.xRot.toDouble(), yaw = entity.yRot.toDouble(), pitch = 180.0)) - font.drawAligned(pose, "PLASMA", TextAlign.CENTER_CENTER, 0f, 0f, 0xFFFFFF) - pose.popPose() + poseStack.pushPose() + + poseStack.mulPose(entityRenderDispatcher.cameraOrientation()) + poseStack.mulPose(Axis.YP.rotationDegrees(180f)) + poseStack.scale(.3f, .3f, .3f) + + val matrix = poseStack.last().pose() + val normal = poseStack.last().normal() + + consumer.vertex(matrix, -.5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex() + consumer.vertex(matrix, .5f, -.5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 1f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex() + consumer.vertex(matrix, .5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(1f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex() + consumer.vertex(matrix, -.5f, .5f, 0f).color(1f, 1f, 1f, 1f).uv(0f, 0f).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(packedLight).normal(normal, 0f, 1f, 0f).endVertex() + + poseStack.popPose() + + super.render(entity, entityYaw, partialTick, poseStack, buffer, packedLight) + } + + companion object { + private val PLASMA_PROJECTILE_LOCATION: ResourceLocation = loc("textures/misc/plasma_ball.png") + private val RENDER_TYPE = RenderType.itemEntityTranslucentCull(PLASMA_PROJECTILE_LOCATION) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt new file mode 100644 index 000000000..09013d598 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/AbstractMatterySprite.kt @@ -0,0 +1,261 @@ +package ru.dbotthepony.mc.otm.client.render.sprites + +import com.google.gson.JsonObject +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.blaze3d.vertex.VertexConsumer +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.client.renderer.RenderStateShard +import net.minecraft.client.renderer.RenderType +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import org.lwjgl.opengl.GL11.GL_ALWAYS +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.IUVCoords +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.render.color +import ru.dbotthepony.mc.otm.client.render.renderTexturedRect +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.linearInterpolation +import java.util.concurrent.ConcurrentHashMap + +sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords { + /** + * Expected image width in pixels, used in calculations + * and as default width argument in render methods + */ + abstract override val width: Float + + /** + * Expected image height in pixels, used in calculations + * and as default height argument in render methods + */ + abstract override val height: Float + + fun partialU(offset: Float): Float { + return u0 + (offset / width) * (u1 - u0) + } + + fun partialV(offset: Float): Float { + return v0 + (offset / height) * (v1 - v0) + } + + abstract val type: SpriteType + override val winding: UVWindingOrder get() = UVWindingOrder.NORMAL + abstract val texture: ResourceLocation + + /** + * See [ru.dbotthepony.mc.otm.client.render.clearDepth] + */ + fun clearDepth( + graphics: MGUIGraphics, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + ) = ru.dbotthepony.mc.otm.client.render.clearDepth(graphics, x, y, width, height) + + fun render( + stack: PoseStack, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding + ) { + renderTexturedRect(stack.last().pose(), x, y, width, height, uvWinding = winding, uv = this, texture = texture) + } + + override fun render( + guiGraphics: MGUIGraphics, + x: Float, + y: Float, + width: Float, + height: Float, + winding: UVWindingOrder, + color: RGBAColor + ) { + guiGraphics.renderTexturedRect(x, y, width, height, uvWinding = winding, color = color, texture = texture, uv = this) + } + + fun renderPartial( + stack: PoseStack, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding, + color: RGBAColor = RGBAColor.WHITE + ) { + val u1 = u0 + linearInterpolation(width / this.width, 0f, u1 - u0) + val v1 = v0 + linearInterpolation(height / this.height, 0f, v1 - v0) + + val winded = winding.translate(u0, v0, u1.coerceIn(0f, 1f), v1.coerceIn(0f, 1f)) + + renderTexturedRect( + stack.last().pose(), + x = x, y = y, + width = width, height = height, + uv = winded, texture = texture, + color = color + ) + } + + fun renderPartial( + graphics: MGUIGraphics, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding, + color: RGBAColor = RGBAColor.WHITE + ) = renderPartial(graphics.pose, x, y, width, height, winding, color) + + protected fun uploadOnto( + pose: PoseStack, + builder: VertexConsumer, + x: Float, + y: Float, + width: Float, + height: Float, + z: Float, + color: RGBAColor?, + u0: Float, + v0: Float, + u1: Float, + v1: Float, + ) { + val matrix = pose.last().pose() + builder.vertex(matrix, x, y + height, z).color(color).uv(u0, v1).endVertex() + builder.vertex(matrix, x + width, y + height, z).color(color).uv(u1, v1).endVertex() + builder.vertex(matrix, x + width, y, z).color(color).uv(u1, v0).endVertex() + builder.vertex(matrix, x, y, z).color(color).uv(u0, v0).endVertex() + } + + fun uploadOnto( + pose: PoseStack, + builder: VertexConsumer, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + z: Float = 0f, + color: RGBAColor? = null, + winding: UVWindingOrder = this.winding, + ) { + val u0 = winding.u0(this) + val v0 = winding.v0(this) + val u1 = winding.u1(this) + val v1 = winding.v1(this) + + uploadOnto(pose, builder, x, y, width, height, z, color, u0, v0, u1, v1) + } + + fun uploadOntoPartial( + pose: PoseStack, + builder: VertexConsumer, + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + z: Float = 0f, + color: RGBAColor? = null, + winding: UVWindingOrder = this.winding, + ) { + val u1 = (u0 + linearInterpolation(width / this.width, 0f, u1 - u0)).coerceAtLeast(0f).coerceAtMost(1f) + val v1 = (v0 + linearInterpolation(height / this.height, 0f, v1 - v0)).coerceAtLeast(0f).coerceAtMost(1f) + val (u0_, v0_, u1_, v1_) = winding.translate(u0, v0, u1, v1) + + uploadOnto(pose, builder, x, y, width, height, z, color, u0_, v0_, u1_, v1_) + } + + fun toJson(): JsonObject { + return type.toActualJson(this) + } + + fun toNetwork(buff: FriendlyByteBuf) { + type.toActualNetwork(this, buff) + } + + val renderTypeNoDepth by lazy { + noDepthCache.computeIfAbsent(texture) { + val builder = RenderType.CompositeState.builder() + + builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false)) + builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) + builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_ALWAYS)) + builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + }, { + RenderSystem.disableBlend() + })) + + @Suppress("INACCESSIBLE_TYPE") + RenderType.create("otm_gui_element_no_depth", + DefaultVertexFormat.POSITION_COLOR_TEX, + VertexFormat.Mode.QUADS, + 2048, + false, + false, + builder.createCompositeState(false)) as RenderType + } + } + + val renderTypeDepth by lazy { + depthCache.computeIfAbsent(texture) { + val builder = RenderType.CompositeState.builder() + + builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false)) + builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) + builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + }, { + RenderSystem.disableBlend() + })) + + @Suppress("INACCESSIBLE_TYPE") + RenderType.create("otm_gui_element_depth", + DefaultVertexFormat.POSITION_COLOR_TEX, + VertexFormat.Mode.QUADS, + 2048, + false, + false, + builder.createCompositeState(false)) as RenderType + } + } + + val renderTypeWorld by lazy { + worldCache.computeIfAbsent(texture) { + val builder = RenderType.CompositeState.builder() + + builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false)) + builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader)) + builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", { + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + }, { + RenderSystem.disableBlend() + })) + + @Suppress("INACCESSIBLE_TYPE") + RenderType.create("otm_gui_element_world", + DefaultVertexFormat.POSITION_COLOR_TEX, + VertexFormat.Mode.QUADS, + 8192, + false, + false, + builder.createCompositeState(false)) as RenderType + } + } + + companion object { + private val noDepthCache = ConcurrentHashMap() + private val depthCache = ConcurrentHashMap() + private val worldCache = ConcurrentHashMap() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinGrid.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridAtlas.kt similarity index 54% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinGrid.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridAtlas.kt index ca4e96896..6d2650488 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinGrid.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridAtlas.kt @@ -1,9 +1,9 @@ -package ru.dbotthepony.mc.otm.client.render +package ru.dbotthepony.mc.otm.client.render.sprites import net.minecraft.resources.ResourceLocation @Suppress("unused") -data class SkinGrid( +data class GridAtlas( val texture: ResourceLocation, val width: Float, val height: Float, @@ -13,13 +13,13 @@ data class SkinGrid( var row = 0 var column = 0 - val imageWidth get() = width * columns - val imageHeight get() = height * rows + val atlasWidth get() = width * columns + val atlasHeight get() = height * rows val currentX: Float get() = column * width val currentY: Float get() = row * height - fun skip(): SkinGrid { + fun skip(): GridAtlas { column++ if (column >= columns) { @@ -29,30 +29,30 @@ data class SkinGrid( return this } - fun jump(): SkinGrid { + fun jump(): GridAtlas { row++ column = 0 return this } - fun next(): SkinElement { - val element = SkinElement(texture, currentX, currentY, width, height, imageWidth, imageHeight) + fun next(): MatterySprite { + val element = MatterySprite(texture, currentX, currentY, width, height, atlasWidth, atlasHeight) skip() return element } - fun reset(): SkinGrid { + fun reset(): GridAtlas { row = 0 column = 0 return this } - fun seek(row: Int, column: Int): SkinGrid { + fun seek(row: Int, column: Int): GridAtlas { this.row = row this.column = column return this } operator fun get(column: Int, row: Int) = - SkinElement(texture, column * width, row * height, width, height, imageWidth, imageHeight) + MatterySprite(texture, column * width, row * height, width, height, atlasWidth, atlasHeight) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinGrid.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridInSprite.kt similarity index 54% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinGrid.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridInSprite.kt index 65cabcfec..be67b84b2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SubSkinGrid.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/GridInSprite.kt @@ -1,8 +1,8 @@ -package ru.dbotthepony.mc.otm.client.render +package ru.dbotthepony.mc.otm.client.render.sprites @Suppress("unused") -data class SubSkinGrid( - val parent: AbstractSkinElement, +data class GridInSprite( + val parent: AbstractMatterySprite, val width: Float, val height: Float, val columns: Int = 16, @@ -14,7 +14,7 @@ data class SubSkinGrid( val currentX: Float get() = column * width val currentY: Float get() = row * height - fun skip(): SubSkinGrid { + fun skip(): GridInSprite { column++ if (column >= columns) { @@ -24,37 +24,37 @@ data class SubSkinGrid( return this } - fun jump(): SubSkinGrid { + fun jump(): GridInSprite { row++ column = 0 return this } - fun next(): AbstractSkinElement { - val element = parent.subElement(currentX, currentY, width, height) + fun next(): AbstractMatterySprite { + val element = parent.sprite(currentX, currentY, width, height) skip() return element } - fun reset(): SubSkinGrid { + fun reset(): GridInSprite { row = 0 column = 0 return this } - fun seek(row: Int, column: Int): SubSkinGrid { + fun seek(row: Int, column: Int): GridInSprite { this.row = row this.column = column return this } operator fun get(column: Int, row: Int) = - parent.subElement(column * width, row * height, width, height) + parent.sprite(column * width, row * height, width, height) } -fun AbstractSkinElement.subGrid( +fun AbstractMatterySprite.grid( width: Float, height: Float, columns: Int = 16, rows: Int = 16, -) = SubSkinGrid(this, width, height, columns, rows) +) = GridInSprite(this, width, height, columns, rows) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatteryAtlas.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatteryAtlas.kt new file mode 100644 index 000000000..d27706892 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatteryAtlas.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.client.render.sprites + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder + +fun ResourceLocation.atlas( + width: Float, + height: Float, + winding: UVWindingOrder = UVWindingOrder.NORMAL, +) = MatteryAtlas(this, width, height, winding) + +data class MatteryAtlas( + val texture: ResourceLocation, + val width: Float, + val height: Float, + val winding: UVWindingOrder = UVWindingOrder.NORMAL, +) { + fun sprite( + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding, + ) = MatterySprite(texture, x = x, y = y, width = width, height = height, atlasHeight = this.height, atlasWidth = this.width, winding = winding) + + fun grid(rows: Int, columns: Int) = GridAtlas(texture, this.width / columns, this.height / rows, rows = rows, columns = columns) + fun grid(width: Float, height: Float, rows: Int, columns: Int) = GridAtlas(texture, width, height, rows, columns) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatterySprite.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatterySprite.kt new file mode 100644 index 000000000..719be2139 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/MatterySprite.kt @@ -0,0 +1,85 @@ +package ru.dbotthepony.mc.otm.client.render.sprites + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder + +fun ResourceLocation.sprite( + x: Float, + y: Float, + width: Float, + height: Float, + textureWidth: Float = 256f, + textureHeight: Float = 256f +) = MatterySprite(this, x, y, width, height, textureWidth, textureHeight) + +fun ResourceLocation.pixel( + x: Float, + y: Float, + textureWidth: Float = 256f, + textureHeight: Float = 256f +) = MatterySprite(this, x, y, 1f, 1f, textureWidth, textureHeight) + +fun ResourceLocation.hLine( + x: Float, + y: Float, + width: Float, + textureWidth: Float = 256f, + textureHeight: Float = 256f +) = MatterySprite(this, x, y, width, 1f, textureWidth, textureHeight) + +fun ResourceLocation.vLine( + x: Float, + y: Float, + height: Float, + textureWidth: Float = 256f, + textureHeight: Float = 256f +) = MatterySprite(this, x, y, 1f, height, textureWidth, textureHeight) + +@Suppress("unused") +class MatterySprite( + override val texture: ResourceLocation, + val x: Float, + val y: Float, + override val width: Float, + override val height: Float, + val atlasWidth: Float = 256f, + val atlasHeight: Float = 256f, + override val winding: UVWindingOrder = UVWindingOrder.NORMAL, +) : AbstractMatterySprite() { + init { + require(x >= 0f) { "Invalid x $x" } + require(y >= 0f) { "Invalid y $y" } + require(width > 0f) { "Invalid width $width" } + require(height > 0f) { "Invalid height $height" } + require(atlasWidth > 0f) { "Invalid image width $atlasWidth" } + require(atlasHeight > 0f) { "Invalid image height $atlasHeight" } + + require(x + width <= atlasWidth) { "${x + width} <= $atlasWidth" } + require(y + height <= atlasHeight) { "${y + height} <= $atlasHeight" } + } + + override val u0 = this.x / atlasWidth + override val v0 = this.y / atlasHeight + override val u1 = (this.x + width) / atlasWidth + override val v1 = (this.y + height) / atlasHeight + + override val type: SpriteType + get() = SpriteType.SINGLE + + fun copy( + x: Float = this.x, + y: Float = this.y, + width: Float = this.width, + height: Float = this.height, + winding: UVWindingOrder = this.winding, + ): MatterySprite { + return MatterySprite(texture, x, y, width, height, atlasWidth, atlasHeight, winding) + } + + companion object { + fun single(texture: ResourceLocation, width: Float, height: Float, winding: UVWindingOrder = UVWindingOrder.NORMAL): MatterySprite { + return MatterySprite(texture, 0f, 0f, width, height, width, height, winding) + } + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt new file mode 100644 index 000000000..9fd4b7efd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SpriteType.kt @@ -0,0 +1,138 @@ +package ru.dbotthepony.mc.otm.client.render.sprites + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSyntaxException +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.core.set + +enum class SpriteType { + SINGLE { + override fun toJson(value: AbstractMatterySprite): JsonObject { + require(value is MatterySprite) { "Invalid skin element provided, expected SkinElement, got ${value::class.qualifiedName}" } + + return JsonObject().also { + it["texture"] = JsonPrimitive(value.texture.toString()) + it["x"] = JsonPrimitive(value.x) + it["y"] = JsonPrimitive(value.y) + it["width"] = JsonPrimitive(value.width) + it["height"] = JsonPrimitive(value.height) + it["imageWidth"] = JsonPrimitive(value.atlasWidth) + it["imageHeight"] = JsonPrimitive(value.atlasHeight) + it["winding"] = JsonPrimitive(value.winding.name) + } + } + + override fun toNetwork(value: AbstractMatterySprite, buff: FriendlyByteBuf) { + require(value is MatterySprite) { "Invalid skin element provided, expected SkinElement, got ${value::class.qualifiedName}" } + + buff.writeUtf(value.texture.toString()) + buff.writeFloat(value.x) + buff.writeFloat(value.y) + buff.writeFloat(value.width) + buff.writeFloat(value.height) + buff.writeFloat(value.atlasWidth) + buff.writeFloat(value.atlasHeight) + buff.writeEnum(value.winding) + } + + override fun fromNetwork(buff: FriendlyByteBuf): AbstractMatterySprite { + val texture = ResourceLocation(buff.readUtf()) + val x = buff.readFloat() + val y = buff.readFloat() + val width = buff.readFloat() + val height = buff.readFloat() + val imageWidth = buff.readFloat() + val imageHeight = buff.readFloat() + val winding = buff.readEnum(UVWindingOrder::class.java) + + return MatterySprite(texture, x, y, width, height, imageWidth, imageHeight, winding) + } + + override fun fromJson(element: JsonObject): AbstractMatterySprite { + val texture = element["texture"]?.asString ?: throw JsonSyntaxException("Missing texture element") + val x = element["x"]?.asFloat ?: throw JsonSyntaxException("Missing x element") + val y = element["y"]?.asFloat ?: throw JsonSyntaxException("Missing y element") + val width = element["width"]?.asFloat ?: throw JsonSyntaxException("Missing width element") + val height = element["height"]?.asFloat ?: throw JsonSyntaxException("Missing height element") + val imageWidth = element["imageWidth"]?.asFloat ?: throw JsonSyntaxException("Missing imageWidth element") + val imageHeight = element["imageHeight"]?.asFloat ?: throw JsonSyntaxException("Missing imageHeight element") + val winding = element["winding"]?.asString ?: throw JsonSyntaxException("Missing winding element") + + return MatterySprite(ResourceLocation.tryParse(texture) ?: throw JsonSyntaxException("Invalid resource location: $texture"), x, y, width, height, imageWidth, imageHeight, UVWindingOrder.valueOf(winding)) + } + }, + SUBSPRITE { + override fun toJson(value: AbstractMatterySprite): JsonObject { + require(value is SubMatterySprite) { "Invalid skin element provided, expected SubSkinElement, got ${value::class.qualifiedName}" } + + return JsonObject().also { + it["parent"] = value.parent.toJson() + it["xOffset"] = JsonPrimitive(value.xOffset) + it["yOffset"] = JsonPrimitive(value.yOffset) + it["subWidth"] = JsonPrimitive(value.width) + it["subHeight"] = JsonPrimitive(value.height) + } + } + + override fun toNetwork(value: AbstractMatterySprite, buff: FriendlyByteBuf) { + require(value is SubMatterySprite) { "Invalid skin element provided, expected SubSkinElement, got ${value::class.qualifiedName}" } + + value.parent.toNetwork(buff) + buff.writeFloat(value.xOffset) + buff.writeFloat(value.yOffset) + buff.writeFloat(value.width) + buff.writeFloat(value.height) + } + + override fun fromNetwork(buff: FriendlyByteBuf): AbstractMatterySprite { + val parent = Companion.fromNetwork(buff) + val xOffset = buff.readFloat() + val yOffset = buff.readFloat() + val subWidth = buff.readFloat() + val subHeight = buff.readFloat() + + return SubMatterySprite(parent, xOffset, yOffset, subWidth, subHeight) + } + + override fun fromJson(element: JsonObject): AbstractMatterySprite { + val parent = element["parent"] as? JsonObject ?: throw JsonSyntaxException("Invalid parent") + val xOffset = element["xOffset"].asFloat + val yOffset = element["yOffset"].asFloat + val subWidth = element["subWidth"].asFloat + val subHeight = element["subHeight"].asFloat + + return SubMatterySprite(Companion.fromJson(parent), xOffset, yOffset, subWidth, subHeight) + } + }; + + protected abstract fun toJson(value: AbstractMatterySprite): JsonObject + protected abstract fun toNetwork(value: AbstractMatterySprite, buff: FriendlyByteBuf) + protected abstract fun fromNetwork(buff: FriendlyByteBuf): AbstractMatterySprite + protected abstract fun fromJson(element: JsonObject): AbstractMatterySprite + + fun toActualJson(value: AbstractMatterySprite): JsonObject { + return toJson(value).also { + it["type"] = JsonPrimitive(name) + } + } + + fun toActualNetwork(value: AbstractMatterySprite, buff: FriendlyByteBuf) { + buff.writeEnum(this) + toNetwork(value, buff) + } + + companion object { + fun fromNetwork(buff: FriendlyByteBuf): AbstractMatterySprite { + val type = buff.readEnum(SpriteType::class.java) + return type.fromNetwork(buff) + } + + fun fromJson(element: JsonObject): AbstractMatterySprite { + val type = SpriteType.valueOf(element["type"].asString) + return type.fromJson(element) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/StretchingRectangleElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/StretchingRectangleElement.kt similarity index 74% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/render/StretchingRectangleElement.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/StretchingRectangleElement.kt index 8fe0010fb..bc68a78e1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/StretchingRectangleElement.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/StretchingRectangleElement.kt @@ -1,19 +1,20 @@ -package ru.dbotthepony.mc.otm.client.render +package ru.dbotthepony.mc.otm.client.render.sprites import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty data class StretchingRectangleElement( - val topLeft: AbstractSkinElement, - val topRight: AbstractSkinElement, - val bottomLeft: AbstractSkinElement, - val bottomRight: AbstractSkinElement, - val left: AbstractSkinElement, - val right: AbstractSkinElement, - val top: AbstractSkinElement, - val bottom: AbstractSkinElement, - val middle: AbstractSkinElement, + val topLeft: AbstractMatterySprite, + val topRight: AbstractMatterySprite, + val bottomLeft: AbstractMatterySprite, + val bottomRight: AbstractMatterySprite, + val left: AbstractMatterySprite, + val right: AbstractMatterySprite, + val top: AbstractMatterySprite, + val bottom: AbstractMatterySprite, + val middle: AbstractMatterySprite, val padding: DockProperty = DockProperty.EMPTY ) { fun render( @@ -43,6 +44,15 @@ data class StretchingRectangleElement( bottomRight.render(stack, x + width - bottomRight.width, y + height - bottomLeft.height) } + @JvmOverloads + fun render( + graphics: MGUIGraphics, + x: Float = 0f, + y: Float = 0f, + width: Float, + height: Float, + ) = render(graphics.pose, x, y, width, height) + companion object { fun square( texture: ResourceLocation, @@ -60,8 +70,8 @@ data class StretchingRectangleElement( val padding = sideThickness - innerThickness return StretchingRectangleElement( - topLeft = texture.element(x, y, cornerWidth, cornerHeight, imageWidth, imageHeight), - topRight = texture.element( + topLeft = texture.sprite(x, y, cornerWidth, cornerHeight, imageWidth, imageHeight), + topRight = texture.sprite( x + width - cornerWidth, y, cornerWidth, @@ -69,7 +79,7 @@ data class StretchingRectangleElement( imageWidth, imageHeight ), - bottomLeft = texture.element( + bottomLeft = texture.sprite( x, y + height - cornerHeight, cornerWidth, @@ -77,7 +87,7 @@ data class StretchingRectangleElement( imageWidth, imageHeight ), - bottomRight = texture.element( + bottomRight = texture.sprite( x + width - cornerWidth, y + height - cornerHeight, cornerWidth, @@ -86,7 +96,7 @@ data class StretchingRectangleElement( imageHeight ), - top = texture.element( + top = texture.sprite( x + cornerWidth, y, width - cornerWidth * 2f, @@ -94,7 +104,7 @@ data class StretchingRectangleElement( imageWidth, imageHeight ), - bottom = texture.element( + bottom = texture.sprite( x + cornerWidth, y + height - sideThickness, width - cornerWidth * 2f, @@ -102,8 +112,8 @@ data class StretchingRectangleElement( imageWidth, imageHeight ), - left = texture.element(x, y, sideThickness, height - cornerHeight * 2f, imageWidth, imageHeight), - right = texture.element( + left = texture.sprite(x, y, sideThickness, height - cornerHeight * 2f, imageWidth, imageHeight), + right = texture.sprite( x, y + width - sideThickness, sideThickness, @@ -112,7 +122,7 @@ data class StretchingRectangleElement( imageHeight ), - middle = texture.element( + middle = texture.sprite( x + innerThickness, y + innerThickness, width - innerThickness * 2f, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SubMatterySprite.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SubMatterySprite.kt new file mode 100644 index 000000000..3ebd1350b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/sprites/SubMatterySprite.kt @@ -0,0 +1,42 @@ +package ru.dbotthepony.mc.otm.client.render.sprites + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder + +class SubMatterySprite( + val parent: AbstractMatterySprite, + val xOffset: Float = 0f, + val yOffset: Float = 0f, + override val width: Float = parent.width, + override val height: Float = parent.height, + private val overrideWinding: UVWindingOrder? = null +) : AbstractMatterySprite() { + override val u0 = parent.u0 + (xOffset / parent.width) * (parent.u1 - parent.u0) + override val v0 = parent.v0 + (yOffset / parent.height) * (parent.v1 - parent.v0) + override val u1 = parent.u0 + ((xOffset + width) / parent.width) * (parent.u1 - parent.u0) + override val v1 = parent.v0 + ((yOffset + height) / parent.height) * (parent.v1 - parent.v0) + + override val texture: ResourceLocation + get() = parent.texture + + override val type: SpriteType + get() = SpriteType.SUBSPRITE + + override val winding: UVWindingOrder + get() = overrideWinding ?: parent.winding + + fun copy( + xOffset: Float = this.xOffset, + yOffset: Float = this.yOffset, + width: Float = this.width, + height: Float = this.height, + overrideWinding: UVWindingOrder? = this.overrideWinding + ) = SubMatterySprite(parent, xOffset, yOffset, width, height, overrideWinding) +} + +fun AbstractMatterySprite.sprite( + x: Float = 0f, + y: Float = 0f, + width: Float = this.width, + height: Float = this.height, +) = SubMatterySprite(this, x, y, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/BatteryBankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/BatteryBankScreen.kt deleted file mode 100644 index 8cb61abca..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/BatteryBankScreen.kt +++ /dev/null @@ -1,28 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.BatteryBankMenu -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel - -class BatteryBankScreen(menu: BatteryBankMenu, p_97742_: Inventory, p_97743_: Component) : - MatteryScreen(menu, p_97742_, p_97743_) { - - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerLevel, LEFT_MARGIN, GAUGE_TOP_WITHOUT_SLOT) - - for (i in 0 .. 5) - BatterySlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * i, 32f) - - for (i in 6 .. 11) - BatterySlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * (i - 6), 32f + 18f) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/CargoCrateScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/CargoCrateScreen.kt deleted file mode 100644 index f941a1f48..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/CargoCrateScreen.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.GridPanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel -import ru.dbotthepony.mc.otm.menu.CargoCrateMenu -import ru.dbotthepony.mc.otm.menu.MinecartCargoCrateMenu - -class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, 22f + 4f + 6f * 18f, getTitle()) - val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) - - for (slot in menu.storageSlots) - SlotPanel(this, grid, slot) - - return frame - } -} - -class MinecartCargoCrateScreen(menu: MinecartCargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, 22f + 4f + 6f * 18f, getTitle()) - val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) - - for (slot in menu.storageSlots) - SlotPanel(this, grid, slot) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ChemicalGeneratorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ChemicalGeneratorScreen.kt deleted file mode 100644 index 3558493f9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ChemicalGeneratorScreen.kt +++ /dev/null @@ -1,35 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel -import ru.dbotthepony.mc.otm.menu.ChemicalGeneratorMenu - -class ChemicalGeneratorScreen(menu: ChemicalGeneratorMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.energy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - val self = this - val progress = object : ProgressGaugePanel(self, frame, menu.progress, 78f, PROGRESS_ARROW_TOP) { - override fun makeTooltip(): MutableList { - val list = super.makeTooltip() - - list.add(TranslatableComponent("otm.gui.power.burn_time", menu.burnTime)) - - return list - } - } - - progress.flop = true - SlotPanel(this, frame, menu.residueSlot, 56f, PROGRESS_SLOT_TOP) - SlotPanel(this, frame, menu.fuelSlot, 104f, PROGRESS_SLOT_TOP) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.kt deleted file mode 100644 index 225590967..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.kt +++ /dev/null @@ -1,29 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.DriveRackMenu -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel - -class DriveRackScreen(menu: DriveRackMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - SlotPanel(this, frame, menu.storageSlots[0], 71f, 32f) - SlotPanel(this, frame, menu.storageSlots[1], 71f + 18f, 32f) - - SlotPanel(this, frame, menu.storageSlots[2], 71f, 32f + 18f) - SlotPanel(this, frame, menu.storageSlots[3], 71f + 18f, 32f + 18f) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt deleted file mode 100644 index 2eada543a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.kt +++ /dev/null @@ -1,120 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.CheckBoxLabelInputPanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem -import ru.dbotthepony.mc.otm.menu.DriveViewerMenu -import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak - -@MouseTweaksDisableWheelTweak -class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, FRAME_WIDTH, FRAME_HEIGHT, getTitle()) - - val views = ArrayList>() - val settings = ArrayList>() - - frame.Tab(onOpen = { - for (panel in views) { - panel.visible = true - } - }, onClose = { - for (panel in views) { - panel.visible = false - } - }) - - frame.Tab(onOpen = { - for (panel in settings) { - panel.visible = true - } - }, onClose = { - for (panel in settings) { - panel.visible = false - } - }) - - views.add(PowerGaugePanel(this, frame, menu.powerWidget, 8f, 16f)) - views.add(BatterySlotPanel(this, frame, menu.batterySlot, 8f, 67f)) - views.add(SlotPanel(this, frame, menu.driveSlot, 8f, 85f)) - - val grid = GridPanel(this, frame, 28f, 16f, GRID_WIDTH * 18f, GRID_HEIGHT * 18f, GRID_WIDTH, GRID_HEIGHT) - - val scrollBar = DiscreteScrollBarPanel(this, frame, { integerDivisionDown(menu.networkedItemView.itemCount, GRID_WIDTH) }, { _, _, _ -> }, 192f, 14f, 92f) - - views.add(grid) - views.add(scrollBar) - - for (i in 0 until GRID_WIDTH * GRID_HEIGHT) { - object : AbstractSlotPanel(this@DriveViewerScreen, grid, 0f, 0f) { - override val itemStack: ItemStack get() { - val index = i + scrollBar.scroll * GRID_WIDTH - return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.item ?: ItemStack.EMPTY - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return scrollBar.mouseScrolledInner(x, y, scroll) - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - val index = i + scrollBar.scroll * GRID_WIDTH - menu.networkedItemView.mouseClick(index, button) - return true - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - renderSlotBackground(stack, mouseX, mouseY, partialTick) - renderRegular(stack, itemStack, "") - } - - override fun getItemStackTooltip(stack: ItemStack): List { - return super.getItemStackTooltip(stack).also { - it as MutableList - val index = i + scrollBar.scroll * GRID_WIDTH - val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack - it.add(TranslatableComponent("otm.gui.item_amount", realStack.count.toString())) - } - } - } - } - - val dock_left = FlexGridPanel(this, frame, 0f, 0f, 90f, 0f) - dock_left.dock = Dock.LEFT - dock_left.setDockMargin(4f, 0f, 4f, 0f) - - val grid_filter = FlexGridPanel(this, frame) - grid_filter.dock = Dock.FILL - settings.add(dock_left) - settings.add(grid_filter) - - for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { - FilterSlotPanel(this, grid_filter, menu.driveFilterSlots[i], 0f, 0f) - } - - CheckBoxLabelInputPanel(this, dock_left, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } - CheckBoxLabelInputPanel(this, dock_left, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } - CheckBoxLabelInputPanel(this, dock_left, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } - - for (panel in settings) { - panel.visible = false - } - - return frame - } - - companion object { - const val FRAME_WIDTH = 210f - const val FRAME_HEIGHT = 110f - const val GRID_WIDTH = 9 - const val GRID_HEIGHT = 5 - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyCounterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyCounterScreen.kt deleted file mode 100644 index 01fd8a4f7..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyCounterScreen.kt +++ /dev/null @@ -1,110 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.ButtonPanel -import ru.dbotthepony.mc.otm.core.formatPower -import ru.dbotthepony.mc.otm.menu.EnergyCounterMenu - -class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - var label: Label = object : Label(this@EnergyCounterScreen, frame) { - override fun tick() { - super.tick() - text = TranslatableComponent( - "otm.item.power.passed", - menu.passed.formatPower() - ) - } - } - - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - label = object : Label(this@EnergyCounterScreen, frame) { - override fun tick() { - super.tick() - text = TranslatableComponent( - "otm.item.power.average", - menu.average.formatPower() - ) - } - } - - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - label = object : Label(this@EnergyCounterScreen, frame) { - override fun tick() { - super.tick() - text = TranslatableComponent( - "otm.item.power.last_20_ticks", - menu.last20Ticks.formatPower() - ) - } - } - - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - label = object : Label(this@EnergyCounterScreen, frame) { - override fun tick() { - super.tick() - text = TranslatableComponent( - "otm.item.power.last_tick", - menu.lastTick.formatPower() - ) - } - } - - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - label = object : Label(this@EnergyCounterScreen, frame) { - override fun tick() { - super.tick() - - text = TranslatableComponent( - "block.overdrive_that_matters.energy_counter.facing", - menu.inputDirection - ) - } - } - - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - if (!menu.ply.isSpectator) { - val button = ButtonPanel(this, frame, 0f, 0f, 0f, 20f, TranslatableComponent("block.overdrive_that_matters.energy_counter.switch")) - button.dock = Dock.TOP - button.setDockMargin(4f, 0f, 4f, 0f) - button.bind { menu.switchDirection.userInput(minecraft?.player) } - } - - val infoPanels = frame.fetchChildren() - - label = Label(this, frame, TranslatableComponent("block.overdrive_that_matters.energy_counter.limit")) - label.dock = Dock.TOP - label.setDockMargin(4f, 0f, 0f, 0f) - - val inputPanel = NumberInputPanel(this, frame, menu.maxIO) - inputPanel.dock = Dock.TOP - inputPanel.setDockMargin(4f, 0f, 4f, 0f) - - val limitsPanels = frame.fetchChildren().filter { !infoPanels.contains(it) } - - val informationTab = frame.Tab() - val limitsTab = frame.Tab() - - informationTab.showHidePanels(infoPanels) - limitsTab.showHidePanels(limitsPanels) - - limitsTab.onClose!!.run() - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyServoScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyServoScreen.kt deleted file mode 100644 index 16cbe0c0c..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/EnergyServoScreen.kt +++ /dev/null @@ -1,65 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.AbstractSlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.Dock -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalPowerGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalPowerGaugePanel -import ru.dbotthepony.mc.otm.menu.EnergyServoMenu - -class EnergyServoScreen(menu: EnergyServoMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel.padded(this, - width = AbstractSlotPanel.SIZE * 2f + HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width + 8f + ProgressGaugePanel.GAUGE_BACKGROUND.width * 2f, - AbstractSlotPanel.SIZE, title) - - BatterySlotPanel(this, frame, menu.dischargeSlot).also { - it.dock = Dock.LEFT - it.dockRight = 2f - } - - object : EditablePanel(this@EnergyServoScreen, frame) { - init { - dock = Dock.LEFT - dockRight = 2f - dockTop = 2f - width = ProgressGaugePanel.GAUGE_BACKGROUND.width - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - ProgressGaugePanel.GAUGE_BACKGROUND.render(stack) - } - } - - TallHorizontalPowerGaugePanel(this, frame, menu.powerGauge).also { - it.dock = Dock.LEFT - it.dockRight = 2f - } - - object : EditablePanel(this@EnergyServoScreen, frame) { - init { - dock = Dock.LEFT - dockRight = 2f - dockTop = 2f - width = ProgressGaugePanel.GAUGE_BACKGROUND.width - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - ProgressGaugePanel.GAUGE_BACKGROUND.render(stack) - } - } - - BatterySlotPanel(this, frame, menu.chargeSlot).also { - it.dock = Dock.LEFT - it.dockRight - } - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt deleted file mode 100644 index 9489bb4b4..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoPackInventoryScreen.kt +++ /dev/null @@ -1,214 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.inventory.InventoryScreen -import ru.dbotthepony.mc.otm.client.mousePos -import ru.dbotthepony.mc.otm.client.moveMousePosScaled -import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.LargeRectangleButtonPanel -import ru.dbotthepony.mc.otm.client.setMousePos -import ru.dbotthepony.mc.otm.client.shouldOpenVanillaInventory -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu -import ru.dbotthepony.mc.otm.network.ExoSuitMenuOpen -import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel -import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak - -@MouseTweaksDisableWheelTweak -class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen(menu, TranslatableComponent("otm.gui.exopack")) { - private lateinit var mainInventoryLine: EditablePanel - private lateinit var scrollPanel: DiscreteScrollBarPanel - - override fun updateInventoryRows(old: Int) { - mainFrame!!.height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE - mainInventoryLine.height = AbstractSlotPanel.SIZE * inventoryRows - - for (i in scrollPanel.scroll + inventoryRows until scrollPanel.scroll + old) { - getInventorySlotsRow(i).visible = false - } - - for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) { - getInventorySlotsRow(i).also { - it.parent = mainInventoryLine - it.y = AbstractSlotPanel.SIZE * (i - scrollPanel.scroll) - it.visible = true - } - } - - scrollPanel.scroll = scrollPanel.scroll - } - - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, width = 200f, height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE, title = this.title) - - val toolbeltLine = EditablePanel(this, frame, height = 18f) - toolbeltLine.dock = Dock.BOTTOM - - toolbeltLine.setDockMargin(top = 3f) - - scrollPanel = DiscreteScrollBarPanel(this, null, maxScroll = { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) }, - scrollCallback = { - _, old, new -> - - for (i in old until old + inventoryRows) { - getInventorySlotsRow(i).visible = false - } - - for (i in new until new + inventoryRows) { - val row = getInventorySlotsRow(i) - row.visible = true - row.y = (i - new) * AbstractSlotPanel.SIZE - row.parent = mainInventoryLine - } - - lastScroll = new - }) - - mainInventoryLine = object : EditablePanel(this@ExoPackInventoryScreen, frame, height = AbstractSlotPanel.SIZE * inventoryRows) { - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return scrollPanel.mouseScrolledInner(x, y, scroll) - } - } - - scrollPanel.scroll = lastScroll - scrollPanel.parent = mainInventoryLine - - mainInventoryLine.dock = Dock.BOTTOM - - for (slot in menu.playerHotbarSlots) { - val panel = SlotPanel(this, toolbeltLine, slot) - panel.dock = Dock.LEFT - } - - val offhand = SlotPanel(this, toolbeltLine, menu.offhandSlot) - offhand.dock = Dock.RIGHT - - for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) { - getInventorySlotsRow(i).also { - it.parent = mainInventoryLine - it.y = AbstractSlotPanel.SIZE * (i - scrollPanel.scroll) - } - } - - val topLine = EditablePanel(this, frame, height = 18f * 4f) - topLine.dock = Dock.TOP - topLine.setDockMargin(top = 3f) - - makeArmorStrip(menu.armorSlots, topLine).also { - it.dock = Dock.LEFT - } - - val playerRectangle = EntityRendererPanel(this, topLine, minecraft!!.player!!) - - playerRectangle.dock = Dock.LEFT - playerRectangle.setDockMargin(right = 3f) - - val craftingCanvas = EditablePanel(this, topLine, width = 90f) - craftingCanvas.dock = Dock.LEFT - - val craftingSlotsCanvas = EditablePanel(this, craftingCanvas) - - if (menu.capability.isExoPackCraftingUpgraded) { - craftingSlotsCanvas.setSize(54f, 54f) - - for (row in 0 .. 2) { - for (column in 0 .. 2) { - SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[row * 3 + column], x = column * 18f, y = row * 18f) - } - } - } else { - craftingSlotsCanvas.setSize(36f, 36f) - - SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[0]) - SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[1], x = 18f) - SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[2], y = 18f) - SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[3], x = 18f, y = 18f) - } - - craftingSlotsCanvas.y = topLine.height / 2f - craftingSlotsCanvas.height / 2f - - val resultPanel = SlotPanel(this, craftingCanvas, menu.craftingResultSlot, x = craftingCanvas.width - 18f, y = topLine.height / 2f - 9f) - - object : EditablePanel( - this@ExoPackInventoryScreen, - craftingCanvas, - x = craftingSlotsCanvas.width, - width = craftingCanvas.width - resultPanel.width - craftingSlotsCanvas.width, - height = topLine.height - ) { - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - CRAFT_ARROW.render(stack, x = width / 2f - CRAFT_ARROW.width / 2f, y = height / 2f - CRAFT_ARROW.height / 2f) - } - } - - scrollPanel.dock = Dock.RIGHT - scrollPanel.setDockMargin(right = 3f) - - LargeRectangleButtonPanel(this, frame, x = frame.width - 2f - LargeRectangleButtonPanel.SIZE, y = -LargeRectangleButtonPanel.SIZE - 2f, skinElement = Widgets18.RETURN_ARROW_LEFT, onPress = { - shouldOpenVanillaInventory = true - val minecraft = minecraft!! - - val (mouseX, mouseY) = mousePos - - onClose() - minecraft.setScreen(InventoryScreen(minecraft.player!!)) - - setMousePos(mouseX, mouseY) - }).also { it.tooltip = TranslatableComponent("otm.gui.exosuit.go_back") } - - makeInventoryRowsControls(frame, frame.width + 2f, frame.height.coerceAtMost(95f)) { movePixels -> - frame.y += movePixels - moveMousePosScaled(y = movePixels) - } - - var x = -4f - - if (menu.curiosSlots.isNotEmpty()) { - val curiosWidth = if (menu.curiosSlots.stream().anyMatch { it.second != null }) AbstractSlotPanel.SIZE * 2f else AbstractSlotPanel.SIZE - val curiosHeight = menu.curiosSlots.size.coerceAtMost(4) * AbstractSlotPanel.SIZE - - val curiosRect = - if (menu.curiosSlots.size > 4) - ScrollbarBackgroundPanel.padded(this, frame, x, - width = curiosWidth, - height = curiosHeight, alwaysShowScrollbar = true) - else - BackgroundPanel.padded(this, frame, x, - width = curiosWidth, - height = curiosHeight) - - x -= curiosRect.width - curiosRect.x = x - - for ((slot, cosmetic) in menu.curiosSlots) { - val row = EditablePanel(this, if (curiosRect is ScrollbarBackgroundPanel) curiosRect.canvas else curiosRect, height = AbstractSlotPanel.SIZE) - row.dock = Dock.TOP - - SlotPanel(this, row, slot).dock = Dock.RIGHT - - if (cosmetic != null) { - SlotPanel(this, row, cosmetic).dock = Dock.LEFT - } - } - } - - EffectListPanel(this, frame, menu.ply, x - 56f, gridHeight = 6).tick() - - return frame - } - - init { - MatteryPlayerNetworkChannel.sendToServer(ExoSuitMenuOpen) - ru.dbotthepony.mc.otm.client.minecraft.player?.containerMenu = menu - } - - companion object { - val ENTITY_RECTANGLE = INVENTORY_LOCATION.element(25f, 7f, 51f, 72f) - val CRAFT_ARROW = INVENTORY_LOCATION.element(135f, 29f, 16f, 13f) - - const val FRAME_BASE_HEIGHT = 126f - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt new file mode 100644 index 000000000..a51f8b21b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt @@ -0,0 +1,305 @@ +package ru.dbotthepony.mc.otm.client.screen + +import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.client.mousePos +import ru.dbotthepony.mc.otm.client.moveMousePosScaled +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.render.sprites.sprite +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.setMousePos +import ru.dbotthepony.mc.otm.client.shouldOpenVanillaInventory +import ru.dbotthepony.mc.otm.compat.curios.isCuriosLoaded +import ru.dbotthepony.mc.otm.compat.curios.openCuriosScreen +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import ru.dbotthepony.mc.otm.network.ExopackMenuOpen +import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel +import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak + +@MouseTweaksDisableWheelTweak +class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen(menu, TranslatableComponent("otm.gui.exopack")) { + private lateinit var mainInventoryLine: EditablePanel + private lateinit var scrollPanel: DiscreteScrollBarPanel + + override fun updateInventoryRows(old: Int) { + mainFrame!!.height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE + mainInventoryLine.height = AbstractSlotPanel.SIZE * inventoryRows + + for (i in scrollPanel.scroll + inventoryRows until scrollPanel.scroll + old) { + getInventorySlotsRow(i).visible = false + } + + for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) { + getInventorySlotsRow(i).also { + it.parent = mainInventoryLine + it.y = AbstractSlotPanel.SIZE * (i - scrollPanel.scroll) + it.visible = true + } + } + + scrollPanel.scroll = scrollPanel.scroll + } + + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, width = 200f, height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE) + + frame.makeCloseButton() + frame.onClose { onClose() } + + frame.makeHelpButton().addSlotFiltersHelp().tooltips.add(TranslatableComponent("otm.gui.help.slot_charging")) + + val hotbarStrip = EditablePanel(this, frame, height = 18f) + hotbarStrip.dock = Dock.BOTTOM + + hotbarStrip.setDockMargin(top = 3f) + + scrollPanel = DiscreteScrollBarPanel(this, null, maxScroll = { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) }, + scrollCallback = { + _, old, new -> + + for (i in old until old + inventoryRows) { + getInventorySlotsRow(i).visible = false + } + + for (i in new until new + inventoryRows) { + val row = getInventorySlotsRow(i) + row.visible = true + row.y = (i - new) * AbstractSlotPanel.SIZE + row.parent = mainInventoryLine + } + + lastScroll = new + }) + + mainInventoryLine = object : EditablePanel(this@ExopackInventoryScreen, frame, height = AbstractSlotPanel.SIZE * inventoryRows) { + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollPanel.mouseScrolledInner(x, y, scroll) + } + } + + scrollPanel.scroll = lastScroll + scrollPanel.parent = mainInventoryLine + + mainInventoryLine.dock = Dock.BOTTOM + + for (slot in menu.playerHotbarSlots) { + InventorySlotPanel(this, hotbarStrip, slot).also { + it.dock = Dock.LEFT + } + } + + val offhand = InventorySlotPanel(this, hotbarStrip, menu.offhandSlot!!) + offhand.dock = Dock.RIGHT + + for (i in scrollPanel.scroll until scrollPanel.scroll + inventoryRows) { + getInventorySlotsRow(i).also { + it.parent = mainInventoryLine + it.y = AbstractSlotPanel.SIZE * (i - scrollPanel.scroll) + } + } + + val topLine = EditablePanel(this, frame, height = PlayerEquipmentPanel.HEIGHT) + topLine.dock = Dock.TOP + topLine.setDockMargin(top = 3f) + + val equipment = PlayerEquipmentPanel(this, topLine, armorSlots = menu.armorSlots) + equipment.dock = Dock.LEFT + equipment.setDockMargin(right = 3f) + + val craftingCanvas = EditablePanel(this, topLine, width = 90f) + craftingCanvas.dock = Dock.LEFT + + val craftingSlotsCanvas = EditablePanel(this, craftingCanvas) + + if (menu.capability.isExopackCraftingUpgraded) { + craftingSlotsCanvas.setSize(54f, 54f) + + for (row in 0 .. 2) { + for (column in 0 .. 2) { + SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[row * 3 + column], x = column * 18f, y = row * 18f) + } + } + } else { + craftingSlotsCanvas.setSize(36f, 36f) + + SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[0]) + SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[1], x = 18f) + SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[2], y = 18f) + SlotPanel(this, craftingSlotsCanvas, menu.craftingSlots[3], x = 18f, y = 18f) + } + + craftingSlotsCanvas.y = topLine.height / 2f - craftingSlotsCanvas.height / 2f + + val resultPanel = SlotPanel(this, craftingCanvas, menu.craftingResultSlot, x = craftingCanvas.width - 18f, y = topLine.height / 2f - 9f) + + SpritePanel(this, craftingCanvas, CRAFT_ARROW, x = craftingSlotsCanvas.width, + width = craftingCanvas.width - resultPanel.width - craftingSlotsCanvas.width, + height = topLine.height, centerSprite = true) + + if (menu.capability.isExopackSmeltingInstalled || menu.capability.isExopackEnderAccessInstalled) { + val craftingTab = frame.Tab() + + craftingTab.activeIcon = ItemStackIcon(ItemStack(Items.CRAFTING_TABLE)) + craftingTab.inactiveIcon = craftingTab.activeIcon + + craftingTab.onOpen = Runnable { craftingCanvas.visible = true } + craftingTab.onClose = Runnable { craftingCanvas.visible = false } + } + + if (menu.capability.isExopackSmeltingInstalled) { + val tab = frame.Tab() + + tab.activeIcon = ItemStackIcon(ItemStack(Items.FURNACE)) + tab.inactiveIcon = tab.activeIcon + + val furnaceCanvas = EditablePanel(this, topLine, width = 90f) + furnaceCanvas.dock = Dock.LEFT + furnaceCanvas.visible = false + + tab.onOpen = Runnable { furnaceCanvas.visible = true; menu.furnaceMenuOpenState.accept(true) } + tab.onClose = Runnable { furnaceCanvas.visible = false; menu.furnaceMenuOpenState.accept(false) } + + for (i in menu.capability.smelters.indices) { + val row = EditablePanel(this, furnaceCanvas, height = AbstractSlotPanel.SIZE) + row.dock = Dock.TOP + row.dockTop = 4f + + SlotPanel(this, row, menu.furnaceInputs[i]).also { + it.dock = Dock.LEFT + it.dockLeft = 12f + it.dockResize = DockResizeMode.NONE + } + + ProgressGaugePanel(this, row, menu.furnaceProgress[i]).also { + it.dock = Dock.LEFT + it.dockLeft = 9f + it.dockResize = DockResizeMode.NONE + } + + SlotPanel(this, row, menu.furnaceOutputs[i]).also { + it.dock = Dock.LEFT + it.dockLeft = 10f + it.dockResize = DockResizeMode.NONE + } + } + + if (menu.furnaceMenuOpenState.get()) { + tab.activate() + } + } + + if (menu.capability.isExopackEnderAccessInstalled) { + val tab = frame.Tab() + + tab.activeIcon = ItemStackIcon(ItemStack(Items.ENDER_CHEST)) + tab.inactiveIcon = tab.activeIcon + + val enderCanvas = EditablePanel(this, topLine) + enderCanvas.dock = Dock.FILL + enderCanvas.visible = false + + tab.onOpen = Runnable { + enderCanvas.visible = true + equipment.visible = false + menu.enderChestOpenState.accept(true) + } + + tab.onClose = Runnable { + enderCanvas.visible = false + equipment.visible = true + menu.enderChestOpenState.accept(false) + } + + val grid = GridPanel.slots(this, enderCanvas, 9, 3) + grid.gravity = RenderGravity.CENTER_LEFT + grid.dock = Dock.LEFT + + val enderControls = DeviceControls(this, enderCanvas) + enderControls.dock = Dock.RIGHT + enderControls.sortingButtons(menu.sortEnderChest!!, false) + enderControls.dockTop = 6f + + menu.enderChestSlots.forEach { + SlotPanel(this, grid, it) + } + + if (menu.enderChestOpenState.get()) { + tab.activate() + } + } + + makeChargePanels(frame) + + scrollPanel.dock = Dock.RIGHT + scrollPanel.setDockMargin(right = 3f) + + val closeButtonPanel = LargeRectangleButtonPanel(this, frame, x = frame.width - 2f - LargeRectangleButtonPanel.SIZE, y = -LargeRectangleButtonPanel.SIZE - 2f, icon = Widgets18.RETURN_ARROW_LEFT, onPress = { + shouldOpenVanillaInventory = true + val minecraft = minecraft!! + + val (mouseX, mouseY) = mousePos + + onClose() + minecraft.setScreen(InventoryScreen(minecraft.player!!)) + + setMousePos(mouseX, mouseY) + }).also { it.tooltips.add(TranslatableComponent("otm.gui.exopack.go_back")) } + + if (isCuriosLoaded) { + LargeRectangleButtonPanel(this, frame, x = closeButtonPanel.x - 2f - LargeRectangleButtonPanel.SIZE, y = closeButtonPanel.y, icon = Widgets18.CURIOS_INVENTORY, onPress = { + openCuriosScreen(minecraft!!.player!!.containerMenu.carried) + }).also { it.tooltips.add(TranslatableComponent("otm.gui.exopack.go_curios")) } + } + + val controls = DeviceControls(this, frame) + + controls.dockTop = 85f + + controls.addButton(makeInventoryRowsControls(frame) { movePixels -> + frame.y += movePixels + moveMousePosScaled(y = movePixels) + } as EditablePanel) + + if (menu.sortInventoryInput != null) { + controls.sortingButtons(menu.sortInventoryInput!!) + } + + var x = -4f + + if (menu.curiosSlots.isNotEmpty()) { + val curios = makeCuriosPanel(this, frame, menu.curiosSlots)!! + + x -= curios.width + curios.x = x + } + + EffectListPanel(this, frame, menu.player, x - 56f, gridHeight = 6).tick() + + return frame + } + + init { + MatteryPlayerNetworkChannel.sendToServer(ExopackMenuOpen) + ru.dbotthepony.mc.otm.client.minecraft.player?.containerMenu = menu + } + + companion object { + val ENTITY_RECTANGLE = INVENTORY_LOCATION.sprite(25f, 7f, 51f, 72f) + val CRAFT_ARROW = INVENTORY_LOCATION.sprite(135f, 29f, 16f, 13f) + + const val FRAME_BASE_HEIGHT = 126f + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt deleted file mode 100644 index 9e8cdc170..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.kt +++ /dev/null @@ -1,221 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.ChatFormatting -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import org.lwjgl.opengl.GL11 -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder -import ru.dbotthepony.mc.otm.client.render.Widgets8 -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.SmallEnumRectangleButtonPanel -import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel -import ru.dbotthepony.mc.otm.core.asGetterSetter -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.core.formatReadableNumber -import ru.dbotthepony.mc.otm.core.formatSiComponent -import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu -import ru.dbotthepony.mc.otm.storage.ITEM_STORAGE -import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak - -@MouseTweaksDisableWheelTweak -class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this@ItemMonitorScreen, null, 0f, 0f, 1f, 1f, getTitle()) - - val topPanel = EditablePanel(this, frame) - topPanel.height = ITEM_GRID_HEIGHT * 18f - topPanel.dock = Dock.TOP - - val bottomPanel = EditablePanel(this, frame) - bottomPanel.height = 3 * 18f - bottomPanel.dock = Dock.TOP - bottomPanel.setDockMargin(top = 6f) - - frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f - frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right - - val viewScrollBar = DiscreteScrollBarPanel(this, topPanel, - { integerDivisionDown(menu.networkedItemView.itemCount, ITEM_GRID_WIDTH) }, - { _, _, _ -> }, - 28f + ITEM_GRID_WIDTH * 18f + 2f, 16f, ITEM_GRID_HEIGHT * 18f) - - viewScrollBar.dock = Dock.RIGHT - viewScrollBar.setDockMargin(left = 2f) - - val gridPanel = GridPanel(this, topPanel, width = ITEM_GRID_WIDTH * 18f, height = ITEM_GRID_HEIGHT * 18f, columns = ITEM_GRID_WIDTH, rows = ITEM_GRID_HEIGHT) - gridPanel.dock = Dock.FILL - - for (i in 0 until ITEM_GRID_WIDTH * ITEM_GRID_HEIGHT) { - object : AbstractSlotPanel(this@ItemMonitorScreen, gridPanel) { - private val index get() = i + viewScrollBar.scroll * ITEM_GRID_WIDTH - - override val itemStack: ItemStack get() { - return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.item ?: ItemStack.EMPTY - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return viewScrollBar.mouseScrolledInner(x, y, scroll) - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - menu.networkedItemView.mouseClick(index, button) - return true - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - renderSlotBackground(stack, mouseX, mouseY, partialTick) - - val itemstack = menu.networkedItemView.sortedView.getOrNull(index)?.stack ?: ITEM_STORAGE.empty - - renderRegular(stack, itemstack.stack, "") - - if (!itemstack.isEmpty) { - stack.pushPose() - - val text = itemstack.count.formatSiComponent(decimalPlaces = 1) - val textWidth = font.width(text) - - stack.translate((width - textWidth * FONT_SCALE).toDouble(), (height - font.lineHeight * FONT_SCALE).toDouble(), 103.0) - - stack.scale(FONT_SCALE, FONT_SCALE, 1f) - - RenderSystem.depthFunc(GL11.GL_ALWAYS) - - font.draw(stack, text, 1f, 1f, 0x0) - font.draw(stack, text, 0f, 0f, 16777215) - - stack.popPose() - } - } - - override fun getItemStackTooltip(stack: ItemStack): List { - return super.getItemStackTooltip(stack).also { - it as MutableList - val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack - it.add(TranslatableComponent("otm.gui.stored_amount", realStack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY)) - } - } - } - } - - val craftingGrid = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) - craftingGrid.dock = Dock.LEFT - - for (i in 0 until 9) { - SlotPanel(this, craftingGrid, menu.craftingSlots[i]) - } - - val arrowAndButtons = object : EditablePanel(this@ItemMonitorScreen, bottomPanel, width = ProgressGaugePanel.GAUGE_BACKGROUND.width) { - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - ProgressGaugePanel.GAUGE_BACKGROUND.render(stack, y = height / 2f - ProgressGaugePanel.GAUGE_BACKGROUND.height / 2f) - } - } - - arrowAndButtons.dock = Dock.LEFT - arrowAndButtons.setDockMargin(left = 4f) - - EditablePanel(this, arrowAndButtons, y = 0f, height = 8f, width = arrowAndButtons.width) - - val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width) - - val refillPriority = SmallEnumRectangleButtonPanel(this, arrowLine, - enum = ItemMonitorPlayerSettings.IngredientPriority::class.java, - prop = menu.settings::ingredientPriority.asGetterSetter(), - defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, - onChange = { menu.sendSettingsToServer() }) - - refillPriority.mainTooltip = TranslatableComponent("otm.gui.item_monitor.refill_source.desc") - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM.component, Widgets8.WHITE_ARROW_DOWN, UVWindingOrder.FLIP) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY, ItemMonitorPlayerSettings.IngredientPriority.INVENTORY.component, Widgets8.WHITE_ARROW_DOWN) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST, ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST.component, Widgets8.ARROW_SIDEWAYS, UVWindingOrder.FLIP) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST.component, Widgets8.ARROW_SIDEWAYS) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.DO_NOT, ItemMonitorPlayerSettings.IngredientPriority.DO_NOT.component, Widgets8.MINUS) - - refillPriority.dock = Dock.LEFT - - val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f) - - resultAndButtons.dock = Dock.LEFT - resultAndButtons.setDockMargin(left = 6f) - - SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f) - - val resultTarget = SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f, - enum = ItemMonitorPlayerSettings.ResultTarget::class.java, - prop = menu.settings::resultTarget.asGetterSetter(), - defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED, - onChange = { menu.sendSettingsToServer() }) - - resultTarget.mainTooltip = TranslatableComponent("otm.gui.item_monitor.result_target.desc") - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.MIXED, ItemMonitorPlayerSettings.ResultTarget.MIXED.component, Widgets8.ARROW_SIDEWAYS) - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY, ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY.component, Widgets8.ARROW_PAINTED_UP, UVWindingOrder.FLIP) - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM.component, Widgets8.ARROW_PAINTED_UP) - - val craftingAmount = SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f, - enum = ItemMonitorPlayerSettings.Amount::class.java, - prop = menu.settings::craftingAmount.asGetterSetter(), - defaultValue = ItemMonitorPlayerSettings.Amount.STACK, - onChange = { menu.sendSettingsToServer() }) - - craftingAmount.mainTooltip = TranslatableComponent("otm.gui.item_monitor.amount.desc") - craftingAmount.add(ItemMonitorPlayerSettings.Amount.ONE, ItemMonitorPlayerSettings.Amount.ONE.component, Widgets8.ONE) - craftingAmount.add(ItemMonitorPlayerSettings.Amount.STACK, ItemMonitorPlayerSettings.Amount.STACK.component, Widgets8.S) - craftingAmount.add(ItemMonitorPlayerSettings.Amount.FULL, ItemMonitorPlayerSettings.Amount.FULL.component, Widgets8.F) - - val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) - craftingHistory.dock = Dock.LEFT - craftingHistory.setDockMargin(left = 4f) - - val craftingHistoryScroll = DiscreteScrollBarPanel(this, bottomPanel, { 0 }, { _, _, _ -> }) - craftingHistoryScroll.dock = Dock.LEFT - craftingHistoryScroll.setDockMargin(left = 2f) - - for (i in 0 until 9) { - object : AbstractSlotPanel(this@ItemMonitorScreen, craftingHistory) { - override val itemStack: ItemStack get() { - return ItemStack(Items.ARROW, 42) - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return craftingHistoryScroll.mouseScrolledInner(x, y, scroll) - } - } - } - - val leftStrip = BackgroundPanel.padded(this, frame, width = AbstractSlotPanel.SIZE, height = 1f) - - BatterySlotPanel(this, leftStrip, menu.batterySlot).also { - it.dock = Dock.BOTTOM - leftStrip.height += it.height - } - - WidePowerGaugePanel(this, leftStrip, menu.powerWidget).also { - it.dock = Dock.FILL - it.dockResize = DockResizeMode.NONE - leftStrip.height += it.height - } - - leftStrip.height += 3f - - leftStrip.x = -leftStrip.width - 2f - leftStrip.y = frame.height - leftStrip.height - - return frame - } - - companion object { - const val ITEM_GRID_WIDTH = 9 - const val ITEM_GRID_HEIGHT = 5 - - const val FONT_SCALE = 0.6f - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterPanelScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterPanelScreen.kt deleted file mode 100644 index 986f64a7e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterPanelScreen.kt +++ /dev/null @@ -1,313 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.ChatFormatting -import net.minecraft.client.gui.components.EditBox -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.capability.matter.IPatternState -import ru.dbotthepony.mc.otm.capability.matter.IReplicationTask -import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.subElement -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.ButtonPanel -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.menu.MatterPanelMenu -import ru.dbotthepony.mc.otm.menu.ReplicationRequestPacket -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel -import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak - -@MouseTweaksDisableWheelTweak -class MatterPanelScreen( - menu: MatterPanelMenu, - inventory: Inventory, - title: Component -) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - var isPatternView = true - - val frame = FramePanel.padded(this, null, GRID_WIDTH * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH + 4f, GRID_HEIGHT * AbstractSlotPanel.SIZE, title) - - val scrollBar = DiscreteScrollBarPanel(this, frame, { - if (isPatternView) { - integerDivisionDown(menu.patterns.size, GRID_WIDTH) - } else { - integerDivisionDown(menu.tasks.size, GRID_WIDTH) - } - }, { _, _, _ -> }) - - scrollBar.dock = Dock.RIGHT - - frame.Tab(onOpen = { isPatternView = true }, activeIcon = PATTERN_LIST_ACTIVE, inactiveIcon = PATTERN_LIST_INACTIVE).also { - it.tooltip = TranslatableComponent("otm.container.matter_panel.patterns") - } - - frame.Tab(onOpen = { isPatternView = false }, activeIcon = TASK_LIST_ACTIVE, inactiveIcon = TASK_LIST_INACTIVE).also { - it.tooltip = TranslatableComponent("otm.container.matter_panel.tasks") - } - - val canvas = object : EditablePanel(this@MatterPanelScreen, frame, width = GRID_WIDTH * AbstractSlotPanel.SIZE) { - init { - dock = Dock.LEFT - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return scrollBar.mouseScrolledInner(x, y, scroll) - } - } - - for (row in 0 until GRID_HEIGHT) { - val rowCanvas = object : EditablePanel(this@MatterPanelScreen, canvas, height = AbstractSlotPanel.SIZE) { - init { - dock = Dock.TOP - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return false - } - } - - for (i in 0 until GRID_WIDTH) { - object : AbstractSlotPanel(this@MatterPanelScreen, rowCanvas) { - init { - dock = Dock.LEFT - } - - private val index: Int get() = (scrollBar.scroll + row) * GRID_WIDTH + i - - override val itemStack: ItemStack get() { - if (isPatternView) { - return menu.patterns.getOrNull(index)?.stack() ?: ItemStack.EMPTY - } else { - return menu.tasks.getOrNull(index)?.let { it.stack(it.required + it.inProgress) } ?: ItemStack.EMPTY - } - } - - override fun getItemStackTooltip(stack: ItemStack): List { - val list = super.getItemStackTooltip(stack).toMutableList() - - if (isPatternView) { - menu.patterns.getOrNull(index)?.let { - list.add(TranslatableComponent( - "otm.item.pattern.research", - String.format("%.2f", it.researchPercent * 100.0) - ).withStyle(ChatFormatting.AQUA)) } - } else { - menu.tasks.getOrNull(index)?.let { - list.add(TranslatableComponent("otm.gui.matter_task.total", it.total).withStyle(ChatFormatting.GRAY)) - list.add(TranslatableComponent("otm.gui.matter_task.required", it.required).withStyle(ChatFormatting.GRAY)) - list.add(TranslatableComponent("otm.gui.matter_task.in_progress", it.inProgress).withStyle(ChatFormatting.GRAY)) - list.add(TranslatableComponent("otm.gui.matter_task.finished", it.finished).withStyle(ChatFormatting.GRAY)) - } - } - - return list - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (isPatternView) { - if (minecraft?.player?.isSpectator != true) - menu.patterns.getOrNull(index)?.let(this@MatterPanelScreen::openPattern) - } else { - menu.tasks.getOrNull(index)?.let(this@MatterPanelScreen::openTask) - } - - return true - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return false - } - } - } - } - - return frame - } - - private fun openTask(task: IReplicationTask<*>) { - val frame = FramePanel.padded(this, null, 170f, 20f, TranslatableComponent("otm.container.matter_panel.task")) - - object : AbstractSlotPanel(this@MatterPanelScreen, frame) { - init { - dock = Dock.LEFT - } - - override val itemStack: ItemStack get() { - return menu.tasks.firstOrNull { it.id == task.id }?.let { it.stack((it.required + it.inProgress).coerceAtLeast(1)) } ?: task.stack((task.required + task.inProgress).coerceAtLeast(1)) - } - - override fun tick() { - super.tick() - - if (!menu.tasks.any { it.id == task.id }) { - frame.remove() - } - } - } - - ButtonPanel(this@MatterPanelScreen, frame, width = 40f, label = TranslatableComponent("otm.container.matter_panel.close"), onPress = frame::remove).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - if (minecraft?.player?.isSpectator != true) { - ButtonPanel(this@MatterPanelScreen, frame, width = 80f, label = TranslatableComponent("otm.container.matter_panel.cancel_task"), onPress = { - menu.requestTaskCancel(task.id) - frame.remove() - }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - } - - addPanel(frame) - popup(frame) - - frame.toScreenCenter() - } - - private fun openPattern(pattern: IPatternState) { - val frame = FramePanel.padded(this, null, 213f, (ButtonPanel.HEIGHT + 3f) * 4f, TranslatableComponent("otm.container.matter_panel.task")) - - val rowTop = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) - val rowInput = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) - val rowBottom = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) - val rowControls = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) - - rowTop.dock = Dock.TOP - rowTop.dockTop = 3f - rowInput.dock = Dock.TOP - rowInput.dockTop = 3f - rowBottom.dock = Dock.TOP - rowBottom.dockTop = 3f - rowControls.dock = Dock.TOP - rowControls.dockTop = 3f - - object : AbstractSlotPanel(this@MatterPanelScreen, rowInput) { - init { - dock = Dock.LEFT - dockRight = 2f - } - - override val itemStack: ItemStack get() { - return pattern.stack() - } - - override fun getItemStackTooltip(stack: ItemStack): List { - return super.getItemStackTooltip(stack).toMutableList().also { - it.add(TranslatableComponent( - "otm.item.pattern.research", - String.format("%.2f", pattern.researchPercent * 100.0) - ).withStyle(ChatFormatting.AQUA)) - } - } - - override fun tick() { - super.tick() - - if (!menu.patterns.any { it.id == pattern.id }) { - frame.remove() - } - } - } - - val input = object : EditBoxPanel(this@MatterPanelScreen, rowInput) { - init { - dock = Dock.FILL - requestFocus() - } - - override fun configureNew(widget: EditBox, recreation: Boolean) { - super.configureNew(widget, recreation) - widget.setMaxLength(6) - - if (!recreation) { - widget.value = "1" - } - } - - fun increase(amount: Int) { - var value = 1 - - try { - value = getOrCreateWidget().value.toInt() - } catch (_: NumberFormatException) { - } - - if (value == 1 && amount > 0) - getOrCreateWidget().value = amount.toString() - else - getOrCreateWidget().value = 1.coerceAtLeast(99999.coerceAtMost(value + amount)).toString() - } - - fun send() { - var value = 1 - - try { - value = getOrCreateWidget().value.toInt() - } catch (_: NumberFormatException) { - } - - MenuNetworkChannel.sendToServer(ReplicationRequestPacket(pattern.id, value)) - frame.remove() - } - } - - ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.container.matter_panel.increase_by", 8), onPress = { input.increase(8) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.container.matter_panel.increase_by", 64), onPress = { input.increase(64) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.container.matter_panel.increase_by", 256), onPress = { input.increase(256) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.container.matter_panel.decrease_by", 8), onPress = { input.increase(-8) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.container.matter_panel.decrease_by", 64), onPress = { input.increase(-64) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.container.matter_panel.decrease_by", 256), onPress = { input.increase(-256) }).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowControls, width = 40f, label = TranslatableComponent("otm.container.matter_panel.cancel"), onPress = frame::remove).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - ButtonPanel(this, rowControls, width = 82f, label = TranslatableComponent("otm.container.matter_panel.send"), onPress = input::send).also { - it.dock = Dock.RIGHT - it.dockLeft = 2f - } - - addPanel(frame) - popup(frame) - - frame.toScreenCenter() - } - - companion object { - const val GRID_HEIGHT = 8 - const val GRID_WIDTH = 9 - - val PATTERN_LIST_ACTIVE = WidgetLocation.PATTERN_PANEL_TABS.subElement(0f, 0f, 14f, 23f) - val TASK_LIST_ACTIVE = WidgetLocation.PATTERN_PANEL_TABS.subElement(28f, 0f, 16f, 16f) - val PATTERN_LIST_INACTIVE = WidgetLocation.PATTERN_PANEL_TABS.subElement(14f, 0f, 14f, 23f) - val TASK_LIST_INACTIVE = WidgetLocation.PATTERN_PANEL_TABS.subElement(44f, 0f, 16f, 16f) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterRecyclerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterRecyclerScreen.kt deleted file mode 100644 index a6e9b0a2d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterRecyclerScreen.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.menu.MatterRecyclerMenu - -class MatterRecyclerScreen(menu: MatterRecyclerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - val m = PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - MatterGaugePanel(this, frame, menu.matter, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) - - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - ProgressGaugePanel(this, frame, menu.progress, 63f, PROGRESS_ARROW_TOP).flop = true - SlotPanel(this, frame, menu.input, 93f, PROGRESS_SLOT_TOP) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt index 01a6a3f8c..252a9a062 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt @@ -2,28 +2,59 @@ package ru.dbotthepony.mc.otm.client.screen import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectFunction import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import net.minecraft.ChatFormatting import net.minecraft.client.gui.Font + import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen -import net.minecraft.client.renderer.entity.ItemRenderer import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack import net.minecraftforge.client.event.ContainerScreenEvent.Render.Background import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground import net.minecraftforge.common.MinecraftForge import org.lwjgl.opengl.GL11 -import org.lwjgl.opengl.GL13 -import ru.dbotthepony.mc.otm.ClientConfig +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.moveMousePosScaled +import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton -import ru.dbotthepony.mc.otm.core.integerDivisionDown +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants +import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.PatternGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.config.ClientConfig +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown import ru.dbotthepony.mc.otm.menu.MatteryMenu -import java.util.Collections +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import java.util.* +import kotlin.collections.ArrayDeque +import kotlin.collections.List +import kotlin.collections.MutableSet +import kotlin.collections.isNotEmpty +import kotlin.collections.withIndex /** * This class encapsulate most of logic for handling EditablePanel and it's children. @@ -44,18 +75,49 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit var mainFrame: FramePanel>? = null private var madeMainFrame = false - var itemRenderer: ItemRenderer - get() = itemRenderer - set(itemRenderer) { super.itemRenderer = itemRenderer } var font: Font - get() = font + get() = super.font set(font) { super.font = font } var hoveredSlot: Slot? - get() = hoveredSlot + get() = super.hoveredSlot set(value) { super.hoveredSlot = value } - val quickCraftSlots: MutableSet get() = quickCraftSlots - val quickCraftingType get() = quickCraftingType - val isQuickCrafting get() = isQuickCrafting + val quickCraftSlots: MutableSet get() = super.quickCraftSlots + val quickCraftingType: Int get() = super.quickCraftingType + val isQuickCrafting: Boolean get() = super.isQuickCrafting + + fun renderItemStack(absoluteX: Float, absoluteY: Float, itemstack: ItemStack, countOverride: String? = null) { + if (!itemstack.isEmpty) { + RenderSystem.enableDepthTest() + + val systemPoseStack = RenderSystem.getModelViewStack() + + systemPoseStack.pushPose() + systemPoseStack.translate(absoluteX + 1f, absoluteY + 1f, 0f) + RenderSystem.applyModelViewMatrix() + RenderSystem.depthFunc(GL11.GL_LESS) + + // Thanks Mojang + // Very cool + // (for int x, int y, which are then cast into doubles anyway) + itemRenderer.blitOffset = 1f // Z pos + + itemRenderer.renderAndDecorateItem( + requireNotNull(ru.dbotthepony.mc.otm.client.minecraft.player) { "yo, dude, what the fuck" }, + itemstack, + 0, + 0, + (absoluteX + absoluteY * 1000f).toInt() + ) + + RenderSystem.depthFunc(GL11.GL_ALWAYS) + itemRenderer.renderGuiItemDecorations(super.font, itemstack, 0, 0, countOverride) + itemRenderer.blitOffset = 0f + + // too big accumulations can lead to Z near clipping issues + systemPoseStack.popPose() + RenderSystem.applyModelViewMatrix() + } + } init { for (slot in menu.slots) { @@ -103,7 +165,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit inventoryScrollbar.scroll = inventoryScrollbar.scroll } - protected fun makeInventoryRowsControls(parent: EditablePanel<*>, x: Float, y: Float, callback: (Float) -> Unit): HeightControls<*> { + protected fun makeInventoryRowsControls(parent: EditablePanel<*>, x: Float = 0f, y: Float = 0f, callback: (Float) -> Unit): HeightControls> { return HeightControls(this, parent, x, y) { inventoryRows += if (it) 1 else -1 callback.invoke(if (it) -AbstractSlotPanel.SIZE / 2f else AbstractSlotPanel.SIZE / 2f) @@ -113,16 +175,60 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } + protected fun makeChargePanels(frame: EditablePanel<*>) { + val chargeWidth = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width + AbstractSlotPanel.SIZE + 6f + ProgressGaugePanel.GAUGE_BACKGROUND.width * 2f + val chargeStrip = BackgroundPanel.paddedCenter(this, frame, frame.width - chargeWidth - 6f, frame.height + 2f, chargeWidth, AbstractSlotPanel.SIZE) + val chargeStrip2 = BackgroundPanel.paddedCenter(this, frame, frame.width + 2f, frame.height - AbstractSlotPanel.SIZE * 3f + 2f, AbstractSlotPanel.SIZE, AbstractSlotPanel.SIZE * 4f) + + chargeStrip.customDock { chargeStrip.setPos(frame.width - chargeWidth - 6f, frame.height + 2f) } + chargeStrip2.customDock { chargeStrip2.setPos(frame.width + 2f, frame.height - AbstractSlotPanel.SIZE * 3f + 2f) } + + BatterySlotPanel(this, chargeStrip, menu.exopackChargeSlots[0]).also { + it.dock = Dock.LEFT + } + + SpritePanel(this, chargeStrip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.LEFT + it.dockLeft = 2f + it.dockResize = DockResizeMode.NONE + } + + TallHorizontalProfiledPowerGaugePanel(this, chargeStrip, menu.exopackPowerLevel).also { + it.dock = Dock.LEFT + it.dockLeft = 2f + } + + SpritePanel(this, chargeStrip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.LEFT + it.dockLeft = 2f + it.dockResize = DockResizeMode.NONE + } + + for (slot in menu.exopackChargeSlots.listIterator(1)) { + SlotPanel(this, chargeStrip2, slot).also { + it.dock = Dock.BOTTOM + it.slotBackground = Widgets18.CHARGE_SLOT_BACKGROUND + } + } + } + init { if (menu.playerInventorySlots.isNotEmpty() && menu.autoCreateInventoryFrame) { - if (menu.playerExoSuitSlots.isEmpty()) { + val deviceControls: DeviceControls> + + if (menu.player.matteryPlayer?.hasExopack != true) { inventoryFrame = FramePanel>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, INVENTORY_FRAME_HEIGHT, inventory.displayName).also(this::addPanel) + inventoryFrame!!.makeHelpButton().addSlotFiltersHelp() + + deviceControls = DeviceControls(this, inventoryFrame!!) val hotbarStrip = EditablePanel(this, inventoryFrame, height = AbstractSlotPanel.SIZE) hotbarStrip.dock = Dock.BOTTOM for (slot in menu.playerHotbarSlots) { - SlotPanel(this, hotbarStrip, slot).also { it.dock = Dock.LEFT } + InventorySlotPanel(this, hotbarStrip, slot).also { + it.dock = Dock.LEFT + } } val canvas = EditablePanel(this, inventoryFrame) @@ -136,8 +242,19 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit it.parent = canvas } } + + InventorySlotPanel( + this, + BackgroundPanel.padded(this, inventoryFrame, inventoryFrame!!.width + 2f, inventoryFrame!!.height - 11f - AbstractSlotPanel.SIZE, AbstractSlotPanel.SIZE, AbstractSlotPanel.SIZE), + menu.offhandSlot!! + ).dock = Dock.FILL } else { inventoryFrame = FramePanel>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH_EXTENDED, BASE_INVENTORY_FRAME_HEIGHT + AbstractSlotPanel.SIZE * inventoryRows, inventory.displayName).also(this::addPanel) + inventoryFrame!!.makeHelpButton().addSlotFiltersHelp().also { + it.tooltips.add(TranslatableComponent("otm.gui.help.slot_charging")) + } + + deviceControls = DeviceControls(this, inventoryFrame!!) inventoryScrollbar = DiscreteScrollBarPanel(this, inventoryFrame, { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) }, { _, old, new -> @@ -169,12 +286,18 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit val hotbarStrip = EditablePanel(this, inventoryFrame, height = AbstractSlotPanel.SIZE) hotbarStrip.dock = Dock.BOTTOM + val offhand = InventorySlotPanel(this, hotbarStrip, menu.offhandSlot!!) + offhand.dock = Dock.RIGHT + for (slot in menu.playerHotbarSlots) { - SlotPanel(this, hotbarStrip, slot).also { it.dock = Dock.LEFT } + InventorySlotPanel(this, hotbarStrip, slot).also { + it.dock = Dock.LEFT + } } inventoryScrollbar.parent = slotListCanvas inventoryScrollbar.dock = Dock.RIGHT + inventoryScrollbar.dockRight = 2f inventoryScrollbar.scroll = lastScroll @@ -185,11 +308,19 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - makeInventoryRowsControls(inventoryFrame!!, inventoryFrame!!.width + 2f, 0f) { movePixels -> + deviceControls.addButton(makeInventoryRowsControls(inventoryFrame!!) { movePixels -> mainFrame?.let { it.y += movePixels } inventoryFrame?.let { it.y += movePixels } moveMousePosScaled(y = movePixels) - } + }) + } + + if (menu.sortInventoryInput != null) { + deviceControls.sortingButtons(menu.sortInventoryInput!!) + } + + if (menu.exopackChargeSlots.isNotEmpty()) { + makeChargePanels(inventoryFrame!!) } } } @@ -209,13 +340,19 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) { - val slot = object : SlotPanel, Slot>(this@MatteryScreen, canvas, menu.playerCombinedInventorySlots[offset + i]) { + val slot = menu.playerCombinedInventorySlots[offset + i] + + object : InventorySlotPanel, MatteryMenu.InventorySlot>(this@MatteryScreen, canvas, slot) { + init { + dock = Dock.LEFT + } + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { return false } - } - slot.dock = Dock.LEFT + override var slotFilter: Item? by slot.filter!! + } } return@Int2ObjectFunction canvas @@ -259,17 +396,67 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit * * @param panel panel to be pushed up */ - fun popup(panel: EditablePanel>) { + fun popup(panel: EditablePanel>): Boolean { val indexOf = panels.indexOf(panel) - - require(indexOf != -1) { "No such panel $panel" } - - if (indexOf == 0) { - return - } - + if (indexOf <= 0) return false panels.removeAt(indexOf) panels.addFirst(panel) + return true + } + + protected fun makeBars( + parent: EditablePanel<*>, + energy: LevelGaugeWidget? = null, + profiledEnergy: ProfiledLevelGaugeWidget<*>? = null, + matter: LevelGaugeWidget? = null, + profiledMatter: ProfiledLevelGaugeWidget<*>? = null, + patterns: LevelGaugeWidget? = null, + batterySlot: MatterySlot? = null, + ) { + var bars = 0 + if (energy != null) bars++ + if (profiledEnergy != null) bars++ + if (matter != null) bars++ + if (patterns != null) bars++ + if (profiledMatter != null) bars++ + + val canvas = EditablePanel(this, parent, width = AbstractSlotPanel.SIZE.coerceAtLeast(9f * bars)) + canvas.dock = Dock.LEFT + canvas.childrenOrder = -1000 + + val gauges = EditablePanel(this, canvas, height = WidgetLocation.VERTICAL_GAUGES.height) + gauges.dock = Dock.TOP + + if (profiledEnergy != null) { + if (bars == 1) { + WideProfiledPowerGaugePanel(this, gauges, profiledEnergy).dock = Dock.TOP + } else { + ProfiledPowerGaugePanel(this, gauges, profiledEnergy).dock = Dock.LEFT + } + } else if (energy != null) { + if (bars == 1) { + WidePowerGaugePanel(this, gauges, energy).dock = Dock.TOP + } else { + PowerGaugePanel(this, gauges, energy).dock = Dock.LEFT + } + } + + if (profiledMatter != null) { + ProfiledMatterGaugePanel(this, gauges, profiledMatter).dock = Dock.LEFT + } else if (matter != null) { + MatterGaugePanel(this, gauges, matter).dock = Dock.LEFT + } + + if (patterns != null) { + PatternGaugePanel(this, gauges, patterns).dock = Dock.LEFT + } + + if (batterySlot != null) { + BatterySlotPanel(this, canvas, batterySlot).also { + it.dock = Dock.BOTTOM + it.dockResize = DockResizeMode.NONE + } + } } /** @@ -280,7 +467,15 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit * @return FramePanel created, or null */ protected open fun makeMainFrame(): FramePanel>? { - return FramePanel(this, null, 0f, 0f, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, getTitle()) + return FramePanel(this, null, 0f, 0f, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, getTitle()).also { + it.onClose { onClose() } + it.makeCloseButton() + } + } + + override fun onClose() { + super.onClose() + panels.forEach { it.markRemoved() } } public override fun recalculateQuickCraftRemaining() { @@ -313,10 +508,11 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit override fun mouseClicked(x: Double, y: Double, button: Int): Boolean { var click = false + var focusKilled = false for (panel in panels) { if (click || !panel.mouseClickedChecked(x, y, button)) { - panel.killFocusForEverythingExceptInner() + focusKilled = panel.killFocus() || focusKilled } else { if (returnSlot != null) { super.mouseClicked(x, y, button) @@ -327,7 +523,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - return true + return click || focusKilled } private var lastDragSlot: Slot? = null @@ -345,12 +541,12 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - return true + return false } override fun mouseReleased(p_97812_: Double, p_97813_: Double, p_97814_: Int): Boolean { if (lastDragSlot != null) { - if (isQuickCrafting) { + if (super.isQuickCrafting) { returnSlot = lastDragSlot val ret = super.mouseReleased(p_97812_, p_97813_, p_97814_) returnSlot = null @@ -376,14 +572,14 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit return super.mouseReleased(p_97812_, p_97813_, p_97814_) } - override fun mouseScrolled(p_94686_: Double, p_94687_: Double, p_94688_: Double): Boolean { + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollY: Double): Boolean { for (panel in panels) { - if (panel.mouseScrolledChecked(p_94686_, p_94687_, p_94688_)) { + if (panel.mouseScrolledChecked(mouseX, mouseY, scrollY)) { return true } } - return true + return false } override fun keyReleased(p_94715_: Int, p_94716_: Int, p_94717_: Int): Boolean { @@ -393,7 +589,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - return true + return false } override fun charTyped(p_94683_: Char, p_94684_: Int): Boolean { @@ -403,7 +599,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } } - return true + return false } override fun keyPressed(key: Int, scancode: Int, mods: Int): Boolean { @@ -421,7 +617,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit return super.keyPressed(key, scancode, mods) } - override fun renderBg(p_97787_: PoseStack, p_97788_: Float, p_97789_: Int, p_97790_: Int) {} + override fun renderBg(p_283065_: PoseStack, p_97788_: Float, p_97789_: Int, p_97790_: Int) {} var returnSlot: Slot? = null @@ -455,13 +651,14 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit } override fun render(poseStack: PoseStack, mouseX: Int, mouseY: Int, partialTick: Float) { + val wrap = MGUIGraphics(poseStack) val mouseXf = mouseX.toFloat() val mouseYf = mouseY.toFloat() // dark background this.renderBackground(poseStack) - hoveredSlot = null + super.hoveredSlot = null var hovered = false for (panel in panels) { @@ -475,14 +672,11 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit RenderSystem.defaultBlendFunc() RenderSystem.enableBlend() RenderSystem.enableDepthTest() - RenderSystem.enableTexture() - RenderSystem.activeTexture(GL13.GL_TEXTURE0) - for (i in panels.indices.reversed()) { - val panel = panels[i] + for (panel in panels.asReversed()) { RenderSystem.depthFunc(GL11.GL_ALWAYS) RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - panel.render(poseStack, mouseXf, mouseYf, partialTick) + panel.render(wrap, mouseXf, mouseYf, partialTick) } RenderSystem.depthFunc(GL11.GL_LESS) @@ -504,7 +698,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit val i2 = if (draggingItem.isEmpty) 8 else 16 var overrideCount: String? = null - if (isQuickCrafting && quickCraftSlots.size > 1) { + if (super.isQuickCrafting && super.quickCraftSlots.size > 1) { itemstack = itemstack.copy() itemstack.count = quickCraftingRemainder @@ -517,53 +711,29 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit renderFloatingItem(itemstack, mouseX - 8, mouseY - i2, overrideCount) } - RenderSystem.disableDepthTest() - if (menu.carried.isEmpty) { - val hoveredSlot = hoveredSlot + val hoveredSlot = super.hoveredSlot if (hoveredSlot != null && hoveredSlot.hasItem()) { - this.renderTooltip(poseStack, hoveredSlot.item, mouseX, mouseY) + this.renderTooltip(poseStack, mouseX, mouseY) } else { for (panel in panels) { - if (panel.renderTooltips(poseStack, mouseXf, mouseYf, partialTick)) { + RenderSystem.disableDepthTest() + + if (panel.renderTooltips(wrap, mouseXf, mouseYf, partialTick)) { break } } + + RenderSystem.enableDepthTest() } } - - RenderSystem.enableDepthTest() } override fun containerTick() { - for (panel in panels) { - panel.tick() - } - } - - fun makeArmorStrip(slots: Collection>, parent: EditablePanel<*>? = null): EditablePanel<*> { - val armorSlotsStrip = EditablePanel(this, parent, width = 18f) - armorSlotsStrip.dock = Dock.LEFT - - for ((slot, cosmeticSlot) in slots) { - if (cosmeticSlot == null) { - SlotPanel(this, armorSlotsStrip, slot).also { - it.dock = Dock.TOP - } - } else { - FoldableSlotPanel(this, armorSlotsStrip, - SlotPanel(this, null, slot).also { - CosmeticToggleButton(this, it, slot.type) - }, - listOf(SlotPanel(this, null, cosmeticSlot)) - ).also { - it.dock = Dock.TOP - } - } - } - - return armorSlotsStrip + val copy = arrayOfNulls>(panels.size) + for ((i, panel) in panels.withIndex()) copy[i] = panel + for (panel in copy) panel!!.tick() } companion object { @@ -571,7 +741,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit const val DEFAULT_FRAME_HEIGHT = 100f const val INVENTORY_FRAME_WIDTH = DEFAULT_FRAME_WIDTH - const val INVENTORY_FRAME_WIDTH_EXTENDED = DEFAULT_FRAME_WIDTH + ScrollBarConstants.WIDTH + 2f + const val INVENTORY_FRAME_WIDTH_EXTENDED = DEFAULT_FRAME_WIDTH + ScrollBarConstants.WIDTH + 6f const val INVENTORY_FRAME_HEIGHT = 3f * AbstractSlotPanel.SIZE + AbstractSlotPanel.SIZE + 24f const val BASE_INVENTORY_FRAME_HEIGHT = AbstractSlotPanel.SIZE + 24f @@ -586,6 +756,6 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit const val MAX_ROWS = 6 var lastScroll = 0 - var lastRows by ClientConfig::EXOPACK_INVENTORY_ROWS + var lastRows by ClientConfig.GUI::EXOPACK_INVENTORY_ROWS } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PlatePressScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PlatePressScreen.kt deleted file mode 100644 index 24cbe87d0..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PlatePressScreen.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel -import ru.dbotthepony.mc.otm.menu.PlatePressMenu - -class PlatePressScreen(menu: PlatePressMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - SlotPanel(this, frame, menu.inputSlot, 56f, PROGRESS_SLOT_TOP) - ProgressGaugePanel(this, frame, menu.progressGauge, 78f, PROGRESS_ARROW_TOP) - SlotPanel(this, frame, menu.outputSlot, 104f, PROGRESS_SLOT_TOP) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt deleted file mode 100644 index e2546153b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageBusScreen.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.CheckBoxLabelInputPanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel - -import ru.dbotthepony.mc.otm.menu.StorageBusMenu - -class StorageBusScreen(menu: StorageBusMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - for (row in 0 .. 2) { - for (column in 0 .. 5) { - FilterSlotPanel(this, frame, menu.busFilterSlots[row + column * 3], 55f + 18f * column, 17f + 18f * row) - } - } - - CheckBoxLabelInputPanel(this, frame, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist"), 59f, 78f, width = 170f) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt deleted file mode 100644 index ff6276da6..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageExporterScreen.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.CheckBoxLabelInputPanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel - -import ru.dbotthepony.mc.otm.menu.StorageExporterMenu - -class StorageExporterScreen(menu: StorageExporterMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - for (row in 0 .. 2) { - for (column in 0 .. 5) { - FilterSlotPanel(this, frame, menu.busFilterSlots[row + column * 3], 55f + 18f * column, 17f + 18f * row) - } - } - - CheckBoxLabelInputPanel(this, frame, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist"), 59f, 78f, width = 170f) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt deleted file mode 100644 index 5da4971ac..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StorageImporterScreen.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.CheckBoxLabelInputPanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel - -import ru.dbotthepony.mc.otm.menu.StorageImporterMenu - -class StorageImporterScreen(menu: StorageImporterMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = super.makeMainFrame()!! - - WidePowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - - for (row in 0 .. 2) { - for (column in 0 .. 5) { - FilterSlotPanel(this, frame, menu.busFilterSlots[row + column * 3], 55f + 18f * column, 17f + 18f * row) - } - } - - CheckBoxLabelInputPanel(this, frame, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist"), 59f, 78f, width = 170f) - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StoragePowerSupplierScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StoragePowerSupplierScreen.kt deleted file mode 100644 index 34b515144..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/StoragePowerSupplierScreen.kt +++ /dev/null @@ -1,68 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen - -import net.minecraft.network.chat.Component -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalPowerGaugePanel -import ru.dbotthepony.mc.otm.core.formatPower - -import ru.dbotthepony.mc.otm.menu.StoragePowerSupplierMenu - -class StoragePowerSupplierScreen(menu: StoragePowerSupplierMenu, inventory: Inventory, title: Component) : - MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, width = 200f, height = 60f, title) - - HorizontalPowerGaugePanel(this, frame, menu.powerWidget).also { - it.dock = Dock.BOTTOM - it.dockResize = DockResizeMode.NONE - } - - val topStrip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE) - - topStrip.dock = Dock.TOP - topStrip.dockTop = 3f - - BatterySlotPanel(this, topStrip, menu.batterySlot).also { - it.dock = Dock.LEFT - it.dockRight = 3f - } - - val labels = EditablePanel(this, topStrip) - - labels.dock = Dock.FILL - - object : Label(this@StoragePowerSupplierScreen, labels) { - init { - dock = Dock.TOP - } - - override fun tick() { - super.tick() - - text = TranslatableComponent( - "otm.item.power.passed", - menu.totalTransferred.formatPower() - ) - } - } - - object : Label(this@StoragePowerSupplierScreen, labels) { - init { - dock = Dock.TOP - } - - override fun tick() { - super.tick() - - text = TranslatableComponent( - "otm.gui.power_supplier.active_nodes", - menu.activeNodes - ) - } - } - - return frame - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt new file mode 100644 index 000000000..26bb90d13 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.client.screen.decorative + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel +import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu + +class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, 22f + 4f + 6f * 18f, getTitle()) + + frame.makeCloseButton() + frame.onClose { onClose() } + frame.makeHelpButton().addSlotFiltersHelp() + + val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) + + for (slot in menu.storageSlots) + UserFilteredSlotPanel.of(this, grid, slot) + + val controls = DeviceControls(this, frame) + + controls.sortingButtons(menu.sort) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt new file mode 100644 index 000000000..1027ce7f7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.client.screen.decorative + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.PlayerEquipmentPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel +import ru.dbotthepony.mc.otm.client.screen.widget.FluidGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu + +class FluidTankScreen(menu: FluidTankMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + FluidGaugePanel(this, frame, menu.fluid, x = LEFT_MARGIN, y = GAUGE_TOP_WITHOUT_SLOT - 6f) + + val s = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 30f) + SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 55f, winding = UVWindingOrder.FLOP) + + SlotPanel(this, frame, menu.fillInput, x = 30f + s.width + 4f, y = 28f) + SlotPanel(this, frame, menu.drainInput, x = 30f + s.width + 4f, y = 53f) + + SlotPanel(this, frame, menu.output, x = 30f + s.width + 4f + 20f, y = 53f) + + makeDeviceControls(this, frame, itemConfig = menu.itemConfig, redstoneConfig = menu.redstoneConfig, fluidConfig = menu.fluidConfig) + makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true) + + PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots).also { + it.leftSided = false + it.dock = Dock.RIGHT + it.dockResize = DockResizeMode.NONE + } + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/HoloSignScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/HoloSignScreen.kt new file mode 100644 index 000000000..0e041f81e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/HoloSignScreen.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.client.screen.decorative + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.input.NetworkedStringInputPanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu + +class HoloSignScreen(menu: HoloSignMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, null, 0f, 0f, 200f, 200f, getTitle()) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val input = NetworkedStringInputPanel(this, frame, backend = menu.text) + input.dock = Dock.FILL + input.multiLine = true + + val lock = CheckBoxLabelInputPanel(this, frame, menu.locked, TranslatableComponent("otm.gui.lock_holo_screen")) + lock.dock = Dock.BOTTOM + lock.dockMargin = DockProperty(2f, 2f, 2f, 2f) + lock.tooltips.add(TranslatableComponent("otm.gui.lock_holo_screen.tip")) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstone) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/MinecartCargoCrateScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/MinecartCargoCrateScreen.kt new file mode 100644 index 000000000..6a65c2e0f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/MinecartCargoCrateScreen.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.client.screen.decorative + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.menu.decorative.MinecartCargoCrateMenu + +class MinecartCargoCrateScreen(menu: MinecartCargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, 22f + 4f + 6f * 18f, getTitle()) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) + + for (slot in menu.storageSlots) + SlotPanel(this, grid, slot) + + val controls = DeviceControls(this, frame) + + controls.sortingButtons(menu.sort) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt new file mode 100644 index 000000000..3f2c4d177 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt @@ -0,0 +1,196 @@ +package ru.dbotthepony.mc.otm.client.screen.decorative + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.util.RandomSource +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraftforge.common.Tags +import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.FlatRectangleIcon +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.RectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollableCanvasPanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.map +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu + +class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + private inner class Bar(parent: EditablePanel<*>, val dye: DyeColor?) : EditablePanel(this@PainterScreen, parent, width = 5f) { + init { + dock = Dock.RIGHT + dockLeft = 1f + + if (dye == null) + tooltips.add(TranslatableComponent("block.minecraft.water")) + else + tooltips.add(TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye")) + + tooltips.add(TextComponent("")) + tooltips.add(TextComponent("")) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.DARK_GRAY) + + val color = RGBAColor.rgb(dye?.textColor ?: DyeColor.LIGHT_BLUE.textColor) + + graphics.renderCheckerboard(0f, 0f, width, height, color = color.copy(alpha = 0.25f)) + + val multiplier = menu.dyeStoredDirect[dye]!!.toFloat() / (if (dye == null) PainterBlockEntity.MAX_WATER_STORAGE.toFloat() else PainterBlockEntity.MAX_STORAGE.toFloat()) + graphics.renderRect(0f, height * (1f - multiplier), width, height * multiplier, color = color) + + tooltips[tooltips.size - 1] = TextComponent("${menu.dyeStoredDirect[dye]} (${(multiplier * 100f).toInt()}%)") + } + } + + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, 200f, 120f, title) + + val strip = EditablePanel(this, frame, width = AbstractSlotPanel.SIZE) + strip.dock = Dock.LEFT + strip.dockRight = 4f + + SlotPanel(this, strip, menu.inputSlot).also { + it.dock = Dock.TOP + it.dockTop = 4f + } + + EditablePanel(this, frame, width = 6f * (DyeColor.entries.size / 2f + 1f)).also { + it.dock = Dock.RIGHT + it.dockLeft = 4f + + Bar(it, null).childrenOrder = -1000 + + EditablePanel(this, it, height = 46f).also { + it.dock = Dock.TOP + + for (i in 0 until DyeColor.entries.size / 2) { + Bar(it, DyeColor.entries[i]) + } + } + + EditablePanel(this, it, height = 46f).also { + it.dock = Dock.BOTTOM + + for (i in DyeColor.entries.size / 2 until DyeColor.entries.size) { + Bar(it, DyeColor.entries[i]) + } + } + } + + SlotPanel(this, strip, menu.outputSlot).also { + it.dock = Dock.BOTTOM + it.dockBottom = 4f + } + + SlotPanel(this, strip, menu.dyeSlot).also { + it.dock = Dock.FILL + it.dockResize = DockResizeMode.NONE + it.slotBackgroundEmpty = + ItemStackIcon(ItemStack(ForgeRegistries.ITEMS.tags()!!.getTag(Tags.Items.DYES).getRandomElement(RandomSource.create()).orElse(Items.AIR)), 16f, 16f) + .fixed() + .composeBefore(FlatRectangleIcon(16f, 16f, RGBAColor.rgb(0x8b8b8b).copy(alpha = 0.6f))) + .fixed() + } + + val column = EditablePanel(this, frame) + column.dock = Dock.FILL + + val bulk = CheckBoxLabelInputPanel(this, column, menu.isBulk, TranslatableComponent("otm.gui.painter.is_bulk")) + bulk.dock = Dock.BOTTOM + bulk.tooltips.add(TranslatableComponent("otm.gui.painter.is_bulk.desc")) + bulk.tooltips.add(TranslatableComponent("otm.gui.painter.is_bulk.desc2").withStyle(ChatFormatting.GRAY)) + bulk.dockBottom = 4f + + val canvas = ScrollableCanvasPanel(this, column) + canvas.dock = Dock.FILL + canvas.dockPaddingBottom = 4f + + val buttons = ArrayList>() + + menu.listeners.addListener { + if (frame.isRemoved) return@addListener + buttons.forEach { it.remove() } + buttons.clear() + + val recipes = menu.possibleRecipes + .filter { !it.value.ingredients.isEmpty() } + .sortedWith(CreativeMenuItemComparator.map { it.value.getOutput(menu.inputContainer).item }) + .plus( + menu.possibleRecipes + .filter { it.value.ingredients.isEmpty() } + .sortedBy { it.id } + ) + + for (recipe in recipes) { + val recipeOutput = recipe.value.getOutput(menu.inputContainer) + + object : LargeRectangleButtonPanel(this@PainterScreen, canvas.canvas, icon = ItemStackIcon(recipeOutput, 14f, 14f).fixed()) { + init { + buttons.add(this) + dockRight = 1f + dockBottom = 1f + } + + override var isDisabled: Boolean + get() = !recipe.value.canCraft(menu.dyeStoredDirect) + set(value) {} + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + val list = getTooltipFromItem(recipeOutput) + + recipe.value.dyes.forEach { + val (dye, amount) = it + + if (dye == null) { + if (amount == 1) + list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("block.minecraft.water"))) + else if (amount > 1) + list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("block.minecraft.water"), amount)) + } else { + if (amount == 1) + list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye"))) + else if (amount > 1) + list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye"), amount)) + } + } + + if (minecraft?.options?.advancedItemTooltips == true) { + list.add(TextComponent(recipe.id.toString()).withStyle(ChatFormatting.DARK_GRAY)) + } + + graphics.renderComponentTooltip(font, list, mouseX.toInt(), mouseY.toInt()) + return true + } + + override fun onClick(mouseButton: Int) { + menu.selectRecipe.accept(recipe.id) + } + } + } + } + + DeviceControls(this, frame, itemConfig = menu.itemConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterBottlerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterBottlerScreen.kt similarity index 53% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterBottlerScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterBottlerScreen.kt index 89ea2cff7..3813f8290 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterBottlerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterBottlerScreen.kt @@ -1,25 +1,31 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.MatterCapacitorSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.menu.MatterBottlerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu class MatterBottlerScreen(menu: MatterBottlerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { private var progress: ProgressGaugePanel? = null - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! - val p = PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - MatterGaugePanel(this, frame, menu.matterWidget, LEFT_MARGIN + p.width, GAUGE_TOP_WITH_SLOT) + val p = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + ProfiledMatterGaugePanel(this, frame, menu.matterWidget, LEFT_MARGIN + p.width, GAUGE_TOP_WITH_SLOT) BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) @@ -34,10 +40,11 @@ class MatterBottlerScreen(menu: MatterBottlerMenu, inventory: Inventory, title: progress = ProgressGaugePanel(this, frame, menu.progressWidget, 90f, PROGRESS_ARROW_TOP) if (minecraft?.player?.isSpectator != true) { - val mode = ButtonPanel(this, frame, 46f, 69f, 100f, 20f, TranslatableComponent("otm.matter_bottler.switch_mode")) - mode.bind { menu.workFlow.switchValue(minecraft?.player) } + ButtonPanel(this, frame, 46f, 69f, 100f, 20f, TranslatableComponent("otm.matter_bottler.switch_mode"), onPress = menu.workFlow::switchValue) } + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, upgrades = menu.upgrades) + return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterCapacitorBankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterCapacitorBankScreen.kt similarity index 58% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterCapacitorBankScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterCapacitorBankScreen.kt index a104919dc..0cfb538d2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterCapacitorBankScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterCapacitorBankScreen.kt @@ -1,20 +1,21 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.MatterCapacitorBankMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.MatterCapacitorSlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.MatterCapacitorSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel class MatterCapacitorBankScreen(p_97741_: MatterCapacitorBankMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! val m = MatterGaugePanel(this, frame, menu.matterGauge, LEFT_MARGIN, GAUGE_TOP_WITHOUT_SLOT) - MatterGaugePanel(this, frame, menu.matterGauge, LEFT_MARGIN + m.width, GAUGE_TOP_WITHOUT_SLOT) + MatterGaugePanel(this, frame, menu.totalMatterGauge, LEFT_MARGIN + m.width, GAUGE_TOP_WITHOUT_SLOT) for (i in 0 .. 5) MatterCapacitorSlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * i, 32f) @@ -22,6 +23,8 @@ class MatterCapacitorBankScreen(p_97741_: MatterCapacitorBankMenu, p_97742_: Inv for (i in 6 .. 11) MatterCapacitorSlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * (i - 6), 32f + 18f) + makeDeviceControls(this, frame, itemConfig = menu.itemConfig) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterDecomposerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt similarity index 53% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterDecomposerScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt index a95600d33..659f914bd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterDecomposerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt @@ -1,13 +1,17 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.MatterDecomposerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel class MatterDecomposerScreen(p_97741_: MatterDecomposerMenu, p_97742_: Inventory, p_97743_: Component) : @@ -16,8 +20,8 @@ class MatterDecomposerScreen(p_97741_: MatterDecomposerMenu, p_97742_: Inventory override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! - val m = PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - MatterGaugePanel(this, frame, menu.matterWidget, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) + val m = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + ProfiledMatterGaugePanel(this, frame, menu.matterWidget, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) @@ -26,6 +30,8 @@ class MatterDecomposerScreen(p_97741_: MatterDecomposerMenu, p_97742_: Inventory SlotPanel(this, frame, menu.outputMain, 74f, PROGRESS_SLOT_TOP) SlotPanel(this, frame, menu.outputStacking, 56f, PROGRESS_SLOT_TOP) + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, upgrades = menu.upgrades) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt new file mode 100644 index 000000000..b706d73ed --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt @@ -0,0 +1,78 @@ +package ru.dbotthepony.mc.otm.client.screen.matter + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.compat.jei.MatterEntanglerRecipeCategory +import ru.dbotthepony.mc.otm.compat.jei.PlatePressRecipeCategory +import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu + +class MatterEntanglerScreen(menu: MatterEntanglerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, DEFAULT_FRAME_WIDTH, 110f, title) + + makeBars(frame, profiledEnergy = menu.profiledEnergy, profiledMatter = menu.profiledMatter, batterySlot = menu.batterySlot) + + GridPanel.slots(this, frame, 3, 3).also { + it.dock = Dock.LEFT + it.dockLeft = 20f + it.dockRight = 4f + it.dockResize = DockResizeMode.NONE + + for (slot in menu.inputs) + SlotPanel(this, it, slot) + } + + ProgressGaugePanel(this, frame, menu.progress).also { + it.dock = Dock.LEFT + it.dockHorizontal(4f) + it.dockResize = DockResizeMode.NONE + it.setRecipeType { listOf(MatterEntanglerRecipeCategory.recipeType) } + } + + SlotPanel(this, frame, menu.outputs[0]).also { + it.dock = Dock.LEFT + it.dockHorizontal(4f) + it.dockResize = DockResizeMode.NONE + } + + val strip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE) + strip.dock = Dock.BOTTOM + strip.dockTop = 4f + strip.childrenOrder = -1 + + SlotPanel(this, strip, menu.entanglingA).also { + it.dock = Dock.LEFT + it.dockLeft = 20f + } + + SlotPanel(this, strip, menu.entanglingB).also { + it.dock = Dock.LEFT + it.dockLeft = 4f + } + + SpritePanel(this, strip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.FILL + it.dockResize = DockResizeMode.NONE + } + + SlotPanel(this, strip, menu.entanglingC).also { + it.dock = Dock.RIGHT + it.dockRight = 20f + } + + DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, upgrades = menu.upgrades, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt new file mode 100644 index 000000000..b1d960325 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt @@ -0,0 +1,507 @@ +package ru.dbotthepony.mc.otm.client.screen.matter + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.capability.matter.PatternState +import ru.dbotthepony.mc.otm.capability.matter.ReplicationTask +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.render.* +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeBooleanRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.core.util.ItemSorter +import ru.dbotthepony.mc.otm.core.util.formatMatter +import ru.dbotthepony.mc.otm.core.util.formatTickDuration +import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu +import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket +import ru.dbotthepony.mc.otm.network.MenuNetworkChannel +import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak +import java.util.function.Predicate +import kotlin.math.ceil +import kotlin.math.roundToInt + +@MouseTweaksDisableWheelTweak +class MatterPanelScreen( + menu: MatterPanelMenu, + inventory: Inventory, + title: Component +) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + var isPatternView = true + var scrollPatterns = 0 + var scrollTasks = 0 + + val frame = FramePanel.padded(this, null, GRID_WIDTH * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH + 4f, GRID_HEIGHT * AbstractSlotPanel.SIZE + 2f, title) + frame.dockPadding = frame.dockPadding.copy(top = frame.dockPadding.top + 2f) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val controls = DeviceControls(this, frame) + + controls.sortingButtons(menu.settings::isAscending.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemSorter.DEFAULT) { + for (v in ItemSorter.entries) { + add(v, skinElement = v.icon, tooltip = v.title) + } + } + + controls.addButton( + LargeBooleanRectangleButtonPanel( + this, + frame, + prop = menu::isProvidingTasks.asGetterSetter(), + iconActive = Widgets18.PLAY, + iconInactive = Widgets18.PAUSE, + tooltipActive = TranslatableComponent("otm.gui.matter_panel.is_providing_tasks"), + tooltipInactive = TranslatableComponent("otm.gui.matter_panel.not_providing_tasks"), + ) + ) + + controls.addButton( + LargeRectangleButtonPanel( + this, + frame, + icon = Widgets18.STOP, + onPress = { + frame.queryUser( + TranslatableComponent("otm.gui.matter_panel.cancel_all"), + listOf(TranslatableComponent("otm.gui.matter_panel.cancel_all.desc")), + { menu.cancelAll.accept(null) } + ) + } + ).also { it.tooltips.add(TranslatableComponent("otm.gui.matter_panel.cancel_all")) } + ) + + val scrollBar = DiscreteScrollBarPanel(this, frame, { + if (isPatternView) { + integerDivisionDown(menu.patternsFiltered.size, GRID_WIDTH) + } else { + integerDivisionDown(menu.tasksFiltered.size, GRID_WIDTH) + } + }, { _, _, new -> + if (isPatternView) + scrollPatterns = new + else + scrollTasks = new + }) + + object : TextInputPanel(this@MatterPanelScreen, frame) { + init { + width = frame.width * 0.4f + x = frame.width - width - FramePanel.PADDING - 6f + y = 4f + placeholder = TranslatableComponent("otm.gui.quicksearch") + } + + override fun onTextChanged(old: String, new: String) { + if (new == "") { + menu.filter = Predicate { true } + } else { + val new = new.lowercase() + menu.filter = Predicate { it.description.string.lowercase().contains(new) } + scrollBar.scroll = scrollBar.scroll + } + } + } + + scrollBar.dock = Dock.RIGHT + + frame.Tab(onOpen = { isPatternView = true; scrollBar.scroll = scrollPatterns }, activeIcon = PATTERN_LIST_ACTIVE, inactiveIcon = PATTERN_LIST_INACTIVE).also { + it.tooltips.add(TranslatableComponent("otm.gui.matter_panel.patterns")) + } + + frame.Tab(onOpen = { isPatternView = false; scrollBar.scroll = scrollTasks }, activeIcon = TASK_LIST_ACTIVE, inactiveIcon = TASK_LIST_INACTIVE).also { + it.tooltips.add(TranslatableComponent("otm.gui.matter_panel.tasks")) + } + + val canvas = object : EditablePanel(this@MatterPanelScreen, frame, width = GRID_WIDTH * AbstractSlotPanel.SIZE) { + init { + dock = Dock.LEFT + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollBar.mouseScrolledInner(x, y, scroll) + } + } + + for (row in 0 until GRID_HEIGHT) { + val rowCanvas = object : EditablePanel(this@MatterPanelScreen, canvas, height = AbstractSlotPanel.SIZE) { + init { + dock = Dock.TOP + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return false + } + } + + for (i in 0 until GRID_WIDTH) { + object : AbstractSlotPanel(this@MatterPanelScreen, rowCanvas) { + init { + dock = Dock.LEFT + } + + private val index: Int get() = (scrollBar.scroll + row) * GRID_WIDTH + i + + override val itemStack: ItemStack get() { + if (isPatternView) { + return menu.patternsFiltered.getOrNull(index)?.stack() ?: ItemStack.EMPTY + } else { + return menu.tasksFiltered.getOrNull(index)?.let { it.stack(it.required + it.inProgress) } ?: ItemStack.EMPTY + } + } + + override fun getItemStackTooltip(stack: ItemStack): MutableList { + val list = super.getItemStackTooltip(stack).toMutableList() + + if (isPatternView) { + menu.patternsFiltered.getOrNull(index)?.let { + list.add(TranslatableComponent( + "otm.item.pattern.research", + String.format("%.2f", it.researchPercent * 100.0) + ).withStyle(ChatFormatting.AQUA)) + + if (ru.dbotthepony.mc.otm.client.minecraft.options.advancedItemTooltips) { + val researchAdvance = MatterManager.getResearchAdvance(it.item) + val required = ceil(1.0 / researchAdvance).toInt() + val researched = (required.toDouble() * it.researchPercent).toInt() + + list.add(TranslatableComponent( + "otm.item.pattern.research.item_count", + researched, + required + ).withStyle(ChatFormatting.DARK_GRAY)) + list.add(TranslatableComponent( + "otm.item.pattern.research.advance", + String.format("%.2f", researchAdvance * 100.0) + ).withStyle(ChatFormatting.DARK_GRAY)) + } + } + } else { + menu.tasksFiltered.getOrNull(index)?.let { + list.add(TranslatableComponent("otm.gui.matter_task.total", it.total).withStyle(ChatFormatting.GRAY)) + list.add(TranslatableComponent("otm.gui.matter_task.required", it.required).withStyle(ChatFormatting.GRAY)) + list.add(TranslatableComponent("otm.gui.matter_task.in_progress", it.inProgress).withStyle(ChatFormatting.GRAY)) + list.add(TranslatableComponent("otm.gui.matter_task.finished", it.finished).withStyle(ChatFormatting.GRAY)) + } + } + + return list + } + + override fun renderRegular(graphics: MGUIGraphics, itemstack: ItemStack, countOverride: String?) { + if (isPatternView) { + super.renderRegular(graphics, itemstack, "") + + menu.patternsFiltered.getOrNull(index)?.let { + if (it.researchPercent < 1f) { + graphics.draw( + TextComponent((it.researchPercent * 100.0).toInt().toString() + "%"), + width - 1f, height - 1f, scale = 0.5f, gravity = RenderGravity.BOTTOM_RIGHT, drawShadow = true, color = RGBAColor.WHITE) + } + } + } else { + super.renderRegular(graphics, itemstack, countOverride) + + menu.tasksFiltered.getOrNull(index)?.let { + val progress = it.finished.toFloat() / it.total.toFloat() + val processing = it.inProgress.toFloat() / it.total.toFloat() + + val barLeft = 2f + val barTop = 2f + val barWidth = width - 4f + val barHeight = 1f + + graphics.renderRect(barLeft - .5f, barTop - .5f, barWidth + 1f, barHeight + 1f, color = RGBAColor.WHITE) + graphics.renderRect(barLeft, barTop, barWidth, barHeight, color = RGBAColor.BLACK) + graphics.renderRect(barLeft, barTop, barWidth * progress, barHeight, color = RGBAColor.GREEN) + graphics.renderRect(barLeft + barWidth * progress, barTop, barWidth * processing, barHeight, color = RGBAColor.YELLOW) + } + } + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (isPatternView) { + if (minecraft?.player?.isSpectator != true) + menu.patternsFiltered.getOrNull(index)?.let(this@MatterPanelScreen::openPattern)?.let { + frame.blockingWindow = it + } + } else { + menu.tasksFiltered.getOrNull(index)?.let(this@MatterPanelScreen::openTask)?.let { + frame.blockingWindow = it + } + } + + return true + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return false + } + } + } + } + + return frame + } + + private fun openTask(task: ReplicationTask): FramePanel { + val frame = FramePanel.padded(this, null, 170f, 20f, TranslatableComponent("otm.gui.matter_panel.task")) + + frame.behaveAsWindow() + + object : AbstractSlotPanel(this@MatterPanelScreen, frame) { + init { + dock = Dock.LEFT + } + + override val itemStack: ItemStack get() { + return menu.tasksFiltered.firstOrNull { it.id == task.id }?.let { it.stack((it.required + it.inProgress).coerceAtLeast(1)) } ?: task.stack((task.required + task.inProgress).coerceAtLeast(1)) + } + + override fun tickInner() { + super.tickInner() + + if (!menu.tasksFiltered.any { it.id == task.id }) { + frame.remove() + } + } + } + + ButtonPanel(this@MatterPanelScreen, frame, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.close"), onPress = frame::remove).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + if (minecraft?.player?.isSpectator != true) { + ButtonPanel(this@MatterPanelScreen, frame, width = 80f, label = TranslatableComponent("otm.gui.matter_panel.cancel_task"), onPress = Runnable { + menu.requestTaskCancel(task.id) + frame.remove() + }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + } + + addPanel(frame) + popup(frame) + + frame.toScreenCenter() + + return frame + } + + private fun openPattern(pattern: PatternState): FramePanel { + val frame = FramePanel.padded(this, null, 213f, (ButtonPanel.HEIGHT + 3f) * 4f + 38f, TranslatableComponent("otm.gui.matter_panel.task")) + + frame.behaveAsWindow() + + val rowTop = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) + val rowInput = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) + val rowBottom = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) + val rowControls = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) + + rowTop.dock = Dock.TOP + rowTop.dockTop = 3f + rowInput.dock = Dock.TOP + rowInput.dockTop = 3f + rowBottom.dock = Dock.TOP + rowBottom.dockTop = 3f + rowControls.dock = Dock.TOP + rowControls.dockTop = 3f + + object : AbstractSlotPanel(this@MatterPanelScreen, rowInput) { + init { + dock = Dock.LEFT + dockRight = 2f + } + + override val itemStack: ItemStack get() { + return pattern.stack() + } + + override fun getItemStackTooltip(stack: ItemStack): MutableList { + return super.getItemStackTooltip(stack).toMutableList().also { + it.add(TranslatableComponent( + "otm.item.pattern.research", + String.format("%.2f", pattern.researchPercent * 100.0) + ).withStyle(ChatFormatting.AQUA)) + } + } + + override fun tickInner() { + super.tickInner() + + if (!menu.patternsFiltered.any { it.id == pattern.id }) { + frame.remove() + } + } + } + + var count = 1 + + val input = object : TextInputPanel(this@MatterPanelScreen, rowInput) { + init { + dock = Dock.FILL + text = "1" + requestFocus() + } + + fun increase(amount: Int) { + count = 1 + + try { + count = text.toInt().coerceIn(1, 99999) + } catch (_: NumberFormatException) { + } + + if (count == 1 && amount > 0) + text = amount.toString() + else + text = (count + amount).coerceIn(1, 99999).toString() + } + + override fun tickInner() { + super.tickInner() + + try { + count = text.toInt().coerceIn(1, 99999) + } catch (_: NumberFormatException) { + } + } + + fun send() { + count = 1 + + try { + count = text.toInt().coerceIn(1, 99999) + } catch (_: NumberFormatException) { + } + + MenuNetworkChannel.sendToServer(ReplicationRequestPacket(pattern.id, count)) + frame.remove() + } + } + + ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.increase_by", 8), onPress = Runnable { input.increase(8) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.increase_by", 64), onPress = Runnable { input.increase(64) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowTop, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.increase_by", 256), onPress = Runnable { input.increase(256) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.decrease_by", 8), onPress = Runnable { input.increase(-8) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.decrease_by", 64), onPress = Runnable { input.increase(-64) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowBottom, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.decrease_by", 256), onPress = Runnable { input.increase(-256) }).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowControls, width = 40f, label = TranslatableComponent("otm.gui.matter_panel.cancel"), onPress = frame::remove).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + ButtonPanel(this, rowControls, width = 82f, label = TranslatableComponent("otm.gui.matter_panel.send"), onPress = input::send).also { + it.dock = Dock.RIGHT + it.dockLeft = 2f + } + + addPanel(frame) + popup(frame) + + object : Label(this@MatterPanelScreen, frame, text = TextComponent("")) { + init { + dock = Dock.TOP + dockTop = 5f + } + + override fun tickInner() { + super.tickInner() + + text = TranslatableComponent( + "otm.gui.matter_panel.matter_stored", + menu.totalMatterStored.formatMatter(formatAsReadable = ShiftPressedCond), + ) + } + } + + object : Label(this@MatterPanelScreen, frame, text = TextComponent("")) { + init { + dock = Dock.TOP + dockTop = 3f + } + + override fun tickInner() { + super.tickInner() + + text = TranslatableComponent( + "otm.gui.matter_panel.matter_required", + (MatterManager.get(pattern.item).matter * count).formatMatter(formatAsReadable = ShiftPressedCond), + ) + } + } + + object : Label(this@MatterPanelScreen, frame, text = TextComponent("")) { + init { + dock = Dock.TOP + dockTop = 3f + } + + override fun tickInner() { + super.tickInner() + + text = TranslatableComponent( + "otm.gui.matter_panel.complexity", + formatTickDuration((MatterManager.get(pattern.item).complexity * count).roundToInt(), true), + ) + } + } + + frame.toScreenCenter() + + return frame + } + + companion object { + const val GRID_HEIGHT = 8 + const val GRID_WIDTH = 9 + + val PATTERN_LIST_ACTIVE = WidgetLocation.PATTERN_PANEL_TABS.sprite(0f, 0f, 14f, 23f) + val TASK_LIST_ACTIVE = WidgetLocation.PATTERN_PANEL_TABS.sprite(28f, 0f, 16f, 16f) + val PATTERN_LIST_INACTIVE = WidgetLocation.PATTERN_PANEL_TABS.sprite(14f, 0f, 14f, 23f) + val TASK_LIST_INACTIVE = WidgetLocation.PATTERN_PANEL_TABS.sprite(44f, 0f, 16f, 16f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt new file mode 100644 index 000000000..7d9d99dff --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.client.screen.matter + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.PlayerEquipmentPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu + +class MatterReconstructorScreen(menu: MatterReconstructorMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + val p = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + ProfiledMatterGaugePanel(this, frame, menu.matterWidget, LEFT_MARGIN + p.width, GAUGE_TOP_WITH_SLOT) + + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + SlotPanel(this, frame, menu.slot, 66f, PROGRESS_SLOT_TOP) + ProgressGaugePanel(this, frame, menu.progress, 37f, PROGRESS_ARROW_TOP) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig, upgrades = menu.upgrades) + makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true) + + PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots).also { + it.leftSided = false + it.dock = Dock.RIGHT + it.dockResize = DockResizeMode.NONE + } + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterRecyclerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterRecyclerScreen.kt new file mode 100644 index 000000000..89a7d6c71 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterRecyclerScreen.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.client.screen.matter + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu + +class MatterRecyclerScreen(menu: MatterRecyclerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + val m = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + ProfiledMatterGaugePanel(this, frame, menu.matter, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) + + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + ProgressGaugePanel(this, frame, menu.progress, 63f, PROGRESS_ARROW_TOP).flop = true + SlotPanel(this, frame, menu.input, 93f, PROGRESS_SLOT_TOP) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig, upgrades = menu.upgrades) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterReplicatorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReplicatorScreen.kt similarity index 51% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterReplicatorScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReplicatorScreen.kt index 134348d2c..5fb05042a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterReplicatorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReplicatorScreen.kt @@ -1,22 +1,26 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.MatterReplicatorMenu import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu class MatterReplicatorScreen(p_97741_: MatterReplicatorMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! - val m = PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) - MatterGaugePanel(this, frame, menu.matter, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) + val m = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + ProfiledMatterGaugePanel(this, frame, menu.matter, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) @@ -28,6 +32,8 @@ class MatterReplicatorScreen(p_97741_: MatterReplicatorMenu, p_97742_: Inventory SlotPanel(this, frame, menu.storageSlots[3], 80f, PROGRESS_SLOT_TOP + 22f) SlotPanel(this, frame, menu.storageSlots[4], 80f + 18f, PROGRESS_SLOT_TOP + 22f) + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig, upgrades = menu.upgrades) + return frame } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterScannerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterScannerScreen.kt similarity index 53% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterScannerScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterScannerScreen.kt index 9e3c414ed..d58e9b963 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatterScannerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterScannerScreen.kt @@ -1,21 +1,24 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.MatterScannerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.client.screen.panels.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.PatternGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel class MatterScannerScreen(p_97741_: MatterScannerMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! - val m = PowerGaugePanel(this, frame, menu.powerWidget, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + val m = ProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) PatternGaugePanel(this, frame, menu.patterns, LEFT_MARGIN + m.width, GAUGE_TOP_WITH_SLOT) BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) @@ -23,6 +26,8 @@ class MatterScannerScreen(p_97741_: MatterScannerMenu, p_97742_: Inventory, p_97 ProgressGaugePanel(this, frame, menu.progress, 63f, PROGRESS_ARROW_TOP).flop = true SlotPanel(this, frame, menu.input, 93f, PROGRESS_SLOT_TOP) + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, upgrades = menu.upgrades) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PatternStorageScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/PatternStorageScreen.kt similarity index 65% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PatternStorageScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/PatternStorageScreen.kt index 9ef311ae5..f6ce0456e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/PatternStorageScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/PatternStorageScreen.kt @@ -1,16 +1,17 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.matter import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.menu.PatternStorageMenu +import ru.dbotthepony.mc.otm.menu.matter.PatternStorageMenu import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel -import ru.dbotthepony.mc.otm.client.screen.panels.PatternSlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.PatternSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.PatternGaugePanel class PatternStorageScreen(p_97741_: PatternStorageMenu, p_97742_: Inventory, p_97743_: Component) : MatteryScreen(p_97741_, p_97742_, p_97743_) { - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { val frame = super.makeMainFrame()!! val m = PatternGaugePanel(this, frame, menu.storedThis, LEFT_MARGIN, GAUGE_TOP_WITHOUT_SLOT) @@ -22,6 +23,8 @@ class PatternStorageScreen(p_97741_: PatternStorageMenu, p_97742_: Inventory, p_ for (i in 4 until 8) PatternSlotPanel(this, frame, menu.storageSlots[i], 62f + (i - 4) * 18, 32f + 18f) + makeDeviceControls(this, frame, itemConfig = menu.itemConfig) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractSlotPanel.kt deleted file mode 100644 index d7bcb2480..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AbstractSlotPanel.kt +++ /dev/null @@ -1,117 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.renderer.GameRenderer -import net.minecraft.network.chat.Component -import net.minecraft.world.item.ItemStack -import net.minecraftforge.client.extensions.common.IClientItemExtensions -import org.lwjgl.opengl.GL11 -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.* -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.core.RGBAColor - -abstract class AbstractSlotPanel> @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = SIZE, - height: Float = SIZE, - open val noItemIcon: SkinElement? = null -) : EditablePanel(screen, parent, x, y, width, height), IItemStackPanel { - protected open fun renderSlotBackground(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - SLOT_BACKGROUND.render(stack, width = width, height = height) - } - - protected open fun renderRegular(stack: PoseStack, itemstack: ItemStack, count_override: String? = null) { - RenderSystem.setShader(GameRenderer::getPositionTexShader) - - if (!itemstack.isEmpty) { - RenderSystem.enableDepthTest() - - val systemPoseStack = RenderSystem.getModelViewStack() - - systemPoseStack.pushPose() - systemPoseStack.translate((absoluteX + 1f).toDouble(), (absoluteY + 1f).toDouble(), 0.0) - RenderSystem.applyModelViewMatrix() - RenderSystem.depthFunc(GL11.GL_LESS) - - // Thanks Mojang - // Very cool - // (for int x, int y, which are then cast into doubles anyway) - screen.itemRenderer.blitOffset = 1f // Z pos - - screen.itemRenderer.renderAndDecorateItem( - requireNotNull(minecraft.player) { "yo, dude, what the fuck" }, - itemstack, - 0, - 0, - (absoluteX + absoluteY * 1000f).toInt() - ) - - RenderSystem.depthFunc(GL11.GL_ALWAYS) - screen.itemRenderer.renderGuiItemDecorations(screen.font, itemstack, 0, 0, count_override) - screen.itemRenderer.blitOffset = 0f - - // too big accumulations can lead to Z near clipping issues - systemPoseStack.popPose() - RenderSystem.applyModelViewMatrix() - - clearDepth(stack) - } - - if (isHovered) { - drawColor = SLOT_HIGHLIGHT - drawRect(stack, 1f, 1f, width - 1, height - 1) - } - } - - protected open fun getItemStackTooltip(stack: ItemStack): List { - return screen.getTooltipFromItem(stack) - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - renderSlotBackground(stack, mouseX, mouseY, partialTick) - val itemStack = itemStack - renderRegular(stack, itemStack) - - if (itemStack.isEmpty) { - noItemIcon?.render(stack, width = width, height = height) - } - } - - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - if (isHovered) { - val itemstack = itemStack - - if (!itemstack.isEmpty) { - // val font = RenderProperties.get(itemstack).getFont(itemstack) - val font = (itemstack.item.renderPropertiesInternal as? IClientItemExtensions)?.getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) - - // TODO: WHERE???????????? - // GuiUtils.preItemToolTip(itemstack); - screen.renderComponentTooltip( - stack, - getItemStackTooltip(itemstack), - mouseX.toInt(), - mouseY.toInt(), - font ?: screen.font, - itemstack - ) - // GuiUtils.postItemToolTip(); - - return true - } - } - return false - } - - companion object { - val SLOT_HIGHLIGHT = RGBAColor(255, 255, 255, 100) - val SLOT_HIGHLIGHT_DRAG = RGBAColor(200, 200, 200, 150) - const val SIZE = 18f - val SLOT_BACKGROUND = WidgetLocation.MISC.subElement(0f, 0f, SIZE, SIZE) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/BackgroundPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/BackgroundPanel.kt deleted file mode 100644 index 34cbe3a26..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/BackgroundPanel.kt +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen -import ru.dbotthepony.mc.otm.client.render.StretchingRectangleElement -import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen - -open class BackgroundPanel( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = 10f, - height: Float = 10f, -) : EditablePanel(screen, parent, x, y, width, height) { - init { - dockPadding = DockProperty(3f, 3f, 3f, 3f) - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - RECTANGLE.render(stack, width = width, height = height) - } - - companion object { - fun padded( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = 10f, - height: Float = 10f, - ) = BackgroundPanel(screen, parent, x, y, width + 6f, height + 6f) - - fun paddedCenter( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = 10f, - height: Float = 10f, - ) = BackgroundPanel(screen, parent, x - 3f, y - 3f, width + 6f, height + 6f) - - val RECTANGLE = StretchingRectangleElement( - topLeft = WidgetLocation.MISC.subElement(18f, 24f, 3f, 3f), - topRight = WidgetLocation.MISC.subElement(27f, 24f, 3f, 3f), - bottomLeft = WidgetLocation.MISC.subElement(18f, 33f, 3f, 3f), - bottomRight = WidgetLocation.MISC.subElement(27f, 33f, 3f, 3f), - top = WidgetLocation.MISC.subElement(21f, 24f, 6f, 2f), - bottom = WidgetLocation.MISC.subElement(21f, 34f, 6f, 2f), - left = WidgetLocation.MISC.subElement(18f, 27f, 2f, 6f), - right = WidgetLocation.MISC.subElement(28f, 27f, 2f, 6f), - middle = WidgetLocation.MISC.subElement(30f, 12f, 6f, 6f), - padding = DockProperty(-1f, -1f, -1f, -1f) - ) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt new file mode 100644 index 000000000..bd2fa0f0c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt @@ -0,0 +1,745 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.datafixers.util.Either +import it.unimi.dsi.fastutil.floats.FloatConsumer +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.HSVColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import java.util.function.Consumer +import kotlin.math.roundToInt + +open class ColorBoxPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float, + y: Float, + width: Float = 64f, + height: Float = 64f, + protected val callback: Consumer? = null, +) : EditablePanel(screen, parent, x, y, width, height) { + var backgroundColor = RGBAColor.RED + private set + var markerPos = backgroundColor.toHSV() + protected set + + fun setColor(color: Either) { + color.map( + { markerPos = it.toHSV(); if (it.canRepresentHue()) backgroundColor = HSVColor(it.toHSV().hue, 1f, 1f).toRGBA() }, + { markerPos = it; backgroundColor = HSVColor(it.hue, 1f, 1f).toRGBA() }) + } + + var isPressed = false + private set + + override fun shouldRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + return super.shouldRenderTooltips(graphics, mouseX, mouseY, partialTick) || isPressed + } + + protected open fun onPickedColor(color: HSVColor) { + callback?.accept(color) + } + + protected fun pickColor(mouseX: Double, mouseY: Double) { + val (x, y) = screenToLocal(mouseX, mouseY) + + val saturation = 1f - x.coerceIn(0f, width) / width + val value = 1f - y.coerceIn(0f, height) / height + + markerPos = HSVColor(backgroundColor.toHSV().hue, saturation, value) + onPickedColor(markerPos) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (!isPressed) { + isPressed = true + grabMouseInput = true + + pickColor(x, y) + playGuiClickSound() + } + + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (isPressed && button == InputConstants.MOUSE_BUTTON_LEFT) { + pickColor(x, y) + return true + } + + return super.mouseDraggedInner(x, y, button, xDelta, yDelta) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (isPressed) { + isPressed = false + grabMouseInput = false + } + + return true + } + + return super.mouseReleasedInner(x, y, button) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.WHITE) + GRADIENT_LEFT.render(graphics, 0f, 0f, width, height, color = backgroundColor) + GRADIENT_DOWN.render(graphics, 0f, 0f, width, height, color = RGBAColor.BLACK) + + val x = (1f - markerPos.saturation) * width + val y = (1f - markerPos.value) * height + + LINE_VERTICAL.render(graphics, x = x - 1f, height = height) + LINE_HORIZONTAL.render(graphics, y = y - 1f, width = width) + LINE_CROSS.render(graphics, x = x - 1f, y = y - 1f) + } + + companion object { + val GRADIENT_UP = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/gradient_v.png"), 0f, 0f, 32f, 256f, 32f, 256f) + val GRADIENT_DOWN = GRADIENT_UP.copy(winding = UVWindingOrder.FLIP) + val GRADIENT_RIGHT = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/gradient_h.png"), 0f, 0f, 256f, 32f, 256f, 32f) + val GRADIENT_LEFT = GRADIENT_RIGHT.copy(winding = UVWindingOrder.FLOP) + + val LINE_VERTICAL = WidgetLocation.MISC.sprite(36f, 0f, 3f, 3f) + val LINE_HORIZONTAL = WidgetLocation.MISC.sprite(36f, 3f, 3f, 3f) + val LINE_CROSS = WidgetLocation.MISC.sprite(36f, 6f, 3f, 3f) + } +} + +abstract class AbstractColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val callback: Consumer? = null, +) : EditablePanel(screen, parent, x, y, width, height) { + abstract val leftColor: RGBAColor + abstract val rightColor: RGBAColor + abstract val wangPosition: Float + abstract fun setColor(color: Either) + protected abstract fun onWangInput(newPosition: Float) + + init { + scissor = true + } + + var isPressed = false + private set + + protected fun updateColor(mouseX: Double) { + onWangInput((screenToLocal(mouseX, 0.0).x / width).coerceIn(0f, 1f)) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (!isPressed) { + isPressed = true + grabMouseInput = true + + updateColor(x) + playGuiClickSound() + } + + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (isPressed && button == InputConstants.MOUSE_BUTTON_LEFT) { + updateColor(x) + return true + } + + return super.mouseDraggedInner(x, y, button, xDelta, yDelta) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (isPressed) { + isPressed = false + grabMouseInput = false + } + + return true + } + + return super.mouseReleasedInner(x, y, button) + } + + protected fun renderGradients(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + ColorBoxPanel.GRADIENT_RIGHT.render(graphics, 0f, 0f, width, height, color = rightColor) + ColorBoxPanel.GRADIENT_LEFT.render(graphics, 0f, 0f, width, height, color = leftColor) + } + + protected fun renderWang(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (wangPosition in 0f .. 1f) { + ColorBoxPanel.LINE_VERTICAL.render(graphics, x = wangPosition * width - 1f, height = height) + } + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderGradients(graphics, mouseX, mouseY, partialTick) + renderWang(graphics, mouseX, mouseY, partialTick) + } +} + +open class RedColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.RED + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(red = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(red = 0f, alpha = 1f) + rightColor = color.copy(red = 1f, alpha = 1f) + wangPosition = color.red + } +} + +open class GreenColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.GREEN + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(green = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(green = 0f, alpha = 1f) + rightColor = color.copy(green = 1f, alpha = 1f) + wangPosition = color.green + } +} + +open class BlueColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.BLUE + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(blue = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(blue = 0f, alpha = 1f) + rightColor = color.copy(blue = 1f, alpha = 1f) + wangPosition = color.blue + } +} + +open class HueWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val hueCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override val leftColor: RGBAColor get() = RGBAColor.WHITE + override val rightColor: RGBAColor get() = RGBAColor.WHITE + override var wangPosition: Float = 1f + protected set + + override fun setColor(color: Either) { + color.map( + { + if (it.canRepresentHue()) { + wangPosition = it.toHSV().hue / 360f + } + }, + { + wangPosition = it.hue / 360f + } + ) + } + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + hueCallback?.accept(newPosition * 360f) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + HSV_BAR.render(graphics, 0f, 0f, width, height) + renderWang(graphics, mouseX, mouseY, partialTick) + } + + companion object { + val HSV_BAR = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/hsv.png"), 0f, 0f, 256f, 16f, 256f, 16f) + } +} + +open class SaturationWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val saturationCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override var leftColor: RGBAColor = RGBAColor.WHITE + protected set + override var rightColor: RGBAColor = RGBAColor.WHITE + protected set + override var wangPosition: Float = 1f + protected set + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + saturationCallback?.accept(newPosition) + } + + override fun setColor(color: Either) { + color.map( + { + val hsv = it.toHSV() + + if (it.canRepresentHue()) { + leftColor = hsv.copy(saturation = 0f).toRGBA() + rightColor = hsv.copy(saturation = 1f).toRGBA() + } else { + leftColor = hsv.copy(hue = leftColor.toHSV().hue, saturation = 0f).toRGBA() + rightColor = hsv.copy(hue = rightColor.toHSV().hue, saturation = 1f).toRGBA() + } + + wangPosition = hsv.saturation + }, + { hsv -> + leftColor = hsv.copy(saturation = 0f).toRGBA() + rightColor = hsv.copy(saturation = 1f).toRGBA() + wangPosition = hsv.saturation + } + ) + } +} + +open class ValueWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val valueCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.WHITE + protected set + override var wangPosition: Float = 1f + protected set + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + valueCallback?.accept(newPosition) + } + + override fun setColor(color: Either) { + color.map( + { + val hsv = it.toHSV() + + if (it.canRepresentHue()) { + leftColor = hsv.copy(value = 0f).toRGBA() + rightColor = hsv.copy(value = 1f).toRGBA() + } else { + leftColor = hsv.copy(hue = leftColor.toHSV().hue, value = 0f).toRGBA() + rightColor = hsv.copy(hue = rightColor.toHSV().hue, value = 1f).toRGBA() + } + + wangPosition = hsv.value + }, + { hsv -> + leftColor = hsv.copy(value = 0f).toRGBA() + rightColor = hsv.copy(value = 1f).toRGBA() + wangPosition = hsv.value + } + ) + } +} + +open class ColorPalettePanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 64f, + height: Float = 64f, + protected val callback: Consumer? = null +) : EditablePanel(screen, parent, x, y, width, height) { + open fun onColorChoose(color: RGBAColor) { + callback?.accept(color) + } + + inner class Button(val color: RGBAColor) : AbstractButtonPanel(screen, this@ColorPalettePanel, 0f, 0f, 8f, 8f) { + init { + tooltips.add(TextComponent("${color.redInt}, ${color.greenInt}, ${color.blueInt}")) + tooltips.add(TextComponent(color.toHexStringRGB())) + } + + override fun onClick(mouseButton: Int) { + onColorChoose(color) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.BLACK) + graphics.renderRect(1f, 1f, width - 2f, height - 2f, color = color) + } + + override fun compareTo(other: EditablePanel<*>): Int { + return super.compareTo(other).let { + if (it != 0 || other !is Button) + it + else + color.compareTo(other.color) + } + } + } + + override fun performLayout() { + super.performLayout() + + var x = 0f + var y = 0f + + for (children in visibleChildren) { + if (children is Button) { + if (x != 0f && x + children.width > width) { + y += 9f + x = 0f + } + + children.x = x + children.y = y + x += 9f + } + } + } +} + +open class ColorPickerPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 164f, + height: Float = 118f, + var callback: Consumer? = null +) : EditablePanel(screen, parent, x, y, width, height) { + open fun onColorChanged(color: RGBAColor) { + callback?.accept(color) + } + + var currentColor: Either = Either.left(RGBAColor.WHITE) + protected set + + fun setColor(color: Either) { + currentColor = color + hexInput.text = color.map({ it }, { it.toRGBA() }).toHexStringRGB() + updateWangs(color) + } + + protected fun updateWangs(color: Either) { + box.setColor(color) + + for (wang in wangs) + wang.update(color) + } + + protected open fun onPaletteChoose(color: RGBAColor) { + setColor(Either.left(color)) + onColorChanged(color) + } + + protected open fun onColorBoxChoose(color: HSVColor) { + setColor(Either.right(color)) + onColorChanged(color.toRGBA()) + } + + protected open fun onWangChoose(color: RGBAColor) { + setColor(Either.left(color)) + onColorChanged(color) + } + + protected open fun onHueChoose(hue: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(hue = hue) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + protected open fun onSaturationChoose(saturation: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(saturation = saturation) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + protected open fun onValueChoose(value: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(value = value) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + val topStrip = EditablePanel(screen, this, 0f, 0f, width = width, height = 70f) + val middleStrip = EditablePanel(screen, this) + val palette = ColorPalettePanel(screen, this, callback = { onPaletteChoose(it) }) + + val hexInput = object : TextInputPanel(screen, middleStrip, width = 50f) { + init { + dock = Dock.RIGHT + } + + override fun onFocusChanged() { + if (!isFocusedThis) { + val newColor = RGBAColor.fromHexStringRGB(text) + + if (newColor == null) { + text = currentColor.map({ it }, { it.toRGBA() }).toHexStringRGB() + } else { + setColor(Either.left(newColor)) + onColorChanged(newColor) + } + } + } + + override fun acceptsCharacter(codepoint: Char, mods: Int, index: Int): Boolean { + return RGBAColor.isHexCharacter(codepoint) + } + } + + init { + topStrip.dock = Dock.TOP + middleStrip.dock = Dock.TOP + palette.dock = Dock.FILL + palette.dockTop = 2f + middleStrip.dockTop = 2f + + for (color in paletteColors) { + palette.Button(color) + } + } + + val box = ColorBoxPanel(screen, topStrip, 0f, 0f, width = 70f, height = 70f, callback = { onColorBoxChoose(it) }) + val wangCanvas = EditablePanel(screen, topStrip, width = 80f) + + inner class WangLine(label: String, val wang: AbstractColorWangPanel, val text: (color: Either) -> Component?) { + val canvas = EditablePanel(screen, wangCanvas, height = 10f) + val label = Label(screen, canvas, width = 10f, text = TranslatableComponent("otm.gui.color.short.$label")) + val textLabel = Label(screen, canvas, width = 25f) + + init { + this.wang.parent = canvas + this.wang.width = 45f + + this.label.childrenOrder = 0 + this.wang.childrenOrder = 1 + this.textLabel.childrenOrder = 2 + + this.canvas.dock = Dock.TOP + this.canvas.dockTop = 2f + + this.label.dock = Dock.LEFT + this.label.dockRight = 2f + + this.wang.dock = Dock.LEFT + this.wang.dockRight = 2f + + this.textLabel.dock = Dock.LEFT + this.textLabel.gravity = RenderGravity.CENTER_LEFT + + this.label.tooltips.add(TranslatableComponent("otm.gui.color.full.$label")) + this.label.gravity = RenderGravity.CENTER_RIGHT + } + + fun update(color: Either) { + wang.setColor(color) + + text.invoke(color)?.let { + textLabel.text = it + } + } + } + + val red = WangLine("red", RedColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).red * 255f).roundToInt().toString()) } + val green = WangLine("green", GreenColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).green * 255f).roundToInt().toString()) } + val blue = WangLine("blue", BlueColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).blue * 255f).roundToInt().toString()) } + + val hue = WangLine("hue", HueWangPanel(screen, wangCanvas, hueCallback = { onHueChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent(it.hue.roundToInt().toString()) } } + val saturation = WangLine("saturation", SaturationWangPanel(screen, wangCanvas, saturationCallback = { onSaturationChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent((it.saturation * 100f).roundToInt().toString() + "%") } } + val value = WangLine("value", ValueWangPanel(screen, wangCanvas, valueCallback = { onValueChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent((it.value * 100f).roundToInt().toString() + "%") } } + + val wangs = listOf(red, green, blue, hue, saturation, value) + + init { + box.dock = Dock.LEFT + wangCanvas.dock = Dock.RIGHT + red.canvas.dockTop = 0f + + setColor(Either.right(HSVColor.WHITE)) + } + + companion object { + val paletteColors = listOf( + RGBAColor.rgb(0x9b59b6L), // Amethyst + RGBAColor.rgb(0x010101L), // Black + RGBAColor.rgb(0x0000ffL), // Blue + RGBAColor.rgb(0x8B4513L), // Brown + RGBAColor.rgb(0xe67e22L), // Carrot + RGBAColor.rgb(0xD2691EL), // Chocolate + RGBAColor.rgb(0xecf0f1L), // Clouds + RGBAColor.rgb(0xFF7F50L), // Coral + RGBAColor.rgb(0xDC143CL), // Crimson + RGBAColor.rgb(0x00FFFFL), // Cyan + RGBAColor.rgb(0x008B8BL), // DarkCyan + RGBAColor.rgb(0xBDB76BL), // DarkGold + RGBAColor.rgb(0xB8860BL), // DarkGoldenRod + RGBAColor.rgb(0x006400L), // DarkGreen + RGBAColor.rgb(0x16a085L), // DarkGreen + RGBAColor.rgb(0x8B008BL), // DarkMagenta + RGBAColor.rgb(0x556B2FL), // DarkOlive + RGBAColor.rgb(0xFF8C00L), // DarkOrange + RGBAColor.rgb(0x8B0000L), // DarkRed + RGBAColor.rgb(0xE9967AL), // DarkSalmon + RGBAColor.rgb(0x9400D3L), // DarkViolet + RGBAColor.rgb(0xFF1493L), // DeepPink + RGBAColor.rgb(0x00BFFFL), // DeepSkyBlue + RGBAColor.rgb(0x2ecc71L), // Emerald + RGBAColor.rgb(0xB22222L), // FireBrick + RGBAColor.rgb(0x228B22L), // ForestGreen + RGBAColor.rgb(0xFF00FFL), // Fuchsia + RGBAColor.rgb(0xDCDCDCL), // Gainsboro + RGBAColor.rgb(0xFFD700L), // Gold + RGBAColor.rgb(0xDAA520L), // GoldenRod + RGBAColor.rgb(0x00B000L), // Green + RGBAColor.rgb(0x808080L), // Grey + RGBAColor.rgb(0xFF69B4L), // HotPink + RGBAColor.rgb(0x4B0082L), // Indigo + RGBAColor.rgb(0xF0E68CL), // Khaki + RGBAColor.rgb(0xE6E6FAL), // Lavender + RGBAColor.rgb(0xFF9FF7L), // LavenderRose + RGBAColor.rgb(0xD3D3D3L), // LightGrey + RGBAColor.rgb(0x87CEFAL), // LightSkyBlue + RGBAColor.rgb(0xFFFFE0L), // LightYellow + RGBAColor.rgb(0x00ff00L), // Lime + RGBAColor.rgb(0x32CD32L), // LimeGreen + RGBAColor.rgb(0xBA55D3L), // MediumOrchid + RGBAColor.rgb(0x9370DBL), // MediumPurple + RGBAColor.rgb(0x7B68EEL), // MediumSlateBlue + RGBAColor.rgb(0x808000L), // Olive + RGBAColor.rgb(0x6B8E23L), // OliveGreen + RGBAColor.rgb(0xFFA500L), // Orange + RGBAColor.rgb(0xDA70D6L), // Orchid + RGBAColor.rgb(0xDB7093L), // PaleVioletRed + RGBAColor.rgb(0xCD853FL), // Peru + RGBAColor.rgb(0xFFC0CBL), // Pink + RGBAColor.rgb(0xB0E0E6L), // PowderBlue + RGBAColor.rgb(0xd35400L), // Pumpkin + RGBAColor.rgb(0x800080L), // Purple + RGBAColor.rgb(0xff0000L), // Red + RGBAColor.rgb(0x4169E1L), // RoyalBlue + RGBAColor.rgb(0xFA8072L), // Salmon + RGBAColor.rgb(0xF4A460L), // SandyBrown + RGBAColor.rgb(0x2E8B57L), // SeaGreen + RGBAColor.rgb(0xA0522DL), // Sienna + RGBAColor.rgb(0xbdc3c7L), // Silver + RGBAColor.rgb(0x87CEEBL), // SkyBlue + RGBAColor.rgb(0x708090L), // SlateGrey + RGBAColor.rgb(0x4682B4L), // SteelBlue + RGBAColor.rgb(0xf1c40fL), // SunFlower + RGBAColor.rgb(0x008080L), // Teal + RGBAColor.rgb(0xD8BFD8L), // Thistle + RGBAColor.rgb(0xEE82EEL), // Violet + ) + + fun > frame(screen: S, callback: Consumer, color: RGBAColor = RGBAColor.RED, title: Component? = TranslatableComponent("otm.gui.color_picker")): FramePanel { + return FramePanel.padded(screen, 164f, 118f, title).also { + ColorPickerPanel(screen, it, 0f, 0f, callback = callback).also { + it.dock = Dock.FILL + it.setColor(Either.left(color)) + } + + screen.addPanel(it) + it.toScreenCenter() + it.behaveAsWindow() + it.popup() + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DynamicLabel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DynamicLabel.kt new file mode 100644 index 000000000..d11bf08a7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DynamicLabel.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import java.util.function.Supplier + +open class DynamicLabel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 100f, + height: Float = 10f, + val textSupplier: Supplier, + shadow: Boolean = false +) : Label(screen, parent, x, y, width, height, textSupplier.get(), shadow) { + init { + text = textSupplier.get() + } + + override fun tickInner() { + super.tickInner() + text = textSupplier.get() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index 9b5f3fac1..96719477e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -1,8 +1,9 @@ package ru.dbotthepony.mc.otm.client.screen.panels import com.google.common.collect.ImmutableList +import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.objects.ReferenceArraySet import net.minecraft.client.gui.Font import net.minecraft.client.gui.components.events.GuiEventListener @@ -13,22 +14,26 @@ import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.SystemTime +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.moveMousePosScaled import ru.dbotthepony.mc.otm.client.render.currentScissorRect import ru.dbotthepony.mc.otm.client.render.popScissorRect import ru.dbotthepony.mc.otm.client.render.pushScissorRect import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel +import ru.dbotthepony.mc.otm.core.collect.concatIterators +import ru.dbotthepony.mc.otm.core.collect.flatMap import java.util.* import kotlin.collections.ArrayList import kotlin.math.roundToInt -@JvmRecord data class ScreenPos(val x: Float, val y: Float) -@JvmRecord -data class DockProperty @JvmOverloads constructor(val left: Float = 0f, val top: Float = 0f, val right: Float = 0f, val bottom: Float = 0f) { +data class DockProperty(val left: Float = 0f, val top: Float = 0f, val right: Float = 0f, val bottom: Float = 0f) { val isEmpty get() = left == 0f && right == 0f && top == 0f && bottom == 0f + val horizontal get() = left + right + val vertical get() = top + bottom companion object { val EMPTY = DockProperty() @@ -67,7 +72,72 @@ open class EditablePanel @JvmOverloads constructor( width: Float = 10f, height: Float = 10f, -) : GuiEventListener { +) : Comparable> { + // layout engine does not support navigation using keyboard + val listener: GuiEventListener = object : GuiEventListener { + override fun changeFocus(p_265728_: Boolean): Boolean { + if (p_265728_) { + if (isVisible()) + requestFocus() + } else { + killFocus() + } + + return true + } + + override fun mouseMoved(p_94758_: Double, p_94759_: Double) { + this@EditablePanel.mouseMoved(p_94758_, p_94759_) + } + + override fun mouseClicked(p_94737_: Double, p_94738_: Double, p_94739_: Int): Boolean { + return this@EditablePanel.mouseClicked(p_94737_, p_94738_, p_94739_) + } + + override fun mouseReleased(p_94753_: Double, p_94754_: Double, p_94755_: Int): Boolean { + return this@EditablePanel.mouseReleased(p_94753_, p_94754_, p_94755_) + } + + override fun mouseDragged(p_94740_: Double, p_94741_: Double, p_94742_: Int, p_94743_: Double, p_94744_: Double): Boolean { + return this@EditablePanel.mouseDragged(p_94740_, p_94741_, p_94742_, p_94743_, p_94744_) + } + + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollY: Double): Boolean { + return this@EditablePanel.mouseScrolled(mouseX, mouseY, scrollY) + } + + override fun keyPressed(p_94745_: Int, p_94746_: Int, p_94747_: Int): Boolean { + return this@EditablePanel.keyPressed(p_94745_, p_94746_, p_94747_) + } + + override fun keyReleased(p_94750_: Int, p_94751_: Int, p_94752_: Int): Boolean { + return this@EditablePanel.keyReleased(p_94750_, p_94751_, p_94752_) + } + + override fun charTyped(p_94732_: Char, p_94733_: Int): Boolean { + return this@EditablePanel.charTyped(p_94732_, p_94733_) + } + + override fun isMouseOver(p_94748_: Double, p_94749_: Double): Boolean { + return this@EditablePanel.isMouseOver(p_94748_, p_94749_) + } + } + + /** + * Bigger values means lesser priority while docking, rendering and processing inputs. + */ + var childrenOrder = 0 + set(value) { + if (field != value) { + field = value + parent?.sortChildren() + } + } + + override fun compareTo(other: EditablePanel<*>): Int { + return childrenOrder.compareTo(other.childrenOrder) + } + var parent: EditablePanel<*>? = null set(value) { if (field === value) @@ -153,26 +223,39 @@ open class EditablePanel @JvmOverloads constructor( } /** - * Width to be utilized in docking code. + * Width of this panel as considered by docking code, updated inside [performLayout] * - * Can only differ from [width] if [dockResize] is not [DockResizeMode.ALL] and not [DockResizeMode.WIDTH] + * If panel is not docked ([dock] is [Dock.NONE]), this value equals to [width] plus [DockProperty.horizontal] of [dockMargin] + * + * If panel is docked, this value will be equal to [width], if [dockResize] allows resizing [width], or could-have value, if [dockResize] doesn't */ var dockedWidth: Float = 0f private set /** - * Height to be utilized in docking code. + * Height of this panel as considered by docking code, updated inside [performLayout] * - * Can only differ from [height] if [dockResize] is not [DockResizeMode.ALL] and not [DockResizeMode.HEIGHT] + * If panel is not docked ([dock] is [Dock.NONE]), this value equals to [height] plus [DockProperty.vertical] of [dockMargin] + * + * If panel is docked, this value will be equal to [height], if [dockResize] allows resizing [height], or could-have value, if [dockResize] doesn't */ var dockedHeight: Float = 0f private set private val childrenInternal = ArrayList>() private val visibleChildrenInternal = ArrayList>() + val children: List> = Collections.unmodifiableList(childrenInternal) val visibleChildren: List> = Collections.unmodifiableList(visibleChildrenInternal) + val allPanels: Iterator> get() { + return concatIterators(listOf(this).iterator(), childrenInternal.iterator().flatMap { it.allPanels }) + } + + val allVisiblePanels: Iterator> get() { + return concatIterators(listOf(this).iterator(), visibleChildrenInternal.iterator().flatMap { it.allVisiblePanels }) + } + var layoutInvalidated = true private set var boundsInvalidated = true @@ -217,6 +300,7 @@ open class EditablePanel @JvmOverloads constructor( // Обрезать отрисовку потомков? var scissor = false + var scissorLock = false var scissorPadding = DockProperty.EMPTY var dock = Dock.NONE set(value) { @@ -256,6 +340,10 @@ open class EditablePanel @JvmOverloads constructor( } } + fun dockHorizontal(value: Float) { + dockMargin = dockMargin.copy(left = value, right = value) + } + var dockTop: Float get() = dockMargin.top set(value) { @@ -280,6 +368,38 @@ open class EditablePanel @JvmOverloads constructor( } } + var dockPaddingLeft: Float + get() = dockPadding.left + set(value) { + if (value != dockPadding.left) { + dockPadding = dockPadding.copy(left = value) + } + } + + var dockPaddingRight: Float + get() = dockPadding.right + set(value) { + if (value != dockPadding.right) { + dockPadding = dockPadding.copy(right = value) + } + } + + var dockPaddingTop: Float + get() = dockPadding.top + set(value) { + if (value != dockPadding.top) { + dockPadding = dockPadding.copy(top = value) + } + } + + var dockPaddingBottom: Float + get() = dockPadding.bottom + set(value) { + if (value != dockPadding.bottom) { + dockPadding = dockPadding.copy(bottom = value) + } + } + var acceptMouseInput = true var acceptKeyboardInput = true var grabMouseInput = false @@ -302,23 +422,7 @@ open class EditablePanel @JvmOverloads constructor( return true } - var tooltip: Component? = null - set(value) { - if (value != null) { - tooltipList = null - } - - field = value - } - - var tooltipList: List? = null - set(value) { - if (value != null) { - tooltip = null - } - - field = value - } + val tooltips = ArrayList() var blockingWindow: EditablePanel<*>? = null get() { @@ -326,6 +430,7 @@ open class EditablePanel @JvmOverloads constructor( return field } + field = null return null } @@ -333,25 +438,33 @@ open class EditablePanel @JvmOverloads constructor( private set var isFlashing: Boolean - get() = (flashingSince?.seconds ?: Long.MAX_VALUE) <= 1 + get() { + val flashingSince = flashingSince ?: return false + + if (flashingSince.millis >= 1200L) { + this.flashingSince = null + return false + } + + return true + } set(value) { if (value) { - flashingSince = SystemTime() + if (screen is MatteryScreen<*>) { + screen.popup(findAbsoluteRoot() as EditablePanel>) + } + + if (!flashAnyBlocker(true)) { + flashingSince = SystemTime() + findAbsoluteRoot().requestFocus() + } } else { flashingSince = null } } - fun flash() { - isFlashing = true - - if (parent == null) { - popup() - } - } - val isFlashFrame: Boolean - get() = flashingSince != null && flashingSince!!.millis % 400L <= 200L + get() = isFlashing && flashingSince!!.millis % 400L <= 200L val isFlashFrameRecursive: Boolean get() { @@ -377,7 +490,7 @@ open class EditablePanel @JvmOverloads constructor( if (blockingWindow != null) { if (doFlash) { - blockingWindow.flash() + blockingWindow.isFlashing = true } return true @@ -393,23 +506,7 @@ open class EditablePanel @JvmOverloads constructor( } protected fun flashAnyBlocker(doFlash: Boolean = true): Boolean { - var blockingWindow = blockingWindow - var parent = parent - - while (blockingWindow == null && parent != null) { - blockingWindow = parent.blockingWindow - parent = parent.parent - } - - if (blockingWindow == null) { - return flashAnyBlockerInner(doFlash) - } - - if (doFlash) { - blockingWindow.flash() - } - - return true + return findAbsoluteRoot().flashAnyBlockerInner(doFlash) } @JvmOverloads @@ -451,6 +548,19 @@ open class EditablePanel @JvmOverloads constructor( return x < 0 || y < 0 || x + width > parent.width || y + height > parent.height } + private var once = false + set(value) { + if (value && field != value) { + if (updateVisibility) { + updateVisible() + } + + field = value + } + } + + private var updateVisibility = false + fun calculateAbsoluteRectangle(): Rect2f { if (scissor) { return Rect2f(absoluteX, absoluteY, width, height) @@ -517,7 +627,7 @@ open class EditablePanel @JvmOverloads constructor( } for (children in visibleChildrenInternal) { - if (children.isOutsideOfParent || !isObstructing) { + if ((children.isOutsideOfParent || !isObstructing) && children.isVisible()) { result.addAll(children.calculateAbsoluteObstructingRectangles()) } } @@ -531,6 +641,9 @@ open class EditablePanel @JvmOverloads constructor( var absoluteY = 0f private set + /** + * If this exact panel is hovered. Panel is not considered hovered if any of its children are hovered + */ var isHovered = false private set(value) { if (value == field) { @@ -543,6 +656,13 @@ open class EditablePanel @JvmOverloads constructor( onHoverUpdate(old, value) } + /** + * If this panel or any of its children are hovered + */ + val isEverHovered: Boolean get() { + return isHovered || visibleChildrenInternal.any { it.isEverHovered } + } + fun unsetHovered() { isHovered = false @@ -562,21 +682,23 @@ open class EditablePanel @JvmOverloads constructor( protected open fun onHovered() {} protected open fun onUnHovered() {} - var isFocused = false - set(value) { + var hasFocusedChildren = false + private set(value) { if (field != value) { - val old = field field = value - - onFocusChanged(old, value) - findAbsoluteRoot().updateFocus() + onHierarchicalFocusChanged() } } - var autoKillFocus = false - protected set + var isFocusedThis = false + private set(value) { + if (field != value) { + field = value + onFocusChanged() + } + } - private var focusedAsParent = false + var autoRequestFocus = true val font: Font get() = if (screen is MatteryScreen<*>) screen.font else minecraft.font @@ -592,9 +714,17 @@ open class EditablePanel @JvmOverloads constructor( this.parent = parent } + var childrenSortingInvalidated = false + private set + + fun invalidateChildrenSorting() { + childrenSortingInvalidated = true + } + private fun onParent(child: EditablePanel<*>) { if (childrenInternal.contains(child)) throw IllegalStateException("Already containing $child") childrenInternal.add(child) + invalidateChildrenSorting() if (child.visible) { visibleChildrenInternal.add(child) @@ -602,7 +732,10 @@ open class EditablePanel @JvmOverloads constructor( } if (child.isVisible() != isVisible()) { - updateVisible() + if (once) + updateVisible() + else + updateVisibility = true } } @@ -610,6 +743,7 @@ open class EditablePanel @JvmOverloads constructor( val indexOf = childrenInternal.indexOf(child) if (indexOf == -1) throw IllegalStateException("Already not containing $child") childrenInternal.removeAt(indexOf) + invalidateChildrenSorting() if (child.visible) { visibleChildrenInternal.remove(child) @@ -621,11 +755,29 @@ open class EditablePanel @JvmOverloads constructor( } } - protected open fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {} - protected open fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + private fun sortChildren() { + childrenSortingInvalidated = false + childrenInternal.sort() + + val old = ArrayList(visibleChildrenInternal) + visibleChildrenInternal.sort() + + if (old != visibleChildrenInternal) { + invalidateLayout() + } + } + + protected open fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + protected open fun preRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + protected open fun innerRenderPost(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + protected open fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { return false } + protected open fun shouldRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + return isEverHovered || isGrabbingMouseInput() + } + fun isVisible(): Boolean { return visible && visibleAsChildren && !isRemoved } @@ -645,6 +797,9 @@ open class EditablePanel @JvmOverloads constructor( } fun performLayoutIfNeeded() { + if (childrenSortingInvalidated) + sortChildren() + if (layoutInvalidated) { performLayout() } else if (boundsInvalidated) { @@ -666,28 +821,21 @@ open class EditablePanel @JvmOverloads constructor( } } - fun render(poseStack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + fun render(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + once = true + if (!isVisible()) { return } + val poseStack = graphics.pose + performLayoutIfNeeded() - val parent = this.parent - if (parent == null) { + preRender(graphics, mouseX, mouseY, partialTick) absoluteX = x absoluteY = y - } else { - if ((x < 0f || y < 0f || x + width > parent.width || y + height > parent.height) && parent.parent == null) { - // no op - we updated ourselves in tickHover - } else { - isHovered = parent.isHovered && - mouseX >= absoluteX && - mouseX < absoluteX + width && - mouseY >= absoluteY && - mouseY < absoluteY + height - } } val scissor = this.scissor @@ -702,6 +850,7 @@ open class EditablePanel @JvmOverloads constructor( ((absoluteY + top) * scale).toInt(), ((width - right - left) * scale).toInt() + 1, ((height - bottom - top) * scale).toInt() + 1, + scissorLock ) } @@ -709,20 +858,23 @@ open class EditablePanel @JvmOverloads constructor( if (currentScissorRect == null || currentScissorRect.crossScaled(absoluteX, absoluteY, width, height)) { // do not render if we are getting cut off by screen scissor - clearDepth(poseStack, absoluteX, absoluteY, width, height) + clearDepth(graphics, absoluteX, absoluteY, width, height) poseStack.pushPose() poseStack.translate(absoluteX.toDouble(), absoluteY.toDouble(), 10.0) RenderSystem.setShaderColor(1f, 1f, 1f, if (isFlashFrameRecursive) 0.5f else 1f) - innerRender(poseStack, mouseX, mouseY, partialTick) + + innerRender(graphics, mouseX, mouseY, partialTick) + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) poseStack.popPose() } - for (child in visibleChildrenInternal) { + for (child in visibleChildrenInternal.asReversed()) { + child.preRender(graphics, mouseX, mouseY, partialTick) child.absoluteX = absoluteX + child.x + xOffset child.absoluteY = absoluteY + child.y + yOffset - child.render(poseStack, mouseX, mouseY, partialTick) + child.render(graphics, mouseX, mouseY, partialTick) } if (scissor) { @@ -730,35 +882,60 @@ open class EditablePanel @JvmOverloads constructor( } } - open fun tickHover(mouseX: Float, mouseY: Float): Boolean { - if (isRemoved) { - return false + fun updateAbsolutePosition() { + val parent = parent + + if (parent == null) { + absoluteX = x + absoluteY = y + } else { + absoluteX = parent.absoluteX + x + parent.xOffset + absoluteY = parent.absoluteY + y + parent.yOffset } - if ((childrenRectHeight > height || childrenRectWidth > width || childrenRectX < 0 || childrenRectY < 0) && parent == null) { + for (child in visibleChildrenInternal) { + child.updateAbsolutePosition() + } + } + + fun tickHover(mouseX: Float, mouseY: Float): Boolean { + if (isRemoved) + return false + + val potentiallyHovered: Boolean + + if (parent == null) { + updateAbsolutePosition() + potentiallyHovered = + mouseX in absoluteX + childrenRectX.coerceAtMost(0f) ..< absoluteX + childrenRectWidth.coerceAtLeast(width) && + mouseY in absoluteY + childrenRectY.coerceAtMost(0f) ..< absoluteY + childrenRectHeight.coerceAtLeast(height) + } else { + potentiallyHovered = + mouseX in absoluteX ..< absoluteX + width && + mouseY in absoluteY ..< absoluteY + height + } + + if (potentiallyHovered) { var hit = false - for (child in childrenInternal) { - if (child.tickHover(mouseX, mouseY)) { + for (child in visibleChildrenInternal) { + if (hit) { + child.unsetHovered() + } else if (child.tickHover(mouseX, mouseY)) { hit = true } } - isHovered = mouseX >= absoluteX && - mouseX <= absoluteX + width && - mouseY >= absoluteY && - mouseY <= absoluteY + height + isHovered = + !hit && + mouseX in absoluteX ..< absoluteX + width && + mouseY in absoluteY ..< absoluteY + height return hit || isHovered + } else { + unsetHovered() + return false } - - isHovered = - mouseX >= absoluteX && - mouseX <= absoluteX + width && - mouseY >= absoluteY && - mouseY <= absoluteY + height - - return isHovered } fun findSlot(mouseX: Float, mouseY: Float): Pair { @@ -801,27 +978,31 @@ open class EditablePanel @JvmOverloads constructor( return false to null } - fun findItemStack(mouseX: Float, mouseY: Float, ignoreMouseInputLock: Boolean = false): Pair { + fun findItemStack(mouseX: Float, mouseY: Float, ignoreMouseInputLock: Boolean = false): Pair?, ItemStack> { if (!isVisible()) { - return false to ItemStack.EMPTY + return null to ItemStack.EMPTY } if (!acceptMouseInput && !ignoreMouseInputLock) { - return (mouseX >= absoluteX && - mouseX <= absoluteX + width && - mouseY >= absoluteY && - mouseY <= absoluteY + height) to ItemStack.EMPTY + if (mouseX >= absoluteX && + mouseX <= absoluteX + width && + mouseY >= absoluteY && + mouseY <= absoluteY + height) { + return this to ItemStack.EMPTY + } else { + return null to ItemStack.EMPTY + } } if (grabMouseInput && this is IItemStackPanel) { - return true to this.itemStack + return this to this.itemStack } for (child in visibleChildrenInternal) { - val (status, itemStack) = child.findItemStack(mouseX, mouseY, ignoreMouseInputLock) + val status = child.findItemStack(mouseX, mouseY, ignoreMouseInputLock) - if (status) { - return true to itemStack + if (status.first != null) { + return status } } @@ -832,16 +1013,16 @@ open class EditablePanel @JvmOverloads constructor( mouseY <= absoluteY + height ) { if (this is IItemStackPanel) { - return true to this.itemStack + return this to this.itemStack } - return true to ItemStack.EMPTY + return this to ItemStack.EMPTY } - return false to ItemStack.EMPTY + return null to ItemStack.EMPTY } - fun renderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + fun renderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { if (!isVisible()) { return false } @@ -853,36 +1034,22 @@ open class EditablePanel @JvmOverloads constructor( } for (child in visibleChildrenInternal) { - if (child.renderTooltips(stack, mouseX, mouseY, partialTick)) { + if (child.renderTooltips(graphics, mouseX, mouseY, partialTick)) { return true } } - if (innerRenderTooltips(stack, mouseX, mouseY, partialTick)) { - return true - } - - if (isHovered) { - val tooltip = tooltip - val tooltipList = tooltipList - - if (tooltip != null) { - screen.renderComponentTooltip( - stack, - listOf(tooltip), - mouseX.toInt(), - mouseY.toInt(), - font - ) - + if (shouldRenderTooltips(graphics, mouseX, mouseY, partialTick)) { + if (innerRenderTooltips(graphics, mouseX, mouseY, partialTick)) { return true - } else if (tooltipList != null) { - screen.renderComponentTooltip( - stack, - tooltipList, + } + + if (tooltips.isNotEmpty()) { + graphics.renderComponentTooltip( + font, + tooltips, mouseX.toInt(), - mouseY.toInt(), - font + mouseY.toInt() ) return true @@ -951,7 +1118,11 @@ open class EditablePanel @JvmOverloads constructor( for (child in visibleChildrenInternal) { when (child.dock) { - Dock.NONE -> {} + Dock.NONE -> { + child.dockedWidth = child.width + child.dockMargin.horizontal + child.dockedHeight = child.height + child.dockMargin.vertical + } + Dock.FILL -> {} Dock.LEFT -> { @@ -1040,6 +1211,21 @@ open class EditablePanel @JvmOverloads constructor( } updateBounds() + + for (child in visibleChildrenInternal) { + child.parentLayoutPerformed() + } + } + + private var repositioningCallback: Runnable? = null + + fun customDock(callback: Runnable?) { + if (callback != null) dock = Dock.NONE + repositioningCallback = callback + } + + open fun parentLayoutPerformed() { + repositioningCallback?.run() } fun updateBounds() { @@ -1068,8 +1254,8 @@ open class EditablePanel @JvmOverloads constructor( * * Performs layout if required */ - open fun sizeToContents() { - if (layoutInvalidated) { + open fun sizeToContents(performLayout: Boolean = true) { + if (layoutInvalidated && performLayout) { performLayout() } @@ -1085,10 +1271,6 @@ open class EditablePanel @JvmOverloads constructor( this.height = height } - companion object { - private val LOGGER = LogManager.getLogger()!! - } - protected open fun visibilityChanges(new: Boolean, old: Boolean) { } @@ -1109,25 +1291,48 @@ open class EditablePanel @JvmOverloads constructor( } } - private fun killFocusInternal() { + private fun killGrabMouseInput() { for (child in childrenInternal) { - child.killFocusInternal() + child.killGrabMouseInput() } - if (isFocused) { - isFocused = false - } + grabMouseInput = false } - fun killFocus() { - if (isEverFocused()) { - killFocusInternal() - findAbsoluteRoot().updateFocus() + private fun killFocusThis(children: Boolean): Boolean { + var status = false + + if (isFocusedThis) { + isFocusedThis = false + status = true } + + if (children && hasFocusedChildren) { + hasFocusedChildren = false + status = true + } + + if (grabMouseInput) { + grabMouseInput = false + status = true + } + + return status + } + + fun killFocus(): Boolean { + var status = false + status = killFocusThis(true) || status + + for (child in childrenInternal) { + status = child.killFocus() || status + } + + return status } fun findHierarchicalFocus(): EditablePanel<*>? { - if (isFocused) { + if (isFocusedThis) { return this } @@ -1146,46 +1351,49 @@ open class EditablePanel @JvmOverloads constructor( return findHierarchicalFocus() != null } - protected open fun onHierarchicalFocusChanged(new: Boolean, old: Boolean) { + /** + * Called when [hasFocusedChildren] changes + */ + protected open fun onHierarchicalFocusChanged() {} - } - - protected open fun onFocusChanged(new: Boolean, old: Boolean) { - - } - - private fun updateFocus() { - val old = focusedAsParent - focusedAsParent = hasHierarchicalFocus() - - if (focusedAsParent != old) { - onHierarchicalFocusChanged(focusedAsParent, old) - } - - for (child in childrenInternal) { - child.updateFocus() - } - } + /** + * Called when [isFocusedThis] changes + */ + protected open fun onFocusChanged() {} fun isEverFocused(): Boolean { - return isFocused || focusedAsParent + return isFocusedThis || hasFocusedChildren } fun requestFocus() { - if (isFocused) { + if (isFocusedThis || !isVisible()) { return } - if (focusedAsParent) { - var child = findHierarchicalFocus() + var filter: EditablePanel<*>? = null + var parent: EditablePanel<*>? = this - while (child != null) { - child.isFocused = false - child = findHierarchicalFocus() + while (parent != null) { + if (parent.parent == null) { + parent.popup(false) + } + + for (child in parent.childrenInternal) { + if (child !== filter) { + child.killFocus() + } + } + + filter = parent + parent = parent.parent + + if (parent != null) { + parent.killFocusThis(false) + parent.hasFocusedChildren = true } } - isFocused = true + isFocusedThis = true } protected open fun xPosUpdated(new: Float, old: Float) {} @@ -1251,9 +1459,8 @@ open class EditablePanel @JvmOverloads constructor( return list } - operator fun get(index: Int): EditablePanel<*>? { - if (index < 0 || index >= childrenInternal.size) return null - return childrenInternal[index] + fun getChildren(index: Int): EditablePanel<*>? { + return childrenInternal.getOrNull(index) } fun isGrabbingMouseInput(): Boolean { @@ -1270,7 +1477,7 @@ open class EditablePanel @JvmOverloads constructor( return false } - override fun mouseMoved(x: Double, y: Double) { + open fun mouseMoved(x: Double, y: Double) { } @@ -1291,47 +1498,32 @@ open class EditablePanel @JvmOverloads constructor( y in pos.y .. pos2.y } - fun killFocusForEverythingExcept(except: EditablePanel<*>) { - for (child in childrenInternal) { - if (child !== except) { - child.killFocusForEverythingExceptInner() - } - } - } - - fun killFocusForEverythingExceptInner() { - for (child in childrenInternal) { - child.killFocusForEverythingExceptInner() - } - - if (autoKillFocus) { - killFocus() - } - } - - final override fun mouseClicked(x: Double, y: Double, button: Int): Boolean { + fun mouseClicked(x: Double, y: Double, button: Int): Boolean { if (!isVisible() || !acceptMouseInput) return false + if (flashAnyBlocker()) return true - if (flashAnyBlocker()) { - return true + if (grabMouseInput) { + if (mouseClickedInner(x, y, button)) { + if (autoRequestFocus) requestFocus() + return true + } + + return false } - if (grabMouseInput) return mouseClickedInner(x, y, button) - for (child in visibleChildrenInternal) { if (child.isGrabbingMouseInput() && child.mouseClickedChecked(x, y, button)) { - killFocusForEverythingExcept(child) return true } } for (child in visibleChildrenInternal) { if (child.mouseClickedChecked(x, y, button)) { - killFocusForEverythingExcept(child) return true } } + if (autoRequestFocus) requestFocus() return mouseClickedInner(x, y, button) } @@ -1339,19 +1531,15 @@ open class EditablePanel @JvmOverloads constructor( if (!isVisible() || !acceptMouseInput) return false if (isGrabbingMouseInput() || withinBounds(x, y)) { - if (acceptMouseInput && parent == null) popup() return mouseClicked(x, y, button) } else if (withinExtendedBounds(x, y)) { + popup(false) + for (child in visibleChildrenInternal) { if (child.mouseClickedChecked(x, y, button)) { - killFocusForEverythingExcept(child) return true } } - - if (autoKillFocus) killFocus() - } else if (autoKillFocus) { - killFocus() } return false @@ -1361,13 +1549,9 @@ open class EditablePanel @JvmOverloads constructor( return true } - final override fun mouseReleased(x: Double, y: Double, button: Int): Boolean { + fun mouseReleased(x: Double, y: Double, button: Int): Boolean { if (!isVisible() || !acceptMouseInput) return false - - if (flashAnyBlocker(false)) { - return true - } - + if (flashAnyBlocker(false)) return true if (grabMouseInput) return mouseReleasedInner(x, y, button) for (child in visibleChildrenInternal) { @@ -1405,13 +1589,9 @@ open class EditablePanel @JvmOverloads constructor( return true } - final override fun mouseDragged(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + fun mouseDragged(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { if (!isVisible() || !acceptMouseInput) return false - - if (flashAnyBlocker(false)) { - return true - } - + if (flashAnyBlocker(false)) return true if (grabMouseInput) return mouseDraggedInner(x, y, button, xDelta, yDelta) for (child in visibleChildrenInternal) { @@ -1445,12 +1625,11 @@ open class EditablePanel @JvmOverloads constructor( return false } - protected open fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return true + return false } - final override fun mouseScrolled(x: Double, y: Double, scroll: Double): Boolean { + fun mouseScrolled(x: Double, y: Double, scroll: Double): Boolean { if (!isVisible() || !acceptMouseInput) return false if (flashAnyBlocker(true)) { @@ -1494,15 +1673,11 @@ open class EditablePanel @JvmOverloads constructor( return false } - final override fun keyPressed(key: Int, scancode: Int, mods: Int): Boolean { + fun keyPressed(key: Int, scancode: Int, mods: Int): Boolean { if (!isVisible() || !acceptKeyboardInput) return false - if (!focusedAsParent) return false - - if (flashAnyBlocker(true)) { - return true - } - - if (isFocused) return keyPressedInternal(key, scancode, mods) + if (!isEverFocused()) return false + if (flashAnyBlocker(key !in ignoreFlashKeys)) return true + if (isFocusedThis) return keyPressedInternal(key, scancode, mods) for (child in visibleChildrenInternal) { if (child.keyPressed(key, scancode, mods)) { @@ -1510,6 +1685,7 @@ open class EditablePanel @JvmOverloads constructor( } } + if (hasFocusedChildren) return keyPressedInternal(key, scancode, mods) return false } @@ -1517,15 +1693,11 @@ open class EditablePanel @JvmOverloads constructor( return false } - final override fun keyReleased(key: Int, scancode: Int, mods: Int): Boolean { + fun keyReleased(key: Int, scancode: Int, mods: Int): Boolean { if (!isVisible() || !acceptKeyboardInput) return false - if (!focusedAsParent) return false - - if (flashAnyBlocker(false)) { - return true - } - - if (isFocused) return keyReleasedInternal(key, scancode, mods) + if (!isEverFocused()) return false + if (flashAnyBlocker(false)) return true + if (isFocusedThis) return keyReleasedInternal(key, scancode, mods) for (child in visibleChildrenInternal) { if (child.keyReleased(key, scancode, mods)) { @@ -1533,6 +1705,7 @@ open class EditablePanel @JvmOverloads constructor( } } + if (hasFocusedChildren) return keyReleasedInternal(key, scancode, mods) return false } @@ -1540,15 +1713,11 @@ open class EditablePanel @JvmOverloads constructor( return false } - final override fun charTyped(codepoint: Char, mods: Int): Boolean { + fun charTyped(codepoint: Char, mods: Int): Boolean { if (!isVisible() || !acceptKeyboardInput) return false - if (!focusedAsParent) return false - - if (flashAnyBlocker()) { - return true - } - - if (isFocused) return charTypedInternal(codepoint, mods) + if (!isEverFocused()) return false + if (flashAnyBlocker(false)) return true + if (isFocusedThis) return charTypedInternal(codepoint, mods) for (child in visibleChildrenInternal) { if (child.charTyped(codepoint, mods)) { @@ -1556,14 +1725,11 @@ open class EditablePanel @JvmOverloads constructor( } } + if (hasFocusedChildren) return charTypedInternal(codepoint, mods) return true } - override fun changeFocus(state: Boolean): Boolean { - return if (!isVisible()) false else super.changeFocus(state) - } - - final override fun isMouseOver(x: Double, y: Double): Boolean { // called to check whenever we are hovering at this + fun isMouseOver(x: Double, y: Double): Boolean { // called to check whenever we are hovering at this if (!isVisible() || !acceptMouseInput) return false if (isGrabbingMouseInput()) return true @@ -1571,10 +1737,16 @@ open class EditablePanel @JvmOverloads constructor( return x >= pos.x && x <= pos.x + width && y >= pos.y && y + height <= pos.y } - protected var tick = 0 + var tickCount = 0 + private set - open fun tick() { - tick++ + protected open fun tickInner() {} + + fun tick() { + once = true + tickCount++ + + tickInner() for (child in Array(visibleChildrenInternal.size) { visibleChildrenInternal[it] }) { child.tick() @@ -1587,6 +1759,14 @@ open class EditablePanel @JvmOverloads constructor( protected open fun beforeRemoved() {} protected open fun onRemoved() {} + internal fun markRemoved() { + if (!isRemoved) { + isRemoved = true + onRemoved() + children.forEach { it.markRemoved() } + } + } + fun remove() { if (isRemoved) { return @@ -1609,26 +1789,18 @@ open class EditablePanel @JvmOverloads constructor( isRemoved = true } - fun popup() { + fun popup(focus: Boolean = true, doFlash: Boolean = true) { if (isRemoved) { return } if (screen is MatteryScreen<*>) { - screen.popup(this as EditablePanel>) + screen.popup(findAbsoluteRoot() as EditablePanel>) } - } - fun asGrid(): FlexGridPanel<*> { - val grid = FlexGridPanel(screen, parent, x, y, width, height) - parent = grid - grid.dock = dock - dock = Dock.NONE - grid.dockPadding = dockPadding - grid.dockMargin = dockMargin - setDockPadding(0f, 0f, 0f, 0f) - setDockMargin(0f, 0f, 0f, 0f) - return grid + if (focus && !flashAnyBlocker(doFlash)) { + findAbsoluteRoot().requestFocus() + } } fun mouseToCenter() { @@ -1638,7 +1810,7 @@ open class EditablePanel @JvmOverloads constructor( /** * See [ru.dbotthepony.mc.otm.client.render.clearDepth] */ - fun clearDepth(stack: PoseStack, x: Float = 0f, y: Float = 0f, width: Float = this.width, height: Float = this.height) = ru.dbotthepony.mc.otm.client.render.clearDepth(stack, x, y, width, height) + fun clearDepth(graphics: MGUIGraphics, x: Float = 0f, y: Float = 0f, width: Float = this.width, height: Float = this.height) = ru.dbotthepony.mc.otm.client.render.clearDepth(graphics, x, y, width, height) fun queryUser(title: Component, text: Component, onConfirm: Runnable, onCancel: Runnable? = null): QueryUserPanel { return QueryUserPanel(screen, title, listOf(text), onConfirm, onCancel).also { blockingWindow = it } @@ -1647,4 +1819,21 @@ open class EditablePanel @JvmOverloads constructor( fun queryUser(title: Component, text: Collection, onConfirm: Runnable, onCancel: Runnable? = null): QueryUserPanel { return QueryUserPanel(screen, title, text, onConfirm, onCancel).also { blockingWindow = it } } + + companion object { + private val LOGGER = LogManager.getLogger()!! + + private val ignoreFlashKeys = IntAVLTreeSet() + + init { + ignoreFlashKeys.add(InputConstants.KEY_LEFT) + ignoreFlashKeys.add(InputConstants.KEY_RIGHT) + ignoreFlashKeys.add(InputConstants.KEY_UP) + ignoreFlashKeys.add(InputConstants.KEY_DOWN) + ignoreFlashKeys.add(InputConstants.KEY_RCONTROL) + ignoreFlashKeys.add(InputConstants.KEY_LCONTROL) + ignoreFlashKeys.add(InputConstants.KEY_RSHIFT) + ignoreFlashKeys.add(InputConstants.KEY_LSHIFT) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt index 97924708c..d682f52f7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt @@ -1,25 +1,23 @@ package ru.dbotthepony.mc.otm.client.screen.panels import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import net.minecraft.client.gui.screens.Screen -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen +import net.minecraft.resources.ResourceLocation import net.minecraft.world.effect.MobEffect import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.entity.LivingEntity +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.TextAlign +import ru.dbotthepony.mc.otm.client.render.RenderGravity import ru.dbotthepony.mc.otm.client.render.determineTooltipPosition -import ru.dbotthepony.mc.otm.client.render.drawAligned -import ru.dbotthepony.mc.otm.client.render.drawScaledAligned -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.render -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.client.render.sprites.sprite +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.formatTickDuration -import ru.dbotthepony.mc.otm.core.integerDivisionDown +import ru.dbotthepony.mc.otm.core.util.formatTickDuration +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown import java.util.stream.Collectors open class EffectListPanel @JvmOverloads constructor( @@ -99,8 +97,8 @@ open class EffectListPanel @JvmOverloads constructor( return true } - override fun tick() { - super.tick() + override fun tickInner() { + super.tickInner() if (!isStillValid) { effectButtons.remove(effectType) @@ -108,29 +106,29 @@ open class EffectListPanel @JvmOverloads constructor( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - clearDepth(stack) - SQUARE_THIN.render(stack, width = width, height = height) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + clearDepth(graphics) + SQUARE_THIN.render(graphics, width = width, height = height) RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f) - minecraft.mobEffectTextures.get(effect.effect).render(stack, x = 3f, y = 3f, width = width - 6f, height = height - 6f) + graphics.renderSprite(minecraft.mobEffectTextures.get(effect.effect), x = 3f, y = 3f, width = width - 6f, height = height - 6f) RenderSystem.setShaderColor(1f, 1f, 1f, 1f) - font.drawScaledAligned(stack, formatTickDuration(effect.duration), 0.75f, TextAlign.CENTER_CENTER, width / 2f + 0.5f, height / 2f, RGBAColor.WHITE) + graphics.draw(formatTickDuration(effect.duration), width / 2f, height / 2f, scale = 0.75f, gravity = RenderGravity.CENTER_CENTER, color = RGBAColor.WHITE) } - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { if (isHovered) { val (x, y) = determineTooltipPosition(mouseX + 4f, mouseY - 12f, BAR.width, BAR.height) - BAR.clearDepth(stack, x, y) - BAR.render(stack, x, y) + BAR.clearDepth(graphics, x, y) + BAR.render(graphics, x, y) val renderWidth = 18f val renderHeight = 18f - minecraft.mobEffectTextures.get(effect.effect).render( - stack, + graphics.renderSprite( + minecraft.mobEffectTextures.get(effect.effect), x = x + 8f, y = y + BAR.height / 2f - renderHeight / 2f, width = renderWidth, @@ -144,8 +142,8 @@ open class EffectListPanel @JvmOverloads constructor( name.append(" ${effect.amplifier + 1}") } - font.drawAligned(stack, name, TextAlign.TOP_LEFT, x + renderWidth + 12f, y + 7f, RGBAColor.WHITE) - font.drawAligned(stack, formatTickDuration(effect.duration, true), TextAlign.TOP_LEFT, x + renderWidth + 12f, y + 7f + font.lineHeight + 2f, 8355711) + graphics.draw(name, x + renderWidth + 12f, y + 7f, gravity = RenderGravity.TOP_LEFT, color = RGBAColor.WHITE) + graphics.draw(formatTickDuration(effect.duration, true), x + renderWidth + 12f, y + 7f + font.lineHeight + 2f, gravity = RenderGravity.TOP_LEFT, color = RGBAColor.LIGHT_GRAY) } return isHovered @@ -162,14 +160,14 @@ open class EffectListPanel @JvmOverloads constructor( canvas.yOffset = newScroll * -26f } - override fun tick() { + override fun tickInner() { for (effect in entity.activeEffects) { effectButtons.computeIfAbsent(effect.effect, Object2ObjectFunction { EffectSquare(effect) }) } - super.tick() + super.tickInner() } val canvas = object : EditablePanel(screen, this@EffectListPanel, 0f, 0f, width, height) { @@ -266,9 +264,7 @@ open class EffectListPanel @JvmOverloads constructor( } companion object { - val BAR = AbstractContainerScreen.INVENTORY_LOCATION.element(0f, 166f, 120f, 32f) - val SQUARE = AbstractContainerScreen.INVENTORY_LOCATION.element(0f, 198f, 32f, 32f) - val SQUARE_THIN = AbstractContainerScreen.INVENTORY_LOCATION.element(141f, 166f, 24f, 24f) - val SQUARE_THIN_HIGHLIGHT = AbstractContainerScreen.INVENTORY_LOCATION.element(165f, 166f, 24f, 24f) + val BAR = ResourceLocation("textures/gui/sprites/container/inventory/effect_background_large.png").sprite(0f, 0f, 120f, 32f, 120f, 32f) + val SQUARE_THIN = ResourceLocation("textures/gui/sprites/hud/effect_background.png").sprite(0f, 0f, 24f, 24f, 24f, 24f) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt index d38d25977..f8d193e91 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EntityRendererPanel.kt @@ -1,23 +1,34 @@ package ru.dbotthepony.mc.otm.client.screen.panels -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.client.gui.screens.inventory.InventoryScreen import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.Widgets8 -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.screen.ExoPackInventoryScreen -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.SmallBooleanRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.render.sprites.sprite +import ru.dbotthepony.mc.otm.client.screen.ExopackInventoryScreen +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallBooleanRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallRectangleButtonPanel import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleRenderButton import ru.dbotthepony.mc.otm.compat.cos.isCosmeticArmorLoaded +import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.asGetterOnly -import ru.dbotthepony.mc.otm.network.DisplayExosuitPacket -import ru.dbotthepony.mc.otm.network.HideExosuitPacket +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.network.DisableExopackGlowPacket +import ru.dbotthepony.mc.otm.network.DisplayExopackPacket +import ru.dbotthepony.mc.otm.network.EnableExopackGlowPacket +import ru.dbotthepony.mc.otm.network.HideExopackPacket import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel +import ru.dbotthepony.mc.otm.network.ResetExopackColorPacket +import ru.dbotthepony.mc.otm.network.SetExopackColorPacket +import java.util.function.IntConsumer private fun calculateScale(width: Float, height: Float): Int { val aspectRatio = width / height @@ -33,6 +44,76 @@ private fun calculateScale(width: Float, height: Float): Int { } } +private fun createExopackAppearanceWindow(screen: MatteryScreen<*>, matteryPlayer: MatteryPlayerCapability): FramePanel<*> { + val frame = FramePanel.padded(screen, width = 200f, height = 90f, title = TranslatableComponent("otm.gui.exopack.customization")) + + screen.addPanel(frame) + + frame.toScreenCenter() + frame.behaveAsWindow() + frame.requestFocus() + + CheckBoxLabelPanel( + screen, + frame, + text = TranslatableComponent("otm.gui.exopack.toggle_visibility"), + isChecked = GetterSetter.of( + { + matteryPlayer.isExopackVisible + }, + { + if (it) { + MatteryPlayerNetworkChannel.sendToServer(DisplayExopackPacket) + } else { + MatteryPlayerNetworkChannel.sendToServer(HideExopackPacket) + } + } + ) + ).also { + it.dock = Dock.TOP + it.dockTop = 2f + } + + CheckBoxLabelPanel( + screen, + frame, + text = TranslatableComponent("otm.gui.exopack.toggle_glow"), + isChecked = GetterSetter.of( + { + matteryPlayer.exopackGlows + }, + { + if (it) { + MatteryPlayerNetworkChannel.sendToServer(EnableExopackGlowPacket) + } else { + MatteryPlayerNetworkChannel.sendToServer(DisableExopackGlowPacket) + } + } + ) + ).also { + it.dock = Dock.TOP + it.dockTop = 2f + } + + ButtonPanel(screen, frame, label = TranslatableComponent("otm.gui.exopack.change_color"), onPress = IntConsumer { + frame.blockingWindow = ColorPickerPanel.frame( + screen, + callback = { MatteryPlayerNetworkChannel.sendToServer(SetExopackColorPacket(it)) }, + color = matteryPlayer.exopackColor ?: RGBAColor.WHITE, + title = TranslatableComponent("otm.gui.exopack.change_color")) + }).also { + it.dock = Dock.TOP + it.dockTop = 2f + } + + ButtonPanel(screen, frame, label = TranslatableComponent("otm.gui.exopack.change_color2"), onPress = IntConsumer { MatteryPlayerNetworkChannel.sendToServer(ResetExopackColorPacket) }).also { + it.dock = Dock.TOP + it.dockTop = 2f + } + + return frame +} + class EntityRendererPanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>?, @@ -51,6 +132,7 @@ class EntityRendererPanel @JvmOverloads constructor( init { dock = Dock.FILL scissor = true + scissorLock = true } private val cosButton: EditablePanel? @@ -66,21 +148,15 @@ class EntityRendererPanel @JvmOverloads constructor( if (entity is Player) { val matteryPlayer = entity.matteryPlayer - if (matteryPlayer != null && matteryPlayer.hasExoPack) { - exosuitButton = SmallBooleanRectangleButtonPanel(screen, this, this.width - 2f - SmallBooleanRectangleButtonPanel.SIZE, 2f, - prop = matteryPlayer::displayExoPack.asGetterOnly(), - skinElementActive = Widgets8.EXOSUIT_SHOWN, - skinElementInactive = Widgets8.EXOSUIT_HIDDEN, - onChange = { - if (it) { - MatteryPlayerNetworkChannel.sendToServer(DisplayExosuitPacket) - } else { - MatteryPlayerNetworkChannel.sendToServer(HideExosuitPacket) - } + if (matteryPlayer != null && matteryPlayer.hasExopack && screen is MatteryScreen<*>) { + exosuitButton = SmallRectangleButtonPanel(screen, this, this.width - 2f - SmallBooleanRectangleButtonPanel.SIZE, 2f, + skinElement = Widgets8.EXOPACK_SHOWN, + onPress = { + blockingWindow = createExopackAppearanceWindow(screen, matteryPlayer) } ) - exosuitButton.tooltip = TranslatableComponent("otm.gui.exosuit.toggle_visibility") + exosuitButton.tooltips.add(TranslatableComponent("otm.gui.exopack.customize")) } else { exosuitButton = null } @@ -89,7 +165,7 @@ class EntityRendererPanel @JvmOverloads constructor( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (entity.isDeadOrDying) { return } @@ -122,11 +198,11 @@ class EntityRendererPanel @JvmOverloads constructor( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - ExoPackInventoryScreen.ENTITY_RECTANGLE.render(stack) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + ExopackInventoryScreen.ENTITY_RECTANGLE.render(graphics) } companion object { - val ENTITY_RECTANGLE = AbstractContainerScreen.INVENTORY_LOCATION.element(25f, 7f, 51f, 72f) + val ENTITY_RECTANGLE = AbstractContainerScreen.INVENTORY_LOCATION.sprite(25f, 7f, 51f, 72f) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt deleted file mode 100644 index ede980d40..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FilterSlotPanel.kt +++ /dev/null @@ -1,34 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import net.minecraft.client.gui.screens.Screen -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot -import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel - -open class FilterSlotPanel> @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - val slot: ItemFilterNetworkSlot, - x: Float = 0f, - y: Float = 0f, - width: Float = SIZE, - height: Float = SIZE -) : AbstractSlotPanel(screen, parent, x, y, width, height) { - override val itemStack: ItemStack get() { - return slot.get() - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (!screen.menu.ply.isSpectator) { - if (screen.menu.carried.isEmpty) { - MenuNetworkChannel.sendToServer(ItemFilterSlotPacket(slot.networkID, ItemStack.EMPTY)) - } else { - MenuNetworkChannel.sendToServer(ItemFilterSlotPacket(slot.networkID, screen.menu.carried)) - } - } - - return true - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt index 25f731094..a2b34e409 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt @@ -1,15 +1,19 @@ package ru.dbotthepony.mc.otm.client.screen.panels +import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.narration.NarratableEntry import net.minecraft.client.gui.narration.NarratableEntry.NarrationPriority import net.minecraft.client.gui.narration.NarrationElementOutput import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import org.lwjgl.opengl.GL30 -import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.* +import ru.dbotthepony.mc.otm.client.render.sprites.StretchingRectangleElement +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor open class FramePanel( screen: S, @@ -18,18 +22,37 @@ open class FramePanel( y: Float, width: Float, height: Float, - protected var title: Component? + var title: Component? = null, + var titleColor: RGBAColor = RGBAColor.HEADING_TEXT ) : EditablePanel(screen, parent, x, y, width, height), NarratableEntry { - constructor(screen: S, width: Float, height: Float, title: Component?) : this(screen, null, 0f, 0f, width, height, title) + constructor(screen: S, width: Float, height: Float, title: Component? = null) : this(screen, null, 0f, 0f, width, height, title) open inner class Tab( var onOpen: Runnable? = null, var onClose: Runnable? = null, - var activeIcon: AbstractSkinElement? = null, - var inactiveIcon: AbstractSkinElement? = null, - ) : EditablePanel(this@FramePanel.screen, this@FramePanel, 0f, 0f, 28f, 28f) { + var activeIcon: IGUIRenderable? = null, + var inactiveIcon: IGUIRenderable? = activeIcon, + ) : AbstractButtonPanel(this@FramePanel.screen, this@FramePanel, 0f, 0f, 26f, 28f) { + constructor(panels: List>, activeIcon: IGUIRenderable? = null, inactiveIcon: IGUIRenderable? = activeIcon) : this(activeIcon = activeIcon, inactiveIcon = inactiveIcon) { + onOpen = Runnable { + for (panel in panels) { + panel.visible = true + } + } + + onClose = Runnable { + for (panel in panels) { + panel.visible = false + } + } + + if (!isActive) { + onClose!!.run() + } + } + var isActive = tabs.isEmpty() - var initial = false + protected set init { tabs.add(this) @@ -49,44 +72,59 @@ open class FramePanel( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - val width: Float - val height: Float + protected fun tabIndex(): Int { + return tabs.indexOf(this) + } + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (isActive) { - width = TAB_WIDTH_ACTIVE - height = TAB_HEIGHT_ACTIVE - RECTANGLE.middle.render(stack, 2f, 2f, width - 4, height - 2) - } else { - width = TAB_WIDTH - height = TAB_HEIGHT - TAB_BACKGROUND.render(stack, 2f, 2f, width - 4, height - 2) - } - - RECTANGLE.top.renderWidth(stack, 3f, 0f, width - 6) - RECTANGLE.left.renderHeight(stack, 0f, 3f, (height - if (isActive) if (initial) 2 else 4 else 3)) - - RECTANGLE.right.renderHeight(stack, width - RECTANGLE.right.width, 3f, (height - if (isActive) 4 else 3)) - - RECTANGLE.topLeft.render(stack, 0f, 0f) - RECTANGLE.topRight.render(stack, width - RECTANGLE.topRight.width, 0f) - - if (isActive) { - if (!initial) { - TAB_LEFT_CONNECTION.render(stack, 0f, height - TAB_LEFT_CONNECTION.height - 1) + if (tabIndex() == 0) { + if (isDisabled) { + TAB_ACTIVE0_DISABLED.render(graphics, x = -2f) + } else if (isPressed) { + TAB_ACTIVE0_PRESSED.render(graphics, x = -2f) + } else if (isHovered) { + TAB_ACTIVE0_HOVERED.render(graphics, x = -2f) + } else { + TAB_ACTIVE0_IDLE.render(graphics, x = -2f) + } + } else { + if (isDisabled) { + TAB_ACTIVE_DISABLED.render(graphics, x = -2f) + } else if (isPressed) { + TAB_ACTIVE_PRESSED.render(graphics, x = -2f) + } else if (isHovered) { + TAB_ACTIVE_HOVERED.render(graphics, x = -2f) + } else { + TAB_ACTIVE_IDLE.render(graphics, x = -2f) + } } - TAB_RIGHT_CONNECTION.render( - stack, - width - TAB_RIGHT_CONNECTION.width, - height - TAB_LEFT_CONNECTION.height - 1 - ) - - val skinActive = activeIcon ?: return - skinActive.render(stack, TAB_WIDTH_ACTIVE / 2f - skinActive.width / 2f, TAB_HEIGHT_ACTIVE / 2f - skinActive.height / 2f) + activeIcon?.render(graphics, x = width / 2f, y = height / 2f + 1f, gravity = RenderGravity.CENTER_CENTER) } else { - val skinInactive = inactiveIcon ?: return - skinInactive.render(stack, width / 2f - skinInactive.width / 2f, TAB_HEIGHT_ACTIVE / 2f - skinInactive.height / 2f) + if (tabIndex() == 0) { + if (isDisabled) { + TAB_INACTIVE0_DISABLED.render(graphics) + } else if (isPressed) { + TAB_INACTIVE0_PRESSED.render(graphics) + } else if (isHovered) { + TAB_INACTIVE0_HOVERED.render(graphics) + } else { + TAB_INACTIVE0_IDLE.render(graphics) + } + } else { + if (isDisabled) { + TAB_INACTIVE_DISABLED.render(graphics) + } else if (isPressed) { + TAB_INACTIVE_PRESSED.render(graphics) + } else if (isHovered) { + TAB_INACTIVE_HOVERED.render(graphics) + } else { + TAB_INACTIVE_IDLE.render(graphics) + } + } + + inactiveIcon?.render(graphics, x = width / 2f, y = height / 2f + 1f, gravity = RenderGravity.CENTER_CENTER) } } @@ -98,25 +136,105 @@ open class FramePanel( onClose?.run() } - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (isActive) { - return true - } - - for (tab in tabs) { - if (tab !== this) { - if (tab.isActive) { - tab.onClose() - tab.isActive = false + fun activate() { + if (!isActive) { + for (tab in tabs) { + if (tab !== this) { + if (tab.isActive) { + tab.onClose() + tab.isActive = false + } } } + + isActive = true + onOpen() } + } - isActive = true - onOpen() - playGuiClickSound() + override fun onClick(mouseButton: Int) { + activate() + } - return true + override fun onRemoved() { + super.onRemoved() + tabs.remove(this) + } + } + + inner class CloseButton : AbstractButtonPanel(screen, this@FramePanel, this@FramePanel.width - CLOSE_BUTTON.width, 0f, CLOSE_BUTTON.width, CLOSE_BUTTON.height) { + override fun onClick(mouseButton: Int) { + close() + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (isPressed) { + CLOSE_BUTTON_PRESSED.render(graphics, 0f, 0f, width, height) + } else if (isHovered) { + CLOSE_BUTTON_HOVERED.render(graphics, 0f, 0f, width, height) + } else { + CLOSE_BUTTON.render(graphics, 0f, 0f, width, height) + } + } + + override fun onRemoved() { + super.onRemoved() + + if (closeButton == this) { + closeButton = null + } + } + } + + inner class HelpButton : EditablePanel(screen, this@FramePanel, this@FramePanel.width - HELP_BUTTON.width - CLOSE_BUTTON.width, 0f, HELP_BUTTON.width, HELP_BUTTON.height) { + // var isActive = false + + /*override fun onClick(mouseButton: Int) { + isActive = !isActive + }*/ + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + /*if (isPressed) { + HELP_BUTTON_PRESSED.render(graphics, 0f, 0f, width, height) + } else*/ if (isHovered /*|| isActive */) { + HELP_BUTTON_HOVERED.render(graphics, 0f, 0f, width, height) + } else { + HELP_BUTTON.render(graphics, 0f, 0f, width, height) + } + } + + /*override val needsPostRender: Boolean + get() = isActive + + override fun innerRenderPost(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + val tooltip = tooltip + val tooltipList = tooltipList + + val list = tooltipList ?: tooltip?.let { listOf(it) } ?: listOf() + + if (list.isNotEmpty()) { + graphics.renderComponentTooltip( + font, list, + mouseX.toInt(), mouseY.toInt() + ) + } + }*/ + + /*override fun shouldRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + return super.shouldRenderTooltips(graphics, mouseX, mouseY, partialTick) && !isActive + }*/ + + fun addSlotFiltersHelp(): HelpButton { + tooltips.add(TranslatableComponent("otm.gui.help.slot_filters")) + return this + } + + override fun onRemoved() { + super.onRemoved() + + if (helpButton == this) { + helpButton = null + } } } @@ -124,15 +242,55 @@ open class FramePanel( override fun performLayout() { for ((i, tab) in tabs.withIndex()) { - tab.setPos(i * TAB_WIDTH, -TAB_HEIGHT) - tab.initial = i == 0 + tab.setPos(i * 28f, -28f) } + closeButton?.setPos(width - CLOSE_BUTTON.width, 0f) + helpButton?.setPos(width - HELP_BUTTON.width - CLOSE_BUTTON.width, 0f) + super.performLayout() } protected var dragging = false + var closeButton: CloseButton? = null + protected set + var helpButton: HelpButton? = null + protected set + + var closeOnEscape = false + + fun makeCloseButton(): CloseButton { + if (closeButton == null) + closeButton = CloseButton() + + return closeButton!! + } + + fun makeHelpButton(): HelpButton { + if (helpButton == null) + helpButton = HelpButton() + + return helpButton!! + } + + fun makeHelpButton(tooltips: Collection): HelpButton { + if (helpButton == null) { + helpButton = HelpButton() + helpButton!!.tooltips.addAll(tooltips) + } + + return helpButton!! + } + + /** + * Adds close button and makes panel [close] when user presses ESC with this panel focused + */ + fun behaveAsWindow() { + closeOnEscape = true + makeCloseButton() + } + init { setDockPadding(PADDING, if (title != null) PADDING_TOP else PADDING, PADDING, PADDING) } @@ -182,13 +340,37 @@ open class FramePanel( } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - RECTANGLE.render(stack, width = width, height = height) + protected val closeCallbacks = ArrayList() + + fun onClose(callback: Runnable) { + closeCallbacks.add(callback) + } + + /** + * Usually just calls [remove] + */ + open fun close() { + if (isRemoved) return + remove() + closeCallbacks.forEach { it.run() } + } + + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { + if (key == InputConstants.KEY_ESCAPE && closeOnEscape) { + close() + return true + } + + return super.keyPressedInternal(key, scancode, mods) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + RECTANGLE.render(graphics, width = width, height = height) // title val title = title ?: return RenderSystem.depthFunc(GL30.GL_ALWAYS) - font.draw(stack, title, 8f, 5f, 4210752) + graphics.draw(title, 8f, 5f, color = titleColor) RenderSystem.depthFunc(GL30.GL_ALWAYS) } @@ -210,7 +392,7 @@ open class FramePanel( y: Float, width: Float, height: Float, - title: Component? + title: Component? = null, ) = FramePanel(screen, parent, x, y, width + PADDING * 2, height + PADDING_TOP + PADDING, title) fun padded( @@ -218,39 +400,58 @@ open class FramePanel( parent: EditablePanel<*>?, width: Float, height: Float, - title: Component? + title: Component? = null, ) = FramePanel(screen, parent, 0f, 0f, width + PADDING * 2, height + PADDING_TOP + PADDING, title) fun padded( screen: S, width: Float, height: Float, - title: Component? + title: Component? = null, ) = FramePanel(screen, null, 0f, 0f, width + PADDING * 2, height + PADDING_TOP + PADDING, title) const val PADDING = 8f const val PADDING_TOP = 14f val RECTANGLE = StretchingRectangleElement( - topLeft = WidgetLocation.MISC.subElement(x = 18f, y = 0f, width = 6f, height = 6f), - topRight = WidgetLocation.MISC.subElement(x = 24f, y = 0f, width = 6f, height = 6f), - left = WidgetLocation.MISC.subElement(x = 18f, y = 4f, width = 3f, height = 5f), - right = WidgetLocation.MISC.subElement(x = 25f, y = 3f, width = 5f, height = 5f), - top = WidgetLocation.MISC.subElement(x = 22f, y = 0f, width = 5f, height = 3f), - bottomLeft = WidgetLocation.MISC.subElement(x = 18f, y = 6f, width = 6f, height = 6f), - bottomRight = WidgetLocation.MISC.subElement(x = 24f, y = 6f, width = 6f, height = 6f), - bottom = WidgetLocation.MISC.subElement(x = 21f, y = 9f, width = 5f, height = 3f), - middle = WidgetLocation.MISC.subElement(x = 30f, y = 12f, width = 6f, height = 6f), + topLeft = WidgetLocation.MISC.sprite(x = 18f, y = 0f, width = 6f, height = 6f), + topRight = WidgetLocation.MISC.sprite(x = 24f, y = 0f, width = 6f, height = 6f), + left = WidgetLocation.MISC.sprite(x = 18f, y = 4f, width = 3f, height = 5f), + right = WidgetLocation.MISC.sprite(x = 25f, y = 3f, width = 5f, height = 5f), + top = WidgetLocation.MISC.sprite(x = 22f, y = 0f, width = 5f, height = 3f), + bottomLeft = WidgetLocation.MISC.sprite(x = 18f, y = 6f, width = 6f, height = 6f), + bottomRight = WidgetLocation.MISC.sprite(x = 24f, y = 6f, width = 6f, height = 6f), + bottom = WidgetLocation.MISC.sprite(x = 21f, y = 9f, width = 5f, height = 3f), + middle = WidgetLocation.MISC.sprite(x = 30f, y = 12f, width = 6f, height = 6f), padding = DockProperty(-3f, -3f, -3f, -3f) ) - val TAB_RIGHT_CONNECTION = WidgetLocation.MISC.subElement(x = 30f, y = 0f, width = 3f, height = 5f) - val TAB_LEFT_CONNECTION = WidgetLocation.MISC.subElement(x = 33f, y = 0f, width = 3f, height = 5f) - val TAB_BACKGROUND = WidgetLocation.MISC.subElement(x = 30f, y = 6f, width = 6f, height = 6f) + val TAB_INACTIVE0_IDLE = WidgetLocation.TABS.sprite(x = 26f * 0f, width = 26f, height = 32f) + val TAB_INACTIVE0_HOVERED = WidgetLocation.TABS.sprite(x = 26f * 1f, width = 26f, height = 32f) + val TAB_INACTIVE0_PRESSED = WidgetLocation.TABS.sprite(x = 26f * 2f, width = 26f, height = 32f) + val TAB_INACTIVE0_DISABLED = WidgetLocation.TABS.sprite(x = 26f * 3f, width = 26f, height = 32f) - const val TAB_HEIGHT = 28f - const val TAB_WIDTH = 28f - const val TAB_HEIGHT_ACTIVE = 32f - const val TAB_WIDTH_ACTIVE = 28f + val TAB_INACTIVE_IDLE = WidgetLocation.TABS.sprite(x = 26f * 0f, y = 32f, width = 26f, height = 32f) + val TAB_INACTIVE_HOVERED = WidgetLocation.TABS.sprite(x = 26f * 1f, y = 32f, width = 26f, height = 32f) + val TAB_INACTIVE_PRESSED = WidgetLocation.TABS.sprite(x = 26f * 2f, y = 32f, width = 26f, height = 32f) + val TAB_INACTIVE_DISABLED = WidgetLocation.TABS.sprite(x = 26f * 3f, y = 32f, width = 26f, height = 32f) + + val TAB_ACTIVE0_IDLE = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 0f, width = 30f, height = 32f) + val TAB_ACTIVE0_HOVERED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 1f, width = 30f, height = 32f) + val TAB_ACTIVE0_PRESSED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 2f, width = 30f, height = 32f) + val TAB_ACTIVE0_DISABLED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 3f, width = 30f, height = 32f) + + val TAB_ACTIVE_IDLE = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 0f, y = 32f, width = 30f, height = 32f) + val TAB_ACTIVE_HOVERED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 1f, y = 32f, width = 30f, height = 32f) + val TAB_ACTIVE_PRESSED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 2f, y = 32f, width = 30f, height = 32f) + val TAB_ACTIVE_DISABLED = WidgetLocation.TABS.sprite(x = 26f * 4f + 30f * 3f, y = 32f, width = 30f, height = 32f) + + val CLOSE_BUTTON = WidgetLocation.MISC.sprite(x = 51f, y = 0f, width = 13f, height = 14f) + val CLOSE_BUTTON_HOVERED = WidgetLocation.MISC.sprite(x = 51f, y = 14f, width = 13f, height = 14f) + val CLOSE_BUTTON_PRESSED = WidgetLocation.MISC.sprite(x = 51f, y = 28f, width = 13f, height = 14f) + + val HELP_BUTTON = WidgetLocation.MISC.sprite(x = 40f, y = 0f, width = 11f, height = 14f) + val HELP_BUTTON_HOVERED = WidgetLocation.MISC.sprite(x = 40f, y = 14f, width = 11f, height = 14f) + val HELP_BUTTON_PRESSED = WidgetLocation.MISC.sprite(x = 40f, y = 28f, width = 11f, height = 14f) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt deleted file mode 100644 index 301fc1189..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/GridPanel.kt +++ /dev/null @@ -1,49 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen - -open class GridPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float, - height: Float, - protected var columns: Int, - protected var rows: Int -) : EditablePanel(screen, parent, x, y, width, height) { - override fun performLayout() { - var currentX = 0f - var currentY = 0f - var lineY = 0f - var index = 0 - - for (row in 0 until rows) { - var column = 0 - - while (column < columns) { - val child = get(index) ?: break - - if (child.visible && child.dock === Dock.NONE) { - lineY = lineY.coerceAtLeast(child.height + child.dockMargin.top + child.dockMargin.bottom) - child.setPos(currentX + child.dockMargin.left, currentY + child.dockMargin.top) - currentX += child.width + child.dockMargin.left + child.dockMargin.right - } else { - column-- - } - - index++ - column++ - } - - currentY += lineY - currentX = 0f - lineY = 0f - - get(index) ?: break - } - - super.performLayout() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt index bd701138d..39dfe7e35 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Label.kt @@ -1,12 +1,11 @@ package ru.dbotthepony.mc.otm.client.screen.panels -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.render.clearDepth +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.RGBAColor -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.math.RGBAColor open class Label @JvmOverloads constructor( screen: S, @@ -20,8 +19,8 @@ open class Label @JvmOverloads constructor( ) : EditablePanel(screen, parent, x, y, width, height) { constructor(screen: S, parent: EditablePanel<*>?, text: Component, shadow: Boolean = false) : this(screen, parent, x = 0f, text = text, shadow = shadow) - var shadowX = 0.75f - var shadowY = 0.75f + var shadowX = 0.25f + var shadowY = 0.25f var shadowColor = RGBAColor.BLACK init { @@ -29,19 +28,40 @@ open class Label @JvmOverloads constructor( } var color = RGBAColor.SLATE_GRAY + var gravity = RenderGravity.TOP_LEFT - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - clearDepth(stack) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + clearDepth(graphics) if (shadow) { - font.draw(stack, text, shadowX, shadowY, shadowColor.toInt()) + when (gravity) { + RenderGravity.TOP_LEFT -> graphics.draw(text, shadowX, shadowY, color = shadowColor, gravity = gravity) + RenderGravity.TOP_CENTER -> graphics.draw(text, shadowX + width / 2f, shadowY, color = shadowColor, gravity = gravity) + RenderGravity.TOP_RIGHT -> graphics.draw(text, shadowX + width, shadowY, color = shadowColor, gravity = gravity) + RenderGravity.CENTER_LEFT -> graphics.draw(text, shadowX, height / 2f + shadowY, color = shadowColor, gravity = gravity) + RenderGravity.CENTER_CENTER -> graphics.draw(text, shadowX + width / 2f, height / 2f + shadowY, color = shadowColor, gravity = gravity) + RenderGravity.CENTER_RIGHT -> graphics.draw(text, shadowX + width, height / 2f + shadowY, color = shadowColor, gravity = gravity) + RenderGravity.BOTTOM_LEFT -> graphics.draw(text, shadowX, height + shadowY, color = shadowColor, gravity = gravity) + RenderGravity.BOTTOM_CENTER -> graphics.draw(text, shadowX + width / 2f, height + shadowY, color = shadowColor, gravity = gravity) + RenderGravity.BOTTOM_RIGHT -> graphics.draw(text, shadowX + width, height + shadowY, color = shadowColor, gravity = gravity) + } } - font.draw(stack, text, 0f, 0f, color.toInt()) + when (gravity) { + RenderGravity.TOP_LEFT -> graphics.draw(text, 0f, 0f, color = color, gravity = gravity) + RenderGravity.TOP_CENTER -> graphics.draw(text, width / 2f, 0f, color = color, gravity = gravity) + RenderGravity.TOP_RIGHT -> graphics.draw(text, width - (if (shadow) shadowX else 0f), 0f, color = color, gravity = gravity) + RenderGravity.CENTER_LEFT -> graphics.draw(text, 0f, height / 2f, color = color, gravity = gravity) + RenderGravity.CENTER_CENTER -> graphics.draw(text, width / 2f, height / 2f, color = color, gravity = gravity) + RenderGravity.CENTER_RIGHT -> graphics.draw(text, width - (if (shadow) shadowX else 0f), height / 2f, color = color, gravity = gravity) + RenderGravity.BOTTOM_LEFT -> graphics.draw(text, 0f, height, color = color, gravity = gravity) + RenderGravity.BOTTOM_CENTER -> graphics.draw(text, width / 2f, height, color = color, gravity = gravity) + RenderGravity.BOTTOM_RIGHT -> graphics.draw(text, width - (if (shadow) shadowX else 0f), height, color = color, gravity = gravity) + } } - override fun sizeToContents() { - super.sizeToContents() + override fun sizeToContents(performLayout: Boolean) { + super.sizeToContents(performLayout) val w = font.width(text) val h = font.lineHeight + 2 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt new file mode 100644 index 000000000..24bb4c00a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt @@ -0,0 +1,108 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.core.util.formatReadableNumber +import ru.dbotthepony.mc.otm.core.util.formatSiComponent +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView +import ru.dbotthepony.mc.otm.storage.StorageStack + +open class NetworkedItemGridPanel>( + screen: S, + parent: EditablePanel<*>, + val view: NetworkedItemView, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 40f, +) : EditablePanel(screen, parent, x, y, width, height) { + constructor( + screen: S, + parent: EditablePanel<*>, + view: NetworkedItemView, + x: Float = 0f, + y: Float = 0f, + width: Int, + height: Int + ) : this(screen, parent, view, x, y, width * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH, height * AbstractSlotPanel.SIZE) + + private val slots = ArrayList() + + val canvas = object : GridPanel(screen, this@NetworkedItemGridPanel, 0f, 0f, 0f, 0f, 0, 0) { + override fun performLayout() { + super.performLayout() + + val count = columns * rows + + while (slots.size < count) { + slots.add(Slot(slots.size)) + } + + while (slots.size > count) { + slots.removeLast().remove() + } + } + } + + val scrollbar = DiscreteScrollBarPanel( + screen, + this, + { integerDivisionDown(view.itemCount, canvas.columns) }, + { _, _, _ -> } + ) + + inner class Slot(private val i: Int) : AbstractSlotPanel(screen, canvas) { + private val index get() = i + scrollbar.scroll * canvas.columns + + override val itemStack: ItemStack get() { + return view.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollbar.mouseScrolledInner(x, y, scroll) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + view.mouseClick(index, button) + return true + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderSlotBackground(graphics, mouseX, mouseY, partialTick) + val itemStack = view.sortedView.getOrNull(index)?.stack ?: StorageStack.ITEMS.empty + renderRegular(graphics, itemStack.toItemStack(), "") + + if (!itemStack.isEmpty) { + graphics.draw(itemStack.count.formatSiComponent(decimalPlaces = 1), x = width - 1f, y = height - 1f, gravity = RenderGravity.BOTTOM_RIGHT, scale = 0.75f, drawShadow = true) + } + } + + override fun getItemStackTooltip(stack: ItemStack): MutableList { + return super.getItemStackTooltip(stack).also { + it.add(TranslatableComponent("otm.gui.stored_amount", view.sortedView[index].stack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY)) + } + } + } + + init { + canvas.dock = Dock.FILL + scrollbar.dock = Dock.RIGHT + } + + override fun performLayout() { + super.performLayout() + + canvas.rows = (canvas.height / AbstractSlotPanel.SIZE).toInt() + canvas.columns = (canvas.width / AbstractSlotPanel.SIZE).toInt() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NumberInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NumberInputPanel.kt deleted file mode 100644 index 79883527b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NumberInputPanel.kt +++ /dev/null @@ -1,57 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.menu.widget.NumberPlayerInputWidget -import java.math.BigDecimal - -open class NumberInputPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - val inputWidget: NumberPlayerInputWidget, - x: Float = 0f, - y: Float = 0f, - width: Float = 0f, - height: Float = 20f, - defaultText: Component = TextComponent("") -) : EditBoxPanel(screen, parent, x, y, width, height, defaultText) { - protected var nextUpdateFromServer = 0L - protected var inputStr = "" - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (inputWidget.ignoreSpectators && minecraft.player?.isSpectator == true) { - return true - } - - return super.mouseClickedInner(x, y, button) - } - - override fun tick() { - super.tick() - - if (isFocused) { - if (inputWidget.ignoreSpectators && minecraft.player?.isSpectator == true) { - killFocus() - return - } - - nextUpdateFromServer = System.currentTimeMillis() + 2000L - } - - if (nextUpdateFromServer < System.currentTimeMillis()) { - getOrCreateWidget().value = inputWidget.value.toPlainString() - inputStr = getOrCreateWidget().value - } else if (!inputWidget.ignoreSpectators || minecraft.player?.isSpectator != true) { - if (inputStr != getOrCreateWidget().value) { - inputStr = getOrCreateWidget().value - - try { - inputWidget.userInput(BigDecimal(inputStr), minecraft.player) - } catch (_: Throwable) { } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt index 235e271ed..6cff8a82f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Panel2Widget.kt @@ -1,26 +1,31 @@ package ru.dbotthepony.mc.otm.client.screen.panels + import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.components.Widget +import net.minecraft.client.gui.components.Renderable import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +// before 1.19.3 Renderable was Widget class Panel2Widget>( val panel: P -) : GuiEventListener, Widget { +) : GuiEventListener, Renderable { init { require(panel.parent == null) { "Widget wrapped panels can't have a parent ($panel has parent ${panel.parent})" } } - override fun render(poseStack: PoseStack, mouseX: Int, mouseY: Int, partialTick: Float) { + override fun render(stack: PoseStack, mouseX: Int, mouseY: Int, partialTick: Float) { panel.tick() val xFloat = mouseX.toFloat() val yFloat = mouseY.toFloat() + val wrap = MGUIGraphics(stack) + panel.tickHover(xFloat, yFloat) - panel.render(poseStack, xFloat, yFloat, partialTick) - panel.renderTooltips(poseStack, xFloat, yFloat, partialTick) + panel.render(wrap, xFloat, yFloat, partialTick) + panel.renderTooltips(wrap, xFloat, yFloat, partialTick) } override fun mouseMoved(mouseX: Double, mouseY: Double) { @@ -45,8 +50,8 @@ class Panel2Widget>( return panel.mouseDraggedChecked(p_94740_, p_94741_, p_94742_, p_94743_, p_94744_) } - override fun mouseScrolled(p_94734_: Double, p_94735_: Double, p_94736_: Double): Boolean { - return panel.mouseScrolledChecked(p_94734_, p_94735_, p_94736_) + override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollY: Double): Boolean { + return panel.mouseScrolledChecked(mouseX, mouseY, scrollY) } override fun keyPressed(p_94745_: Int, p_94746_: Int, p_94747_: Int): Boolean { @@ -61,11 +66,12 @@ class Panel2Widget>( return panel.charTyped(p_94732_, p_94733_) } - override fun changeFocus(p_94756_: Boolean): Boolean { - return false - } - override fun isMouseOver(p_94748_: Double, p_94749_: Double): Boolean { return panel.isMouseOver(p_94748_, p_94749_) } + + override fun changeFocus(p_265728_: Boolean): Boolean { + // no op + return false + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt new file mode 100644 index 000000000..b0e2781cb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt @@ -0,0 +1,92 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.world.inventory.Slot +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.FoldableSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel +import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.PlayerSlot + +fun > makeCuriosPanel(screen: S, parent: FramePanel, slots: List>, autoAlign: Boolean = false): EditablePanel? { + if (slots.isEmpty()) { + return null + } + + val curiosWidth = if (slots.any { it.cosmetic != null }) AbstractSlotPanel.SIZE * 2f else AbstractSlotPanel.SIZE + val curiosHeight = slots.size.coerceAtMost(4) * AbstractSlotPanel.SIZE + + val curiosRect = + if (slots.size > 4) + ScrollbarBackgroundPanel.padded(screen, parent, 0f, + width = curiosWidth, + height = curiosHeight, alwaysShowScrollbar = true) + else + BackgroundPanel.padded(screen, parent, 0f, + width = curiosWidth, + height = curiosHeight) + + if (autoAlign) { + curiosRect.x = -4f - curiosRect.width + } + + for ((slot, cosmetic) in slots) { + val row = EditablePanel(screen, if (curiosRect is ScrollbarBackgroundPanel) curiosRect.canvas else curiosRect, height = AbstractSlotPanel.SIZE) + row.dock = Dock.TOP + + SlotPanel(screen, row, slot).dock = Dock.RIGHT + + if (cosmetic != null) { + SlotPanel(screen, row, cosmetic).dock = Dock.LEFT + } + } + + return curiosRect +} + +@Suppress("LeakingThis") +open class PlayerEquipmentPanel>( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + val armorSlots: List> +) : EditablePanel(screen, parent, x, y, height = HEIGHT, width = WIDTH) { + val armorSlotsStrip = EditablePanel(screen, this, width = AbstractSlotPanel.SIZE) + val entityPanel = EntityRendererPanel(screen, this, minecraft.player!!) + + init { + entityPanel.dock = Dock.LEFT + armorSlotsStrip.dock = Dock.LEFT + + for ((slot, cosmeticSlot) in armorSlots) { + if (cosmeticSlot == null) { + InventorySlotPanel(screen, armorSlotsStrip, slot).dock = Dock.TOP + } else { + FoldableSlotPanel(screen, armorSlotsStrip, + InventorySlotPanel(screen, null, slot).also { CosmeticToggleButton(screen, it, slot.type) }, + listOf(SlotPanel(screen, null, cosmeticSlot))).dock = Dock.TOP + + } + } + } + + var leftSided: Boolean + get() = armorSlotsStrip.dock == Dock.LEFT + set(value) { + if (value) { + armorSlotsStrip.dock = Dock.LEFT + } else { + armorSlotsStrip.dock = Dock.RIGHT + } + } + + companion object { + val HEIGHT = EntityRendererPanel.ENTITY_RECTANGLE.height + val WIDTH = AbstractSlotPanel.SIZE + EntityRendererPanel.ENTITY_RECTANGLE.width + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt deleted file mode 100644 index ab67f2dca..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt +++ /dev/null @@ -1,35 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels - -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.AtlasSkinElement -import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement - -object ScrollBarConstants { - const val WIDTH = 14f - const val SLIM_WIDTH = 8f - - val BACKGROUND = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar/background"), 14f, 8f) - val BACKGROUND_SLIM = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar_slim/background"), 8f, 8f) - - val TOP = BACKGROUND.subElement(height = 2f) - val BODY = BACKGROUND.subElement(y = 2f, height = 5f) - val BOTTOM = BACKGROUND.subElement(y = 6f, height = 2f) - - val BUTTON = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar/idle"), 12f, 15f) - val BUTTON_HOVER = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar/hovered"), 12f, 15f) - val BUTTON_PRESS = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar/pressed"), 12f, 15f) - val BUTTON_DISABLED = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar/disabled"), 12f, 15f) - - val SLIM_TOP = BACKGROUND_SLIM.subElement(height = 2f) - val SLIM_BODY = BACKGROUND_SLIM.subElement(y = 2f, height = 5f) - val SLIM_BOTTOM = BACKGROUND_SLIM.subElement(y = 6f, height = 2f) - - val SLIM_BUTTON = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar_slim/idle"), 6f, 15f) - val SLIM_BUTTON_HOVER = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar_slim/hovered"), 6f, 15f) - val SLIM_BUTTON_PRESS = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar_slim/pressed"), 6f, 15f) - val SLIM_BUTTON_DISABLED = AtlasSkinElement(ResourceLocation(OverdriveThatMatters.MOD_ID, "widgets/scrollbar_slim/disabled"), 6f, 15f) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt index 8a6be9bb6..9584cc0a0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt @@ -1,6 +1,10 @@ package ru.dbotthepony.mc.otm.client.screen.panels import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.AnalogScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants open class ScrollbarBackgroundPanel( screen: S, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SpritePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SpritePanel.kt new file mode 100644 index 000000000..2e2b610e3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SpritePanel.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder + +class SpritePanel( + screen: S, + parent: EditablePanel<*>? = null, + val sprite: AbstractMatterySprite, + x: Float = 0f, + y: Float = 0f, + width: Float = sprite.width, + height: Float = sprite.height, + var winding: UVWindingOrder = sprite.winding, + var centerSprite: Boolean = false +) : EditablePanel(screen, parent, x, y, width, height) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (centerSprite) { + sprite.render(graphics, width / 2f - sprite.width / 2f, height / 2f - sprite.height / 2f, sprite.width, sprite.height, winding) + } else { + sprite.render(graphics, 0f, 0f, width, height, winding) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Widget2Panel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Widget2Panel.kt index 6b5b6fd1e..d904817e7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Widget2Panel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/Widget2Panel.kt @@ -1,10 +1,10 @@ package ru.dbotthepony.mc.otm.client.screen.panels import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.components.AbstractWidget import net.minecraft.client.gui.screens.Screen import org.lwjgl.opengl.GL11 +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics abstract class Widget2Panel( screen: S, @@ -26,8 +26,8 @@ abstract class Widget2Panel( val isWidgetDisabled: Boolean get() = disableTicks > 0 - override fun tick() { - super.tick() + override fun tickInner() { + super.tickInner() if (disableTicks > 0) { disableTicks-- @@ -89,9 +89,9 @@ abstract class Widget2Panel( return instance } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { RenderSystem.depthFunc(GL11.GL_ALWAYS) - getOrCreateWidget().render(stack, mouseX.toInt(), mouseY.toInt(), partialTick) + getOrCreateWidget().render(graphics.pose, mouseX.toInt(), mouseY.toInt(), partialTick) RenderSystem.depthFunc(GL11.GL_ALWAYS) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractButtonPanel.kt new file mode 100644 index 000000000..e1da380a4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractButtonPanel.kt @@ -0,0 +1,71 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import java.util.function.IntPredicate + +abstract class AbstractButtonPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 10f, + height: Float = 10f, +) : EditablePanel(screen, parent, x, y, width, height), IntPredicate { + var isPressed = false + protected set + + open var isDisabled = false + set(value) { + if (field != value) { + if (!value) { + isPressed = false + grabMouseInput = false + } + + field = value + } + } + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT + } + + abstract fun onClick(mouseButton: Int) + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (isDisabled || isPressed || !test(button)) { + return true + } + + if (!tryToGrabMouseInput()) { + return true + } + + isPressed = true + playGuiClickSound() + return true + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (!isPressed || !test(button)) { + return true + } + + grabMouseInput = false + isPressed = false + + if (isHovered) { + onClick(button) + } + + return true + } + + override fun shouldRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + return super.shouldRenderTooltips(graphics, mouseX, mouseY, partialTick) || isPressed + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractCheckBoxLabelPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractCheckBoxLabelPanel.kt new file mode 100644 index 000000000..3e6384b24 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/AbstractCheckBoxLabelPanel.kt @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Label +import kotlin.math.roundToInt + +abstract class AbstractCheckBoxLabelPanel @JvmOverloads constructor( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = CheckBoxPanel.REGULAR_DIMENSIONS + 120f, + height: Float = CheckBoxPanel.REGULAR_DIMENSIONS +) : EditablePanel(screen, parent, x, y, width, height) { + abstract val checkbox: CheckBoxPanel + abstract val label: Label + + override fun performLayout() { + super.performLayout() + + checkbox.x = dockPadding.left + checkbox.y = dockPadding.top + (height / 2f - checkbox.height / 2f).roundToInt().toFloat() + + label.x = checkbox.x + checkbox.width + 4f + label.y = ((height - dockPadding.top - dockPadding.bottom) / 2f - font.lineHeight / 2f).roundToInt().toFloat() + label.width = width - checkbox.width - dockPadding.right + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt new file mode 100644 index 000000000..2ca9ba092 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt @@ -0,0 +1,95 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback + +abstract class BooleanRectangleButtonPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float, + height: Float, + val prop: GetterSetter, + var iconActive: IGUIRenderable? = null, + var iconInactive: IGUIRenderable? = null, + val onChange: ((newValue: Boolean) -> Unit)? = null, + var tooltipActive: Component? = null, + var tooltipInactive: Component? = null, +) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { + override fun onClick(mouseButton: Int) { + val newValue = !prop.value + prop.value = newValue + onChange?.invoke(newValue) + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered) { + val tooltipActive = tooltipActive + val tooltipInactive = tooltipInactive + + if (tooltipActive != null || tooltipInactive != null || tooltips.isNotEmpty()) { + val tooltips = ArrayList(2 + this.tooltips.size) + + if (this.tooltips.isNotEmpty()) { + tooltips.addAll(this.tooltips) + + if (tooltipActive != null || tooltipInactive != null) + tooltips.add(SPACE) + } + + if (tooltipActive != null) { + if (prop.get()) + tooltips.add(TranslatableComponent("otm.gui.tooltip.enum.active", tooltipActive.copy().withStyle(ChatFormatting.WHITE))) + else + tooltips.add(tooltipActive.copy().withStyle(ChatFormatting.GRAY)) + } + + if (tooltipInactive != null) { + if (prop.get()) + tooltips.add(tooltipInactive.copy().withStyle(ChatFormatting.GRAY)) + else + tooltips.add(TranslatableComponent("otm.gui.tooltip.enum.active", tooltipInactive.copy().withStyle(ChatFormatting.WHITE))) + } + + graphics.renderComponentTooltip(font, tooltips, mouseX.toInt(), mouseY.toInt()) + } + } + + return super.innerRenderTooltips(graphics, mouseX, mouseY, partialTick) + } + + override var isDisabled: Boolean + get() { + if (prop is IPlayerInputWithFeedback) { + return !prop.test(minecraft.player) + } else { + return super.isDisabled + } + } + set(value) { super.isDisabled = value } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.innerRender(graphics, mouseX, mouseY, partialTick) + + if (prop.value) { + iconActive?.render(graphics, width = width, height = height) + } else { + iconInactive?.render(graphics, width = width, height = height) + } + } + + companion object { + private val SPACE = TextComponent("") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/ButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/ButtonPanel.kt new file mode 100644 index 000000000..ec7906a9d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/ButtonPanel.kt @@ -0,0 +1,68 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import java.util.function.IntConsumer + +open class ButtonPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = HEIGHT, + var label: Component, + var onPress: IntConsumer? = null +) : AbstractButtonPanel(screen, parent, x, y, width, height) { + constructor(screen: S, parent: EditablePanel<*>?, label: Component, onPress: IntConsumer? = null) : this(screen, parent, x = 0f, label = label, onPress = onPress) + + constructor( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = HEIGHT, + label: Component, + onPress: Runnable + ) : this(screen, parent, x, y, width, height, label, onPress = IntConsumer { onPress.run() }) + + var textColor = RGBAColor.WHITE + + override fun onClick(mouseButton: Int) { + onPress?.accept(mouseButton) + } + + protected fun renderStretchableBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (isDisabled) { + Widgets18.BUTTON_DISABLED_STRETCHABLE.render(graphics, width = width, height = height) + } else if (isPressed) { + Widgets18.BUTTON_PRESSED_STRETCHABLE.render(graphics, width = width, height = height) + } else if (isHovered) { + Widgets18.BUTTON_HOVERED_STRETCHABLE.render(graphics, width = width, height = height) + } else { + Widgets18.BUTTON_IDLE_STRETCHABLE.render(graphics, width = width, height = height) + } + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderStretchableBackground(graphics, mouseX, mouseY, partialTick) + graphics.draw(label, width / 2f, height / 2f, color = textColor, gravity = RenderGravity.CENTER_CENTER) + } + + override fun sizeToContents(performLayout: Boolean) { + super.sizeToContents(performLayout) + + height = height.coerceAtLeast(HEIGHT).coerceAtLeast(font.lineHeight.toFloat() + 2f) + width = width.coerceAtLeast(HEIGHT).coerceAtLeast(font.width(label) + 4f) + } + + companion object { + const val HEIGHT = 18f + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt new file mode 100644 index 000000000..9d6c1ded8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt @@ -0,0 +1,671 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.addUpgradeTooltipLines +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.isCtrlDown +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.config.ClientConfig +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.util.ItemStackSorter +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.UpgradeSlots +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import java.util.function.Predicate +import kotlin.math.ceil +import kotlin.math.pow + +private val gunpowder = ItemStackIcon(ItemStack(Items.GUNPOWDER)).fixed() +private val barrier = ItemStackIcon(ItemStack(Items.BARRIER)).fixed() +private val redstone = ItemStackIcon(ItemStack(Items.REDSTONE)).fixed() +private val redstoneUnderBarrier = barrier.composeAfter(redstone).fixed() + +private fun > makeRedstoneSettingButton( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + control: IPlayerInputWithFeedback +): LargeEnumRectangleButtonPanel { + return object : LargeEnumRectangleButtonPanel( + screen, parent, x = x, y = y, + defaultValue = RedstoneSetting.LOW, enum = RedstoneSetting::class.java, + prop = control, + ) { + override var isDisabled: Boolean + get() = !control.test(minecraft.player) + set(value) {} + + init { + add(RedstoneSetting.IGNORED, tooltip = RedstoneSetting.IGNORED.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) gunpowder else Widgets18.REDSTONE_IGNORED) + add(RedstoneSetting.LOW, tooltip = RedstoneSetting.LOW.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) redstoneUnderBarrier else Widgets18.REDSTONE_LOW) + add(RedstoneSetting.HIGH, tooltip = RedstoneSetting.HIGH.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) redstone else Widgets18.REDSTONE_HIGH) + } + } +} + +private class PullPushButton, T : Enum>( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + enum: Class, + prop: GetterSetter, + defaultValue: T, + val pullProp: BooleanInputWithFeedback, + val pushProp: BooleanInputWithFeedback +) : LargeEnumRectangleButtonPanel(screen, parent, x = x, y = y, enum = enum, prop = prop, defaultValue = defaultValue) { + init { + if (pullProp.test(minecraft.player)) { + tooltips.add(TranslatableComponent("otm.gui.sides.pull_help").withStyle(ChatFormatting.GRAY)) + } + + if (pushProp.test(minecraft.player)) { + tooltips.add(TranslatableComponent("otm.gui.sides.push_help").withStyle(ChatFormatting.GRAY)) + } + } + + data class State>(val pull: Boolean, val push: Boolean, val value: T) + private val sprites = HashMap, AbstractMatterySprite>() + + fun addSprite(pull: Boolean, push: Boolean, value: T, sprite: AbstractMatterySprite) { + sprites[State(pull, push, value)] = sprite + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.innerRender(graphics, mouseX, mouseY, partialTick) + sprites[State(pullProp.value, pushProp.value, prop.get())]?.render(graphics, 0f, 0f, width, height) + } + + override fun onClick(mouseButton: Int) { + if (mouseButton != InputConstants.MOUSE_BUTTON_MIDDLE && minecraft.window.isShiftDown) { + if (pullProp.test(minecraft.player)) { + pullProp.switchValue() + } + } else if (mouseButton != InputConstants.MOUSE_BUTTON_MIDDLE && minecraft.window.isCtrlDown) { + if (pushProp.test(minecraft.player)) { + pushProp.switchValue() + } + } else { + super.onClick(mouseButton) + } + } +} + +private fun > makeItemModeButton(screen: S, parent: FramePanel, input: ItemConfigPlayerInput.Piece, side: RelativeSide): LargeEnumRectangleButtonPanel { + val button = PullPushButton(screen, parent, enum = MatteryDeviceBlockEntity.ItemHandlerMode::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push) + val widgets = Widgets18.CONTROLS[side]!! + + for (v in MatteryDeviceBlockEntity.ItemHandlerMode.values()) { + button.add(v, tooltip = TranslatableComponent(v.translationKey)) + button.addSprite(false, false, v, widgets.items[v]!!) + button.addSprite(true, false, v, widgets.itemsPull[v]!!) + button.addSprite(false, true, v, widgets.itemsPush[v]!!) + button.addSprite(true, true, v, widgets.itemsPullPush[v]!!) + } + + button.finish() + button.predicate = Predicate { input.isAllowed(it) } + + return button +} + +private fun > makeEnergyModeButton(screen: S, parent: FramePanel, input: EnergyConfigPlayerInput.Piece, side: RelativeSide): LargeEnumRectangleButtonPanel { + val button = PullPushButton(screen, parent, enum = FlowDirection::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push) + val widgets = Widgets18.CONTROLS[side]!! + + for (v in FlowDirection.values()) { + button.add(v, tooltip = TranslatableComponent(v.translationKey)) + button.addSprite(false, false, v, widgets.flow[v]!!) + button.addSprite(true, false, v, widgets.flowPull[v]!!) + button.addSprite(false, true, v, widgets.flowPush[v]!!) + button.addSprite(true, true, v, widgets.flowPullPush[v]!!) + } + + button.finish() + + if (input.possibleModes == FlowDirection.NONE) { + button.visible = false + } + + button.predicate = Predicate { input.possibleModes.isSupertype(it) } + + return button +} + +private fun > makeFluidModeButton(screen: S, parent: FramePanel, input: FluidConfigPlayerInput.Piece, side: RelativeSide): LargeEnumRectangleButtonPanel { + val button = PullPushButton(screen, parent, enum = FlowDirection::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push) + val widgets = Widgets18.CONTROLS[side]!! + + for (v in FlowDirection.values()) { + button.add(v, tooltip = TranslatableComponent(v.translationKey)) + button.addSprite(false, false, v, widgets.flow[v]!!) + button.addSprite(true, false, v, widgets.flowPull[v]!!) + button.addSprite(false, true, v, widgets.flowPush[v]!!) + button.addSprite(true, true, v, widgets.flowPullPush[v]!!) + } + + button.finish() + + return button +} + +private fun moveButtons( + front: EditablePanel<*>, + back: EditablePanel<*>, + left: EditablePanel<*>, + right: EditablePanel<*>, + top: EditablePanel<*>, + bottom: EditablePanel<*>, +) { + top.tooltips.add(0, TranslatableComponent("otm.gui.sides.top")) + bottom.tooltips.add(0, TranslatableComponent("otm.gui.sides.bottom")) + back.tooltips.add(0, TranslatableComponent("otm.gui.sides.back")) + front.tooltips.add(0, TranslatableComponent("otm.gui.sides.front")) + left.tooltips.add(0, TranslatableComponent("otm.gui.sides.left")) + right.tooltips.add(0, TranslatableComponent("otm.gui.sides.right")) + + top.x = 30f + top.y = 14f + + right.x = 30f - 20f + right.y = 14f + 20f + + left.x = 30f + 20f + left.y = 14f + 20f + + front.x = 30f + front.y = 14f + 20f + + bottom.x = 30f + bottom.y = 14f + 42f + + back.x = 30f - 20f + back.y = 14f + 42f +} + +private fun > makeItemHandlerControlPanel( + screen: S, + inputs: ItemConfigPlayerInput +): FramePanel { + val frame = object : FramePanel(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.item_config")) { + override fun tickInner() { + super.tickInner() + + if (!isEverFocused()) { + remove() + } + } + } + + val front = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT) + val back = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK) + val left = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT) + val right = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT) + val top = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP) + val bottom = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM) + + moveButtons(front, back, left, right, top, bottom) + screen.addPanel(frame) + frame.requestFocus() + frame.closeOnEscape = true + + return frame +} + +private fun > makeEnergyConfigPanel( + screen: S, + inputs: EnergyConfigPlayerInput +): FramePanel { + val frame = object : FramePanel(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.energy_config")) { + override fun tickInner() { + super.tickInner() + + if (!isEverFocused()) { + remove() + } + } + } + + val front = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT) + val back = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK) + val left = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT) + val right = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT) + val top = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP) + val bottom = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM) + + moveButtons(front, back, left, right, top, bottom) + screen.addPanel(frame) + frame.requestFocus() + frame.closeOnEscape = true + + return frame +} + +private fun > makeFluidConfigPanel( + screen: S, + inputs: FluidConfigPlayerInput +): FramePanel { + val frame = object : FramePanel(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.fluid_config")) { + override fun tickInner() { + super.tickInner() + + if (!isEverFocused()) { + remove() + } + } + } + + val front = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val back = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val left = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val right = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val top = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + val bottom = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } } + + moveButtons(front, back, left, right, top, bottom) + screen.addPanel(frame) + frame.requestFocus() + frame.closeOnEscape = true + + return frame +} + +class DeviceControls>( + screen: S, + parent: EditablePanel<*>, + extra: Iterable> = listOf(), + val redstoneConfig: IPlayerInputWithFeedback? = null, + val itemConfig: ItemConfigPlayerInput? = null, + val energyConfig: EnergyConfigPlayerInput? = null, + val fluidConfig: FluidConfigPlayerInput? = null, + val balanceInputs: BooleanInputWithFeedback? = null, + val upgrades: UpgradeSlots? = null, +) : EditablePanel(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) { + val itemConfigButton: LargeRectangleButtonPanel? + val energyConfigButton: LargeRectangleButtonPanel? + val fluidConfigButton: LargeRectangleButtonPanel? + val redstoneControlsButton: LargeEnumRectangleButtonPanel? + val balanceInputsButton: LargeBooleanRectangleButtonPanel? + val upgradesButton: LargeRectangleButtonPanel? + + init { + childrenOrder = -1000 + } + + private var upgradeWindow: FramePanel? = null + + private val buttons = ArrayList>() + + fun alignButtons() { + buttons.removeIf { it.isRemoved || it.parent != this } + + var y = 0f + + for (button in buttons) { + button.setPos(button.dockLeft, y + button.dockTop) + y += button.height + button.dockMargin.vertical + 2f + width = width.coerceAtLeast(button.width + button.dockMargin.horizontal) + } + + height = height.coerceAtLeast(y - 2f) + } + + fun removeButton(button: EditablePanel<*>) { + buttons.remove(button) + alignButtons() + } + + fun

> addButton(button: P): P { + buttons.add(button) + button.parent = this + alignButtons() + return button + } + + fun

> addButton(button: P, after: EditablePanel<*>): P { + val index = buttons.indexOf(after) + if (index == -1) throw NoSuchElementException("Unknown panel to add button after: $after") + buttons.add(index + 1, button) + button.parent = this + alignButtons() + return button + } + + fun

> prependButton(button: P): P { + buttons.add(0, button) + button.parent = this + alignButtons() + return button + } + + override fun performLayout() { + super.performLayout() + alignButtons() + } + + fun addStorageMode(prop: GetterSetter) { + val mode = LargeEnumRectangleButtonPanel(screen, this, prop = prop, defaultValue = FlowDirection.BI_DIRECTIONAL, enum = FlowDirection::class.java) + + mode.add(FlowDirection.INPUT, Widgets18.ONLY_STORE, FlowDirection.INPUT.title) + mode.add(FlowDirection.OUTPUT, Widgets18.ONLY_EXTRACT, FlowDirection.OUTPUT.title) + mode.add(FlowDirection.BI_DIRECTIONAL, Widgets18.STORE_EXTRACT, FlowDirection.BI_DIRECTIONAL.title) + mode.finish() + + addButton(mode) + } + + inline fun > sortingButtons(ascending: GetterSetter, sorting: GetterSetter, default: T, configurator: LargeEnumRectangleButtonPanel.() -> Unit): List> { + val result = ArrayList>() + + LargeBooleanRectangleButtonPanel( + screen, + this, + prop = ascending, + iconActive = Widgets18.SORT_ASCENDING, + iconInactive = Widgets18.SORT_DESCENDING, + tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"), + tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"), + ).also { + prependButton(it) + result.add(it) + } + + LargeEnumRectangleButtonPanel(screen, this, enum = T::class.java, prop = sorting, defaultValue = default).also { + prependButton(it) + configurator.invoke(it) + it.finish() + result.add(it) + } + + return result + } + + fun sortingButtons(input: MatteryMenu.SortInput, unfoldableSettings: Boolean = true) { + object : LargeRectangleButtonPanel(screen, this@DeviceControls) { + var buttons: List>? = null + + init { + tooltips.add(TranslatableComponent("otm.gui.sorting.sort_now")) + + if (unfoldableSettings) { + tooltips.add(TextComponent("")) + tooltips.add(TranslatableComponent("otm.gui.sorting.sort_settings").withStyle(ChatFormatting.GRAY)) + } + + icon = Widgets18.SORT_NOW + + addButton(this) + + if (!unfoldableSettings) { + makeButtons() + } + } + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || unfoldableSettings && value == InputConstants.MOUSE_BUTTON_RIGHT + } + + override var isDisabled: Boolean + get() = !input.input.test(minecraft.player) + set(value) {} + + private fun makeButtons() { + buttons = sortingButtons(input.settings::isAscending.asGetterSetter(), input.settings::sorting.asGetterSetter(), ItemStackSorter.DEFAULT) { + for (v in ItemStackSorter.entries) { + add(v, v.icon, v.title) + } + + finish() + } + + buttons!!.forEach { removeButton(it) } + buttons!!.forEach { addButton(it as EditablePanel, this) } + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + input.clientInput() + } else { + if (buttons == null) { + makeButtons() + } else { + buttons!!.forEach { it.remove() } + buttons = null + } + } + } + } + } + + init { + for (button in extra) { + addButton(button) + } + + if (redstoneConfig != null) { + redstoneControlsButton = addButton(makeRedstoneSettingButton(screen, this, control = redstoneConfig)) + } else { + redstoneControlsButton = null + } + + if (upgrades != null) { + upgradesButton = addButton(object : LargeRectangleButtonPanel( + screen, this@DeviceControls, + icon = Widgets18.UPGRADES + ) { + init { + tooltips.add(TranslatableComponent("otm.gui.upgrades")) + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + graphics.renderComponentTooltip(font, ArrayList().also { + it.add(TranslatableComponent("otm.gui.upgrades")) + it.add(TextComponent("")) + + if (upgrades.allowedTypes.isEmpty()) { + it.add(TranslatableComponent("otm.gui.upgrade_type.allowed_none").withStyle(ChatFormatting.DARK_GRAY)) + } else { + it.add(TranslatableComponent("otm.gui.upgrade_type.allowed").withStyle(ChatFormatting.GRAY)) + + for (upgrade in upgrades.allowedTypes) { + it.add(upgrade.component.copy().withStyle(ChatFormatting.GRAY)) + } + } + + val i = it.size + upgrades.currentStats.addUpgradeTooltipLines(it) + + if (it.size != i) { + it.add(i, TranslatableComponent("otm.gui.upgrades.current").withStyle(ChatFormatting.GRAY)) + it.add(i, TextComponent("")) + } + }, mouseX.toInt(), mouseY.toInt()) + + return true + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + if (upgradeWindow != null && !upgradeWindow!!.isRemoved) { + upgradeWindow!!.toScreenCenter() + upgradeWindow!!.popup() + } else { + val (columns, rows) = SPECIAL_UPGRADE_CASES.getOrElse(upgrades.slots.size - 1) { + val square = ceil(it.toDouble().pow(0.5)).toInt() + square to square + } + + upgradeWindow = FramePanel(screen, (columns * AbstractSlotPanel.SIZE + 40f).coerceAtLeast(120f), 30f + rows * AbstractSlotPanel.SIZE, TranslatableComponent("otm.gui.upgrades")).also { frame -> + val grid = GridPanel(screen, frame, columns = columns, rows = rows) + + for (slot in upgrades.slots) { + SlotPanel(screen, grid, slot) + } + + grid.dock = Dock.FILL + + screen.addPanel(frame) + + val parentFrame = this@DeviceControls.parent!! + + frame.behaveAsWindow() + frame.setPos(parentFrame.absoluteX + parentFrame.width / 2f - frame.width / 2f, parentFrame.absoluteY + parentFrame.height / 2f - frame.height / 2f) + frame.popup() + + upgrades.openState.value = true + frame.onClose { upgrades.openState.value = false } + + parentFrame.blockingWindow = frame + } + } + } + } + }) + } else { + upgradesButton = null + } + + if (balanceInputs != null) { + balanceInputsButton = addButton(LargeBooleanRectangleButtonPanel( + screen, this, + prop = balanceInputs, + iconActive = Widgets18.BALANCING_ENABLED, + iconInactive = Widgets18.BALANCING_DISABLED).also { + it.tooltips.add(TranslatableComponent("otm.gui.balance_inputs")) + }) + } else { + balanceInputsButton = null + } + + if (itemConfig != null) { + itemConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, icon = Widgets18.ITEMS_CONFIGURATION) { + init { + tooltips.add(TranslatableComponent("otm.gui.sides.item_config")) + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeItemHandlerControlPanel(screen, itemConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + itemConfigButton = null + } + + if (energyConfig != null) { + energyConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, icon = Widgets18.ENERGY_CONFIGURATION) { + init { + tooltips.add(TranslatableComponent("otm.gui.sides.energy_config")) + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeEnergyConfigPanel(screen, energyConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + energyConfigButton = null + } + + if (fluidConfig != null) { + fluidConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, icon = Widgets18.FLUID_CONFIGURATION) { + init { + tooltips.add(TranslatableComponent("otm.gui.sides.fluid_config")) + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeFluidConfigPanel(screen, fluidConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + fluidConfigButton = null + } + } + + override fun preRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (parent is FramePanel<*>) { + x = parent!!.width + 3f + y = dockTop + } + } + + // не съедаем ввод мыши + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + return false + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + return false + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + return false + } + + companion object { + val SPECIAL_UPGRADE_CASES = immutableList { + accept(1 to 1) + accept(2 to 1) + accept(3 to 1) + accept(2 to 2) + } + } +} + +fun > makeDeviceControls( + screen: S, + parent: FramePanel, + extra: Iterable> = listOf(), + redstoneConfig: IPlayerInputWithFeedback? = null, + itemConfig: ItemConfigPlayerInput? = null, + energyConfig: EnergyConfigPlayerInput? = null, + fluidConfig: FluidConfigPlayerInput? = null, + balanceInputs: BooleanInputWithFeedback? = null, + upgrades: UpgradeSlots? = null, +): DeviceControls { + return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig, + itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig, + balanceInputs = balanceInputs, upgrades = upgrades) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxInputPanel.kt new file mode 100644 index 000000000..7d266e86b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxInputPanel.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback + +open class CheckBoxInputPanel( + screen: S, + parent: EditablePanel<*>?, + val widget: IPlayerInputWithFeedback, + x: Float = 0f, + y: Float = 0f, + width: Float = REGULAR_DIMENSIONS + 120f, + height: Float = REGULAR_DIMENSIONS +) : CheckBoxPanel(screen, parent, x, y, width, height, isChecked = widget) { + override var isDisabled: Boolean + get() = !widget.test(minecraft.player) + set(value) {} +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelInputPanel.kt new file mode 100644 index 000000000..ac46a9b5a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelInputPanel.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Label +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback +import kotlin.math.roundToInt + +open class CheckBoxLabelInputPanel( + screen: S, + parent: EditablePanel<*>?, + val widget: IPlayerInputWithFeedback, + text: Component, + x: Float = 0f, + y: Float = 0f, + width: Float = CheckBoxPanel.REGULAR_DIMENSIONS + 120f, + height: Float = CheckBoxPanel.REGULAR_DIMENSIONS +) : AbstractCheckBoxLabelPanel(screen, parent, x, y, width, height) { + override val checkbox = CheckBoxInputPanel(screen = screen, parent = this, widget = widget, x = 0f, y = 0f, width = CheckBoxPanel.REGULAR_DIMENSIONS, height = CheckBoxPanel.REGULAR_DIMENSIONS) + override val label = Label(screen, this, CheckBoxPanel.REGULAR_DIMENSIONS + 4f, 4f, text = text) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelPanel.kt new file mode 100644 index 000000000..19893eb3f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxLabelPanel.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Label +import ru.dbotthepony.mc.otm.core.GetterSetter + +open class CheckBoxLabelPanel( + screen: S, + parent: EditablePanel<*>?, + text: Component, + x: Float = 0f, + y: Float = 0f, + width: Float = CheckBoxPanel.REGULAR_DIMENSIONS + 120f, + height: Float = CheckBoxPanel.REGULAR_DIMENSIONS, + isChecked: GetterSetter = GetterSetter.box(false) +) : AbstractCheckBoxLabelPanel(screen, parent, x, y, width, height) { + override val checkbox = CheckBoxPanel(screen, this, 0f, 0f, isChecked = isChecked) + override val label = Label(screen, this, CheckBoxPanel.REGULAR_DIMENSIONS + 4f, 4f, text = text) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxPanel.kt new file mode 100644 index 000000000..7e7fa776f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/CheckBoxPanel.kt @@ -0,0 +1,92 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.GetterSetter + +open class CheckBoxPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = REGULAR_DIMENSIONS, + height: Float = REGULAR_DIMENSIONS, + open val isChecked: GetterSetter = GetterSetter.box(false) +) : AbstractButtonPanel(screen, parent, x, y, width, height) { + open val IDLE_UNCHECKED: AbstractMatterySprite = Companion.IDLE_UNCHECKED + open val IDLE_CHECKED: AbstractMatterySprite = Companion.IDLE_CHECKED + open val HOVERED_UNCHECKED: AbstractMatterySprite = Companion.HOVERED_UNCHECKED + open val HOVERED_CHECKED: AbstractMatterySprite = Companion.HOVERED_CHECKED + open val PRESSED_UNCHECKED: AbstractMatterySprite = Companion.PRESSED_UNCHECKED + open val PRESSED_CHECKED: AbstractMatterySprite = Companion.PRESSED_CHECKED + open val DISABLED_UNCHECKED: AbstractMatterySprite = Companion.DISABLED_UNCHECKED + open val DISABLED_CHECKED: AbstractMatterySprite = Companion.DISABLED_CHECKED + + protected fun renderCheckboxBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (isDisabled) { + if (isChecked.get()) { + DISABLED_CHECKED.render(graphics, width = width, height = height) + } else { + DISABLED_UNCHECKED.render(graphics, width = width, height = height) + } + } else { + if (isPressed) { + if (isChecked.get()) { + PRESSED_CHECKED.render(graphics, width = width, height = height) + } else { + PRESSED_UNCHECKED.render(graphics, width = width, height = height) + } + } else if (isHovered) { + if (isChecked.get()) { + HOVERED_CHECKED.render(graphics, width = width, height = height) + } else { + HOVERED_UNCHECKED.render(graphics, width = width, height = height) + } + } else { + if (isChecked.get()) { + IDLE_CHECKED.render(graphics, width = width, height = height) + } else { + IDLE_UNCHECKED.render(graphics, width = width, height = height) + } + } + } + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderCheckboxBackground(graphics, mouseX, mouseY, partialTick) + } + + override fun onClick(mouseButton: Int) { + isChecked.accept(!isChecked.get()) + } + + companion object { + const val REGULAR_DIMENSIONS = 15f + + val IDLE_UNCHECKED: AbstractMatterySprite + val IDLE_CHECKED: AbstractMatterySprite + val HOVERED_UNCHECKED: AbstractMatterySprite + val HOVERED_CHECKED: AbstractMatterySprite + val PRESSED_UNCHECKED: AbstractMatterySprite + val PRESSED_CHECKED: AbstractMatterySprite + val DISABLED_UNCHECKED: AbstractMatterySprite + val DISABLED_CHECKED: AbstractMatterySprite + + init { + val grid = WidgetLocation.CHECKBOX.grid(columns = 2, rows = 4) + + IDLE_CHECKED = grid.next() + IDLE_UNCHECKED = grid.next() + HOVERED_CHECKED = grid.next() + HOVERED_UNCHECKED = grid.next() + PRESSED_CHECKED = grid.next() + PRESSED_UNCHECKED = grid.next() + DISABLED_CHECKED = grid.next() + DISABLED_UNCHECKED = grid.next() + } + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt new file mode 100644 index 000000000..ede8a66dd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt @@ -0,0 +1,174 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback +import java.util.* +import java.util.function.Predicate +import kotlin.collections.ArrayList + +abstract class EnumRectangleButtonPanel>( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float, + height: Float, + enum: Class, + val prop: GetterSetter, + val defaultValue: T, +) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { + val enum = EnumValueCodec.searchClass(enum) + private val constants: Array = enum.enumConstants + private var isBuilding = true + var predicate: Predicate = Predicate { true } + + override var isDisabled: Boolean + get() { + if (prop is IPlayerInputWithFeedback) { + return !prop.test(minecraft.player) + } else { + return super.isDisabled + } + } + set(value) { super.isDisabled = value } + + data class Entry>( + val key: T, + val skinElement: IGUIRenderable?, + val winding: UVWindingOrder = UVWindingOrder.NORMAL, + val tooltip: Component? = null + ) + + protected val enumMapping = EnumMap>(enum) + + fun add(key: T, skinElement: IGUIRenderable? = null, tooltip: Component? = null, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel { + return add(Entry(key = key, skinElement = skinElement, winding = winding, tooltip = tooltip)) + } + + fun add(entry: Entry): EnumRectangleButtonPanel { + check(isBuilding) { "Not building" } + check(enumMapping.put(entry.key, entry) == null) { "Already has mapping for ${entry.key}" } + if (enumMapping.size == enum.enumConstants.size) finish() + return this + } + + fun finish(): EnumRectangleButtonPanel { + if (!isBuilding) return this + check(enumMapping.isNotEmpty()) { "No enums were defined, like, at all." } + check(enumMapping.containsKey(defaultValue)) { "Default value $defaultValue is missing from mapping" } + isBuilding = false + return this + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + check(!isBuilding) { "Still building this button!" } + super.innerRender(graphics, mouseX, mouseY, partialTick) + val entry = enumMapping[prop.get()] ?: return + entry.skinElement?.render(graphics, 0f, 0f, width, height, entry.winding) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + check(!isBuilding) { "Still building this button!" } + return super.mouseClickedInner(x, y, button) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + check(!isBuilding) { "Still building this button!" } + return super.mouseReleasedInner(x, y, button) + } + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || + value == InputConstants.MOUSE_BUTTON_RIGHT || + value == InputConstants.MOUSE_BUTTON_MIDDLE + } + + override fun onClick(mouseButton: Int) { + check(!isBuilding) { "Still building this button!" } + if (enumMapping.size == 1) return + + when (mouseButton) { + InputConstants.MOUSE_BUTTON_LEFT -> { + var i = constants.size + var ordinal = prop.value.ordinal + + while (i >= 0) { + i-- + ordinal++ + + if (ordinal >= constants.size) { + ordinal = 0 + } + + if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) { + prop.value = constants[ordinal] + break + } + } + } + + InputConstants.MOUSE_BUTTON_RIGHT -> { + var i = constants.size + var ordinal = prop.value.ordinal + + while (i >= 0) { + i-- + ordinal-- + + if (ordinal < 0) { + ordinal = constants.size - 1 + } + + if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) { + prop.value = constants[ordinal] + break + } + } + } + + InputConstants.MOUSE_BUTTON_MIDDLE -> { + if (prop.value != defaultValue) { + prop.value = defaultValue + } + } + } + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (tooltips.isEmpty() && enumMapping.entries.none { predicate.test(it.key) && it.value.tooltip != null }) { + return super.innerRenderTooltips(graphics, mouseX, mouseY, partialTick) + } + + val listing = ArrayList() + + if (tooltips.isNotEmpty()) { + listing.addAll(tooltips) + listing.add(SPACE) + } + + for ((k, entry) in enumMapping) { + if (entry.tooltip != null && predicate.test(k)) { + val active = entry.key == prop.get() + val tooltip = entry.tooltip.copy().withStyle(if (active) ChatFormatting.WHITE else ChatFormatting.GRAY) + listing.add(if (active) TranslatableComponent("otm.gui.tooltip.enum.active", tooltip) else tooltip) + } + } + + graphics.renderComponentTooltip(font, listing, mouseX.toInt(), mouseY.toInt()) + return true + } + + companion object { + private val SPACE = TextComponent("") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeBooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt similarity index 67% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeBooleanRectangleButtonPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt index 506bb2f8d..714d701b9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeBooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt @@ -1,7 +1,8 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons +package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter @@ -14,10 +15,12 @@ open class LargeBooleanRectangleButtonPanel( width: Float = SIZE, height: Float = SIZE, prop: GetterSetter, - skinElementActive: AbstractSkinElement? = null, - skinElementInactive: AbstractSkinElement? = null, + iconActive: IGUIRenderable? = null, + iconInactive: IGUIRenderable? = null, onChange: ((newValue: Boolean) -> Unit)? = null, -) : BooleanRectangleButtonPanel(screen, parent, x, y, width, height, prop, skinElementActive, skinElementInactive, onChange) { + tooltipActive: Component? = null, + tooltipInactive: Component? = null, +) : BooleanRectangleButtonPanel(screen, parent, x, y, width, height, prop, iconActive, iconInactive, onChange, tooltipActive, tooltipInactive) { final override val IDLE = Widgets18.BUTTON_IDLE final override val HOVERED = Widgets18.BUTTON_HOVERED final override val PRESSED = Widgets18.BUTTON_PRESSED diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeEnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt similarity index 79% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeEnumRectangleButtonPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt index c39145854..d4bff231b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeEnumRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeEnumRectangleButtonPanel.kt @@ -1,9 +1,11 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons +package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback open class LargeEnumRectangleButtonPanel>( screen: S, @@ -15,8 +17,7 @@ open class LargeEnumRectangleButtonPanel>( enum: Class, prop: GetterSetter, defaultValue: T, - onChange: ((newValue: T) -> Unit)? = null, -) : EnumRectangleButtonPanel(screen, parent, x, y, width, height, enum, prop, defaultValue, onChange) { +) : EnumRectangleButtonPanel(screen, parent, x, y, width, height, enum, prop, defaultValue) { final override val IDLE = Widgets18.BUTTON_IDLE final override val HOVERED = Widgets18.BUTTON_HOVERED final override val PRESSED = Widgets18.BUTTON_PRESSED diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeRectangleButtonPanel.kt new file mode 100644 index 000000000..be7a3cc60 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeRectangleButtonPanel.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +open class LargeRectangleButtonPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, + onPress: ((clickButton: Int) -> Unit)? = null, + var icon: IGUIRenderable? = null, + var iconWinding: UVWindingOrder? = null, +) : RectangleButtonPanel(screen, parent, x, y, width, height, onPress) { + final override val IDLE = Widgets18.BUTTON_IDLE + final override val HOVERED = Widgets18.BUTTON_HOVERED + final override val PRESSED = Widgets18.BUTTON_PRESSED + final override val DISABLED = Widgets18.BUTTON_DISABLED + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.innerRender(graphics, mouseX, mouseY, partialTick) + + val color = if (isDisabled || isPressed) RGBAColor.DARK_GRAY else RGBAColor.WHITE + if (iconWinding != null) { + icon?.render(graphics, width = width, height = height, winding = iconWinding!!, color = color) + } else { + icon?.render(graphics, width = width, height = height, color = color) + } + } + + companion object { + const val SIZE = 18f + + fun input( + screen: S, + parent: EditablePanel<*>?, + input: MatteryMenu.PlayerInput, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, + icon: IGUIRenderable? = null, + iconWinding: UVWindingOrder? = null, + ): LargeRectangleButtonPanel { + return object : LargeRectangleButtonPanel(screen, parent, x, y, width, height, null, icon, iconWinding) { + override fun onClick(mouseButton: Int) { + input.accept(null) + } + + override var isDisabled: Boolean + get() = !input.test(minecraft.player) + set(value) {} + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/RectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/RectangleButtonPanel.kt new file mode 100644 index 000000000..3f862a190 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/RectangleButtonPanel.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.button + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import java.util.function.IntConsumer + +@Suppress("PropertyName") +abstract class RectangleButtonPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float, + height: Float, + var onPress: IntConsumer? = null, +) : AbstractButtonPanel(screen, parent, x, y, width, height) { + abstract val PRESSED: IGUIRenderable + abstract val HOVERED: IGUIRenderable + abstract val IDLE: IGUIRenderable + abstract val DISABLED: IGUIRenderable + + override fun onClick(mouseButton: Int) { + onPress?.accept(mouseButton) + } + + protected fun renderSquareButton(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (isDisabled) { + DISABLED.render(graphics, 0f, 0f, width, height) + } else if (isPressed) { + PRESSED.render(graphics, 0f, 0f, width, height) + } else if (isHovered) { + HOVERED.render(graphics, 0f, 0f, width, height) + } else { + IDLE.render(graphics, 0f, 0f, width, height) + } + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderSquareButton(graphics, mouseX, mouseY, partialTick) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallBooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallBooleanRectangleButtonPanel.kt similarity index 78% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallBooleanRectangleButtonPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallBooleanRectangleButtonPanel.kt index 60babbc23..c9106179d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallBooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallBooleanRectangleButtonPanel.kt @@ -1,7 +1,8 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons +package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen import ru.dbotthepony.mc.otm.client.render.* +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter @@ -13,8 +14,8 @@ open class SmallBooleanRectangleButtonPanel( width: Float = SIZE, height: Float = SIZE, prop: GetterSetter, - skinElementActive: AbstractSkinElement? = null, - skinElementInactive: AbstractSkinElement? = null, + skinElementActive: AbstractMatterySprite? = null, + skinElementInactive: AbstractMatterySprite? = null, onChange: ((newValue: Boolean) -> Unit)? = null, ) : BooleanRectangleButtonPanel(screen, parent, x, y, width, height, prop, skinElementActive, skinElementInactive, onChange) { final override val IDLE = Widgets8.BUTTON_IDLE diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallEnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallEnumRectangleButtonPanel.kt similarity index 84% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallEnumRectangleButtonPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallEnumRectangleButtonPanel.kt index 7563207a1..29444e7db 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallEnumRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallEnumRectangleButtonPanel.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons +package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen import ru.dbotthepony.mc.otm.client.render.Widgets8 @@ -15,8 +15,7 @@ open class SmallEnumRectangleButtonPanel>( enum: Class, prop: GetterSetter, defaultValue: T, - onChange: ((newValue: T) -> Unit)? = null, -) : EnumRectangleButtonPanel(screen, parent, x, y, width, height, enum, prop, defaultValue, onChange) { +) : EnumRectangleButtonPanel(screen, parent, x, y, width, height, enum, prop, defaultValue) { final override val IDLE = Widgets8.BUTTON_IDLE final override val HOVERED = Widgets8.BUTTON_HOVERED final override val PRESSED = Widgets8.BUTTON_PRESSED diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallRectangleButtonPanel.kt similarity index 62% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallRectangleButtonPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallRectangleButtonPanel.kt index 55db58e60..b7b5a1cf6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/SmallRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/SmallRectangleButtonPanel.kt @@ -1,8 +1,8 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons +package ru.dbotthepony.mc.otm.client.screen.panels.button -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.render.SkinElement +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets8 import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel @@ -15,7 +15,7 @@ open class SmallRectangleButtonPanel( width: Float = SIZE, height: Float = SIZE, onPress: ((clickButton: Int) -> Unit)? = null, - val skinElement: SkinElement? = null, + val skinElement: MatterySprite? = null, val skinElementWinding: UVWindingOrder? = null, ) : RectangleButtonPanel(screen, parent, x, y, width, height, onPress) { final override val IDLE = Widgets8.BUTTON_IDLE @@ -23,13 +23,13 @@ open class SmallRectangleButtonPanel( final override val PRESSED = Widgets8.BUTTON_PRESSED final override val DISABLED = Widgets8.BUTTON_DISABLED - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - super.innerRender(stack, mouseX, mouseY, partialTick) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.innerRender(graphics, mouseX, mouseY, partialTick) if (skinElementWinding != null) { - skinElement?.render(stack, width = width, height = height, winding = skinElementWinding) + skinElement?.render(graphics, width = width, height = height, winding = skinElementWinding) } else { - skinElement?.render(stack, width = width, height = height) + skinElement?.render(graphics, width = width, height = height) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/BooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/BooleanRectangleButtonPanel.kt deleted file mode 100644 index 3fa9707d0..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/BooleanRectangleButtonPanel.kt +++ /dev/null @@ -1,40 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.GetterSetter -import ru.dbotthepony.mc.otm.core.value - -abstract class BooleanRectangleButtonPanel( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float, - height: Float, - val prop: GetterSetter, - val skinElementActive: AbstractSkinElement? = null, - val skinElementInactive: AbstractSkinElement? = null, - val onChange: ((newValue: Boolean) -> Unit)? = null, -) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { - override fun onClick(clickButton: Int) { - if (clickButton == InputConstants.MOUSE_BUTTON_LEFT) { - val newValue = !prop.value - prop.value = newValue - onChange?.invoke(newValue) - } - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - super.innerRender(stack, mouseX, mouseY, partialTick) - - if (prop.value) { - skinElementActive?.render(stack, width = width, height = height) - } else { - skinElementInactive?.render(stack, width = width, height = height) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/ButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/ButtonPanel.kt deleted file mode 100644 index 1b4b32b51..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/ButtonPanel.kt +++ /dev/null @@ -1,115 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.playGuiClickSound -import ru.dbotthepony.mc.otm.client.render.TextAlign -import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.render.drawAligned -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.RGBAColor - -open class ButtonPanel( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = 40f, - height: Float = HEIGHT, - var label: Component -) : EditablePanel(screen, parent, x, y, width, height) { - constructor(screen: S, parent: EditablePanel<*>?, label: Component) : this(screen, parent, x = 0f, label = label) - - constructor( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = 40f, - height: Float = HEIGHT, - label: Component, - onPress: Runnable - ) : this(screen, parent, x, y, width, height, label) { - this.callback = onPress - } - - protected var callback: Runnable? = null - protected var pressed = false - - protected open fun onClick(button: Int) { - callback?.run() - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (isDisabled || pressed) { - return true - } - - if (!tryToGrabMouseInput()) { - return true - } - - pressed = true - playGuiClickSound() - return true - } - - override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { - if (!pressed) { - return true - } - - grabMouseInput = false - pressed = false - - if (isHovered) { - onClick(button) - } - - return true - } - - fun bind(runnable: Runnable) { - callback = runnable - } - - var textColor = RGBAColor.WHITE - - var isDisabled = false - set(value) { - if (field != value) { - if (!value) { - pressed = false - grabMouseInput = false - } - - field = value - } - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (isDisabled) { - Widgets18.BUTTON_DISABLED_STRETCHABLE.render(stack, width = width, height = height) - } else if (pressed) { - Widgets18.BUTTON_PRESSED_STRETCHABLE.render(stack, width = width, height = height) - } else if (isHovered) { - Widgets18.BUTTON_HOVERED_STRETCHABLE.render(stack, width = width, height = height) - } else { - Widgets18.BUTTON_IDLE_STRETCHABLE.render(stack, width = width, height = height) - } - - font.drawAligned(stack, label, TextAlign.CENTER_CENTER, width / 2f, height / 2f, textColor.toInt()) - } - - override fun sizeToContents() { - super.sizeToContents() - - height = height.coerceAtLeast(HEIGHT).coerceAtLeast(font.lineHeight.toFloat() + 2f) - width = width.coerceAtLeast(HEIGHT).coerceAtLeast(font.width(label) + 4f) - } - - companion object { - const val HEIGHT = 18f - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxInputPanel.kt deleted file mode 100644 index a5bfd55fa..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxInputPanel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget - -open class CheckBoxInputPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - val widget: BooleanPlayerInputWidget, - x: Float = 0f, - y: Float = 0f, - width: Float = REGULAR_DIMENSIONS + 120f, - height: Float = REGULAR_DIMENSIONS -) : CheckBoxPanel(screen, parent, x, y, width, height) { - override var checked: Boolean - get() = widget.value - set(value) {} - - override var isDisabled: Boolean - get() = !widget.ignoreSpectators || minecraft.player?.isSpectator == true - set(value) {} - - override fun onClick() { - widget.userInput(!checked, minecraft.player) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelInputPanel.kt deleted file mode 100644 index d42d56c43..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelInputPanel.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.client.screen.panels.Label -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget - -open class CheckBoxLabelInputPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - widget: BooleanPlayerInputWidget, - text: Component, - x: Float = 0f, - y: Float = 0f, - width: Float = CheckBoxPanel.REGULAR_DIMENSIONS + 120f, - height: Float = CheckBoxPanel.REGULAR_DIMENSIONS -) : EditablePanel(screen, parent, x, y, width, height) { - val widget get() = checkbox.widget - val checkbox = CheckBoxInputPanel( - screen, - this, - widget, - 0f, - 0f, - CheckBoxPanel.REGULAR_DIMENSIONS, - CheckBoxPanel.REGULAR_DIMENSIONS - ) - val label = Label(screen, this, CheckBoxPanel.REGULAR_DIMENSIONS + 4f, 4f, text = text) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelPanel.kt deleted file mode 100644 index 4d24b4ac8..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxLabelPanel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.client.screen.panels.Label - -open class CheckBoxLabelPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - text: Component, - x: Float = 0f, - y: Float = 0f, - width: Float = CheckBoxPanel.REGULAR_DIMENSIONS + 120f, - height: Float = CheckBoxPanel.REGULAR_DIMENSIONS -) : EditablePanel(screen, parent, x, y, width, height) { - val checkbox = - CheckBoxPanel(screen, this, 0f, 0f, CheckBoxPanel.REGULAR_DIMENSIONS, CheckBoxPanel.REGULAR_DIMENSIONS) - val label = Label(screen, this, CheckBoxPanel.REGULAR_DIMENSIONS + 4f, 4f, text = text) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxPanel.kt deleted file mode 100644 index 084ee8479..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/CheckBoxPanel.kt +++ /dev/null @@ -1,107 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.playGuiClickSound -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.subGrid -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel - -open class CheckBoxPanel @JvmOverloads constructor( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = REGULAR_DIMENSIONS, - height: Float = REGULAR_DIMENSIONS -) : EditablePanel(screen, parent, x, y, width, height) { - open var checked = false - open var isDisabled = false - protected var isPressed = false - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (isDisabled) { - if (checked) { - DISABLED_CHECKED.render(stack, width = width, height = height) - } else { - DISABLED_UNCHECKED.render(stack, width = width, height = height) - } - } else { - if (isPressed) { - if (checked) { - PRESSED_CHECKED.render(stack, width = width, height = height) - } else { - PRESSED_UNCHECKED.render(stack, width = width, height = height) - } - } else if (isHovered) { - if (checked) { - HOVERED_CHECKED.render(stack, width = width, height = height) - } else { - HOVERED_UNCHECKED.render(stack, width = width, height = height) - } - } else { - if (checked) { - IDLE_CHECKED.render(stack, width = width, height = height) - } else { - IDLE_UNCHECKED.render(stack, width = width, height = height) - } - } - } - } - - protected open fun onClick() { - checked = !checked - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (!isDisabled && button == InputConstants.MOUSE_BUTTON_LEFT) { - grabMouseInput = true - isPressed = true - playGuiClickSound() - } - - return true - } - - override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { - if (isPressed && button == InputConstants.MOUSE_BUTTON_LEFT) { - if (isHovered) { - onClick() - } - - isPressed = false - grabMouseInput = false - } - - return true - } - - companion object { - const val REGULAR_DIMENSIONS = 15f - - val IDLE_UNCHECKED: AbstractSkinElement - val IDLE_CHECKED: AbstractSkinElement - val HOVERED_UNCHECKED: AbstractSkinElement - val HOVERED_CHECKED: AbstractSkinElement - val PRESSED_UNCHECKED: AbstractSkinElement - val PRESSED_CHECKED: AbstractSkinElement - val DISABLED_UNCHECKED: AbstractSkinElement - val DISABLED_CHECKED: AbstractSkinElement - - init { - val grid = WidgetLocation.CHECKBOX.subGrid(REGULAR_DIMENSIONS, REGULAR_DIMENSIONS, 2, 4) - - IDLE_UNCHECKED = grid.next() - IDLE_CHECKED = grid.next() - HOVERED_UNCHECKED = grid.next() - HOVERED_CHECKED = grid.next() - PRESSED_UNCHECKED = grid.next() - PRESSED_CHECKED = grid.next() - DISABLED_UNCHECKED = grid.next() - DISABLED_CHECKED = grid.next() - } - } -} - diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/EnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/EnumRectangleButtonPanel.kt deleted file mode 100644 index 3649a5e45..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/EnumRectangleButtonPanel.kt +++ /dev/null @@ -1,186 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.ChatFormatting -import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.GetterSetter -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.next -import ru.dbotthepony.mc.otm.core.prev -import ru.dbotthepony.mc.otm.core.value -import java.util.* -import kotlin.collections.ArrayList - -abstract class EnumRectangleButtonPanel>( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float, - height: Float, - val enum: Class, - val prop: GetterSetter, - val defaultValue: T, - val onChange: ((newValue: T) -> Unit)? = null, -) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { - private var building = true - - protected val enumMapping = EnumMap>(enum) - protected val tooltipMapping = EnumMap(enum) - - fun addTooltip(value: T, component: Component): EnumRectangleButtonPanel { - check(tooltipMapping.put(value, component) == null) { "Already has mapping for $value" } - return this - } - - var mainTooltip: Component? = null - set(value) { - if (field != null) { - throw UnsupportedOperationException("Write once only") - } - - field = value - } - - fun isFullyDefined(): Boolean { - for (value in enum.enumConstants) { - if (enumMapping[value] == null) { - return false - } - } - - return true - } - - val missingValues: List get() { - val missing = ArrayList() - - for (value in enum.enumConstants) { - if (enumMapping[value] == null) { - missing.add(value) - } - } - - return missing - } - - fun add(value: T, skinElement: AbstractSkinElement, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel { - return add(value, skinElement to winding) - } - - fun add(value: T, skinElement: AbstractSkinElement, component: Component, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel { - return add(value, component, skinElement to winding) - } - - fun add(value: T, component: Component, skinElement: AbstractSkinElement, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel { - return add(value, component, skinElement to winding) - } - - fun add(value: T, pair: Pair): EnumRectangleButtonPanel { - check(building) { "Not building" } - check(enumMapping.put(value, pair) == null) { "Already has mapping for $value" } - - if (enumMapping.size == enum.enumConstants.size) { - finish() - } - - return this - } - - fun add(value: T, component: Component, pair: Pair): EnumRectangleButtonPanel { - addTooltip(value, component) - return add(value, pair) - } - - fun finish(): EnumRectangleButtonPanel { - check(building) { "Not building" } - check(isFullyDefined()) { - "Not all enums having their mapping defined, missing are: ${missingValues.joinToString(", ")}" - } - - building = false - return this - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - check(!building) { "Still building this button!" } - super.innerRender(stack, mouseX, mouseY, partialTick) - val pair = checkNotNull(enumMapping[prop.get()]) { "HOW" } - pair.first.render(stack, 0f, 0f, width, height, pair.second) - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - check(!building) { "Still building this button!" } - return super.mouseClickedInner(x, y, button) - } - - override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { - check(!building) { "Still building this button!" } - return super.mouseReleasedInner(x, y, button) - } - - override fun onClick(clickButton: Int) { - when (clickButton) { - InputConstants.MOUSE_BUTTON_LEFT -> { - prop.value = prop.value.next(enum.enumConstants) - onChange?.invoke(prop.get()) - } - - InputConstants.MOUSE_BUTTON_RIGHT -> { - prop.value = prop.value.prev(enum.enumConstants) - onChange?.invoke(prop.get()) - } - - InputConstants.MOUSE_BUTTON_MIDDLE -> { - if (prop.get() != defaultValue) { - prop.value = defaultValue - onChange?.invoke(prop.get()) - } - } - } - } - - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - if (!isHovered && !isGrabbingMouseInput()) { - return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) - } - - if (mainTooltip == null && tooltipMapping.size == 0) { - return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) - } - - val listing = ArrayList() - - if (mainTooltip != null) { - listing.add(mainTooltip!!) - listing.add(SPACE) - } - - for ((key, value) in tooltipMapping) { - if (key == prop.get()) { - listing.add(value.copy().withStyle(ChatFormatting.WHITE)) - } else { - listing.add(value.copy().withStyle(ChatFormatting.GRAY)) - } - } - - screen.renderComponentTooltip( - stack, - listing, - mouseX.toInt(), - mouseY.toInt(), - font - ) - - return true - } - - companion object { - private val SPACE = TextComponent("") - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeRectangleButtonPanel.kt deleted file mode 100644 index 4ebf8e9d9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/LargeRectangleButtonPanel.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder -import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel - -open class LargeRectangleButtonPanel( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float = SIZE, - height: Float = SIZE, - onPress: ((clickButton: Int) -> Unit)? = null, - val skinElement: AbstractSkinElement? = null, - val skinElementWinding: UVWindingOrder? = null, -) : RectangleButtonPanel(screen, parent, x, y, width, height, onPress) { - final override val IDLE = Widgets18.BUTTON_IDLE - final override val HOVERED = Widgets18.BUTTON_HOVERED - final override val PRESSED = Widgets18.BUTTON_PRESSED - final override val DISABLED = Widgets18.BUTTON_DISABLED - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - super.innerRender(stack, mouseX, mouseY, partialTick) - - if (skinElementWinding != null) { - skinElement?.render(stack, width = width, height = height, winding = skinElementWinding) - } else { - skinElement?.render(stack, width = width, height = height) - } - } - - companion object { - const val SIZE = 18f - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/RectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/RectangleButtonPanel.kt deleted file mode 100644 index fe808675d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/buttons/RectangleButtonPanel.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ru.dbotthepony.mc.otm.client.screen.panels.buttons - -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.playGuiClickSound -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement -import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel - -@Suppress("PropertyName") -abstract class RectangleButtonPanel( - screen: S, - parent: EditablePanel<*>?, - x: Float = 0f, - y: Float = 0f, - width: Float, - height: Float, - val onPress: ((clickButton: Int) -> Unit)? = null, -) : EditablePanel(screen, parent, x, y, width, height) { - protected var pressed = false - - protected open fun onClick(clickButton: Int) { - onPress?.invoke(clickButton) - } - - abstract val PRESSED: AbstractSkinElement - abstract val HOVERED: AbstractSkinElement - abstract val IDLE: AbstractSkinElement - abstract val DISABLED: AbstractSkinElement - - var isDisabled = false - set(value) { - if (field != value) { - if (!value) { - pressed = false - grabMouseInput = false - } - - field = value - } - } - - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (isDisabled) { - DISABLED.render(stack, 0f, 0f, width, height) - } else if (pressed) { - PRESSED.render(stack, 0f, 0f, width, height) - } else if (isHovered) { - HOVERED.render(stack, 0f, 0f, width, height) - } else { - IDLE.render(stack, 0f, 0f, width, height) - } - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (!isDisabled) { - if (!pressed) { - playGuiClickSound() - } - - if (tryToGrabMouseInput()) { - pressed = true - } - } - - return true - } - - override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { - if (!isDisabled && pressed) { - pressed = false - - if (isHovered) { - onClick(button) - } - } - - grabMouseInput = false - - return true - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditBoxPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/EditBoxPanel.kt similarity index 62% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditBoxPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/EditBoxPanel.kt index 41678aacd..c2f003189 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditBoxPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/EditBoxPanel.kt @@ -1,8 +1,11 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.input +import com.mojang.blaze3d.platform.InputConstants import net.minecraft.client.gui.components.EditBox import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Widget2Panel import ru.dbotthepony.mc.otm.core.TextComponent open class EditBoxPanel( @@ -21,30 +24,21 @@ open class EditBoxPanel( } override fun isFocused(): Boolean { - return this@EditBoxPanel.isFocused + return this@EditBoxPanel.isFocusedThis } } } - init { - autoKillFocus = true - } - override fun copyValues(new_widget: EditBox, old_widget: EditBox) { new_widget.value = old_widget.value } - override fun tick() { - super.tick() - widget?.tick() - } - override fun configureNew(widget: EditBox, recreation: Boolean) { - widget.setFocus(isFocused) + widget.changeFocus(isFocusedThis) } - override fun onFocusChanged(new: Boolean, old: Boolean) { - widget?.setFocus(new) + override fun onFocusChanged() { + widget?.changeFocus(isFocusedThis) } override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { @@ -52,4 +46,13 @@ open class EditBoxPanel( requestFocus() return true } -} \ No newline at end of file + + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { + if (key == InputConstants.KEY_ESCAPE && widget?.isActive == true) { + widget?.changeFocus(false) + return true + } + + return super.keyPressedInternal(key, scancode, mods) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkNumberInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkNumberInputPanel.kt new file mode 100644 index 000000000..f37fc4501 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkNumberInputPanel.kt @@ -0,0 +1,83 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.input + +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.math.BigDecimal +import java.util.function.BooleanSupplier + +open class NetworkNumberInputPanel @JvmOverloads constructor( + screen: S, + parent: EditablePanel<*>?, + val networkValue: () -> BigDecimal, + val callback: (BigDecimal) -> Unit, + val isAvailable: BooleanSupplier = BooleanSupplier { true }, + x: Float = 0f, + y: Float = 0f, + width: Float = 0f, + height: Float = 20f, + defaultText: Component = TextComponent("") +) : EditBoxPanel(screen, parent, x, y, width, height, defaultText) { + constructor( + screen: S, + parent: EditablePanel<*>?, + widget: MatteryMenu.PlayerInput, + networkValue: () -> BigDecimal, + x: Float = 0f, + y: Float = 0f, + width: Float = 0f, + height: Float = 20f, + defaultText: Component = TextComponent("") + ) : this( + screen = screen, + parent = parent, + callback = widget::accept, + isAvailable = { widget.allowSpectators || minecraft.player?.isSpectator != true }, + networkValue = networkValue, + x = x, + y = y, + width = width, + height = height, + defaultText = defaultText, + ) + + protected var nextUpdateFromServer = 0L + protected var inputStr = "" + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (!isAvailable.asBoolean) { + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun tickInner() { + super.tickInner() + + if (isFocusedThis) { + if (!isAvailable.asBoolean) { + killFocus() + return + } + + nextUpdateFromServer = System.currentTimeMillis() + 2000L + } + + if (nextUpdateFromServer < System.currentTimeMillis()) { + getOrCreateWidget().value = networkValue.invoke().toPlainString() + inputStr = getOrCreateWidget().value + } else if (isAvailable.asBoolean) { + if (inputStr != getOrCreateWidget().value) { + inputStr = getOrCreateWidget().value + + try { + callback.invoke(BigDecimal(inputStr)) + } catch (_: Throwable) { } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkedStringInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkedStringInputPanel.kt new file mode 100644 index 000000000..c5ad5d7d8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NetworkedStringInputPanel.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.input + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.menu.input.AbstractPlayerInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback +import ru.dbotthepony.mc.otm.milliTime + +open class NetworkedStringInputPanel( + screen: S, + parent: EditablePanel<*>?, + val backend: IPlayerInputWithFeedback, + x: Float = 0f, + y: Float = 0f, + width: Float = 60f, + height: Float = 11f, +) : TextInputPanel(screen, parent, x, y, width, height) { + override var isActive: Boolean + get() = backend.test(minecraft.player) + set(value) {} + + override fun onFocusChanged() { + super.onFocusChanged() + + if (isFocusedThis && !backend.test(minecraft.player)) { + killFocus() + } + } + + private var lastChanges = 0L + + override fun onTextChanged(new: String, old: String) { + lastChanges = milliTime + 1000L + backend.accept(new) + } + + override fun tickInner() { + super.tickInner() + + if (milliTime >= lastChanges) { + text = backend.get() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NumberInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NumberInputPanel.kt new file mode 100644 index 000000000..b96963ee7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/NumberInputPanel.kt @@ -0,0 +1,215 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.input + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.Widgets +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.RectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal + +abstract class NumberInputPanel( + screen: S, + parent: EditablePanel<*>, + val prop: GetterSetter, + x: Float = 0f, + y: Float = 0f, + width: Float = WIDTH, + height: Float = HeightControls.BUTTON_HEIGHT * 2f, + var min: N? = null, + var max: N? = null, +) : EditablePanel(screen, parent, x, y, width, height) { + abstract fun toNumber(input: String): N? + abstract fun increase(input: N): N + abstract fun decrease(input: N): N + abstract val isFractional: Boolean + + fun clamp(value: N): N { + val min = min + val max = max + if (min != null && (value as Comparable) < min) return min + if (max != null && (value as Comparable) > max) return max + return value + } + + fun increase() { + prop.accept(clamp(increase(prop.get()))) + } + + fun decrease() { + prop.accept(clamp(decrease(prop.get()))) + } + + val textInput = object : TextInputPanel(screen, this@NumberInputPanel) { + init { + allowNumbersAndSign() + dock = Dock.FILL + text = prop.get().toString() + } + + override fun acceptsCharacter(codepoint: Char, mods: Int, index: Int): Boolean { + if (!isFractional && codepoint == '.') return false + return super.acceptsCharacter(codepoint, mods, index) + } + + override fun tickInner() { + super.tickInner() + + if (!hasHierarchicalFocus()) { + text = prop.get().toString() + } + } + + override fun onTextChanged(new: String, old: String) { + if (hasHierarchicalFocus()) { + val i = toNumber(new) + if (i != null) prop.accept(i) + } + } + } + + val controls = EditablePanel(screen, this, width = HeightControls.BUTTON_WIDTH, height = HeightControls.BUTTON_HEIGHT) + val increaseControl = Control(true) + val decreaseControl = Control(false) + + init { + increaseControl.childrenOrder = 1 + decreaseControl.childrenOrder = 2 + textInput.dockRight = 2f + } + + init { + controls.dock = Dock.RIGHT + } + + inner class Control(val isIncrease: Boolean) : RectangleButtonPanel(screen, controls, width = HeightControls.BUTTON_WIDTH, height = HeightControls.BUTTON_HEIGHT) { + override val PRESSED = if (!isIncrease) Widgets.ARROW_DOWN_BUTTON_PRESSED else Widgets.ARROW_UP_BUTTON_PRESSED + override val HOVERED = if (!isIncrease) Widgets.ARROW_DOWN_BUTTON_HOVERED else Widgets.ARROW_UP_BUTTON_HOVERED + override val IDLE = if (!isIncrease) Widgets.ARROW_DOWN_BUTTON_IDLE else Widgets.ARROW_UP_BUTTON_IDLE + override val DISABLED = if (!isIncrease) Widgets.ARROW_DOWN_BUTTON_DISABLED else Widgets.ARROW_UP_BUTTON_DISABLED + + init { + dock = Dock.TOP + + if (isIncrease) + tooltips.add(TranslatableComponent("otm.gui.increase")) + else + tooltips.add(TranslatableComponent("otm.gui.decrease")) + } + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || value == InputConstants.MOUSE_BUTTON_RIGHT + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + if (isIncrease) { + increase() + } else { + decrease() + } + } else if (mouseButton == InputConstants.MOUSE_BUTTON_RIGHT) { + if (isIncrease) { + decrease() + } else { + increase() + } + } + } + } + + companion object { + const val WIDTH = 50f + } +} + +abstract class LNumberInputPanel( + screen: S, + parent: EditablePanel<*>, + prop: GetterSetter, + x: Float = 0f, + y: Float = 0f, + width: Float = WIDTH, + height: Float = HeightControls.BUTTON_HEIGHT * 2f, + min: N? = null, + max: N? = null, + val toNumber: (String) -> N?, + val increase: (N) -> N, + val decrease: (N) -> N, + final override val isFractional: Boolean +) : NumberInputPanel(screen, parent, prop, x, y, width, height, min, max) { + final override fun toNumber(input: String): N? { + return toNumber.invoke(input) + } + + final override fun increase(input: N): N { + return increase.invoke(input) + } + + final override fun decrease(input: N): N { + return decrease.invoke(input) + } +} + +open class IntInputPanel( + screen: S, + parent: EditablePanel<*>, + prop: GetterSetter, + x: Float = 0f, + y: Float = 0f, + width: Float = WIDTH, + height: Float = HeightControls.BUTTON_HEIGHT * 2f, + var step: Int = 1, + min: Int? = null, + max: Int? = null, +) : NumberInputPanel(screen, parent, prop, x, y, width, height, min, max) { + final override fun toNumber(input: String): Int? { + return input.toIntOrNull() + } + + final override fun increase(input: Int): Int { + return input + step + } + + final override fun decrease(input: Int): Int { + return input - step + } + + final override val isFractional: Boolean + get() = false +} + +open class DecimalInputPanel( + screen: S, + parent: EditablePanel<*>, + prop: GetterSetter, + x: Float = 0f, + y: Float = 0f, + width: Float = WIDTH, + height: Float = HeightControls.BUTTON_HEIGHT * 2f, + var step: Decimal = Decimal.ONE, + min: Decimal? = null, + max: Decimal? = null, +) : NumberInputPanel(screen, parent, prop, x, y, width, height, min, max) { + final override fun toNumber(input: String): Decimal? { + return try { + Decimal(input) + } catch (err: NumberFormatException) { + null + } + } + + final override fun increase(input: Decimal): Decimal { + return input + step + } + + final override fun decrease(input: Decimal): Decimal { + return input - step + } + + final override val isFractional: Boolean + get() = true +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QueryUserPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt similarity index 66% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QueryUserPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt index 6b6a9fcc0..b36deebb1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QueryUserPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt @@ -1,9 +1,14 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.input +import com.mojang.blaze3d.platform.InputConstants import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Label +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel import ru.dbotthepony.mc.otm.core.TranslatableComponent open class QueryUserPanel( @@ -40,22 +45,16 @@ open class QueryUserPanel( this.height = height + bottom.height + PADDING + PADDING_TOP + 4f - ButtonPanel(screen, bottom, width = font.width(TranslatableComponent("otm.gui.cancel")) + 12f, label = TranslatableComponent("otm.gui.cancel")).also { - it.bind { - onCancel?.run() - this.remove() - } - - it.dock = Dock.RIGHT - } + ButtonPanel(screen, bottom, width = font.width(TranslatableComponent("otm.gui.cancel")) + 12f, label = TranslatableComponent("otm.gui.cancel"), onPress = Runnable { + onCancel?.run() + this.remove() + }).dock = Dock.RIGHT if (onConfirm != null) { - ButtonPanel(screen, bottom, width = font.width(TranslatableComponent("otm.gui.confirm")) + 12f, label = TranslatableComponent("otm.gui.confirm")).also { - it.bind { - onConfirm.run() - this.remove() - } - + ButtonPanel(screen, bottom, width = font.width(TranslatableComponent("otm.gui.confirm")) + 12f, label = TranslatableComponent("otm.gui.confirm"), onPress = Runnable { + onConfirm.run() + this.remove() + }).also { it.dock = Dock.RIGHT it.dockRight = 2f } @@ -64,5 +63,16 @@ open class QueryUserPanel( this.width = maxWidth.coerceAtLeast(bottom.children.stream().mapToDouble { it.width.toDouble() }.sum().toFloat() + 2f) + PADDING * 2f toScreenCenter() + behaveAsWindow() + } + + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { + if (key == InputConstants.KEY_ESCAPE) { + onCancel?.run() + remove() + return true + } + + return super.keyPressedInternal(key, scancode, mods) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt new file mode 100644 index 000000000..a18bfcdf9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/TextInputPanel.kt @@ -0,0 +1,1518 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.input + +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.blaze3d.vertex.VertexFormat +import it.unimi.dsi.fastutil.chars.Char2IntFunction +import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap +import it.unimi.dsi.fastutil.chars.CharOpenHashSet +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.network.chat.Component +import org.joml.Vector2i +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.isCtrlDown +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.tesselator +import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.milliTime +import java.util.function.Predicate +import kotlin.math.roundToInt + +open class TextInputPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 60f, + height: Float = 11f, +) : EditablePanel(screen, parent, x, y, width, height) { + private data class TextSelection(val cursor: Int, val shift: Int) { + val isLeft get() = shift < 0 + val isRight get() = shift > 0 + + fun move(value: Int, lineLength: Int): TextSelection { + return TextSelection(cursor, (shift + value).coerceIn(-cursor, lineLength - cursor + 1)) + } + + val isValid get() = shift != 0 + val start get() = if (isRight) cursor else cursor + shift + val end get() = if (isRight) cursor + shift else cursor + + fun sub(line: String): Pair { + val before = line.substring(0, start.coerceIn(0, line.length)) + val selected = line.substring(start.coerceIn(0, line.length), end.coerceAtMost(line.length)) + + return before to selected + } + + fun coversEntireString(value: String): Boolean { + return start <= 0 && value.length <= end + } + + fun coversEntireLine(value: String): Boolean { + return start <= 0 && value.length < end + } + + fun coversNewline(value: String): Boolean { + return value.length < end + } + } + + private fun putSelection(index: Int, selection: TextSelection) { + if (selection.isValid) { + selections[index] = selection + } else { + selections.remove(index) + } + } + + private inner class Snapshot { + private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy + private val cursorLine = this@TextInputPanel.cursorLine + private val cursorCharacter = this@TextInputPanel.cursorRow + private val selections = Int2ObjectAVLTreeMap(this@TextInputPanel.selections) + private val multiLine = this@TextInputPanel.multiLine + + fun apply() { + this@TextInputPanel.lines.clear() + + if (this@TextInputPanel.multiLine) + this@TextInputPanel.lines.addAll(lines) + else + this@TextInputPanel.lines.add(lines.joinToString("")) + + + this@TextInputPanel.selections.clear() + if (this@TextInputPanel.multiLine && multiLine) + this@TextInputPanel.selections.putAll(selections) + + this@TextInputPanel.cursorRow = cursorCharacter + this@TextInputPanel.cursorLine = cursorLine + triggerChangeCallback() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TextInputPanel<*>.Snapshot + + if (lines != other.lines) return false + if (cursorLine != other.cursorLine) return false + if (cursorCharacter != other.cursorCharacter) return false + if (selections != other.selections) return false + + return true + } + + override fun hashCode(): Int { + var result = lines.hashCode() + result = 31 * result + cursorLine + result = 31 * result + cursorCharacter + return result + } + } + + var debugDraw = false + var multiLine = false + set(value) { + if (field == value) return + + if (!value && lines.size > 1) { + val merge = lines.joinToString("") + lines.clear() + lines.add(merge) + triggerChangeCallback() + } + + field = value + } + + var cursorLine = 0 + var cursorRow = 0 + open var placeholder: Component = TextComponent("") + open var textColor = RGBAColor.WHITE + open var placeholderColor = RGBAColor.DARK_GRAY + open var cursorColor = RGBAColor.GREEN + open var backgroundColor = RGBAColor.BLACK + open var isActive = true + + init { + scissor = true + dockPadding = DockProperty(2f, 2f, 2f, 2f) + } + + private var oldText = ArrayList() + private var textCache: String? = null + private val lines = ArrayList() + private val selections = Int2ObjectAVLTreeMap() + private val undo = ArrayDeque() + private val redo = ArrayDeque() + + /** + * scroll in rows (up-down) + */ + private var scrollLines = 0 + + /** + * scroll in pixels (left-right) + */ + private var scrollPixels = 0f + + var rowSpacing = 2f + + private fun triggerChangeCallback() { + if (oldText != lines) { + textCache = null + val old = oldText.joinToString("\n") + oldText = ArrayList(lines) + onTextChanged(textCache(), old) + } + } + + private var snapshotTimer: Long? = null + + override fun tickInner() { + super.tickInner() + + if (snapshotTimer != null && snapshotTimer!! <= milliTime) { + pushbackSnapshot() + snapshotTimer = null + redo.clear() + } + } + + private fun pushbackSnapshot() { + val snapshot = Snapshot() + + if (undo.isEmpty() || undo.last() != snapshot) + undo.addLast(snapshot) + } + + private fun pushbackSnapshotIfNoTimer() { + if (snapshotTimer == null) + pushbackSnapshot() + } + + private fun recordHistory(hard: Boolean = false) { + if (hard) { + pushbackSnapshot() + snapshotTimer = null + } else if (snapshotTimer == null) { + snapshotTimer = milliTime + 800L + } else if (snapshotTimer!! <= milliTime) { + pushbackSnapshot() + snapshotTimer = null + } + + redo.clear() + } + + // it gets really dirty + // idk man :( + private fun undo() { + if (undo.isEmpty()) + return + + if (snapshotTimer != null || redo.isEmpty()) { + pushbackSnapshot() + snapshotTimer = null + } + + val current = Snapshot() + + while (undo.isNotEmpty()) { + val removed = undo.removeLast() + redo.addFirst(removed) + + if (removed != current) { + removed.apply() + break + } + } + } + + // it gets really dirty + // idk man :( + private fun redo() { + if (redo.isEmpty()) + return + + val current = Snapshot() + + snapshotTimer = null + + while (redo.isNotEmpty()) { + val removed = redo.removeFirst() + undo.addLast(removed) + + if (current != removed) { + removed.apply() + break + } + } + } + + operator fun get(index: Int): String? { + return lines.getOrNull(index) + } + + operator fun set(index: Int, value: String) { + if (index < 0) + throw IndexOutOfBoundsException("negative index $index") + + if (!multiLine && index != 0) + throw IllegalStateException("Not accepting newlines") + + lines.ensureCapacity(index) + + while (lines.size <= index) { + lines.add("") + } + + lines[index] = value + } + + private fun selectionIndex(index: Int) = object : Int2ObjectMap.Entry { + override fun setValue(newValue: TextSelection): TextSelection? { + return selections.put(index, newValue) + } + + override fun getIntKey(): Int { + return index + } + + override val value: TextSelection + get() = selections[index] ?: throw NoSuchElementException() + } + + fun insertLine(index: Int, value: String = "") { + if (!multiLine && lines.isNotEmpty()) + throw IllegalStateException("Not accepting newlines") + + lines.ensureCapacity(index) + + while (lines.size < index) { + lines.add("") + } + + val upperLines = ArrayList>() + upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index))) + + for (entry in upperLines) { + selections.remove(entry.intKey) + } + + for (entry in upperLines) { + selections.put(entry.intKey + 1, entry.value) + } + + lines.add(index, value) + } + + fun removeLine(index: Int): Boolean { + if (index < 0 || index >= lines.size) + return false + + if (cursorLine == index) { + if (index != 0) + cursorLine-- + + cursorRow = lines.getOrNull(cursorLine)?.length ?: 0 + } + + lines.removeAt(index) + selections.remove(index) + + val upperLines = ArrayList>() + upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index))) + + for (entry in upperLines) { + selections.remove(entry.intKey) + } + + for (entry in upperLines) { + selections.put(entry.intKey - 1, entry.value) + } + + return true + } + + protected fun moveCursors(line: Int, character: Int, moveBy: Int) { + @Suppress("name_shadowing") + val moveBy = if (character + moveBy < 0) -character else moveBy + + if (cursorLine == line && cursorRow >= character) { + cursorRow = (cursorRow + moveBy).coerceIn(0, this[cursorLine]?.length ?: Int.MAX_VALUE) + } + + val selection = selections[line] + + if (selection != null) { + val start = selection.start + val end = selection.end + + if (character < start) { + putSelection(line, TextSelection(start + moveBy, end + moveBy)) + } else if (character > end && character + moveBy < end) { + if (character + moveBy < start) { + // selections.remove(line) + selections[line] = TextSelection(0, 0) + } else { + putSelection(line, TextSelection(start, character + moveBy)) + } + } else if (character in start .. end) { + if (moveBy > 0 || character + moveBy <= start) { + putSelection(line, TextSelection(start, end + moveBy)) + } else { + putSelection(line, TextSelection(character + moveBy, end + moveBy)) + } + } + } + } + + protected fun wipeSelection() { + if (selections.isEmpty()) + return + + pushbackSnapshotIfNoTimer() + val set = selections.int2ObjectEntrySet() + + while (set.isNotEmpty()) { + val (lineNumber, selection) = set.last() + selections.remove(lineNumber) + val line = this[lineNumber] ?: continue + if (!selection.isValid) continue // ??? + + if (selection.coversEntireLine(line)) { + removeLine(lineNumber) + } else if (selection.coversEntireString(line)) { + this[lineNumber] = "" + } else if (selection.coversNewline(line)) { + val before = line.substring(0, selection.start.coerceIn(0, line.length)) + val next = this[lineNumber + 1] + + if (next == null) { + this[lineNumber] = before + } else { + this[lineNumber] = before + next + removeLine(lineNumber + 1) + } + } else { + val before = line.substring(0, selection.start.coerceIn(0, line.length)) + val after = line.substring(selection.end.coerceIn(0, line.length)) + this[lineNumber] = before + after + } + + if (lineNumber < cursorLine) { + cursorLine = lineNumber + cursorRow = selection.start + } else if (lineNumber == cursorLine && cursorRow > selection.start) { + cursorRow = selection.start + } + } + + triggerChangeCallback() + } + + private fun textCache(): String { + var textCache = textCache + + if (textCache == null) { + textCache = lines.joinToString("\n") + this.textCache = textCache + } + + return textCache + } + + var text: String + get() { + return textCache() + } + set(value) { + if (textCache() == value) + return + + snapshotTimer = null + selections.clear() + lines.clear() + redo.clear() + undo.clear() + cursorLine = 0 + cursorRow = 0 + textCache = null + + if (multiLine) { + lines.addAll(value.split(NEWLINES)) + } else { + lines.add(value.replace(NEWLINES, "")) + } + } + + data class CursorAdvanceResult( + val oldLine: Int, + val newLine: Int, + val oldCharacter: Int, + val newCharacter: Int, + val couldHaveChangedLine: Boolean + ) { + val linesChanged get() = oldLine != newLine + val charsChanged get() = linesChanged || oldCharacter != newCharacter + val advancedChars get() = newCharacter - oldCharacter + } + + fun advanceCursorLeft(greedy: Boolean = false): CursorAdvanceResult { + val oldLine = cursorLine + val line = this[cursorLine] + var couldHaveChangedLine = false + + if (line != null && cursorRow > line.length) { + cursorRow = line.length + } else if (cursorRow < 0) { + cursorRow = 0 + } + + val oldChar = cursorRow + + if (cursorRow > 0) { + if (greedy && line != null) { + cursorRow = greedyAdvanceLeft(line, cursorRow) + } else { + cursorRow-- + } + } else if (cursorLine > 0) { + cursorLine-- + cursorRow = 0 + couldHaveChangedLine = true + + @Suppress("name_shadowing") + val line = this[cursorLine] + + if (line != null) { + cursorRow = line.length + } + } else { + couldHaveChangedLine = true + } + + return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorRow, couldHaveChangedLine = couldHaveChangedLine) + } + + fun advanceCursorRight(greedy: Boolean = false): CursorAdvanceResult { + val oldLine = cursorLine + var couldHaveChangedLine = false + + val line = this[cursorLine] + + if (line != null && cursorRow > line.length) { + cursorRow = line.length + } else if (cursorRow < 0) { + cursorRow = 0 + } + + val oldChar = cursorRow + + if (greedy && line != null && cursorRow + 1 < line.length) { + cursorRow = greedyAdvanceRight(line, cursorRow) + } else { + cursorRow++ + } + + if (line != null && cursorRow > line.length) { + couldHaveChangedLine = true + + if (lines.size <= cursorLine + 1) { + cursorRow = line.length + } else { + cursorLine++ + cursorRow = 0 + } + } else if (line == null) { + cursorRow = 0 + } + + return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorRow, couldHaveChangedLine = couldHaveChangedLine) + } + + private fun simulateSelectLeft(greedy: Boolean) { + val line = this[cursorLine] ?: return + val existing = selections[cursorLine] ?: TextSelection(cursorRow, 0) + val result = advanceCursorLeft(greedy) + + if (result.couldHaveChangedLine) { + putSelection(result.oldLine, existing.move(-line.length, line.length)) + } else { + putSelection(result.oldLine, existing.move(result.newCharacter - result.oldCharacter, line.length)) + } + + if (result.linesChanged) { + val existingNew = selections[result.newLine] + val lineNew = this[result.newLine] ?: return + + if (existingNew == null) { + putSelection(result.newLine, TextSelection(result.newCharacter + 1, -1)) + } else { + putSelection(result.newLine, existingNew.move(-1, lineNew.length)) + } + } + } + + private fun simulateSelectRight(greedy: Boolean) { + val line = this[cursorLine] ?: return + val existing = selections[cursorLine] ?: TextSelection(cursorRow, 0) + val result = advanceCursorRight(greedy) + + if (result.couldHaveChangedLine) { + if (result.oldLine != lines.size - 1) + putSelection(result.oldLine, existing.move(line.length.coerceAtLeast(1), line.length)) + } else { + putSelection(result.oldLine, existing.move(result.newCharacter - result.oldCharacter, line.length)) + } + } + + private fun simulateSelectionUp(): Boolean { + if (cursorLine <= 0) + return false + + val cursorCharacter = cursorRow + val cursorLine = cursorLine + + while (this.cursorRow >= 0 && this.cursorLine == cursorLine) { + simulateSelectLeft(false) + } + + val line = this[this.cursorLine]!! + + if (cursorCharacter < line.length) { + this.cursorRow = line.length + + while (this.cursorRow > cursorCharacter) { + simulateSelectLeft(false) + } + } + + this.cursorRow = cursorCharacter + return true + } + + private fun simulateSelectionDown(): Boolean { + if (cursorLine >= lines.size - 1) + return false + + val cursorCharacter = cursorRow + val cursorLine = cursorLine + + while (this.cursorRow >= cursorCharacter && this.cursorLine == cursorLine) { + simulateSelectRight(true) + } + + val line = this[this.cursorLine]!! + + if (cursorCharacter < line.length) { + this.cursorRow = 0 + + while (this.cursorRow < cursorCharacter) { + simulateSelectRight(false) + } + } else { + if (line.isEmpty()) { + val existing = selections[this.cursorLine] + + if (existing != null && existing.isLeft) { + selections.remove(this.cursorLine) + } + } else { + val store = this.cursorLine + + for (i in 0 .. line.length) { + simulateSelectRight(false) + } + + this.cursorLine = store + } + } + + this.cursorRow = cursorCharacter + return true + } + + var onEnter: Runnable? = null + + open fun onEnter() { + onEnter?.run() + } + + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { + if (key == InputConstants.KEY_ESCAPE || !isActive) { + killFocus() + return true + } + + if (key == InputConstants.KEY_RETURN) { + if (multiLine) { + if (!minecraft.window.isShiftDown && !minecraft.window.isCtrlDown) + wipeSelection() + + val line = this[cursorLine] + + if (line != null && !minecraft.window.isShiftDown) { + if (cursorRow <= 0) { + recordHistory(true) + insertLine(cursorLine, "") + + if (!minecraft.window.isCtrlDown) { + cursorLine++ + cursorRow = 0 + } + } else if (cursorRow >= line.length) { + recordHistory(true) + insertLine(cursorLine + 1, "") + + if (!minecraft.window.isCtrlDown) { + cursorLine++ + cursorRow = 0 + } + } else { + val before = line.substring(0, cursorRow) + val after = line.substring(cursorRow) + + recordHistory(true) + this[cursorLine] = before + insertLine(cursorLine + 1, after) + + if (!minecraft.window.isCtrlDown) { + cursorLine++ + cursorRow = 0 + } + } + } else { + recordHistory(true) + insertLine(cursorLine + 1) + cursorLine++ + cursorRow = 0 + } + + triggerChangeCallback() + + return true + } else { + killFocus() + triggerChangeCallback() + onEnter() + return true + } + } + + if (key == InputConstants.KEY_LEFT) { + if (!minecraft.window.isShiftDown) { + advanceCursorLeft(minecraft.window.isCtrlDown) + selections.clear() + } else { + if (this[cursorLine] == null) + return true + + simulateSelectLeft(minecraft.window.isCtrlDown) + } + + return true + } else if (key == InputConstants.KEY_RIGHT) { + if (!minecraft.window.isShiftDown) { + advanceCursorRight(minecraft.window.isCtrlDown) + selections.clear() + } else { + if (this[cursorLine] == null) + return true + + simulateSelectRight(minecraft.window.isCtrlDown) + } + + return true + } else if (key == InputConstants.KEY_UP) { + if (minecraft.window.isShiftDown) { + simulateSelectionUp() + } else { + if (cursorLine > 0) { + cursorLine-- + } + + selections.clear() + } + + return true + } else if (key == InputConstants.KEY_DOWN) { + if (minecraft.window.isShiftDown) { + simulateSelectionDown() + } else { + if (cursorLine < lines.size - 1) { + cursorLine++ + } + + selections.clear() + } + + return true + } + + if (key == InputConstants.KEY_BACKSPACE) { + if (selections.isNotEmpty()) { + wipeSelection() + return true + } + + val line = this[cursorLine] + + if (cursorLine <= 0 && cursorRow <= 0) { + return true + } else if (cursorRow <= 0 || line?.length == 0) { + if (line == null) { + cursorRow = this[--cursorLine]?.length ?: 0 + recordHistory() + } else { + removeLine(cursorLine) + val newLine = this[cursorLine] + + if (newLine == null) { + this[cursorLine] = line + } else { + this[cursorLine] = newLine + line + } + + recordHistory() + triggerChangeCallback() + } + } else { + if (line != null) { + pushbackSnapshotIfNoTimer() + + // remove from very end + if (cursorRow >= line.length) { + val newLine = line.substring(0, line.length - 1) + moveCursors(cursorLine, cursorRow, -1) + this[cursorLine] = newLine + } else { + val newLine = line.substring(0, cursorRow - 1) + line.substring(cursorRow) + moveCursors(cursorLine, cursorRow, -1) + this[cursorLine] = newLine + } + + recordHistory() + triggerChangeCallback() + } else if (cursorLine > 0) { + cursorLine-- + recordHistory() + } + } + + return true + } + + if (key == InputConstants.KEY_DELETE) { + if (selections.isNotEmpty()) { + wipeSelection() + return true + } + + if (cursorLine !in 0 until lines.size) { + cursorLine = (lines.size - 1).coerceAtLeast(0) + return true + } + + if (minecraft.window.isShiftDown) { + pushbackSnapshot() + removeLine(cursorLine) + + if (cursorLine >= lines.size) + cursorLine = lines.size + + cursorRow = 0 + recordHistory(true) + triggerChangeCallback() + return true + } + + val line = this[cursorLine]!! + + if (cursorRow >= line.length) { + if (cursorLine + 1 == lines.size) { + return true + } + + pushbackSnapshotIfNoTimer() + + val bottomLine = this[cursorLine + 1]!! + cursorRow = line.length + this[cursorLine] = line + bottomLine + removeLine(cursorLine + 1) + } else { + pushbackSnapshotIfNoTimer() + + val cursorCharacter = cursorRow + moveCursors(cursorLine, cursorCharacter, -1) + this[cursorLine] = line.substring(0, cursorCharacter) + line.substring(cursorCharacter + 1) + + if (cursorCharacter != 0) + this.cursorRow++ + } + + recordHistory() + triggerChangeCallback() + return true + } + + if (key == InputConstants.KEY_Z && minecraft.window.isCtrlDown) { + undo() + return true + } else if (key == InputConstants.KEY_Y && minecraft.window.isCtrlDown) { + redo() + return true + } + + if (key == InputConstants.KEY_A && minecraft.window.isCtrlDown) { + selections.clear() + + for ((i, line) in lines.withIndex()) { + selections[i] = TextSelection(0, line.length + 1) + } + + if (lines.isNotEmpty()) { + cursorLine = lines.size - 1 + cursorRow = lines[lines.size - 1].length + } + + return true + } + + if (key == InputConstants.KEY_V && minecraft.window.isCtrlDown) { + wipeSelection() + pushbackSnapshot() + + if (multiLine) { + var index = cursorRow + (0 until cursorLine).iterator().map { this[it]?.length ?: 0 }.reduce(0, Int::plus) + val insert = minecraft.keyboardHandler.clipboard.replace("\t", " ").filter { acceptsCharacter(it, 0, index++) }.split(NEWLINES).toMutableList() + val actualLastSize = insert.lastOrNull()?.length ?: 0 + val line = this[cursorLine] + + if (line == null) { + insertLine(cursorLine, insert[0]) + cursorRow = insert[0].length + } else if (line.isEmpty()) { + this[cursorLine] = insert[0] + cursorRow = insert[0].length + } else { + if (insert.size == 1) { + this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert[0] + line.substring(cursorRow.coerceAtMost(line.length)) + } else { + this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert[0] + insert[insert.size - 1] += line.substring(cursorRow.coerceAtMost(line.length)) + } + + cursorRow += insert[0].length + } + + for (i in 1 until insert.size - 1) { + insertLine(++cursorLine, insert[i]) + cursorRow = insert[i].length + } + + if (insert.size >= 2) { + val line2 = this[++cursorLine] + val last = insert.last() + + if (line2 == null) { + insertLine(cursorLine, last) + } else { + this[cursorLine] = last + line2 + } + + cursorRow = actualLastSize + } + } else { + var index = cursorRow + (0 until cursorLine).iterator().map { this[it]?.length ?: 0 }.reduce(0, Int::plus) + val insert = minecraft.keyboardHandler.clipboard.replace("\t", " ").replace(NEWLINES, "").filter { acceptsCharacter(it, 0, index++) } + val line = this[cursorLine] + + if (line == null) { + insertLine(cursorLine, insert) + cursorRow = insert.length + } else if (line.isEmpty()) { + this[cursorLine] = insert + cursorRow = insert.length + } else { + this[cursorLine] = line.substring(0, cursorRow.coerceAtMost(line.length - 1)) + insert + line.substring(cursorRow.coerceAtMost(line.length)) + cursorRow += insert.length + } + } + + pushbackSnapshot() + triggerChangeCallback() + return true + } + + if (key == InputConstants.KEY_C && minecraft.window.isCtrlDown) { + if (selections.isEmpty()) { + return true + } + + val build = ArrayList(selections.size) + + for ((lineNumber, selection) in selections.int2ObjectEntrySet()) { + val line = this[lineNumber] ?: continue + + if (selection.coversEntireLine(line)) { + build.add(line + "\n") + } else { + val (_, value) = selection.sub(line) + build.add(if (selection.coversNewline(line)) value + "\n" else value) + } + } + + minecraft.keyboardHandler.clipboard = build.joinToString("") + playGuiClickSound() + + return true + } + + if (key == InputConstants.KEY_HOME) { + if (minecraft.window.isShiftDown && cursorRow != 0) { + val thisLine = cursorLine + + while (cursorRow > 0 && thisLine == cursorLine) { + simulateSelectLeft(false) + } + } else { + if (!minecraft.window.isShiftDown) selections.clear() + cursorRow = 0 + } + + return true + } + + if (key == InputConstants.KEY_END) { + if (minecraft.window.isShiftDown) { + val line = this[cursorLine] ?: return true + if (cursorRow >= line.length) return true + val thisLine = cursorLine + + while (cursorRow < line.length && thisLine == cursorLine) { + simulateSelectRight(false) + } + } else { + if (!minecraft.window.isShiftDown) selections.clear() + cursorRow = this[cursorLine]?.length ?: 0 + } + + return true + } + + return true + } + + fun interface CharacterFilter : Predicate { + fun acceptsCharacter(codepoint: Char, mods: Int, index: Int): Boolean + + override fun test(t: Char): Boolean { + return acceptsCharacter(t, 0, 0) + } + } + + var characterFilter: CharacterFilter? = null + + fun allowEverything() { + characterFilter = null + } + + fun allowNumbersAndSign() { + characterFilter = CharacterFilter { it, _, i -> if (i == 0) it in SIGNS || it in NUMBERS else it in NUMBERS } + } + + fun allowNumbers() { + characterFilter = CharacterFilter { it, _, _ -> it in NUMBERS } + } + + fun allowHexNumbersAndSign() { + characterFilter = CharacterFilter { it, _, i -> if (i == 0) it in SIGNS || it in HEX else it in HEX } + } + + fun allowHexNumbers() { + characterFilter = CharacterFilter { it, _, _ -> it in HEX } + } + + open fun acceptsCharacter(codepoint: Char, mods: Int = 0, index: Int): Boolean { + return characterFilter?.acceptsCharacter(codepoint, mods, index) ?: true + } + + override fun charTypedInternal(codepoint: Char, mods: Int): Boolean { + if (!isActive) { + killFocus() + return true + } + + wipeSelection() + + if (!multiLine) + cursorLine = 0 + + var line = this[cursorLine] + + if (line == null) { + set(cursorLine, "") + line = "" + cursorRow = 0 + } + + var index = 0 + for (i in 0 until cursorLine) index += this[i]?.length ?: 0 + index += cursorRow + + if (!acceptsCharacter(codepoint, mods, index)) { + playGuiClickSound() + return true + } + + if (cursorRow >= line.length) + line += codepoint + else + line = line.substring(0, cursorRow) + codepoint + line.substring(cursorRow) + + pushbackSnapshotIfNoTimer() + + set(cursorLine, line) + moveCursors(cursorLine, cursorRow, 1) + recordHistory() + triggerChangeCallback() + + return true + } + + private val characterWidthCache = Char2IntOpenHashMap() + + private fun width(char: Char): Int { + return characterWidthCache.computeIfAbsent(char, Char2IntFunction { font.width(it.toString()) }) + } + + private fun width(text: String, beginning: Int = 0, end: Int = text.length - 1): Int { + var accumulate = 0 + + for (i in beginning.coerceAtLeast(0) .. end.coerceAtMost(text.length - 1)) { + accumulate += width(text[i]) + } + + return accumulate + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (!backgroundColor.isFullyTransparent) + graphics.renderRect(0f, 0f, width, height, color = backgroundColor) + + var topPadding = dockPadding.top + + if (multiLine) { + val heightInLines = ((height - dockPadding.top - dockPadding.bottom) / (font.lineHeight + rowSpacing)).toInt() + + if (heightInLines > 0) { + if (cursorLine < scrollLines) { + scrollLines = (cursorLine - 1).coerceAtLeast(0) + } else if (heightInLines + scrollLines < cursorLine) { + scrollLines = (cursorLine - heightInLines).coerceIn(0, lines.size - 2) + } + } + } else { + scrollLines = 0 + + topPadding += ((height - dockPadding.top - dockPadding.bottom - font.lineHeight).coerceAtLeast(0f) / 2f).roundToInt() + } + + val selectedLine = this[cursorLine] + + if (selectedLine != null) { + val w = width(selectedLine, end = cursorRow) - scrollPixels + + if (w < 0f) { + scrollPixels = (scrollPixels - 30f).coerceAtLeast(0f) + } else if (w >= width - dockPadding.right) { + scrollPixels += 30f + } + } else { + scrollPixels = 0f + } + + val stack = graphics.pose + + stack.pushPose() + stack.translate(-scrollPixels, 0f, 0f) + + var y = topPadding + + if (lines.isEmpty() || lines.size == 1 && lines[0] == "") { + graphics.draw( + font = font, + text = placeholder, + gravity = RenderGravity.TOP_LEFT, + x = dockPadding.left, + y = y, + color = placeholderColor + ) + } + + for (i in scrollLines until lines.size) { + val line = lines[i] + val selection = selections[i] + + graphics.draw( + text = line, + gravity = RenderGravity.TOP_LEFT, + x = dockPadding.left, + y = y, + color = textColor + ) + + if (selection != null && selection.isValid) { + val (before, selected) = selection.sub(line) + + var x = dockPadding.left + + if (before.isNotEmpty()) { + x += font.width(before).toFloat() + } + + val width = if (selection.coversNewline(line) && i != lines.size - 1) this.width - x + scrollPixels else font.width(selected).toFloat() + + RenderSystem.setShader(GameRenderer::getPositionShader) + RenderSystem.setShaderColor(cursorColor.red, cursorColor.green, cursorColor.blue, 0.4f) + //RenderSystem.enableColorLogicOp() + //RenderSystem.logicOp(GlStateManager.LogicOp.NOR) + RenderSystem.disableDepthTest() + RenderSystem.defaultBlendFunc() + RenderSystem.enableBlend() + + val builder = tesselator.builder + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION) + + builder.vertex(stack.last().pose(), x, y + font.lineHeight + rowSpacing, 0f).endVertex() + builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + rowSpacing, 0f).endVertex() + builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex() + builder.vertex(stack.last().pose(), x, y, 0f).endVertex() + + tesselator.end() + + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f) + //RenderSystem.disableColorLogicOp() + RenderSystem.enableDepthTest() + } + + y += font.lineHeight + rowSpacing + + if (y > height - dockPadding.bottom) + break + } + + if (isFocusedThis && milliTime % 1000L > 500L) { + val activeLine = this[cursorLine] + + if (activeLine == null || cursorRow >= activeLine.length) { + graphics.draw( + text = "_", + gravity = RenderGravity.TOP_LEFT, + x = dockPadding.left + (if (activeLine == null) 0f else font.width(activeLine).toFloat()), + y = topPadding + (cursorLine - scrollLines) * (font.lineHeight + rowSpacing), + color = cursorColor + ) + } else { + graphics.draw( + text = "|", + gravity = RenderGravity.TOP_LEFT, + x = dockPadding.left + font.width(activeLine.substring(0, cursorRow)).toFloat() - 1f, + y = topPadding + (cursorLine - scrollLines) * (font.lineHeight + rowSpacing), + color = cursorColor + ) + } + } + + stack.popPose() + + if (debugDraw) { + graphics.draw( + text = cursorLine.toString(), + gravity = RenderGravity.TOP_RIGHT, + x = width, + y = topPadding, + color = cursorColor + ) + + graphics.draw( + text = cursorRow.toString(), + gravity = RenderGravity.TOP_RIGHT, + x = width - dockPadding.right, + y = topPadding + font.lineHeight + rowSpacing, + color = cursorColor + ) + + graphics.draw( + text = lines.size.toString(), + gravity = RenderGravity.TOP_RIGHT, + x = width - dockPadding.right, + y = topPadding + font.lineHeight * 2f + rowSpacing * 2f, + color = cursorColor + ) + } + + BUFFER.endBatch() + } + + var isSelecting = false + protected set + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (!isActive) + return true + + if (isEverFocused()) + selections.clear() + + requestFocus() + + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (selections.isEmpty()) { + val (lx, ly) = screenToLocal(x, y) + val pos = localToText(lx, ly) + cursorLine = pos.y + cursorRow = pos.x + } + + isSelecting = true + tryToGrabMouseInput() + } + + return true + } + + @Suppress("name_shadowing") + fun localToText(x: Float, y: Float): Vector2i { + if (lines.isEmpty()) + return Vector2i(0, 0) + + var x = x - dockPadding.left + var y = y - dockPadding.top + + if (y > height - dockPadding.bottom) + y = height - dockPadding.bottom + + if (x > width - dockPadding.right) + x = width - dockPadding.right + + x += scrollPixels + + val line = (scrollLines + (y / (font.lineHeight + rowSpacing)).toInt()).coerceIn(0, lines.size - 1) + val sLine = this[line] ?: return Vector2i(0, line) + + if (x <= 0f) + return Vector2i(0, line) + else if (x >= width(sLine)) + return Vector2i(sLine.length, line) + + var accumulatedWidth = 0f + + for ((i, char) in sLine.withIndex()) { + val width = width(char) + + if (x in accumulatedWidth .. accumulatedWidth + width) { + if (x - accumulatedWidth < width / 2) + return Vector2i(i, line) + else + return Vector2i(i + 1, line) + } else { + accumulatedWidth += width + } + } + + return Vector2i(sLine.length, line) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (isSelecting) { + grabMouseInput = false + isSelecting = false + } + } + + return true + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (button != InputConstants.MOUSE_BUTTON_LEFT || !isSelecting) + return true + + val (lx, ly) = screenToLocal(x, y) + val pos = localToText(lx, ly) + val cursorLine = pos.y + val cursorCharacter = pos.x + + while (cursorLine > this.cursorLine && simulateSelectionDown()) {} + while (cursorLine < this.cursorLine && simulateSelectionUp()) {} + + var lastCursorCharacter = this.cursorRow + 1 + + while (cursorCharacter < this.cursorRow && lastCursorCharacter != this.cursorRow) { + lastCursorCharacter = this.cursorRow + simulateSelectLeft(false) + } + + while (cursorCharacter > this.cursorRow && lastCursorCharacter != this.cursorRow) { + lastCursorCharacter = this.cursorRow + simulateSelectRight(false) + } + + return true + } + + protected open fun onTextChanged(new: String, old: String) { + changeCallback?.invoke(new, old) + } + + protected var changeCallback: ((new: String, old: String) -> Unit)? = null + + fun onTextChange(callback: (new: String, old: String) -> Unit) { + changeCallback = callback + } + + private enum class CharType { + SPACES { + override fun contains(input: Char): Boolean { + return _SPACES.contains(input) + } + + override fun canJumpInto(category: CharType): Boolean { + return true + } + }, + CONTROL { + override fun contains(input: Char): Boolean { + return _CONTROL.contains(input) + } + + override fun canJumpInto(category: CharType): Boolean { + return category == CONTROL + } + }, + OTHER { + override fun contains(input: Char): Boolean { + return !_SPACES.contains(input) && !_CONTROL.contains(input) + } + + override fun canJumpInto(category: CharType): Boolean { + return category == OTHER + } + }; + + abstract fun contains(input: Char): Boolean + abstract fun canJumpInto(category: CharType): Boolean + + companion object { + private val values = values() + + operator fun get(input: Char): CharType { + for (value in values) { + if (value.contains(input)) { + return value + } + } + + throw NoSuchElementException("for $input") + } + } + } + + companion object { + val NEWLINES = Regex("\r?\n") + private val BUFFER = DynamicBufferSource() + + private val _SPACES = CharOpenHashSet().also { + for (char in " \t\n\r\u0000") + it.add(char) + } + + private val _CONTROL = CharOpenHashSet().also { + for (char in "!@\"'#$%^&?*()~`[]/\\;:|<>.,") + it.add(char) + } + + private val NUMBERS = CharOpenHashSet().also { + for (char in "0123456789.") + it.add(char) + } + + private val SIGNS = CharOpenHashSet().also { + for (char in "-+") + it.add(char) + } + + private val HEX = CharOpenHashSet().also { + for (char in "0123456789ABCDEFabcdef") + it.add(char) + } + + private fun greedyAdvanceLeft(input: String, position: Int): Int { + if (position <= 1) + return -1 + + @Suppress("name_shadowing") + var position = position + var type = CharType[input[--position]] + + while (position > 0) { + val newType = CharType[input[position]] + + if (type.canJumpInto(newType)) { + position-- + type = newType + } else { + return position + 1 + } + } + + return position + } + + private fun greedyAdvanceRight(input: String, position: Int): Int { + @Suppress("name_shadowing") + var position = position + + if (position < 0) + position = 0 + else if (position >= input.length) + return position + 1 + + var type = CharType[input[position]] + + while (position < input.length) { + val newType = CharType[input[position]] + + if (type.canJumpInto(newType)) { + position++ + type = newType + } else { + return position + } + } + + return position + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt new file mode 100644 index 000000000..ea53cd915 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt @@ -0,0 +1,86 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.slot + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import net.minecraftforge.client.extensions.common.IClientItemExtensions +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.* +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.IItemStackPanel +import ru.dbotthepony.mc.otm.core.math.RGBAColor + +abstract class AbstractSlotPanel> @JvmOverloads constructor( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, +) : EditablePanel(screen, parent, x, y, width, height), IItemStackPanel { + open var slotBackground: IGUIRenderable? = null + open var slotBackgroundEmpty: IGUIRenderable? = null + + protected open fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + SLOT_BACKGROUND.render(graphics, width = width, height = height) + slotBackground?.render(graphics, 0f, 0f, width, height) + } + + protected open fun renderRegular(graphics: MGUIGraphics, itemstack: ItemStack, countOverride: String? = null) { + RenderSystem.setShader(GameRenderer::getPositionTexShader) + + if (!itemstack.isEmpty) { + screen.renderItemStack(absoluteX, absoluteY, itemstack, countOverride) + clearDepth(graphics) + } + + if (isHovered) { + graphics.renderRect(1f, 1f, width - 1, height - 1, color = SLOT_HIGHLIGHT) + } + } + + protected open fun getItemStackTooltip(stack: ItemStack): MutableList { + return screen.getTooltipFromItem(stack) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderSlotBackground(graphics, mouseX, mouseY, partialTick) + val itemStack = itemStack + renderRegular(graphics, itemStack) + + if (itemStack.isEmpty) { + slotBackgroundEmpty?.render(graphics, 0f, 0f, width, height) + } + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered) { + val itemstack = itemStack + + if (!itemstack.isEmpty) { + val font = IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) + + graphics.renderComponentTooltip( + font ?: screen.font, + getItemStackTooltip(itemstack), + mouseX.toInt(), + mouseY.toInt(), + itemstack + ) + + return true + } + } + return false + } + + companion object { + val SLOT_HIGHLIGHT = RGBAColor(255, 255, 255, 100) + val SLOT_HIGHLIGHT_DRAG = RGBAColor(200, 200, 200, 150) + const val SIZE = 18f + val SLOT_BACKGROUND = WidgetLocation.MISC.sprite(0f, 0f, SIZE, SIZE) + val FILTERED_SLOT_BACKGROUND = WidgetLocation.MISC.sprite(46f, 0f, SIZE, SIZE) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt new file mode 100644 index 000000000..e3f67d5ea --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.slot + +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.GetterSetter + +open class FilterSlotPanel> @JvmOverloads constructor( + screen: S, + parent: EditablePanel<*>?, + val slot: GetterSetter, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE +) : AbstractSlotPanel(screen, parent, x, y, width, height) { + override val itemStack: ItemStack get() { + return slot.get() + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + slot.accept(screen.menu.carried) + return true + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FoldableSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt similarity index 86% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FoldableSlotPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt index d0b3a2a82..a74d550bb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FoldableSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FoldableSlotPanel.kt @@ -1,9 +1,12 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.slot import net.minecraft.world.inventory.Slot import ru.dbotthepony.mc.otm.SystemTime import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.nanoTime +import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.ISlotPanel open class FoldableSlotPanel, out T : Slot> @JvmOverloads constructor( screen: S, @@ -61,8 +64,8 @@ open class FoldableSlotPanel, out T : Slot> @JvmOverloa remove() } - override fun tick() { - super.tick() + override fun tickInner() { + super.tickInner() x = this@FoldableSlotPanel.absoluteX - dockPadding.left y = this@FoldableSlotPanel.absoluteY - dockPadding.top @@ -89,8 +92,8 @@ open class FoldableSlotPanel, out T : Slot> @JvmOverloa hoverPanel?.remove() } - override fun tick() { - super.tick() + override fun tickInner() { + super.tickInner() val hoveringSince = hoveringSince diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt new file mode 100644 index 000000000..caa01f6aa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.slot + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.world.item.Item +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.isAltDown +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +open class InventorySlotPanel, out T : MatteryMenu.InventorySlot>( + screen: S, + parent: EditablePanel<*>?, + slot: T, + x: Float = 0f, + y: Float = 0f, +) : UserFilteredSlotPanel(screen, parent, slot, x, y, SIZE, SIZE) { + override var slotFilter: Item? + get() = slot.filter?.get() + set(value) { slot.filter?.accept(value) } + + override fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (slot.chargeFlag?.get() == true) { + Widgets18.CHARGE_SLOT_BACKGROUND.render(graphics, 0f, 0f, width, height) + } + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isAltDown) { + val chargeFlag = slot.chargeFlag + + if (chargeFlag == null) { + return super.mouseClickedInner(x, y, button) + } else { + chargeFlag.accept(!chargeFlag.get()) + playGuiClickSound() + return true + } + } + + return super.mouseClickedInner(x, y, button) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt similarity index 69% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SlotPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt index 263da4a72..5ec271a7d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/SlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt @@ -1,22 +1,19 @@ @file:Suppress("FunctionName") -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.slot import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.ChatFormatting -import net.minecraft.client.gui.GuiComponent -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.client.renderer.GameRenderer import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.SkinElement import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.render.drawRect -import ru.dbotthepony.mc.otm.client.render.setDrawColor import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.ISlotPanel import javax.annotation.Nonnull import kotlin.math.roundToInt @@ -28,8 +25,7 @@ open class SlotPanel, out T : Slot> @JvmOverloads const y: Float = 0f, width: Float = SIZE, height: Float = SIZE, - noItemIcon: SkinElement? = null -) : AbstractSlotPanel(screen, parent, x, y, width, height, noItemIcon), ISlotPanel { +) : AbstractSlotPanel(screen, parent, x, y, width, height), ISlotPanel { override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { screen.returnSlot = slot return true @@ -55,10 +51,10 @@ open class SlotPanel, out T : Slot> @JvmOverloads const } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { slot.x = absoluteX.roundToInt() - screen.guiLeft slot.y = absoluteY.roundToInt() - screen.guiTop - renderSlotBackground(stack, mouseX, mouseY, partialTick) + renderSlotBackground(graphics, mouseX, mouseY, partialTick) var itemstack = slot.item val carried = screen.menu.carried @@ -81,7 +77,8 @@ open class SlotPanel, out T : Slot> @JvmOverloads const if (slot.item.isEmpty) 0 else slot.item.count ) - val k = Math.min(itemstack.maxStackSize, slot.getMaxStackSize(itemstack)) + val k = itemstack.maxStackSize.coerceAtMost(slot.getMaxStackSize(itemstack)) + if (itemstack.count > k) { countOverride = ChatFormatting.YELLOW.toString() + k itemstack.count = k @@ -95,8 +92,7 @@ open class SlotPanel, out T : Slot> @JvmOverloads const RenderSystem.setShader(GameRenderer::getPositionTexShader) if (dragHit) { - setDrawColor(SLOT_HIGHLIGHT_DRAG) - drawRect(stack, 1f, 1f, width - 1, height - 1) + graphics.renderRect(1f, 1f, width - 1, height - 1, color = SLOT_HIGHLIGHT_DRAG) } if (itemstack.isEmpty) { @@ -104,21 +100,21 @@ open class SlotPanel, out T : Slot> @JvmOverloads const if (icon != null) { val texture = minecraft.getTextureAtlas(icon.first).apply(icon.second) - RenderSystem.setShaderTexture(0, texture.atlas().location()) - GuiComponent.blit(stack, 1, 1, 0, 16, 16, texture) + RenderSystem.setShaderTexture(0, texture.atlasLocation()) + graphics.renderSprite(texture, 1f, 1f, 16f, 16f) } else { - noItemIcon?.render(stack, width = width, height = height) + slotBackgroundEmpty?.render(graphics, 0f, 0f, width = width, height = height) } } - renderRegular(stack, itemstack, countOverride) + renderRegular(graphics, itemstack, countOverride) if (isHovered) { screen.hoveredSlot = slot } } - override fun innerRenderTooltips(@Nonnull stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(@Nonnull graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { // no op, screen does it for us (completely) return false } @@ -132,7 +128,7 @@ fun , T : Slot> BatterySlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height, Widgets18.BATTERY_SLOT_BACKGROUND) +) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.BATTERY_SLOT_BACKGROUND } fun , T : Slot> EquipmentBatterySlotPanel( screen: S, @@ -142,7 +138,7 @@ fun , T : Slot> EquipmentBatterySlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height, Widgets18.EQUIPMENT_BATTERY_SLOT_BACKGROUND) +) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.EQUIPMENT_BATTERY_SLOT_BACKGROUND } fun , T : Slot> PatternSlotPanel( screen: S, @@ -152,7 +148,7 @@ fun , T : Slot> PatternSlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height, Widgets18.PATTERN_SLOT_BACKGROUND) +) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.PATTERN_SLOT_BACKGROUND } fun , T : Slot> MatterCapacitorSlotPanel( screen: S, @@ -162,4 +158,4 @@ fun , T : Slot> MatterCapacitorSlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height, Widgets18.MATTER_CAPACITOR_SLOT_BACKGROUND) +) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.MATTER_CAPACITOR_SLOT_BACKGROUND } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt new file mode 100644 index 000000000..e8dcf674c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt @@ -0,0 +1,148 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.slot + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraftforge.client.extensions.common.IClientItemExtensions +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.isCtrlDown +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.menu.UserFilteredSlot + +abstract class UserFilteredSlotPanel, out T : Slot>( + screen: S, + parent: EditablePanel<*>?, + slot: T, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, +) : SlotPanel(screen, parent, slot, x, y, width, height) { + abstract var slotFilter: Item? + + protected open fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + + override fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.renderSlotBackground(graphics, mouseX, mouseY, partialTick) + + renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick) + + if (slotFilter != null) { + if (slotFilter !== Items.AIR) { + val itemStack = ItemStack(slotFilter!!, 1) + + screen.renderItemStack(absoluteX, absoluteY, itemStack, null) + clearDepth(graphics) + + graphics.renderRect(0f, 0f, width, height, color = SLOT_FILTER_COLOR) + } else { + graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR) + } + } + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered && slotFilter != null && slotFilter !== Items.AIR && itemStack.isEmpty) { + val itemstack = ItemStack(slotFilter!!, 1) + + graphics.renderComponentTooltip( + IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font, + getItemStackTooltip(itemstack).toMutableList().also { + it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY)) + it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) + it.add(2, TextComponent("")) + }, + mouseX.toInt(), + mouseY.toInt(), + itemstack + ) + } else if (isHovered && slotFilter === Items.AIR && itemStack.isEmpty) { + graphics.renderComponentTooltip( + font, + ArrayList().also { + it.add(TranslatableComponent("otm.gui.slot_filter.forbidden").withStyle(ChatFormatting.GRAY)) + it.add(TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) + }, + mouseX.toInt(), + mouseY.toInt() + ) + } + + return super.innerRenderTooltips(graphics, mouseX, mouseY, partialTick) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) { + if (slotFilter === null) { + if (screen.menu.carried.isEmpty) { + slotFilter = slot.item.item + } else { + slotFilter = screen.menu.carried.item + } + } else { + slotFilter = null + } + + playGuiClickSound() + + return true + } else { + return super.mouseClickedInner(x, y, button) + } + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) { + return true + } + + return super.mouseReleasedInner(x, y, button) + } + + companion object { + val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150) + val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150) + + fun , T : Slot> of( + screen: S, + parent: EditablePanel<*>?, + slot: T, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, + filter: GetterSetter + ): UserFilteredSlotPanel { + return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) { + override var slotFilter: Item? by filter + } + } + + fun , T : UserFilteredSlot> of( + screen: S, + parent: EditablePanel<*>?, + slot: T, + x: Float = 0f, + y: Float = 0f, + width: Float = SIZE, + height: Float = SIZE, + ): UserFilteredSlotPanel { + return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) { + override var slotFilter: Item? + get() = slot.filter?.get() + set(value) { slot.filter?.accept(value) } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/AnalogScrollBarPanel.kt similarity index 74% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/AnalogScrollBarPanel.kt index 226cbac40..83a7f8f7d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/AnalogScrollBarPanel.kt @@ -1,9 +1,11 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.util import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.core.linearInterpolation +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.core.math.linearInterpolation import ru.dbotthepony.mc.otm.milliTimeD import kotlin.math.roundToInt @@ -24,26 +26,26 @@ open class AnalogScrollBarPanel( var isScrolling = false private set - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (this@AnalogScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) { if (isScrolling) { - ScrollBarConstants.SLIM_BUTTON_PRESS.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_PRESS.render(graphics, width = width, height = height) } else if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0) { - ScrollBarConstants.SLIM_BUTTON_DISABLED.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_DISABLED.render(graphics, width = width, height = height) } else if (isHovered) { - ScrollBarConstants.SLIM_BUTTON_HOVER.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_HOVER.render(graphics, width = width, height = height) } else { - ScrollBarConstants.SLIM_BUTTON.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON.render(graphics, width = width, height = height) } } else { if (isScrolling) { - ScrollBarConstants.BUTTON_PRESS.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_PRESS.render(graphics, width = width, height = height) } else if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0) { - ScrollBarConstants.BUTTON_DISABLED.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_DISABLED.render(graphics, width = width, height = height) } else if (isHovered) { - ScrollBarConstants.BUTTON_HOVER.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_HOVER.render(graphics, width = width, height = height) } else { - ScrollBarConstants.BUTTON.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON.render(graphics, width = width, height = height) } } } @@ -96,15 +98,15 @@ open class AnalogScrollBarPanel( private var lastRender = milliTimeD - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (width == ScrollBarConstants.SLIM_WIDTH) { - ScrollBarConstants.SLIM_BODY.render(stack, y = 2f, height = height - 4f) - ScrollBarConstants.SLIM_TOP.render(stack) - ScrollBarConstants.SLIM_BOTTOM.render(stack, y = height - 2f) + ScrollBarConstants.SLIM_BODY.render(graphics, y = 2f, height = height - 4f) + ScrollBarConstants.SLIM_TOP.render(graphics) + ScrollBarConstants.SLIM_BOTTOM.render(graphics, y = height - 2f) } else { - ScrollBarConstants.BODY.render(stack, y = 2f, height = height - 4f) - ScrollBarConstants.TOP.render(stack) - ScrollBarConstants.BOTTOM.render(stack, y = height - 2f) + ScrollBarConstants.BODY.render(graphics, y = 2f, height = height - 4f) + ScrollBarConstants.TOP.render(graphics) + ScrollBarConstants.BOTTOM.render(graphics, y = height - 2f) } val time = milliTimeD diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/BackgroundPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/BackgroundPanel.kt new file mode 100644 index 000000000..c2f416be8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/BackgroundPanel.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.util + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.sprites.StretchingRectangleElement +import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel + +open class BackgroundPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 10f, + height: Float = 10f, +) : EditablePanel(screen, parent, x, y, width, height) { + init { + dockPadding = DockProperty(3f, 3f, 3f, 3f) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + RECTANGLE.render(graphics, width = width, height = height) + } + + companion object { + fun padded( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 10f, + height: Float = 10f, + ) = BackgroundPanel(screen, parent, x, y, width + 6f, height + 6f) + + fun paddedCenter( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 10f, + height: Float = 10f, + ) = BackgroundPanel(screen, parent, x - 3f, y - 3f, width + 6f, height + 6f) + + val RECTANGLE = StretchingRectangleElement( + topLeft = WidgetLocation.MISC.sprite(18f, 24f, 3f, 3f), + topRight = WidgetLocation.MISC.sprite(27f, 24f, 3f, 3f), + bottomLeft = WidgetLocation.MISC.sprite(18f, 33f, 3f, 3f), + bottomRight = WidgetLocation.MISC.sprite(27f, 33f, 3f, 3f), + top = WidgetLocation.MISC.sprite(21f, 24f, 6f, 2f), + bottom = WidgetLocation.MISC.sprite(21f, 34f, 6f, 2f), + left = WidgetLocation.MISC.sprite(18f, 27f, 2f, 6f), + right = WidgetLocation.MISC.sprite(28f, 27f, 2f, 6f), + middle = WidgetLocation.MISC.sprite(30f, 12f, 6f, 6f), + padding = DockProperty(-1f, -1f, -1f, -1f) + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DiscreteScrollBarPanel.kt similarity index 69% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DiscreteScrollBarPanel.kt index a2abea412..e5f9a23f2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DiscreteScrollBarPanel.kt @@ -1,8 +1,9 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.util import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import kotlin.math.roundToInt open class DiscreteScrollBarPanel( @@ -19,26 +20,26 @@ open class DiscreteScrollBarPanel( var isScrolling = false private set - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (this@DiscreteScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) { if (isScrolling) { - ScrollBarConstants.SLIM_BUTTON_PRESS.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_PRESS.render(graphics, width = width, height = height) } else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) { - ScrollBarConstants.SLIM_BUTTON_DISABLED.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_DISABLED.render(graphics, width = width, height = height) } else if (isHovered) { - ScrollBarConstants.SLIM_BUTTON_HOVER.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON_HOVER.render(graphics, width = width, height = height) } else { - ScrollBarConstants.SLIM_BUTTON.render(stack, width = width, height = height) + ScrollBarConstants.SLIM_BUTTON.render(graphics, width = width, height = height) } } else { if (isScrolling) { - ScrollBarConstants.BUTTON_PRESS.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_PRESS.render(graphics, width = width, height = height) } else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) { - ScrollBarConstants.BUTTON_DISABLED.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_DISABLED.render(graphics, width = width, height = height) } else if (isHovered) { - ScrollBarConstants.BUTTON_HOVER.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON_HOVER.render(graphics, width = width, height = height) } else { - ScrollBarConstants.BUTTON.render(stack, width = width, height = height) + ScrollBarConstants.BUTTON.render(graphics, width = width, height = height) } } } @@ -89,15 +90,15 @@ open class DiscreteScrollBarPanel( val scrollButton = Button() - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (width == ScrollBarConstants.SLIM_WIDTH) { - ScrollBarConstants.SLIM_BODY.render(stack, y = 2f, height = height - 4f) - ScrollBarConstants.SLIM_TOP.render(stack) - ScrollBarConstants.SLIM_BOTTOM.render(stack, y = height - 2f) + ScrollBarConstants.SLIM_BODY.render(graphics, y = 2f, height = height - 4f) + ScrollBarConstants.SLIM_TOP.render(graphics) + ScrollBarConstants.SLIM_BOTTOM.render(graphics, y = height - 2f) } else { - ScrollBarConstants.BODY.render(stack, y = 2f, height = height - 4f) - ScrollBarConstants.TOP.render(stack) - ScrollBarConstants.BOTTOM.render(stack, y = height - 2f) + ScrollBarConstants.BODY.render(graphics, y = 2f, height = height - 4f) + ScrollBarConstants.TOP.render(graphics) + ScrollBarConstants.BOTTOM.render(graphics, y = height - 2f) } } @@ -108,7 +109,7 @@ open class DiscreteScrollBarPanel( var scroll = 0 set(value) { - val newValue = value.coerceAtLeast(0).coerceAtMost(maxScroll.invoke(this)) + val newValue = value.coerceIn(0, maxScroll.invoke(this)) if (newValue != field) { val old = field diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DraggableCanvasPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DraggableCanvasPanel.kt similarity index 87% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DraggableCanvasPanel.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DraggableCanvasPanel.kt index ca49b34a5..ec27abccf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DraggableCanvasPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/DraggableCanvasPanel.kt @@ -1,6 +1,7 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.util import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel open class DraggableCanvasPanel @JvmOverloads constructor( screen: S, @@ -40,4 +41,4 @@ open class DraggableCanvasPanel @JvmOverloads constructor( init { scissor = true } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt new file mode 100644 index 000000000..55a550420 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt @@ -0,0 +1,89 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.util + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.maybe + +open class GridPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 0f, + height: Float = 0f, + columns: Int, + rows: Int +) : EditablePanel(screen, parent, x, y, width, height) { + var columns: Int = columns + set(value) { + field = value + invalidateLayout() + } + + var rows: Int = rows + set(value) { + field = value + invalidateLayout() + } + + var gravity: RenderGravity = RenderGravity.CENTER_CENTER + set(value) { + field = value + invalidateLayout() + } + + override fun performLayout() { + super.performLayout() + var children = visibleChildren.iterator().filter { it.dock == Dock.NONE } + + var totalWidth = 0f + var totalHeight = 0f + + for (row in 0 until rows) { + var maxHeight = 0f + var width = 0f + + for (column in 0 until columns) { + val child = children.maybe() ?: break + width += child.dockedWidth + maxHeight = maxHeight.coerceAtLeast(child.dockedHeight) + } + + totalWidth = totalWidth.coerceAtLeast(width) + totalHeight += maxHeight + } + + val alignX = gravity.repositionX(width, totalWidth) + val alignY = gravity.repositionY(height, totalHeight) + children = visibleChildren.iterator().filter { it.dock == Dock.NONE } + + totalWidth = 0f + totalHeight = 0f + + for (row in 0 until rows) { + var maxHeight = 0f + var width = 0f + + for (column in 0 until columns) { + val child = children.maybe() ?: break + child.x = alignX + width + child.y = alignY + totalHeight + width += child.dockedWidth + maxHeight = maxHeight.coerceAtLeast(child.dockedHeight) + } + + totalWidth = totalWidth.coerceAtLeast(width) + totalHeight += maxHeight + } + } + + companion object { + fun slots(screen: S, parent: EditablePanel<*>?, columns: Int, rows: Int): GridPanel { + return GridPanel(screen, parent, width = columns * AbstractSlotPanel.SIZE, height = rows * AbstractSlotPanel.SIZE, rows = rows, columns = columns) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/HeightControls.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HeightControls.kt similarity index 53% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/HeightControls.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HeightControls.kt index 4ebe6a900..653bfe9f5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/HeightControls.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HeightControls.kt @@ -1,10 +1,12 @@ -package ru.dbotthepony.mc.otm.client.screen.panels +package ru.dbotthepony.mc.otm.client.screen.panels.util import com.mojang.blaze3d.platform.InputConstants import net.minecraft.client.gui.screens.Screen -import ru.dbotthepony.mc.otm.client.render.AbstractSkinElement +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.Widgets -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.RectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.RectangleButtonPanel open class HeightControls( screen: S, @@ -44,21 +46,25 @@ open class HeightControls( decrease.isDisabled = !value.canDecrease } - open inner class Control(val isIncrease: Boolean) : RectangleButtonPanel(screen, this@HeightControls, width = BUTTON_WIDTH, height = BUTTON_HEIGHT) { - override val PRESSED: AbstractSkinElement = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_PRESSED else Widgets.ARROW_UP_BUTTON_PRESSED - override val HOVERED: AbstractSkinElement = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_HOVERED else Widgets.ARROW_UP_BUTTON_HOVERED - override val IDLE: AbstractSkinElement = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_IDLE else Widgets.ARROW_UP_BUTTON_IDLE - override val DISABLED: AbstractSkinElement = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_DISABLED else Widgets.ARROW_UP_BUTTON_DISABLED + inner class Control(val isIncrease: Boolean) : RectangleButtonPanel(screen, this@HeightControls, width = BUTTON_WIDTH, height = BUTTON_HEIGHT) { + override val PRESSED: AbstractMatterySprite = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_PRESSED else Widgets.ARROW_UP_BUTTON_PRESSED + override val HOVERED: AbstractMatterySprite = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_HOVERED else Widgets.ARROW_UP_BUTTON_HOVERED + override val IDLE: AbstractMatterySprite = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_IDLE else Widgets.ARROW_UP_BUTTON_IDLE + override val DISABLED: AbstractMatterySprite = if (isIncrease) Widgets.ARROW_DOWN_BUTTON_DISABLED else Widgets.ARROW_UP_BUTTON_DISABLED init { dock = Dock.TOP dockBottom = 2f } - override fun onClick(clickButton: Int) { - if (clickButton == InputConstants.MOUSE_BUTTON_LEFT) { + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || value == InputConstants.MOUSE_BUTTON_RIGHT + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { this@HeightControls.onClick(isIncrease) - } else if (clickButton == InputConstants.MOUSE_BUTTON_RIGHT) { + } else if (mouseButton == InputConstants.MOUSE_BUTTON_RIGHT) { this@HeightControls.onClick(!isIncrease) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt new file mode 100644 index 000000000..fa8b8e496 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/HorizontalStripPanel.kt @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.util + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import kotlin.math.roundToInt + +class HorizontalStripPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 0f, + height: Float = 0f, +) : EditablePanel(screen, parent, x, y, width, height) { + override fun sizeToContents(performLayout: Boolean) { + var w = 0f + var h = 0f + + for (child in visibleChildren) { + if (child.dock == Dock.NONE) { + w += child.width + child.dockMargin.horizontal + h = h.coerceAtLeast(child.height) + } + } + + width = w + height = h + } + + override fun performLayout() { + super.performLayout() + + var w = 0f + + for (child in visibleChildren) { + if (child.dock == Dock.NONE) { + w += child.width + child.dockMargin.horizontal + } + } + + w = width / 2f - w / 2f + + for (child in visibleChildren) { + if (child.dock == Dock.NONE) { + child.y = (height / 2f - child.height / 2f).roundToInt().toFloat() + child.x = (w + child.dockMargin.left).roundToInt().toFloat() + w += child.dockMargin.horizontal + child.width + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt new file mode 100644 index 000000000..5309d2144 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollBarConstants.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.util + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas +import ru.dbotthepony.mc.otm.client.render.sprites.sprite + +object ScrollBarConstants { + const val WIDTH = 14f + const val SLIM_WIDTH = 8f + + val NORMAL_ATLAS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/scrollbar.png"), 62f, 15f) + val SLIM_ATLAS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/scrollbar_slim.png"), 32f, 15f) + + val BACKGROUND = NORMAL_ATLAS.sprite(width = 14f, height = 8f) + val BACKGROUND_SLIM = SLIM_ATLAS.sprite(width = 8f, height = 8f) + + val TOP = BACKGROUND.sprite(height = 2f) + val BODY = BACKGROUND.sprite(y = 2f, height = 5f) + val BOTTOM = BACKGROUND.sprite(y = 6f, height = 2f) + + val BUTTON = NORMAL_ATLAS.sprite(x = 14f, width = 12f) + val BUTTON_HOVER = NORMAL_ATLAS.sprite(x = 14f + 12f, width = 12f) + val BUTTON_PRESS = NORMAL_ATLAS.sprite(x = 14f + 12f * 2f, width = 12f) + val BUTTON_DISABLED = NORMAL_ATLAS.sprite(x = 14f + 12f * 3f, width = 12f) + + val SLIM_TOP = BACKGROUND_SLIM.sprite(height = 2f) + val SLIM_BODY = BACKGROUND_SLIM.sprite(y = 2f, height = 5f) + val SLIM_BOTTOM = BACKGROUND_SLIM.sprite(y = 6f, height = 2f) + + val SLIM_BUTTON = SLIM_ATLAS.sprite(x = 8f, width = 6f) + val SLIM_BUTTON_HOVER = SLIM_ATLAS.sprite(x = 8f + 6f, width = 6f) + val SLIM_BUTTON_PRESS = SLIM_ATLAS.sprite(x = 8f + 6f * 2f, width = 6f) + val SLIM_BUTTON_DISABLED = SLIM_ATLAS.sprite(x = 8f + 6f * 3f, width = 6f) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollableCanvasPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollableCanvasPanel.kt new file mode 100644 index 000000000..4a9f266a0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/ScrollableCanvasPanel.kt @@ -0,0 +1,72 @@ +package ru.dbotthepony.mc.otm.client.screen.panels.util + +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel + +class ScrollableCanvasPanel( + screen: S, + parent: EditablePanel<*>, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 40f, + slimScrollbar: Boolean = false +) : EditablePanel(screen, parent, x, y, width, height) { + val canvas = object : EditablePanel(screen, this@ScrollableCanvasPanel) { + init { + dock = Dock.FILL + dockRight = 1f + scissor = true + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollbar.mouseScrolledInner(x, y, scroll) + } + + override fun performLayout() { + super.performLayout() + + var y = 0f + var maxHeight = 0f + var x = 0f + + for (child in visibleChildren.filter { it.dock == Dock.NONE }) { + if (x > 0f && x + child.width > this.width) { + y += maxHeight + x = 0f + maxHeight = 0f + } + + child.x = child.dockLeft + x + child.y = child.dockTop + y + x += child.width + child.dockMargin.horizontal + maxHeight = maxHeight.coerceAtLeast(child.height + child.dockMargin.vertical) + } + + updateBounds() + + if (getMaxScroll(scrollbar) <= scrollbar.scroll) { + scrollbar.scroll = getMaxScroll(scrollbar) + } + } + } + + val scrollbar = AnalogScrollBarPanel(screen, this, maxScroll = ::getMaxScroll, smoothScrollCallback = ::onScrollUpdate, isSlim = slimScrollbar) + + init { + scrollbar.dock = Dock.RIGHT + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollbar.mouseScrolledInner(x, y, scroll) + } + + private fun getMaxScroll(it: AnalogScrollBarPanel<*>): Float { + return (canvas.childrenRectHeight - canvas.height).coerceAtLeast(0f) + } + + private fun onScrollUpdate(it: AnalogScrollBarPanel<*>, old: Float, new: Float) { + this.canvas.yOffset = -new + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveRackScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveRackScreen.kt new file mode 100644 index 000000000..a153f8e41 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveRackScreen.kt @@ -0,0 +1,55 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeEnumRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.input.IntInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent + +class DriveRackScreen(menu: DriveRackMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + makeBars(frame, profiledEnergy = menu.profiledEnergy, batterySlot = menu.batterySlot) + + val grid = GridPanel.slots(this, frame, 2, 2) + grid.dock = Dock.FILL + + for (slot in menu.storageSlots) + SlotPanel(this, grid, slot) + + IntInputPanel(this, frame, menu.insertPriority).also { + it.dock = Dock.BOTTOM + it.tooltips.add(TranslatableComponent("otm.gui.insert_priority")) + it.childrenOrder = -1 + it.dockMargin = DockProperty(left = 20f, right = 10f, bottom = 2f) + } + + IntInputPanel(this, frame, menu.extractPriority).also { + it.dock = Dock.BOTTOM + it.tooltips.add(TranslatableComponent("otm.gui.extract_priority")) + it.childrenOrder = -2 + it.dockMargin = DockProperty(left = 20f, right = 10f, bottom = 2f) + } + + val controls = DeviceControls(this, frame, energyConfig = menu.energyConfig, redstoneConfig = menu.redstoneConfig) + controls.addStorageMode(menu.mode) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt new file mode 100644 index 000000000..95769454c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt @@ -0,0 +1,84 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter +import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem +import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu +import ru.dbotthepony.mc.otm.registry.MItems +import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak + +@MouseTweaksDisableWheelTweak +class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, null, 0f, 0f, 200f, 114f, getTitle()) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val controls = DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + + controls.sortingButtons(menu.settings::isAscending.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) { + for (v in ItemStorageStackSorter.entries) { + add(v, v.icon, v.title) + } + } + + val view = ArrayList>() + val settings = ArrayList>() + + view.add(EditablePanel(this, frame, width = AbstractSlotPanel.SIZE).also { + it.dock = Dock.LEFT + WideProfiledPowerGaugePanel(this, it, menu.profiledEnergy).also { + it.dock = Dock.TOP + it.dockBottom = 6f + } + + BatterySlotPanel(this, it, menu.batterySlot).dock = Dock.TOP + SlotPanel(this, it, menu.driveSlot).dock = Dock.TOP + }) + + view.add(NetworkedItemGridPanel(this, frame, menu.networkedItemView).also { + it.dock = Dock.FILL + it.setDockMargin(4f, 0f, 0f, 0f) + }) + + val filterGrid = GridPanel(this, frame, width = AbstractSlotPanel.SIZE * 3f, height = AbstractSlotPanel.SIZE * 4f, rows = 3, columns = 4) + filterGrid.dock = Dock.FILL + filterGrid.dockResize = DockResizeMode.NONE + settings.add(filterGrid) + + for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { + FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f) + } + + settings.add(EditablePanel(this, frame, width = 90f).also { + it.dock = Dock.LEFT + CheckBoxLabelInputPanel(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } + CheckBoxLabelInputPanel(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP } + CheckBoxLabelInputPanel(this, it, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP } + }) + + frame.Tab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE))) + frame.Tab(settings, activeIcon = ItemStackIcon(ItemStack(Items.HOPPER))) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt new file mode 100644 index 000000000..03ea70ec7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt @@ -0,0 +1,174 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.NetworkedItemGridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallEnumRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter +import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu +import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak + +@MouseTweaksDisableWheelTweak +class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this@ItemMonitorScreen, null, 0f, 0f, 1f, 1f, getTitle()) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val topPanel = EditablePanel(this, frame) + topPanel.height = ITEM_GRID_HEIGHT * 18f + topPanel.dock = Dock.TOP + + val bottomPanel = EditablePanel(this, frame) + bottomPanel.height = 3 * 18f + bottomPanel.dock = Dock.TOP + bottomPanel.setDockMargin(top = 6f) + + frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f + frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right + + val controls = DeviceControls(this, frame) + + controls.sortingButtons(menu.settings::ascendingSort.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) { + for (v in ItemStorageStackSorter.entries) { + add(v, skinElement = v.icon, tooltip = v.title) + } + } + + val grid = NetworkedItemGridPanel(this, topPanel, menu.networkedItemView) + grid.dock = Dock.FILL + + val craftingGrid = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) + craftingGrid.dock = Dock.LEFT + + for (i in 0 until 9) { + SlotPanel(this, craftingGrid, menu.craftingSlots[i]) + } + + val arrowAndButtons = object : EditablePanel(this@ItemMonitorScreen, bottomPanel, width = ProgressGaugePanel.GAUGE_BACKGROUND.width) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + ProgressGaugePanel.GAUGE_BACKGROUND.render(graphics, y = height / 2f - ProgressGaugePanel.GAUGE_BACKGROUND.height / 2f) + } + } + + arrowAndButtons.dock = Dock.LEFT + arrowAndButtons.setDockMargin(left = 4f) + + EditablePanel(this, arrowAndButtons, y = 0f, height = 8f, width = arrowAndButtons.width) + + val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width) + + SmallEnumRectangleButtonPanel(this, arrowLine, + enum = ItemMonitorPlayerSettings.IngredientPriority::class.java, + prop = menu.settings::ingredientPriority.asGetterSetter(), + defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc")) + + for (setting in ItemMonitorPlayerSettings.IngredientPriority.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } + + it.dock = Dock.LEFT + } + + val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f) + + resultAndButtons.dock = Dock.LEFT + resultAndButtons.setDockMargin(left = 6f) + + SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f) + + SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f, + enum = ItemMonitorPlayerSettings.ResultTarget::class.java, + prop = menu.settings::resultTarget.asGetterSetter(), + defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc")) + + for (setting in ItemMonitorPlayerSettings.ResultTarget.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } + } + + SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f, + enum = ItemMonitorPlayerSettings.Amount::class.java, + prop = menu.settings::craftingAmount.asGetterSetter(), + defaultValue = ItemMonitorPlayerSettings.Amount.STACK) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc")) + + for (setting in ItemMonitorPlayerSettings.Amount.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } + } + + val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) + craftingHistory.dock = Dock.LEFT + craftingHistory.setDockMargin(left = 4f) + + val craftingHistoryScroll = DiscreteScrollBarPanel(this, bottomPanel, { 0 }, { _, _, _ -> }) + craftingHistoryScroll.dock = Dock.LEFT + craftingHistoryScroll.setDockMargin(left = 2f) + + for (i in 0 until 9) { + object : AbstractSlotPanel(this@ItemMonitorScreen, craftingHistory) { + override val itemStack: ItemStack get() { + return ItemStack(Items.ARROW, 42) + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return craftingHistoryScroll.mouseScrolledInner(x, y, scroll) + } + } + } + + val leftStrip = BackgroundPanel.padded(this, frame, width = AbstractSlotPanel.SIZE, height = 1f) + + BatterySlotPanel(this, leftStrip, menu.batterySlot).also { + it.dock = Dock.BOTTOM + leftStrip.height += it.height + } + + WideProfiledPowerGaugePanel(this, leftStrip, menu.profiledEnergy).also { + it.dock = Dock.FILL + it.dockResize = DockResizeMode.NONE + leftStrip.height += it.height + } + + leftStrip.height += 3f + + leftStrip.x = -leftStrip.width - 2f + leftStrip.y = frame.height - leftStrip.height + + return frame + } + + companion object { + const val ITEM_GRID_WIDTH = 9 + const val ITEM_GRID_HEIGHT = 5 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageBusScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageBusScreen.kt new file mode 100644 index 000000000..c504cb6a1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageBusScreen.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeEnumRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.input.IntInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel + +import ru.dbotthepony.mc.otm.menu.storage.StorageBusMenu + +class StorageBusScreen(menu: StorageBusMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, 150f, 126f, title) + + makeBars(frame, profiledEnergy = menu.profiledEnergy, batterySlot = menu.batterySlot) + val right = EditablePanel(this, frame, width = AbstractSlotPanel.SIZE * 6f) + right.dock = Dock.RIGHT + val grid = GridPanel(this, right, columns = 6, rows = 3, height = AbstractSlotPanel.SIZE * 3f) + grid.dock = Dock.TOP + grid.dockBottom = 2f + + for (slot in menu.busFilterSlots) + FilterSlotPanel(this, grid, slot) + + IntInputPanel(this, right, menu.insertPriority).also { + it.dock = Dock.BOTTOM + it.dockBottom = 2f + it.tooltips.add(TranslatableComponent("otm.gui.insert_priority")) + it.childrenOrder = -1 + } + + IntInputPanel(this, right, menu.extractPriority).also { + it.dock = Dock.BOTTOM + it.dockBottom = 2f + it.tooltips.add(TranslatableComponent("otm.gui.extract_priority")) + it.childrenOrder = -2 + } + + CheckBoxLabelInputPanel(this, right, menu.busFilterState, TranslatableComponent("otm.gui.filter.is_whitelist")).also { + it.dock = Dock.BOTTOM + it.childrenOrder = -3 + } + + val controls = DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + controls.addStorageMode(menu.mode) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt new file mode 100644 index 000000000..75f24ef60 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt @@ -0,0 +1,55 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel + +import ru.dbotthepony.mc.otm.menu.storage.StorageImporterExporterMenu + +class StorageImporterExporterScreen(menu: StorageImporterExporterMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, 148f, 130f, title) + + makeBars(frame, profiledEnergy = menu.profiledEnergy, batterySlot = menu.batterySlot) + + val right = EditablePanel(this, frame, width = AbstractSlotPanel.SIZE * 6f) + right.dock = Dock.RIGHT + val grid = GridPanel(this, right, columns = 6, rows = 3, height = AbstractSlotPanel.SIZE * 3f) + grid.dock = Dock.TOP + + for (slot in menu.filterSlots) { + FilterSlotPanel(this, grid, slot) + } + + CheckBoxLabelInputPanel(this, right, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { + it.dock = Dock.BOTTOM + it.dockTop = 2f + it.childrenOrder = -1 + } + + CheckBoxLabelInputPanel(this, right, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { + it.dock = Dock.BOTTOM + it.dockTop = 2f + it.childrenOrder = -2 + } + + CheckBoxLabelInputPanel(this, right, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { + it.dock = Dock.BOTTOM + it.dockTop = 2f + it.childrenOrder = -3 + } + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StoragePowerSupplierScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StoragePowerSupplierScreen.kt new file mode 100644 index 000000000..f6cd39c54 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StoragePowerSupplierScreen.kt @@ -0,0 +1,85 @@ +package ru.dbotthepony.mc.otm.client.screen.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalPowerGaugePanel +import ru.dbotthepony.mc.otm.core.util.formatPower + +import ru.dbotthepony.mc.otm.menu.storage.StoragePowerSupplierMenu + +class StoragePowerSupplierScreen(menu: StoragePowerSupplierMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel(this, width = 200f, height = 68f, title) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val strip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE) + + strip.dock = Dock.BOTTOM + strip.dockTop = 3f + + BatterySlotPanel(this, strip, menu.batterySlot).also { + it.dock = Dock.LEFT + it.dockRight = 3f + } + + SpritePanel(this, strip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.LEFT + it.dockRight = 3f + it.dockResize = DockResizeMode.NONE + } + + TallHorizontalPowerGaugePanel(this, strip, menu.energyWidget).also { + it.dock = Dock.LEFT + it.dockResize = DockResizeMode.NONE + } + + val labels = EditablePanel(this, frame) + labels.dock = Dock.FILL + labels.dockTop = 4f + + object : Label(this@StoragePowerSupplierScreen, labels) { + init { + dock = Dock.TOP + } + + override fun tickInner() { + super.tickInner() + + text = TranslatableComponent( + "otm.item.power.passed", + menu.totalTransferred.formatPower(formatAsReadable = ShiftPressedCond) + ) + } + } + + object : Label(this@StoragePowerSupplierScreen, labels) { + init { + dock = Dock.TOP + } + + override fun tickInner() { + super.tickInner() + + text = TranslatableComponent( + "otm.gui.power_supplier.active_nodes", + menu.activeNodes + ) + } + } + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidChargerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidChargerScreen.kt new file mode 100644 index 000000000..27b80cb22 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidChargerScreen.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu + +class AndroidChargerScreen(menu: AndroidChargerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + frame.height = 42f + frame.width -= 20f + + val strip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE) + strip.dock = Dock.BOTTOM + + BatterySlotPanel(this, strip, menu.batterySlot).also { + it.dock = Dock.LEFT + it.dockRight = 2f + } + + SpritePanel(this, strip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.LEFT + it.dockRight = 2f + it.dockTop = 2f + } + + TallHorizontalProfiledPowerGaugePanel(this, strip, menu.profiledEnergy).also { + it.dock = Dock.LEFT + it.dockRight = 2f + } + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt similarity index 80% rename from src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt index 6ea310399..f4067d725 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt @@ -1,8 +1,6 @@ -package ru.dbotthepony.mc.otm.client.screen +package ru.dbotthepony.mc.otm.client.screen.tech import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack import it.unimi.dsi.fastutil.ints.Int2FloatAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectFunction import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap @@ -11,29 +9,32 @@ import net.minecraft.client.Minecraft import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack -import org.lwjgl.opengl.GL11 import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.block.entity.AndroidStationBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.RenderGravity import ru.dbotthepony.mc.otm.client.render.Widgets18 -import ru.dbotthepony.mc.otm.client.render.drawColor -import ru.dbotthepony.mc.otm.client.render.drawLine -import ru.dbotthepony.mc.otm.client.render.drawRect +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.* -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.ButtonPanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.EquipmentBatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DraggableCanvasPanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.core.ifPresentK -import ru.dbotthepony.mc.otm.menu.AndroidStationMenu +import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel import ru.dbotthepony.mc.otm.network.AndroidResearchRequestPacket -import ru.dbotthepony.mc.otm.registry.MRegistry import java.util.* import kotlin.collections.ArrayList import kotlin.properties.Delegates @@ -283,106 +284,74 @@ private class AndroidResearchButton( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { val hovered = screen.hoveredResearch val isBlockedByHovered = hovered != null && node.type in hovered.type.allBlocking if (isBlockedByHovered) { - AndroidStationScreen.CAN_NOT_BE_RESEARCHED.setSystemColor() - - drawRect(stack, 0f, 0f, width, height) + graphics.renderRect(0f, 0f, width, height, color = AndroidStationScreen.CAN_NOT_BE_RESEARCHED2) } - if (node.isResearched) { - AndroidStationScreen.RESEARCHED.setSystemColor() + val nodeColor = if (node.isResearched) { + AndroidStationScreen.RESEARCHED } else if (node.isAnyBlockerResearchedIndirect) { - AndroidStationScreen.ALREADY_BLOCKED.setSystemColor() + AndroidStationScreen.ALREADY_BLOCKED } else if (node.canResearch) { - AndroidStationScreen.CAN_BE_RESEARCHED.setSystemColor() + AndroidStationScreen.CAN_BE_RESEARCHED } else { - AndroidStationScreen.CAN_NOT_BE_RESEARCHED.setSystemColor() + AndroidStationScreen.CAN_NOT_BE_RESEARCHED } val icon = node.type.resolvedSkinIcon val itemIcon = node.type.itemIcon if (icon != null) { - icon.render(stack, 0f, 0f, width, height) + icon.render(graphics, 0f, 0f, width, height, color = nodeColor) } else if (itemIcon != null) { val itemstack = ItemStack(itemIcon, 1) - val systemPoseStack = RenderSystem.getModelViewStack() - systemPoseStack.pushPose() - systemPoseStack.translate((absoluteX + 1f).toDouble(), (absoluteY + 1f).toDouble(), 0.0) - RenderSystem.applyModelViewMatrix() - RenderSystem.depthFunc(GL11.GL_LESS) + val stack = graphics.pose + stack.pushPose() + stack.translate(1f, 1f, 0f) + screen.renderItemStack(absoluteX, absoluteY, itemstack) + stack.popPose() - // Thanks Mojang - // Very cool - // (for int x, int y, which are then cast into doubles anyway) - screen.itemRenderer.blitOffset = 1f // Z pos - - screen.itemRenderer.renderAndDecorateItem( - requireNotNull(minecraft.player) { "yo, dude, what the fuck" }, - itemstack, - 0, - 0, - (absoluteX + absoluteY * 1000f).toInt() - ) - - RenderSystem.depthFunc(GL11.GL_ALWAYS) - screen.itemRenderer.renderGuiItemDecorations(screen.font, itemstack, 0, 0, null) - screen.itemRenderer.blitOffset = 0f - - // too big accumulations can lead to Z near clipping issues - systemPoseStack.popPose() - RenderSystem.applyModelViewMatrix() - - clearDepth(stack) + clearDepth(graphics) } else { - drawRect(stack, 0f, 0f, width, height) + graphics.renderRect(0f, 0f, width, height, color = nodeColor) } if (isBlockedByHovered) { - RGBAColor.RED.setSystemColor() - - Widgets18.CROSS.render(stack) + Widgets18.CROSS.render(graphics, color = RGBAColor.RED) } else if (node.isAnyBlockerResearched) { - RGBAColor.RED.setSystemColor() - - Widgets18.FORWARD_SLASH.render(stack) + Widgets18.FORWARD_SLASH.render(graphics, color = RGBAColor.RED) } val text = node.type.iconText if (text != null) { - font.drawShadow(stack, text, width - font.width(text), height - font.lineHeight, -0x1) + graphics.draw(text.visualOrderText, width - font.width(text), height - font.lineHeight, color = RGBAColor.WHITE, drawShadow = true) } - drawColor = RGBAColor.WHITE - RGBAColor.WHITE.setShaderColor() - for (line in lines) { val (pos1, pos2) = line val (x1, y1) = pos1 val (x2, y2) = pos2 - drawLine(stack, x1, y1, x2, y2, 0.5f) + graphics.drawLine(x1, y1, x2, y2, 0.5f, color = RGBAColor.WHITE) } hovered ?: return val pathLines = highlightLines[hovered.type] if (pathLines != null) { - drawColor = RGBAColor.LIGHT_GREEN - for (line in pathLines) { val (pos1, pos2) = line val (x1, y1) = pos1 val (x2, y2) = pos2 - drawLine(stack, x1, y1, x2, y2, 0.5f) + graphics.drawLine(x1, y1, x2, y2, 0.5f, color = RGBAColor.LIGHT_GREEN) } } @@ -394,8 +363,6 @@ private class AndroidResearchButton( val blockLines = highlightLines[blocker] if (blockLines != null) { - drawColor = RGBAColor.RED - for (line in blockLines) { if (drawn.containsKey(line)) { continue @@ -407,7 +374,7 @@ private class AndroidResearchButton( val (x1, y1) = pos1 val (x2, y2) = pos2 - drawLine(stack, x1, y1, x2, y2, 0.5f) + graphics.drawLine(x1, y1, x2, y2, 0.5f, color = RGBAColor.RED) } } } @@ -415,7 +382,7 @@ private class AndroidResearchButton( override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.player?.isSpectator != true) { - if (node.canResearch && !node.isResearched && (parent?.screen as AndroidStationScreen).menu.powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_RESEARCH) { + if (node.canResearch && !node.isResearched && (parent?.screen as AndroidStationScreen).menu.energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_RESEARCH) { if (node.type.flatBlocking.isNotEmpty()) { queryUser( TranslatableComponent("otm.android_station.research.confirm", node.type.displayName), @@ -438,11 +405,11 @@ private class AndroidResearchButton( return true } - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { if (isHovered) { val list = ArrayList().also { it.addAll(node.screenTooltipLines) } - val enoughPower = (parent?.screen as AndroidStationScreen).menu.powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_RESEARCH + val enoughPower = (parent?.screen as AndroidStationScreen).menu.energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_RESEARCH if (node.isResearched) { list.add(TranslatableComponent("otm.android_station.research.researched").withStyle(ChatFormatting.DARK_AQUA)) @@ -460,7 +427,7 @@ private class AndroidResearchButton( } } - screen.renderComponentTooltip(stack, list, mouseX.toInt(), mouseY.toInt()) + graphics.renderComponentTooltip(font, list, mouseX.toInt(), mouseY.toInt()) } return isHovered @@ -531,9 +498,8 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I private var scroller: PreviewScrollers = PreviewScrollers.values().let { it[random.nextInt(it.size)] } private var firstTick = false - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - drawColor = RGBAColor.BLACK - drawRect(stack, 0f, 0f, width, height) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.BLACK) } override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { @@ -546,8 +512,8 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I return super.mouseClickedInner(x, y, button) } - override fun tick() { - super.tick() + override fun tickInner() { + super.tickInner() if (isPreview && !layoutInvalidated) { if (firstTick) { @@ -627,6 +593,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I if (research?.isRemoved == false) { research!!.toScreenCenter() popup(research!!) + research!!.requestFocus() return } @@ -639,18 +606,18 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I researchCanvas.parent = research val bottom = EditablePanel(this, research, 0f, 0f, 0f, 20f) - val close = ButtonPanel(this, bottom, 0f, 0f, 90f, 20f, TranslatableComponent("otm.container.matter_panel.close")) + val close = ButtonPanel(this, bottom, 0f, 0f, 90f, 20f, TranslatableComponent("otm.gui.matter_panel.close"), onPress = Runnable { research!!.remove() }) bottom.dock = Dock.BOTTOM - close.dock = Dock.RIGHT - close.bind { research!!.remove() } bottom.setDockMargin(0f, 0f, 4f, 4f) researchCanvas.setDockMargin(4f, 4f, 4f, 4f) research!!.toScreenCenter() + research!!.behaveAsWindow() addPanel(research!!) + research!!.requestFocus() } override fun resize(minecraft: Minecraft, p_96576_: Int, p_96577_: Int) { @@ -668,17 +635,21 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I override fun makeMainFrame(): FramePanel { val frame = FramePanel(this, 200f, 108f, title) + frame.makeCloseButton() + frame.onClose { onClose() } + object : Label(this@AndroidStationScreen, frame, height = 11f, shadow = true) { init { dock = Dock.BOTTOM dockTop = 2f + gravity = RenderGravity.TOP_RIGHT } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (menu.powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_RESEARCH) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (menu.energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_RESEARCH) { text = POWER_OK color = RGBAColor.LIGHT_GREEN - } else if (menu.powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_OPERATION) { + } else if (menu.energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION) { text = LOW_POWER_1 color = RGBAColor.DARK_RED } else { @@ -686,7 +657,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I color = RGBAColor.RED } - super.innerRender(stack, mouseX, mouseY, partialTick) + super.innerRender(graphics, mouseX, mouseY, partialTick) } } @@ -695,7 +666,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I stripLeft.dock = Dock.LEFT stripLeft.dockRight = 3f - WidePowerGaugePanel(this, stripLeft, menu.powerWidget).also { + WideProfiledPowerGaugePanel(this, stripLeft, menu.profiledEnergy).also { it.dock = Dock.TOP } @@ -704,17 +675,11 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I it.dockTop = 6f } - val playerStrip = EditablePanel(this, frame, height = 72f) + makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true) + + val playerStrip = PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots) playerStrip.dock = Dock.TOP - makeArmorStrip(menu.armorSlots, playerStrip).also { - it.dock = Dock.LEFT - } - - EntityRendererPanel(this, playerStrip, minecraft!!.player!!).also { - it.dock = Dock.LEFT - } - val androidStrip = EditablePanel(this, playerStrip, width = AbstractSlotPanel.SIZE) androidStrip.dock = Dock.LEFT @@ -728,6 +693,8 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I this.playerStrip = playerStrip + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + return frame } @@ -737,6 +704,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I val RESEARCHED = RGBAColor(150, 150, 200) val CAN_BE_RESEARCHED = RGBAColor(150, 200, 150) val CAN_NOT_BE_RESEARCHED = RGBAColor(200, 150, 150) + val CAN_NOT_BE_RESEARCHED2 = RGBAColor(200, 150, 150, 127) val ALREADY_BLOCKED = RGBAColor(105, 79, 79) private val LOW_POWER_0 = TranslatableComponent("otm.android_station.low_power_0") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BatteryBankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BatteryBankScreen.kt new file mode 100644 index 000000000..6b8035b4b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/BatteryBankScreen.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel + +class BatteryBankScreen(menu: BatteryBankMenu, p_97742_: Inventory, p_97743_: Component) : + MatteryScreen(menu, p_97742_, p_97743_) { + + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + WideProfiledPowerGaugePanel(this, frame, menu.powerLevel, LEFT_MARGIN, GAUGE_TOP_WITHOUT_SLOT) + + for (i in 0 .. 5) + BatterySlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * i, 32f) + + for (i in 6 .. 11) + BatterySlotPanel(this, frame, menu.storageSlots[i], 44f + 18 * (i - 6), 32f + 18f) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstone, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt new file mode 100644 index 000000000..c56e2283c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import mezz.jei.api.constants.RecipeTypes +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu + +class ChemicalGeneratorScreen(menu: ChemicalGeneratorMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + WideProfiledPowerGaugePanel(this, frame, menu.energy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + val progress = object : ProgressGaugePanel(this@ChemicalGeneratorScreen, frame, menu.progress, 78f, PROGRESS_ARROW_TOP) { + override fun makeTooltip(): MutableList { + val list = super.makeTooltip() + + list.add(TranslatableComponent("otm.gui.power.burn_time", menu.burnTime)) + + return list + } + } + + progress.flop = true + + progress.setRecipeType { listOf(RecipeTypes.FUELING) } + + SlotPanel(this, frame, menu.residueSlot, 56f, PROGRESS_SLOT_TOP) + SlotPanel(this, frame, menu.fuelSlot, 104f, PROGRESS_SLOT_TOP) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt new file mode 100644 index 000000000..e067b90ba --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/CobblerScreen.kt @@ -0,0 +1,27 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu + +class CobblerScreen(menu: CobblerMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + ProgressGaugePanel(this, frame, menu.progress, 30f, 42f) + + for (row in 0 .. 2) + for (column in 0 .. 2) + SlotPanel(this, frame, menu.storageSlots[row * 3 + column], 80f + column * AbstractSlotPanel.SIZE, 26f + row * AbstractSlotPanel.SIZE) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstone, itemConfig = menu.itemConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt new file mode 100644 index 000000000..838d9e2a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt @@ -0,0 +1,76 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.client.screen.panels.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.input.NetworkNumberInputPanel +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu + +class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + frame.height = 160f + frame.width += 40f + + val labels = ArrayList>() + + labels.add(Label(this, frame, text = TranslatableComponent("otm.gui.power.passed"))) + labels.add(DynamicLabel(this, frame, textSupplier = { menu.passed.formatPower(formatAsReadable = ShiftPressedCond) })) + labels.add(Label(this, frame, text = TranslatableComponent("otm.gui.power.average"))) + labels.add(DynamicLabel(this, frame, textSupplier = { menu.average.formatPower(formatAsReadable = ShiftPressedCond) })) + labels.add(Label(this, frame, text = TranslatableComponent("otm.gui.power.last_20_ticks"))) + labels.add(DynamicLabel(this, frame, textSupplier = { menu.last20Ticks.formatPower(formatAsReadable = ShiftPressedCond) })) + labels.add(Label(this, frame, text = TranslatableComponent("otm.gui.power.last_tick"))) + labels.add(DynamicLabel(this, frame, textSupplier = { menu.lastTick.formatPower(formatAsReadable = ShiftPressedCond) })) + labels.add(DynamicLabel(this, frame, textSupplier = { TranslatableComponent("block.overdrive_that_matters.energy_counter.facing", menu.inputDirection) })) + + for ((i, label) in labels.withIndex()) { + if (i == 0) { + label.dockTop = 5f + } else { + label.dockTop = 2f + } + + label.dock = Dock.TOP + } + + if (!menu.player.isSpectator) { + val button = ButtonPanel(this, frame, 0f, 0f, 0f, 20f, TranslatableComponent("block.overdrive_that_matters.energy_counter.switch"), onPress = Runnable { menu.switchDirection.accept(null) }) + button.dock = Dock.TOP + button.setDockMargin(4f, 5f, 4f, 0f) + } + + val infoPanels = frame.fetchChildren() + + Label(this, frame, TranslatableComponent("block.overdrive_that_matters.energy_counter.limit")).also { + it.dock = Dock.TOP + it.dockTop = 10f + } + + NetworkNumberInputPanel(this, frame, widget = menu.maxIOInput, networkValue = menu::maxIO).also { + it.dock = Dock.TOP + it.dockTop = 4f + } + + val limitsPanels = frame.fetchChildren().filter { !infoPanels.contains(it) } + + val informationTab = frame.Tab() + val limitsTab = frame.Tab() + + informationTab.showHidePanels(infoPanels) + limitsTab.showHidePanels(limitsPanels) + + limitsTab.onClose!!.run() + + makeDeviceControls(this, frame, redstoneConfig = menu.redstone) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyServoScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyServoScreen.kt new file mode 100644 index 000000000..658283ef4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyServoScreen.kt @@ -0,0 +1,78 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.PlayerEquipmentPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel +import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel +import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalPowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu + +class EnergyServoScreen(menu: EnergyServoMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel.padded(this, + width = AbstractSlotPanel.SIZE * 2f + HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width + 8f + ProgressGaugePanel.GAUGE_BACKGROUND.width * 2f, + AbstractSlotPanel.SIZE + PlayerEquipmentPanel.HEIGHT + 4f, title) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val equipment = PlayerEquipmentPanel(this, frame, armorSlots = menu.equipment.armorSlots) + equipment.dock = Dock.FILL + equipment.dockResize = DockResizeMode.NONE + + makeCuriosPanel(this, frame, menu.equipment.curiosSlots, autoAlign = true) + + val strip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE) + strip.dock = Dock.BOTTOM + + BatterySlotPanel(this, strip, menu.dischargeSlot).also { + it.dock = Dock.LEFT + it.dockRight = 2f + } + + SpritePanel(this, strip, ProgressGaugePanel.GAUGE_BACKGROUND).also { + it.dock = Dock.LEFT + it.dockRight = 2f + it.dockTop = 2f + } + + TallHorizontalProfiledPowerGaugePanel(this, strip, menu.powerGauge).also { + it.dock = Dock.LEFT + it.dockRight = 2f + } + + object : EditablePanel(this@EnergyServoScreen, strip) { + init { + dock = Dock.LEFT + dockRight = 2f + dockTop = 2f + width = ProgressGaugePanel.GAUGE_BACKGROUND.width + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + ProgressGaugePanel.GAUGE_BACKGROUND.render(graphics) + } + } + + BatterySlotPanel(this, strip, menu.chargeSlot).also { + it.dock = Dock.LEFT + it.dockRight + } + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt new file mode 100644 index 000000000..ae05cd7f8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt @@ -0,0 +1,298 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.enchantment.Enchantments +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.sprites.MatteryAtlas +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty +import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.Label +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.HorizontalStripPanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.util.getLevelFromXp +import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel +import ru.dbotthepony.mc.otm.core.util.getXpRequiredForLevelUp +import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu +import ru.dbotthepony.mc.otm.registry.MItems + +class EssenceStorageScreen(menu: EssenceStorageMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel.padded(this, width = DEFAULT_FRAME_WIDTH, height = 18f * 2f + 36f + 25f, title) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val inputs = HorizontalStripPanel(this, frame, height = 18f) + inputs.dock = Dock.TOP + inputs.dockMargin = DockProperty(bottom = 3f, top = 3f) + + SlotPanel(this, inputs, menu.capsuleSlot).also { + it.dock = Dock.RIGHT + it.tooltips.add(MItems.ESSENCE_CAPSULE.description.copy().withStyle(ChatFormatting.GRAY)) + } + + HorizontalStripPanel(this, frame, height = 18f).also { + it.dock = Dock.TOP + it.dockMargin = DockProperty(bottom = 3f) + + object : EditablePanel(this@EssenceStorageScreen, it, width = 108f, height = 15f) { + init { + dockResize = DockResizeMode.NONE + dockMargin = DockProperty(bottom = 3f) + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + BAR_BACKGROUND.render(graphics, width = width, height = height) + val level = getLevelFromXp(menu.experienceStored) + val progress = (menu.experienceStored - getTotalXpRequiredForLevel(level)).toDouble() / getXpRequiredForLevelUp(level).toDouble() + BAR_FOREGROUND.renderPartial(graphics, width = width * progress.toFloat(), height = height) + } + } + + SlotPanel(this, it, menu.servoSlot).also { + it.dock = Dock.RIGHT + it.tooltips.add(MItems.ESSENCE_SERVO.description.copy().withStyle(ChatFormatting.GRAY)) + } + } + + object : Label(this@EssenceStorageScreen, frame, text = TextComponent("")) { + init { + dock = Dock.TOP + dockMargin = DockProperty(bottom = 3f) + gravity = RenderGravity.TOP_CENTER + } + + override fun tickInner() { + super.tickInner() + + if (minecraft?.window?.isShiftDown == true) { + text = TranslatableComponent("otm.gui.experience", menu.experienceStored) + } else { + text = TranslatableComponent("otm.gui.experience_levels", getLevelFromXp(menu.experienceStored)) + } + } + } + + val outputs = HorizontalStripPanel(this, frame, width = 18f * 3 + 3f * 3, height = 18f) + outputs.dock = Dock.TOP + outputs.dockResize = DockResizeMode.NONE + outputs.dockMargin = DockProperty(bottom = 3f) + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, inputs, icon = STORE_1) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.store", 1)) + } + + override fun onClick(mouseButton: Int) { + menu.storeLevels.accept(1) + } + + override var isDisabled: Boolean + get() = !menu.storeLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, inputs, icon = STORE_10) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.store", 10)) + } + + override fun onClick(mouseButton: Int) { + menu.storeLevels.accept(10) + } + + override var isDisabled: Boolean + get() = !menu.storeLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, inputs, icon = STORE_ALL) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.store_all")) + } + + override fun onClick(mouseButton: Int) { + menu.storeLevels.accept((minecraft?.player?.experienceLevel ?: 0) + 1) + } + + override var isDisabled: Boolean + get() = !menu.storeLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, outputs, icon = DISPENSE_1) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.dispense", 1)) + } + + override fun onClick(mouseButton: Int) { + menu.dispenseLevels.accept(1) + } + + override var isDisabled: Boolean + get() = !menu.dispenseLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, outputs, icon = DISPENSE_10) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.dispense", 10)) + } + + override fun onClick(mouseButton: Int) { + menu.dispenseLevels.accept(10) + } + + override var isDisabled: Boolean + get() = !menu.dispenseLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, outputs, icon = DISPENSE_ALL) { + init { + dockRight = 3f + tooltips.add(TranslatableComponent("otm.gui.experience.dispense_all")) + } + + override fun onClick(mouseButton: Int) { + menu.dispenseLevels.accept(getLevelFromXp(menu.experienceStored) + 1) + } + + override var isDisabled: Boolean + get() = !menu.dispenseLevels.test(minecraft?.player) + set(value) {} + } + + val customBar = HorizontalStripPanel(this, frame, height = 18f) + customBar.dock = Dock.TOP + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, customBar, icon = STORE_CUSTOM) { + init { + tooltips.add(TranslatableComponent("otm.gui.experience.store", customDispense)) + } + + override fun onClick(mouseButton: Int) { + menu.storeLevels.accept(customDispense) + } + + override var isDisabled: Boolean + get() = !menu.storeLevels.test(minecraft?.player) + set(value) {} + } + + object : TextInputPanel(this@EssenceStorageScreen, customBar, width = 60f) { + init { + dockMargin = DockProperty(left = 3f, right = 3f) + text = customDispense.toString() + } + + override fun onEnter() { + val player = minecraft?.player ?: return + + if (player.experienceLevel == customDispense) { + if (player.experienceProgress > 0f) { + menu.storeLevels.accept(1) + } + } else if (player.experienceLevel > customDispense) { + menu.storeLevels.accept(player.experienceLevel - customDispense) + } else { + menu.dispenseLevels.accept(customDispense - player.experienceLevel) + } + + playGuiClickSound() + } + + override fun onTextChanged(old: String, new: String) { + customDispense = (new.toIntOrNull() ?: 30).coerceAtLeast(1) + } + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, customBar, icon = DISPENSE_CUSTOM) { + init { + tooltips.add(TranslatableComponent("otm.gui.experience.dispense", customDispense)) + } + + override fun onClick(mouseButton: Int) { + menu.dispenseLevels.accept(customDispense) + } + + override var isDisabled: Boolean + get() = !menu.dispenseLevels.test(minecraft?.player) + set(value) {} + } + + object : LargeRectangleButtonPanel(this@EssenceStorageScreen, customBar, icon = SET_EXACT) { + init { + tooltips.add(TranslatableComponent("otm.gui.experience.set_exact", customDispense)) + dock = Dock.RIGHT + dockMargin = DockProperty(right = 4f) + } + + override fun onClick(mouseButton: Int) { + val player = minecraft?.player ?: return + + if (player.experienceLevel == customDispense) { + if (player.experienceProgress > 0f) { + menu.storeLevels.accept(1) + } + } else if (player.experienceLevel > customDispense) { + menu.storeLevels.accept(player.experienceLevel - customDispense) + } else { + menu.dispenseLevels.accept(customDispense - player.experienceLevel) + } + } + + override var isDisabled: Boolean + get() = !menu.dispenseLevels.test(minecraft?.player) && !menu.storeLevels.test(minecraft?.player) + set(value) {} + } + + SlotPanel(this, inputs, menu.mendingSlot).also { + it.dock = Dock.LEFT + it.tooltips.add(Enchantments.MENDING.getFullname(1).copy().withStyle(ChatFormatting.GRAY)) + } + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig) + + return frame + } + + companion object { + private var customDispense = 30 + + val ATLAS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/essence_storage.png"), 108f, 66f) + val STORE_1 = ATLAS.sprite(width = 18f, height = 18f) + val STORE_10 = ATLAS.sprite(x = 18f, width = 18f, height = 18f) + val STORE_ALL = ATLAS.sprite(x = 18f * 2, width = 18f, height = 18f) + val DISPENSE_1 = ATLAS.sprite(x = 18f * 3, width = 18f, height = 18f) + val DISPENSE_10 = ATLAS.sprite(x = 18f * 4, width = 18f, height = 18f) + val DISPENSE_ALL = ATLAS.sprite(x = 18f * 5, width = 18f, height = 18f) + val BAR_BACKGROUND = ATLAS.sprite(y = 18f, height = 15f) + val BAR_FOREGROUND = ATLAS.sprite(y = 18f + 15f, height = 15f) + + val STORE_CUSTOM = ATLAS.sprite(width = 18f, height = 18f, y = 48f) + val DISPENSE_CUSTOM = ATLAS.sprite(width = 18f, height = 18f, y = 48f, x = 18f) + val SET_EXACT = ATLAS.sprite(width = 18f, height = 18f, y = 48f, x = 18f * 2f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt new file mode 100644 index 000000000..8fd28ea77 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.compat.jei.PlatePressRecipeCategory +import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu + +class PlatePressScreen(menu: PlatePressMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + WideProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + SlotPanel(this, frame, menu.inputSlot, 56f, PROGRESS_SLOT_TOP) + ProgressGaugePanel(this, frame, menu.progressGauge, 78f, PROGRESS_ARROW_TOP).setRecipeType { listOf(PlatePressRecipeCategory.recipeType) } + SlotPanel(this, frame, menu.outputSlot, 104f, PROGRESS_SLOT_TOP) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, upgrades = menu.upgrades) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PoweredFurnaceScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PoweredFurnaceScreen.kt new file mode 100644 index 000000000..11892fdd2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PoweredFurnaceScreen.kt @@ -0,0 +1,54 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import mezz.jei.api.constants.RecipeTypes +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.compat.jei.MicrowaveRecipeCategory +import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu +import ru.dbotthepony.mc.otm.registry.MMenus + +class PoweredFurnaceScreen(menu: PoweredFurnaceMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + WideProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + SlotPanel(this, frame, menu.inputSlots[0], 56f, PROGRESS_SLOT_TOP - 10f) + val a = ProgressGaugePanel(this, frame, menu.progressGauge[0], 78f, PROGRESS_ARROW_TOP - 10f) + SlotPanel(this, frame, menu.outputSlots[0], 104f, PROGRESS_SLOT_TOP - 10f) + + SlotPanel(this, frame, menu.inputSlots[1], 56f, PROGRESS_SLOT_TOP + 10f) + val b = ProgressGaugePanel(this, frame, menu.progressGauge[1], 78f, PROGRESS_ARROW_TOP + 10f) + SlotPanel(this, frame, menu.outputSlots[1], 104f, PROGRESS_SLOT_TOP + 10f) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, balanceInputs = menu.balanceInputs, upgrades = menu.upgrades) + + when (menu.type) { + MMenus.POWERED_FURNACE -> { + a.setRecipeType { listOf(RecipeTypes.SMELTING) } + b.setRecipeType { listOf(RecipeTypes.SMELTING) } + } + + MMenus.POWERED_BLAST_FURNACE -> { + a.setRecipeType { listOf(RecipeTypes.BLASTING) } + b.setRecipeType { listOf(RecipeTypes.BLASTING) } + } + + MMenus.POWERED_SMOKER -> { + a.setRecipeType { listOf(MicrowaveRecipeCategory.recipeType, RecipeTypes.SMOKING) } + b.setRecipeType { listOf(MicrowaveRecipeCategory.recipeType, RecipeTypes.SMOKING) } + } + } + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt new file mode 100644 index 000000000..a762523c2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.client.screen.tech + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.compat.jei.PlatePressRecipeCategory +import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu + +class TwinPlatePressScreen(menu: TwinPlatePressMenu, inventory: Inventory, title: Component) : + MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = super.makeMainFrame()!! + + WideProfiledPowerGaugePanel(this, frame, menu.profiledEnergy, LEFT_MARGIN, GAUGE_TOP_WITH_SLOT) + BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) + + SlotPanel(this, frame, menu.inputSlots[0], 56f, PROGRESS_SLOT_TOP - 10f) + ProgressGaugePanel(this, frame, menu.progressGauge0, 78f, PROGRESS_ARROW_TOP - 10f).setRecipeType { listOf(PlatePressRecipeCategory.recipeType) } + SlotPanel(this, frame, menu.outputSlots[0], 104f, PROGRESS_SLOT_TOP - 10f) + + SlotPanel(this, frame, menu.inputSlots[1], 56f, PROGRESS_SLOT_TOP + 10f) + ProgressGaugePanel(this, frame, menu.progressGauge1, 78f, PROGRESS_ARROW_TOP + 10f).setRecipeType { listOf(PlatePressRecipeCategory.recipeType) } + SlotPanel(this, frame, menu.outputSlots[1], 104f, PROGRESS_SLOT_TOP + 10f) + + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, balanceInputs = menu.balanceInputs, upgrades = menu.upgrades) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt new file mode 100644 index 000000000..670246d22 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/FluidGaugePanel.kt @@ -0,0 +1,104 @@ +package ru.dbotthepony.mc.otm.client.screen.widget + +import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.DefaultVertexFormat +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.gui.screens.Screen +import net.minecraft.client.renderer.GameRenderer +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.inventory.InventoryMenu +import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions +import org.lwjgl.opengl.GL11 +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite +import ru.dbotthepony.mc.otm.client.render.tesselator +import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.linearInterpolation +import ru.dbotthepony.mc.otm.core.util.formatFluidLevel +import ru.dbotthepony.mc.otm.menu.widget.FluidGaugeWidget + +open class FluidGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + val widget: FluidGaugeWidget, + x: Float = 0f, + y: Float = 0f +) : EditablePanel(screen, parent, x, y, width = GAUGE.width, height = GAUGE.height) { + init { + scissor = true + } + + protected open fun makeTooltip(): MutableList { + return mutableListOf( + if (widget.fluid.isEmpty) TranslatableComponent("otm.gui.empty") else TextComponent(String.format("%s: %.2f%%", widget.fluid.displayName.string, widget.percentage * 100.0)), + formatFluidLevel(if (widget.fluid.isEmpty) 0 else widget.fluid.amount, widget.maxCapacity, formatAsReadable = ShiftPressedCond) + ) + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered) { + graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt()) + return true + } + + return false + } + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (widget.percentage > 0.01f && widget.fluid.isNotEmpty) { + val data = IClientFluidTypeExtensions.of(widget.fluid.fluid) + val texture = data.stillTexture!! + val sprite = minecraft.getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(texture)!! + val tint = RGBAColor.argb(data.getTintColor(widget.fluid)) + var height = (height * widget.percentage) / 16f + var bottom = this.height + + val matrix = graphics.pose.last().pose() + val builder = tesselator.builder + + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX) + + while (height > 0.01f) { + val thisHeight = height.coerceAtMost(1f) + height -= thisHeight + val actualHeight = thisHeight * 16f + + val interp = linearInterpolation(thisHeight, sprite.v1, sprite.v0) + + builder.vertex(matrix, 0f, bottom, 0f).uv(sprite.u0, sprite.v1).endVertex() + builder.vertex(matrix, width, bottom, 0f).uv(sprite.u1, sprite.v1).endVertex() + builder.vertex(matrix, width, bottom - actualHeight, 0f).uv(sprite.u1, interp).endVertex() + builder.vertex(matrix, 0f, bottom - actualHeight, 0f).uv(sprite.u0, interp).endVertex() + + bottom -= actualHeight + } + + RenderSystem.setShader(GameRenderer::getPositionTexShader) + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + RenderSystem.depthFunc(GL11.GL_ALWAYS) + + RenderSystem.setShaderColor(tint.red, tint.green, tint.blue, tint.alpha) + RenderSystem.setShaderTexture(0, sprite.atlasLocation()) + + tesselator.end() + + RenderSystem.setShaderColor(1f, 1f, 1f, 1f) + RenderSystem.depthFunc(GL11.GL_LESS) + } + + GAUGE.render(graphics, 0f, 0f, width = width, height = height) + } + + companion object { + val GAUGE = MatterySprite.single(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/fluid_level.png"), 18f, 61f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt index 2d27ff716..60cbc2ed8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/HorizontalPowerGaugePanel.kt @@ -1,13 +1,36 @@ package ru.dbotthepony.mc.otm.client.screen.widget -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget + +private fun PowerGaugePanel<*>.doRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float, flop: Boolean) { + if (height >= 18f) { + if (flop) { + HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.render(graphics, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) + val width = this.width * widget.percentage + HorizontalPowerGaugePanel.GAUGE_FOREGROUND_TALL.renderPartial(graphics, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) + } else { + HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.render(graphics, height = height, width = this.width) + val width = this.width * widget.percentage + HorizontalPowerGaugePanel.GAUGE_FOREGROUND_TALL.renderPartial(graphics, height = height, width = width) + } + } else { + if (flop) { + HorizontalPowerGaugePanel.GAUGE_BACKGROUND.render(graphics, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) + val width = this.width * widget.percentage + HorizontalPowerGaugePanel.GAUGE_FOREGROUND.renderPartial(graphics, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) + } else { + HorizontalPowerGaugePanel.GAUGE_BACKGROUND.render(graphics, height = height, width = this.width) + val width = this.width * widget.percentage + HorizontalPowerGaugePanel.GAUGE_FOREGROUND.renderPartial(graphics, height = height, width = width) + } + } +} open class HorizontalPowerGaugePanel( screen: S, @@ -20,42 +43,23 @@ open class HorizontalPowerGaugePanel( ) : PowerGaugePanel(screen, parent, widget, x, y, width, height) { var flop = false - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (height >= 18f) { - if (flop) { - GAUGE_BACKGROUND_TALL.render(stack, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) - val width = this.width * widget.percentage() - GAUGE_FOREGROUND_TALL.renderPartial(stack, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) - } else { - GAUGE_BACKGROUND_TALL.render(stack, height = height, width = this.width) - val width = this.width * widget.percentage() - GAUGE_FOREGROUND_TALL.renderPartial(stack, height = height, width = width) - } - } else { - if (flop) { - GAUGE_BACKGROUND.render(stack, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) - val width = this.width * widget.percentage() - GAUGE_FOREGROUND.renderPartial(stack, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) - } else { - GAUGE_BACKGROUND.render(stack, height = height, width = this.width) - val width = this.width * widget.percentage() - GAUGE_FOREGROUND.renderPartial(stack, height = height, width = width) - } - } + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + doRender(graphics, mouseX, mouseY, partialTick, flop) } companion object { - val GAUGE_BACKGROUND_TALL = WidgetLocation.HORIZONTAL_GAUGES.subElement(width = 96f, height = 18f) - val GAUGE_FOREGROUND_TALL = WidgetLocation.HORIZONTAL_GAUGES.subElement(y = 18f, width = 96f, height = 18f) + val GAUGE_BACKGROUND_TALL = WidgetLocation.HORIZONTAL_GAUGES.sprite(width = 96f, height = 18f) + val GAUGE_FOREGROUND_TALL = WidgetLocation.HORIZONTAL_GAUGES.sprite(y = 18f, width = 96f, height = 18f) - val GAUGE_BACKGROUND = WidgetLocation.HORIZONTAL_GAUGES.subElement(y = 36f, width = 96f, height = 9f) - val GAUGE_FOREGROUND = WidgetLocation.HORIZONTAL_GAUGES.subElement(y = 45f, width = 96f, height = 9f) + val GAUGE_BACKGROUND = WidgetLocation.HORIZONTAL_GAUGES.sprite(y = 36f, width = 96f, height = 9f) + val GAUGE_FOREGROUND = WidgetLocation.HORIZONTAL_GAUGES.sprite(y = 45f, width = 96f, height = 9f) } } /** * Shortcut to [HorizontalPowerGaugePanel] with doubled height */ +@Suppress("FunctionName") fun TallHorizontalPowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, @@ -65,3 +69,33 @@ fun TallHorizontalPowerGaugePanel( width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width, height: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.height ) = HorizontalPowerGaugePanel(screen, parent, widget, x, y, width, height) + +open class HorizontalProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + widget: ProfiledLevelGaugeWidget<*>, + x: Float = 0f, + y: Float = 0f, + width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.width, + height: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND.height +) : ProfiledPowerGaugePanel(screen, parent, widget, x, y, width, height) { + var flop = false + + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + doRender(graphics, mouseX, mouseY, partialTick, flop) + } +} + +/** + * Shortcut to [HorizontalProfiledPowerGaugePanel] with doubled height + */ +@Suppress("FunctionName") +fun TallHorizontalProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + widget: ProfiledLevelGaugeWidget<*>, + x: Float = 0f, + y: Float = 0f, + width: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.width, + height: Float = HorizontalPowerGaugePanel.GAUGE_BACKGROUND_TALL.height +) = HorizontalProfiledPowerGaugePanel(screen, parent, widget, x, y, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt index 9c7980c41..c31f5db8a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/MatterGaugePanel.kt @@ -3,27 +3,35 @@ package ru.dbotthepony.mc.otm.client.screen.widget import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.BufferUploader import com.mojang.blaze3d.vertex.DefaultVertexFormat -import com.mojang.blaze3d.vertex.PoseStack import com.mojang.blaze3d.vertex.VertexFormat +import it.unimi.dsi.fastutil.ints.IntArrayList +import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.client.renderer.GameRenderer import net.minecraft.network.chat.Component import org.lwjgl.opengl.GL11 +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement import ru.dbotthepony.mc.otm.client.render.tesselator import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.formatMatterLevel +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatMatter +import ru.dbotthepony.mc.otm.core.util.formatMatterLevel import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.nanoTime import kotlin.math.absoluteValue import kotlin.math.cos import kotlin.math.pow import kotlin.math.sin -open class MatterGaugePanel @JvmOverloads constructor( +open class MatterGaugePanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, @@ -44,35 +52,53 @@ open class MatterGaugePanel @JvmOverloads constructor( return mutableListOf( TranslatableComponent( "otm.gui.matter.percentage_level", - String.format("%.2f", widget.percentage() * 100.0) + String.format("%.2f", widget.percentage * 100.0) ), - formatMatterLevel(widget.level(), widget.maxLevel()) + formatMatterLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond) ) } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (lastAbsoluteX == 0f && lastAbsoluteY == 0f) { lastAbsoluteX = absoluteX lastAbsoluteY = absoluteY lastDraw = nanoTime - lastLevel = widget.percentage() + lastLevel = widget.percentage } else { - val diff = ((lastAbsoluteX - absoluteX).pow(2f) + (lastAbsoluteY - absoluteY).pow(2f)).pow(0.5f) / 10f + (lastLevel - widget.percentage()).absoluteValue * 1.5f + val diff = ((lastAbsoluteX - absoluteX).pow(2f) + (lastAbsoluteY - absoluteY).pow(2f)).pow(0.5f) / 10f + (lastLevel - widget.percentage).absoluteValue * 1.5f lastAbsoluteX = absoluteX lastAbsoluteY = absoluteY wavesStrength = (wavesStrength + diff - (nanoTime - lastDraw) / 400_000_000f).coerceAtLeast(0.5f).coerceAtMost(16f) lastDraw = nanoTime - lastLevel = widget.percentage() + lastLevel = widget.percentage } - GAUGE_BACKGROUND.render(stack) - val height = this.height * (1f - widget.percentage()) + GAUGE_BACKGROUND.render(graphics) - if (widget.percentage() > 0.01f) { + if (widget.percentage > 0.01f) { + renderLevel(graphics, 0f, 0f, width, height, widget.percentage, wavesStrength) + } + } + + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered) { + graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt()) + return true + } + + return false + } + + companion object { + val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 18f, width = 9f) + val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 27f, width = 9f) + + fun renderLevel(graphics: MGUIGraphics, x: Float, y: Float, width: Float, height: Float, percentage: Float, wavesStrength: Float = 0.5f) { + graphics.pose.pushPose() + graphics.pose.translate(x, y, 0f) RenderSystem.setShader(GameRenderer::getPositionTexShader) - RenderSystem.enableTexture() RenderSystem.enableBlend() RenderSystem.defaultBlendFunc() RenderSystem.depthFunc(GL11.GL_ALWAYS) @@ -83,20 +109,20 @@ open class MatterGaugePanel @JvmOverloads constructor( val u1 = GAUGE_FOREGROUND.u1 val v1 = GAUGE_FOREGROUND.v1 - val matrix = stack.last().pose() + val matrix = graphics.pose.last().pose() val builder = tesselator.builder builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_TEX) - builder.vertex(matrix, 0f, this.height, 0f).uv(u0, v1).endVertex() - builder.vertex(matrix, width, this.height, 0f).uv(u1, v1).endVertex() + builder.vertex(matrix, 0f, height, 0f).uv(u0, v1).endVertex() + builder.vertex(matrix, width, height, 0f).uv(u1, v1).endVertex() for (i in 4 downTo 0) { val sin = sin((System.currentTimeMillis() / 50L + i * 2L).toDouble() / 4.0).toFloat() * wavesStrength.coerceAtMost(4f) val cos = cos((System.currentTimeMillis() / 50L + i * 3L + 27L).toDouble() / 4.0).toFloat() * wavesStrength.coerceAtMost(4f) val thisX = (width * (i / 4f)) - val thisY = (height + sin + cos).coerceAtLeast(0f) + val thisY = (height * (1f - percentage) + sin + cos).coerceAtLeast(0f) builder.vertex(matrix, thisX, thisY, 0f).uv( GAUGE_FOREGROUND.partialU((i / 4f) * GAUGE_FOREGROUND.width), @@ -105,20 +131,58 @@ open class MatterGaugePanel @JvmOverloads constructor( } BufferUploader.drawWithShader(builder.end()) + graphics.pose.popPose() + } + } +} + +private fun formatLevel(a: Decimal, b: Decimal): Component { + val diff = a - b + + val fa = a.formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN) + val fb = b.formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED) + + if (diff.isZero) { + return TranslatableComponent("otm.gui.diff", diff.formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.GRAY), fa, fb) + } else if (diff.isPositive) { + return TranslatableComponent("otm.gui.diff", diff.formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN), fa, fb) + } else { + return TranslatableComponent("otm.gui.diff", (-diff).formatMatter(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED), fa, fb) + } +} + +open class ProfiledMatterGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + val profiledWidget: ProfiledLevelGaugeWidget<*>, + x: Float = 0f, + y: Float = 0f +): MatterGaugePanel(screen, parent, profiledWidget.gauge, x, y) { + override fun makeTooltip(): MutableList { + return super.makeTooltip().also { + it.add(TextComponent("")) + + if (minecraft.window.isShiftDown) { + it.add(formatLevel(profiledWidget.lastTickReceive, profiledWidget.lastTickTransfer)) + it.add(TextComponent("---")) + } + + it.add(formatLevel( + profiledWidget.weightedReceive, + profiledWidget.weightedTransfer, + )) + + if (minecraft.window.isShiftDown && minecraft.options.advancedItemTooltips) { + it.add(TextComponent("---")) + val values = IntArrayList() + + values.addAll(profiledWidget.tick downTo 0) + values.addAll(AbstractProfiledStorage.HISTORY_SIZE - 1 downTo profiledWidget.tick + 1) + + for (i in values.intIterator()) { + it.add(formatLevel(profiledWidget.historyReceive[i].value, profiledWidget.historyTransfer[i].value)) + } + } } } - - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - if (isHovered) { - screen.renderComponentTooltip(stack, makeTooltip(), mouseX.toInt(), mouseY.toInt()) - return true - } - - return false - } - - companion object { - val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(x = 18f, width = 9f) - val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(x = 27f, width = 9f) - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt index 3fe2eb7f6..669a3d969 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PatternGaugePanel.kt @@ -1,17 +1,14 @@ package ru.dbotthepony.mc.otm.client.screen.widget -import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -open class PatternGaugePanel @JvmOverloads constructor( +open class PatternGaugePanel @JvmOverloads constructor( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, @@ -26,21 +23,21 @@ open class PatternGaugePanel @JvmOverloads constructor( return mutableListOf( TranslatableComponent( "otm.gui.pattern.percentage_level", - String.format("%.2f", widget.percentage() * 100.0) + String.format("%.2f", widget.percentage * 100.0) ), - TranslatableComponent("otm.gui.pattern.format", widget.level().toString(0), widget.maxLevel().toString(0)) + TranslatableComponent("otm.gui.pattern.format", widget.level.toString(0), widget.maxLevel.toString(0)) ) } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - GAUGE_BACKGROUND.render(stack) - val height = this.height * widget.percentage() - GAUGE_FOREGROUND.renderPartial(stack, y = this.height - height, height = height) + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + GAUGE_BACKGROUND.render(graphics) + val height = this.height * widget.percentage + GAUGE_FOREGROUND.renderPartial(graphics, y = this.height - height, height = height) } - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { if (isHovered) { - screen.renderComponentTooltip(stack, makeTooltip(), mouseX.toInt(), mouseY.toInt()) + graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt()) return true } @@ -48,7 +45,7 @@ open class PatternGaugePanel @JvmOverloads constructor( } companion object { - val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(width = 9f) - val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(x = 9f, width = 9f) + val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(width = 9f) + val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 9f, width = 9f) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt index ec2bab3ab..51bc1a6c4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/PowerGaugePanel.kt @@ -1,15 +1,25 @@ package ru.dbotthepony.mc.otm.client.screen.widget -import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntArrayList +import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.render.* import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.formatPowerLevel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.core.util.formatPowerLevel import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget -open class PowerGaugePanel @JvmOverloads constructor( +open class PowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, val widget: LevelGaugeWidget, @@ -24,26 +34,26 @@ open class PowerGaugePanel @JvmOverloads constructor( protected open fun makeTooltip(): MutableList { return mutableListOf( - TranslatableComponent("otm.gui.power.percentage_level", String.format("%.2f", widget.percentage() * 100.0)), - formatPowerLevel(widget.level(), widget.maxLevel()) + TranslatableComponent("otm.gui.power.percentage_level", String.format("%.2f", widget.percentage * 100.0)), + formatPowerLevel(widget.level, widget.maxLevel, formatAsReadable = ShiftPressedCond) ) } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - val height = this.height * widget.percentage() + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + val height = this.height * widget.percentage if (width >= 18f) { - GAUGE_BACKGROUND_WIDE.render(stack, width = width, height = this.height) + GAUGE_BACKGROUND_WIDE.render(graphics, width = width, height = this.height) GAUGE_FOREGROUND_WIDE.renderPartial( - stack, + graphics, y = this.height - height, height = height, width = width, winding = UVWindingOrder.U0_V1_U1_V0) } else { - GAUGE_BACKGROUND.render(stack, width = width, height = this.height) + GAUGE_BACKGROUND.render(graphics, width = width, height = this.height) GAUGE_FOREGROUND.renderPartial( - stack, + graphics, y = this.height - height, height = height, width = width, @@ -51,9 +61,9 @@ open class PowerGaugePanel @JvmOverloads constructor( } } - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { if (isHovered) { - screen.renderComponentTooltip(stack, makeTooltip(), mouseX.toInt(), mouseY.toInt()) + graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt()) return true } @@ -61,17 +71,18 @@ open class PowerGaugePanel @JvmOverloads constructor( } companion object { - val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(x = 36f, width = 9f) - val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.subElement(x = 45f, width = 9f) + val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 36f, width = 9f) + val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 45f, width = 9f) - val GAUGE_BACKGROUND_WIDE = WidgetLocation.VERTICAL_GAUGES.subElement(x = 54f, width = 18f) - val GAUGE_FOREGROUND_WIDE = WidgetLocation.VERTICAL_GAUGES.subElement(x = 72f, width = 18f) + val GAUGE_BACKGROUND_WIDE = WidgetLocation.VERTICAL_GAUGES.sprite(x = 54f, width = 18f) + val GAUGE_FOREGROUND_WIDE = WidgetLocation.VERTICAL_GAUGES.sprite(x = 72f, width = 18f) } } /** * Shortcut to [PowerGaugePanel] with doubled width */ +@Suppress("FunctionName") fun WidePowerGaugePanel( screen: S, parent: EditablePanel<*>? = null, @@ -81,3 +92,70 @@ fun WidePowerGaugePanel( width: Float = 18f, height: Float = 48f ) = PowerGaugePanel(screen, parent, widget, x, y, width, height) + +private fun formatLevel(a: Decimal, b: Decimal): Component { + val diff = a - b + + val fa = a.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN) + val fb = b.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED) + + if (diff.isZero) { + return TranslatableComponent("otm.gui.diff", diff.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.GRAY), fa, fb) + } else if (diff.isPositive) { + return TranslatableComponent("otm.gui.diff", diff.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN), fa, fb) + } else { + return TranslatableComponent("otm.gui.diff", (-diff).formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_RED), fa, fb) + } +} + +open class ProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + val profiledWidget: ProfiledLevelGaugeWidget<*>, + x: Float = 0f, + y: Float = 0f, + width: Float = GAUGE_BACKGROUND.width, + height: Float = GAUGE_BACKGROUND.height +) : PowerGaugePanel(screen, parent, profiledWidget.gauge, x, y, width, height) { + override fun makeTooltip(): MutableList { + return super.makeTooltip().also { + it.add(TextComponent("")) + + if (minecraft.window.isShiftDown) { + it.add(formatLevel(profiledWidget.lastTickReceive, profiledWidget.lastTickTransfer)) + it.add(TextComponent("---")) + } + + it.add(formatLevel( + profiledWidget.weightedReceive, + profiledWidget.weightedTransfer, + )) + + if (minecraft.window.isShiftDown && minecraft.options.advancedItemTooltips) { + it.add(TextComponent("---")) + val values = IntArrayList() + + values.addAll(profiledWidget.tick downTo 0) + values.addAll(AbstractProfiledStorage.HISTORY_SIZE - 1 downTo profiledWidget.tick + 1) + + for (i in values.intIterator()) { + it.add(formatLevel(profiledWidget.historyReceive[i].value, profiledWidget.historyTransfer[i].value)) + } + } + } + } +} + +/** + * Shortcut to [ProfiledPowerGaugePanel] with doubled width + */ +@Suppress("FunctionName") +fun WideProfiledPowerGaugePanel( + screen: S, + parent: EditablePanel<*>? = null, + widget: ProfiledLevelGaugeWidget<*>, + x: Float = 0f, + y: Float = 0f, + width: Float = 18f, + height: Float = 48f +) = ProfiledPowerGaugePanel(screen, parent, widget, x, y, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt index 5b157ea43..97f7e4205 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/widget/ProgressGaugePanel.kt @@ -1,40 +1,46 @@ package ru.dbotthepony.mc.otm.client.screen.widget +import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack +import mezz.jei.api.recipe.RecipeType import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.WidgetLocation -import ru.dbotthepony.mc.otm.client.render.element -import ru.dbotthepony.mc.otm.client.render.subElement import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel +import ru.dbotthepony.mc.otm.compat.jei.JEIPlugin +import ru.dbotthepony.mc.otm.compat.jei.isJeiLoaded +import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import java.util.function.Supplier import kotlin.math.roundToInt -open class ProgressGaugePanel @JvmOverloads constructor( +open class ProgressGaugePanel( screen: S, parent: EditablePanel<*>? = null, val widget: ProgressGaugeWidget, x: Float = 0f, y: Float = 0f -): EditablePanel(screen, parent, x, y, width = GAUGE_BACKGROUND.width, height = GAUGE_BACKGROUND.height) { +): AbstractButtonPanel(screen, parent, x, y, width = GAUGE_BACKGROUND.width, height = GAUGE_BACKGROUND.height) { init { scissor = true } var flop = false + private var recipeTypeSupplier: Supplier>>? = null protected open fun makeTooltip(): MutableList { val tooltip: MutableList - if (widget.isStuck()) { + if (widget.isStuck) { tooltip = mutableListOf( TranslatableComponent( "otm.gui.progress_widget", - String.format("%.2f", widget.percentage() * 100f) + String.format("%.2f", widget.percentage * 100f) ), TranslatableComponent("otm.gui.progress_widget_stuck").withStyle(ChatFormatting.DARK_RED) ) @@ -42,45 +48,69 @@ open class ProgressGaugePanel @JvmOverloads constructor( tooltip = mutableListOf( TranslatableComponent( "otm.gui.progress_widget", - String.format("%.2f", widget.percentage() * 100f) + String.format("%.2f", widget.percentage * 100f) ) ) } + if (recipeTypeSupplier != null) { + tooltip.add(TextComponent("")) + tooltip.add(TranslatableComponent("jei.tooltip.show.recipes").withStyle(ChatFormatting.GRAY)) + } + return tooltip } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (widget.isStuck() && tick % 40 <= 20) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (widget.isStuck && tickCount % 40 <= 20) { RenderSystem.setShaderColor(0.75f, 0.4f, 0.4f, 1f) } if (flop) { - GAUGE_BACKGROUND.render(stack, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) - val width = (this.width * widget.percentage()).roundToInt().toFloat() - GAUGE_FOREGROUND.renderPartial(stack, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) + GAUGE_BACKGROUND.render(graphics, height = height, width = this.width, winding = UVWindingOrder.U1_V0_U0_V1) + val width = (this.width * widget.percentage).roundToInt().toFloat() + GAUGE_FOREGROUND.renderPartial(graphics, x = this.width - width, height = height, width = width, winding = UVWindingOrder.U1_V0_U0_V1) } else { - GAUGE_BACKGROUND.render(stack, height = height, width = this.width) - val width = (this.width * widget.percentage()).roundToInt().toFloat() - GAUGE_FOREGROUND.renderPartial(stack, height = height, width = width) + GAUGE_BACKGROUND.render(graphics, height = height, width = this.width) + val width = (this.width * widget.percentage).roundToInt().toFloat() + GAUGE_FOREGROUND.renderPartial(graphics, height = height, width = width) } - if (widget.isStuck() && tick % 40 <= 20) { + if (widget.isStuck && tickCount % 40 <= 20) { RenderSystem.setShaderColor(1f, 1f, 1f, 1f) } } - override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - if (isHovered) { - screen.renderComponentTooltip(stack, makeTooltip(), mouseX.toInt(), mouseY.toInt()) + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (shouldRenderTooltips(graphics, mouseX, mouseY, partialTick)) { + graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt()) return true } return false } + override fun test(value: Int): Boolean { + return recipeTypeSupplier != null && value == InputConstants.MOUSE_BUTTON_LEFT + } + + override fun onClick(mouseButton: Int) { + val recipeTypeSupplier = recipeTypeSupplier ?: return + JEIPlugin.RUNTIME.recipesGui.showTypes(recipeTypeSupplier.get()) + } + + private fun setRecipeType0(provider: Supplier>>) { + recipeTypeSupplier = provider + } + + fun setRecipeType(provider: Supplier>>) { + if (isJeiLoaded) { + setRecipeType0(provider) + } + } + companion object { - val GAUGE_BACKGROUND = WidgetLocation.PROGRESS_ARROWS.subElement(y = 0f, width = 22f, height = 15f) - val GAUGE_FOREGROUND = WidgetLocation.PROGRESS_ARROWS.subElement(y = 15f, width = 22f, height = 15f) + val GAUGE_BACKGROUND = WidgetLocation.PROGRESS_ARROWS.sprite(y = 0f, width = 22f, height = 15f) + val GAUGE_FOREGROUND = WidgetLocation.PROGRESS_ARROWS.sprite(y = 15f, width = 22f, height = 15f) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt new file mode 100644 index 000000000..29c3c79fa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/adastra/AdAstraCompat.kt @@ -0,0 +1,56 @@ +package ru.dbotthepony.mc.otm.compat.adastra + +import earth.terrarium.ad_astra.AdAstra +import earth.terrarium.ad_astra.common.data.Planet +import earth.terrarium.ad_astra.common.data.PlanetData +import earth.terrarium.ad_astra.common.item.armor.SpaceSuit +import earth.terrarium.ad_astra.common.registry.ModDamageSource +import net.minecraft.world.entity.player.Player +import net.minecraftforge.event.entity.living.LivingHurtEvent +import net.minecraftforge.fml.ModList +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.config.ServerCompatConfig +import ru.dbotthepony.mc.otm.registry.CosmicRaysDamageSource +import ru.dbotthepony.mc.otm.registry.MItems + +val isAdAstraLoaded by lazy { + ModList.get().isLoaded(AdAstra.MOD_ID) +} + +// для надёжности +fun onDamageEvent(event: LivingHurtEvent) { + check(isAdAstraLoaded) { "Ad Astra is not loaded!" } + val ply = event.entity as? Player ?: return + + if (ServerCompatConfig.AdAstra.ANDROIDS_DO_NOT_NEED_OXYGEN) { + if (ply.matteryPlayer?.isAndroid != true) return + + if (event.source == ModDamageSource.OXYGEN) { + event.amount = 0f + event.isCanceled = true + } + } +} + +fun onMatteryTick(event: MatteryPlayerCapability.PostTick) { + check(isAdAstraLoaded) { "Ad Astra is not loaded!" } + + if ( + ServerCompatConfig.AdAstra.ANDROID_COSMIC_RAYS && + !event.player.abilities.invulnerable && + event.capability.isAndroid && + !PlanetData.getPlanetFromLevel(event.level.dimension()).map(Planet::hasAtmosphere).orElse(true) + ) { + val rand = event.level.random + + val noSpacesuits = event.player.armorSlots.count { it.item !is SpaceSuit } + val yesTritanium0 = if (!ServerCompatConfig.AdAstra.TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS) 0.0 else event.player.armorSlots.count { it.item in MItems.TRITANIUM_ARMOR } * 0.75 + val yesTritanium1 = if (!ServerCompatConfig.AdAstra.TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS) 0.0 else event.player.armorSlots.count { it.item in MItems.SIMPLE_TRITANIUM_ARMOR } * 0.2 + val yesTritanium = yesTritanium0 + yesTritanium1 + + if (rand.nextDouble() <= (noSpacesuits - yesTritanium) * ServerCompatConfig.AdAstra.ANDROID_COSMIC_RAYS_CHANCE) { + event.player.hurt(CosmicRaysDamageSource(), 1f) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt index f3f98b207..9ad73c8cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.compat.cos import com.mojang.blaze3d.platform.InputConstants -import com.mojang.blaze3d.vertex.PoseStack import lain.mods.cos.impl.ModObjects import lain.mods.cos.impl.client.PlayerRenderHandler import lain.mods.cos.impl.client.gui.GuiCosArmorInventory @@ -16,16 +15,18 @@ import net.minecraft.world.inventory.InventoryMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraftforge.fml.ModList +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.SkinElement -import ru.dbotthepony.mc.otm.client.render.element +import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite +import ru.dbotthepony.mc.otm.client.render.sprites.sprite import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.client.screen.panels.buttons.RectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.RectangleButtonPanel import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton.Companion.BUTTON_ACTIVE import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton.Companion.BUTTON_INACTIVE -import ru.dbotthepony.mc.otm.container.awareStream -import ru.dbotthepony.mc.otm.container.stream -import ru.dbotthepony.mc.otm.core.AwareItemStack +import ru.dbotthepony.mc.otm.container.util.awareStream +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.AwareItemStack +import ru.dbotthepony.mc.otm.core.collect.emptyIterator import ru.dbotthepony.mc.otm.menu.MatterySlot import java.util.stream.Stream @@ -71,16 +72,16 @@ private class CosmeticSlot(container: Container, private val slot: EquipmentSlot } } -fun Player.cosmeticArmorStream(): Stream { +fun Player.cosmeticArmorStream(): Iterator { if (!isCosmeticArmorLoaded) { - return Stream.empty() + return emptyIterator() } return cosmeticArmorStreamImpl() } -private fun Player.cosmeticArmorStreamImpl(): Stream { - val manager = ModObjects.invMan ?: return Stream.empty() +private fun Player.cosmeticArmorStreamImpl(): Iterator { + val manager = ModObjects.invMan ?: return emptyIterator() val container = if (this !is ServerPlayer) { manager.getCosArmorInventoryClient(uuid) @@ -88,7 +89,7 @@ private fun Player.cosmeticArmorStreamImpl(): Stream { manager.getCosArmorInventory(uuid) } - return (container as Container).stream() + return (container as Container).iterator() } fun Player.cosmeticArmorAwareStream(): Stream { @@ -149,13 +150,13 @@ class CosmeticToggleButton( width: Float = 5f, height: Float = 5f ) : RectangleButtonPanel(screen, parent, x, y, width, height) { - override val PRESSED: SkinElement + override val PRESSED: MatterySprite get() = BUTTON_ACTIVE - override val HOVERED: SkinElement + override val HOVERED: MatterySprite get() = BUTTON_ACTIVE - override val IDLE: SkinElement + override val IDLE: MatterySprite get() = BUTTON_INACTIVE - override val DISABLED: SkinElement + override val DISABLED: MatterySprite get() = BUTTON_INACTIVE private val index = when (index) { @@ -174,19 +175,19 @@ class CosmeticToggleButton( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { val inv = ModObjects.invMan.getCosArmorInventoryClient(minecraft.player?.uuid ?: throw ConcurrentModificationException()) if (inv.isSkinArmor(index)) { - BUTTON_ACTIVE.render(stack, width = width, height = height) + BUTTON_ACTIVE.render(graphics, width = width, height = height) } else { - BUTTON_INACTIVE.render(stack, width = width, height = height) + BUTTON_INACTIVE.render(graphics, width = width, height = height) } } companion object { - val BUTTON_INACTIVE = GuiCosArmorInventory.TEXTURE.element(0f, 176f, 5f, 5f) - val BUTTON_ACTIVE = GuiCosArmorInventory.TEXTURE.element(5f, 176f, 5f, 5f) + val BUTTON_INACTIVE = GuiCosArmorInventory.TEXTURE.sprite(0f, 176f, 5f, 5f) + val BUTTON_ACTIVE = GuiCosArmorInventory.TEXTURE.sprite(5f, 176f, 5f, 5f) } } @@ -198,13 +199,13 @@ class CosmeticToggleRenderButton( width: Float = 5f, height: Float = 5f ) : RectangleButtonPanel(screen, parent, x, y, width, height) { - override val PRESSED: SkinElement + override val PRESSED: MatterySprite get() = BUTTON_ACTIVE - override val HOVERED: SkinElement + override val HOVERED: MatterySprite get() = BUTTON_ACTIVE - override val IDLE: SkinElement + override val IDLE: MatterySprite get() = BUTTON_INACTIVE - override val DISABLED: SkinElement + override val DISABLED: MatterySprite get() = BUTTON_INACTIVE override fun onClick(clickButton: Int) { @@ -213,11 +214,11 @@ class CosmeticToggleRenderButton( } } - override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (PlayerRenderHandler.Disabled) { - BUTTON_ACTIVE.render(stack, width = width, height = height) + BUTTON_ACTIVE.render(graphics, width = width, height = height) } else { - BUTTON_INACTIVE.render(stack, width = width, height = height) + BUTTON_INACTIVE.render(graphics, width = width, height = height) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt index ddda5ccb1..0966efef3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt @@ -5,32 +5,50 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraftforge.fml.ModList +import net.minecraftforge.fml.loading.FMLEnvironment +import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.container.awareStream -import ru.dbotthepony.mc.otm.container.stream -import ru.dbotthepony.mc.otm.core.AwareItemStack +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.container.util.awareStream +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.AwareItemStack +import ru.dbotthepony.mc.otm.core.collect.concatIterators +import ru.dbotthepony.mc.otm.core.collect.emptyIterator import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.menu.PlayerSlot import top.theillusivec4.curios.api.CuriosApi +import top.theillusivec4.curios.api.event.SlotModifiersUpdatedEvent import top.theillusivec4.curios.common.inventory.CosmeticCurioSlot import top.theillusivec4.curios.common.inventory.CurioSlot -import java.util.* +import top.theillusivec4.curios.common.network.NetworkHandler +import top.theillusivec4.curios.common.network.client.CPacketOpenCurios import java.util.stream.Stream +import kotlin.collections.ArrayList val isCuriosLoaded by lazy { ModList.get().isLoaded(CuriosApi.MODID) } -private fun Player.getCuriosSlotsImpl(): Collection> { +fun onCuriosSlotModifiersUpdated(event: SlotModifiersUpdatedEvent) { + check(isCuriosLoaded) { "Curios is not loaded!" } + event.entity.matteryPlayer?.recreateExoPackMenu() +} + +fun openCuriosScreen(carriedStack: ItemStack = ItemStack.EMPTY) { + if (FMLEnvironment.dist.isClient) NetworkHandler.INSTANCE.send(PacketDistributor.SERVER.noArg(), CPacketOpenCurios(carriedStack)) +} + +private fun Player.getCuriosSlotsImpl(): List> { val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return listOf() - val result = ArrayList>() + val result = ArrayList>() val sortedIdentifiers = ArrayList(handler.curios.keys.size) sortedIdentifiers.addAll(handler.curios.keys) - if (handler.curios !is LinkedHashMap) { - sortedIdentifiers.sort() - } + //if (handler.curios !is LinkedHashMap) { + // sortedIdentifiers.sort() + //} for (identifier in sortedIdentifiers) { val curio = handler.curios[identifier]!! @@ -44,9 +62,9 @@ private fun Player.getCuriosSlotsImpl(): Collection> { if (curio.hasCosmetic()) { val cosmetic = CosmeticCurioSlot(this, curio.cosmeticStacks, slot, identifier, 0, 0) - result.add(regular to cosmetic) + result.add(PlayerSlot(regular, cosmetic)) } else { - result.add(regular to null) + result.add(PlayerSlot(regular)) } } } @@ -58,7 +76,9 @@ private fun Player.getCuriosSlotsImpl(): Collection> { /** * [Pair] */ -val Player.curiosSlots: Collection> get() { +val Player.curiosSlots: List> get() { + return listOf() + if (!isCuriosLoaded) { return listOf() } @@ -66,25 +86,27 @@ val Player.curiosSlots: Collection> get() { return getCuriosSlotsImpl() } -private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Stream { - val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty() +private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Iterator { + val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return emptyIterator() - val result = LinkedList>() + val result = ArrayList>() for ((identifier, curio) in handler.curios) { - result.add(curio.stacks.stream()) + result.add(curio.stacks.iterator()) if (includeCosmetics && curio.hasCosmetic()) { - result.add(curio.cosmeticStacks.stream()) + result.add(curio.cosmeticStacks.iterator()) } } - return Streams.concat(*result.toTypedArray()) + return concatIterators(result) } -fun Player.curiosStream(includeCosmetics: Boolean = true): Stream { +fun Player.curiosStream(includeCosmetics: Boolean = true): Iterator { + return emptyIterator() + if (!isCuriosLoaded) { - return Stream.empty() + return emptyIterator() } return curiosStreamImpl(includeCosmetics) @@ -93,7 +115,7 @@ fun Player.curiosStream(includeCosmetics: Boolean = true): Stream private fun Player.curiosAwareStreamImpl(includeCosmetics: Boolean): Stream { val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty() - val result = LinkedList>() + val result = ArrayList>() for ((identifier, curio) in handler.curios) { result.add(curio.stacks.awareStream()) @@ -107,6 +129,8 @@ private fun Player.curiosAwareStreamImpl(includeCosmetics: Boolean): Stream { + return Stream.empty() + if (!isCuriosLoaded) { return Stream.empty() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadeCompatData.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadeCompatData.kt new file mode 100644 index 000000000..7678283fb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadeCompatData.kt @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.compat.jade + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters.loc +import ru.dbotthepony.mc.otm.core.math.RGBAColor + +object JadeUids { + val MATTERY_ENERGY: ResourceLocation = loc("mattery_energy") + val MATTER_STORAGE: ResourceLocation = loc("matter_storage") + val MATTERY_WORKER: ResourceLocation = loc("mattery_worker") + + val MATTER_BOTTLER: ResourceLocation = loc("matter_bottler") + val MATTER_RECONSTRUCTOR: ResourceLocation = loc("matter_reconstructor") +} + +object JadeTagKeys { + val MATTERY_ENERGY_DATA = "otmJadeMatteryEnergyData" + val MATTER_STORAGE_DATA = "otmJadeMatterStorageData" + val MATTERY_WORKER_DATA = "otmJadeMatteryWorkerData" + + val MATTER_BOTTLER_DATA = "otmJadeMatterBottlerData" + val MATTER_RECONSTRUCTOR_DATA = "otmJadeMatterReconstructorData" +} + +object JadeColors { + val ENERGY_COLOR = RGBAColor(231, 232, 0) + val ENERGY_COLOR2 = RGBAColor(192, 193, 0) + val MATTER_COLOR = RGBAColor(71, 255, 187) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadePlugin.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadePlugin.kt new file mode 100644 index 000000000..d5ca7cba5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/JadePlugin.kt @@ -0,0 +1,38 @@ +package ru.dbotthepony.mc.otm.compat.jade + +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.block.MatteryBlock +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity +import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity +import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock +import ru.dbotthepony.mc.otm.block.matter.MatterReconstructorBlock +import ru.dbotthepony.mc.otm.block.matter.MatterReplicatorBlock +import ru.dbotthepony.mc.otm.block.matter.MatterScannerBlock +import ru.dbotthepony.mc.otm.compat.jade.providers.* +import snownee.jade.api.IWailaClientRegistration +import snownee.jade.api.IWailaCommonRegistration +import snownee.jade.api.IWailaPlugin +import snownee.jade.api.WailaPlugin + +@WailaPlugin +class JadePlugin : IWailaPlugin { + override fun register(reg: IWailaCommonRegistration) { + reg.registerBlockDataProvider(MatterStorageProvider, BlockEntity::class.java) + reg.registerBlockDataProvider(MatteryEnergyProvider, BlockEntity::class.java) + reg.registerBlockDataProvider(MatteryWorkerProvider, MatteryWorkerBlockEntity::class.java) + reg.registerBlockDataProvider(MatterBottlerProvider, MatterBottlerBlockEntity::class.java) + reg.registerBlockDataProvider(MatterReconstructorProvider, MatterReconstructorBlockEntity::class.java) + } + + override fun registerClient(reg: IWailaClientRegistration) { + reg.registerBlockComponent(MatteryEnergyProvider, Block::class.java) + reg.registerBlockComponent(MatterStorageProvider, Block::class.java) + reg.registerBlockComponent(MatteryWorkerProvider, MatteryBlock::class.java) + reg.registerBlockComponent(MatterBottlerProvider, MatterBottlerBlock::class.java) + reg.registerBlockComponent(MatterReconstructorProvider, MatterReconstructorBlock::class.java) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterBottlerProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterBottlerProvider.kt new file mode 100644 index 000000000..47d5048a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterBottlerProvider.kt @@ -0,0 +1,66 @@ +package ru.dbotthepony.mc.otm.compat.jade.providers + +import net.minecraft.nbt.CompoundTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity +import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys +import ru.dbotthepony.mc.otm.compat.jade.JadeUids +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import snownee.jade.api.BlockAccessor +import snownee.jade.api.IBlockComponentProvider +import snownee.jade.api.IServerDataProvider +import snownee.jade.api.ITooltip +import snownee.jade.api.config.IPluginConfig +import snownee.jade.api.ui.BoxStyle +import snownee.jade.api.ui.IElementHelper + +object MatterBottlerProvider : IBlockComponentProvider, IServerDataProvider { + override fun getUid(): ResourceLocation = JadeUids.MATTER_BOTTLER + + override fun appendServerData(data: CompoundTag, player: ServerPlayer, level: Level, bottler: BlockEntity?, state: Boolean) { + if (bottler is MatterBottlerBlockEntity) { + val bottlerData = CompoundTag() + bottlerData.putBoolean("isBottling", bottler.isBottling) + bottlerData.putBoolean("isIdling", bottler.blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.WORKING) + bottlerData.putFloat("workProgress", bottler.workProgress) + data.put(JadeTagKeys.MATTER_BOTTLER_DATA, bottlerData) + } + } + + override fun appendTooltip(tooltip: ITooltip, accessor: BlockAccessor, config: IPluginConfig) { + val data = accessor.serverData + + if (!data.contains(JadeTagKeys.MATTER_BOTTLER_DATA)) return + val bottlerData = data.getCompound(JadeTagKeys.MATTER_BOTTLER_DATA) + + val elementHelper = IElementHelper.get() + tooltip.add( + elementHelper.text( + TranslatableComponent( + "otm.jade.mode", + if (bottlerData.getBoolean("isBottling")) + TranslatableComponent("otm.jade.matter_bottler.mode.fill") + else + TranslatableComponent("otm.jade.matter_bottler.mode.drain") + ) + ) + ) + + if (!data.getBoolean("isIdling")) { + tooltip.add( + elementHelper.progress( + data.getFloat("workProgress"), + null, + elementHelper.progressStyle().color(RGBAColor.WHITE.toARGB()), + BoxStyle.DEFAULT, + true + ) + ) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterReconstructorProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterReconstructorProvider.kt new file mode 100644 index 000000000..215f4e589 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterReconstructorProvider.kt @@ -0,0 +1,66 @@ +package ru.dbotthepony.mc.otm.compat.jade.providers + +import net.minecraft.nbt.CompoundTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys +import ru.dbotthepony.mc.otm.compat.jade.JadeUids +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import snownee.jade.api.BlockAccessor +import snownee.jade.api.IBlockComponentProvider +import snownee.jade.api.IServerDataProvider +import snownee.jade.api.ITooltip +import snownee.jade.api.config.IPluginConfig +import snownee.jade.api.ui.BoxStyle +import snownee.jade.api.ui.IElementHelper + +object MatterReconstructorProvider : IBlockComponentProvider, IServerDataProvider { + override fun getUid(): ResourceLocation = JadeUids.MATTER_RECONSTRUCTOR + + override fun appendServerData(data: CompoundTag, player: ServerPlayer, level: Level, reconstructor: BlockEntity?, state: Boolean) { + if (reconstructor is MatterReconstructorBlockEntity) { + val item = reconstructor.repairContainer.get(0) + + if (!item.isEmpty && item.isDamageableItem && item.maxDamage != 0) { + val reconstructorData = CompoundTag() + + reconstructorData.putInt("damage", item.maxDamage - item.damageValue) + reconstructorData.putInt("maxDamage", item.maxDamage) + + data.put(JadeTagKeys.MATTER_RECONSTRUCTOR_DATA, reconstructorData) + } + } + } + + override fun appendTooltip(tooltip: ITooltip, accessor: BlockAccessor, config: IPluginConfig) { + val data = accessor.serverData + + if (!data.contains(JadeTagKeys.MATTER_RECONSTRUCTOR_DATA)) return + val reconstructorData = data.getCompound(JadeTagKeys.MATTER_RECONSTRUCTOR_DATA) + + val damage = reconstructorData.getInt("damage") + val maxDamage = reconstructorData.getInt("maxDamage") + + val ratio = damage.toFloat() / maxDamage.toFloat() + + val elementHelper = IElementHelper.get() + + tooltip.add( + elementHelper.progress( + ratio, + TranslatableComponent( + "item.durability", + damage, + maxDamage + ), + elementHelper.progressStyle().color(RGBAColor.DARK_GREEN.toARGB()).textColor(RGBAColor.WHITE.toARGB()), + BoxStyle.DEFAULT, + true + ) + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt new file mode 100644 index 000000000..90cba644c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt @@ -0,0 +1,108 @@ +package ru.dbotthepony.mc.otm.compat.jade.providers + +import net.minecraft.ChatFormatting +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.compat.jade.JadeColors +import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys +import ru.dbotthepony.mc.otm.compat.jade.JadeUids +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.math.putDecimal +import ru.dbotthepony.mc.otm.core.util.formatMatter +import snownee.jade.api.BlockAccessor +import snownee.jade.api.IBlockComponentProvider +import snownee.jade.api.IServerDataProvider +import snownee.jade.api.ITooltip +import snownee.jade.api.config.IPluginConfig +import snownee.jade.api.ui.BoxStyle +import snownee.jade.api.ui.IElementHelper + +object MatterStorageProvider : IBlockComponentProvider, IServerDataProvider { + override fun getUid(): ResourceLocation = JadeUids.MATTER_STORAGE + + override fun appendServerData(data: CompoundTag, player: ServerPlayer, level: Level, blockEntity: BlockEntity?, state: Boolean) { + blockEntity?.getCapability(MatteryCapability.MATTER)?.ifPresentK { + val matterData = CompoundTag() + + matterData.putDecimal("storedMatter", it.storedMatter) + matterData.putDecimal("maxStoredMatter", it.maxStoredMatter) + + if (it is AbstractProfiledStorage<*>) { + val profiledData = CompoundTag() + +// profiledData.putDecimal("lastTickReceive", it.lastTickReceive) +// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) + + profiledData.putDecimal("weightedReceive", it.weightedReceive) + profiledData.putDecimal("weightedTransfer", it.weightedTransfer) + + matterData.put("profiledData", profiledData) + } + + data.put(JadeTagKeys.MATTER_STORAGE_DATA, matterData) + } + } + + override fun appendTooltip(tooltip: ITooltip, accessor: BlockAccessor, config: IPluginConfig) { + val data = accessor.serverData + if (!data.contains(JadeTagKeys.MATTER_STORAGE_DATA)) return + + val matterData = data.getCompound(JadeTagKeys.MATTER_STORAGE_DATA) + val storedMatter = matterData.getDecimal("storedMatter") + val maxStoredMatter = matterData.getDecimal("maxStoredMatter") + + val elementHelper = IElementHelper.get() + + tooltip.add( + elementHelper.progress( + storedMatter.percentage(maxStoredMatter), + TranslatableComponent( + "otm.gui.level", + storedMatter.formatMatter(), + maxStoredMatter.formatMatter() + ), + elementHelper.progressStyle().color(JadeColors.MATTER_COLOR.toARGB()).textColor(RGBAColor.WHITE.toARGB()), + BoxStyle.DEFAULT, + true + ) + ) + + if (matterData.contains("profiledData")) { + val profiledData = matterData.getCompound("profiledData") + + tooltip.add( + elementHelper.text( + formatLevel( + profiledData.getDecimal("weightedReceive"), + profiledData.getDecimal("weightedTransfer") + ) + ) + ) + } + } + + private fun formatLevel(a: Decimal, b: Decimal): Component { + val diff = a - b + + val fa = a.formatMatter().copy().withStyle(ChatFormatting.DARK_GREEN) + val fb = b.formatMatter().copy().withStyle(ChatFormatting.DARK_RED) + + return if (diff.isZero) { + TranslatableComponent("otm.gui.diff", diff.formatMatter().copy().withStyle(ChatFormatting.GRAY), fa, fb) + } else if (diff.isPositive) { + TranslatableComponent("otm.gui.diff", diff.formatMatter().copy().withStyle(ChatFormatting.DARK_GREEN), fa, fb) + } else { + TranslatableComponent("otm.gui.diff", (-diff).formatMatter().copy().withStyle(ChatFormatting.DARK_RED), fa, fb) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt new file mode 100644 index 000000000..4ba4a6b93 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt @@ -0,0 +1,106 @@ +package ru.dbotthepony.mc.otm.compat.jade.providers + +import net.minecraft.ChatFormatting +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.compat.jade.JadeColors +import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys +import ru.dbotthepony.mc.otm.compat.jade.JadeUids +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.math.putDecimal +import ru.dbotthepony.mc.otm.core.util.formatPower +import snownee.jade.api.* +import snownee.jade.api.config.IPluginConfig +import snownee.jade.api.ui.BoxStyle +import snownee.jade.api.ui.IElementHelper + +object MatteryEnergyProvider : IBlockComponentProvider, IServerDataProvider { + override fun getUid(): ResourceLocation = JadeUids.MATTERY_ENERGY + + override fun appendServerData(data: CompoundTag, player: ServerPlayer, level: Level, blockEntity: BlockEntity?, state: Boolean) { + blockEntity?.getCapability(MatteryCapability.ENERGY)?.ifPresentK { + val energyData = CompoundTag() + + energyData.putDecimal("batteryLevel", it.batteryLevel) + energyData.putDecimal("maxBatteryLevel", it.maxBatteryLevel) + + if (it is AbstractProfiledStorage<*>) { + val profiledData = CompoundTag() + +// profiledData.putDecimal("lastTickReceive", it.lastTickReceive) +// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer) + + profiledData.putDecimal("weightedReceive", it.weightedReceive) + profiledData.putDecimal("weightedTransfer", it.weightedTransfer) + + energyData.put("profiledData", profiledData) + } + + data.put(JadeTagKeys.MATTERY_ENERGY_DATA, energyData) + } + } + + override fun appendTooltip(tooltip: ITooltip, accessor: BlockAccessor, config: IPluginConfig) { + val data = accessor.serverData + + if (!data.contains(JadeTagKeys.MATTERY_ENERGY_DATA)) return + val energyData = data.getCompound(JadeTagKeys.MATTERY_ENERGY_DATA) + + val batteryLevel = energyData.getDecimal("batteryLevel") + val maxBatteryLevel = energyData.getDecimal("maxBatteryLevel") + + val elementHelper = IElementHelper.get() + + tooltip.add( + elementHelper.progress( + batteryLevel.percentage(maxBatteryLevel), + TranslatableComponent( + "otm.gui.level", + batteryLevel.formatPower(), + maxBatteryLevel.formatPower() + ), + elementHelper.progressStyle().color(JadeColors.ENERGY_COLOR.toARGB(), JadeColors.ENERGY_COLOR2.toARGB()).textColor(RGBAColor.WHITE.toARGB()), + BoxStyle.DEFAULT, + true + ) + ) + + if (energyData.contains("profiledData")) { + val profiledData = energyData.getCompound("profiledData") + + tooltip.add( + elementHelper.text( + formatLevel( + profiledData.getDecimal("weightedReceive"), + profiledData.getDecimal("weightedTransfer") + ) + ) + ) + } + } + + private fun formatLevel(a: Decimal, b: Decimal): Component { + val diff = a - b + + val fa = a.formatPower().copy().withStyle(ChatFormatting.DARK_GREEN) + val fb = b.formatPower().copy().withStyle(ChatFormatting.DARK_RED) + + return if (diff.isZero) { + TranslatableComponent("otm.gui.diff", diff.formatPower().copy().withStyle(ChatFormatting.GRAY), fa, fb) + } else if (diff.isPositive) { + TranslatableComponent("otm.gui.diff", diff.formatPower().copy().withStyle(ChatFormatting.DARK_GREEN), fa, fb) + } else { + TranslatableComponent("otm.gui.diff", (-diff).formatPower().copy().withStyle(ChatFormatting.DARK_RED), fa, fb) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt new file mode 100644 index 000000000..c6ccd4173 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt @@ -0,0 +1,86 @@ +package ru.dbotthepony.mc.otm.compat.jade.providers + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.block.entity.ItemJob +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys +import ru.dbotthepony.mc.otm.compat.jade.JadeUids +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.nbt.getCompoundList +import ru.dbotthepony.mc.otm.core.nbt.getItemStack +import ru.dbotthepony.mc.otm.core.nbt.set +import snownee.jade.api.BlockAccessor +import snownee.jade.api.IBlockComponentProvider +import snownee.jade.api.IServerDataProvider +import snownee.jade.api.ITooltip +import snownee.jade.api.config.IPluginConfig +import snownee.jade.api.ui.BoxStyle +import snownee.jade.api.ui.IElementHelper + +object MatteryWorkerProvider : IBlockComponentProvider, IServerDataProvider { + override fun getUid(): ResourceLocation = JadeUids.MATTERY_WORKER + + override fun appendServerData(data: CompoundTag, player: ServerPlayer, level: Level, worker: BlockEntity?, state: Boolean) { + if (worker is MatteryWorkerBlockEntity<*>) { + val workerData = CompoundTag() + + workerData["jobs"] = ListTag().also { + for (job in worker.jobEventLoops) { + val jobData = CompoundTag() + jobData.putFloat("workProgress", job.workProgress) + jobData.putBoolean("isIdling", job.isIdling) + jobData.putBoolean("isUnableToProcess", job.isUnableToProcess) + + if (job.currentJob is ItemJob) { + val currentJob = job.currentJob as ItemJob + jobData.put("itemStack", currentJob.itemStack.serializeNBT()) + } + + it.add(jobData) + } + } + + data.put(JadeTagKeys.MATTERY_WORKER_DATA, workerData) + } + } + + override fun appendTooltip(tooltip: ITooltip, accessor: BlockAccessor, config: IPluginConfig) { + val data = accessor.serverData + + if (!data.contains(JadeTagKeys.MATTERY_WORKER_DATA)) return + val workerData = data.getCompound(JadeTagKeys.MATTERY_WORKER_DATA) + + val elementHelper = IElementHelper.get() + val style = elementHelper.progressStyle().color(RGBAColor.WHITE.toARGB()) + val styleError = elementHelper.progressStyle().color(RGBAColor.REDDISH.toARGB()) + + for (job in workerData.getCompoundList("jobs")) { + val progress = job.getFloat("workProgress") + val isIdling = job.getBoolean("isIdling") + val isUnableToProcess = job.getBoolean("isUnableToProcess") + val itemStack = job.getItemStack("itemStack") + + if (!isIdling) { + if (!itemStack.isEmpty) { + tooltip.add(elementHelper.smallItem(itemStack)) + tooltip.append(elementHelper.text(itemStack.hoverName)) + } + + tooltip.add( + elementHelper.progress( + progress, + null, + if (isUnableToProcess) styleError else style, + BoxStyle.DEFAULT, + true + ) + ) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/ExopackInventoryTransferHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/ExopackInventoryTransferHandler.kt new file mode 100644 index 000000000..3572f1770 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/ExopackInventoryTransferHandler.kt @@ -0,0 +1,154 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap +import it.unimi.dsi.fastutil.ints.IntArraySet +import mezz.jei.api.constants.RecipeTypes +import mezz.jei.api.gui.ingredient.IRecipeSlotView +import mezz.jei.api.gui.ingredient.IRecipeSlotsView +import mezz.jei.api.recipe.RecipeIngredientRole +import mezz.jei.api.recipe.transfer.IRecipeTransferError +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper +import mezz.jei.api.recipe.transfer.IRecipeTransferInfo +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.MenuType +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.crafting.CraftingRecipe +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.toImmutableList +import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import java.util.* +import kotlin.collections.ArrayList + +class ExopackInventoryTransferHandler(private val helper: IRecipeTransferHandlerHelper) : IRecipeTransferHandler { + private val transfer = helper.createUnregisteredRecipeTransferHandler(object : IRecipeTransferInfo { + override fun getContainerClass(): Class { + return ExopackInventoryMenu::class.java + } + + override fun getMenuType(): Optional> { + return Optional.empty() + } + + override fun getRecipeType(): mezz.jei.api.recipe.RecipeType { + return RecipeTypes.CRAFTING + } + + override fun canHandle(container: ExopackInventoryMenu, recipe: CraftingRecipe): Boolean { + return true + } + + override fun getRecipeSlots( + container: ExopackInventoryMenu, + recipe: CraftingRecipe + ): List { + return container.craftingSlots + } + + override fun getInventorySlots( + container: ExopackInventoryMenu, + recipe: CraftingRecipe + ): List { + return container.playerInventorySlots + } + }) + + override fun getContainerClass(): Class { + return ExopackInventoryMenu::class.java + } + + override fun getMenuType(): Optional> { + return Optional.empty() + } + + override fun getRecipeType(): mezz.jei.api.recipe.RecipeType { + return RecipeTypes.CRAFTING + } + + private val validSlots = IntArraySet().also { + it.add(0) + it.add(1) + it.add(3) + it.add(4) + } + + private val directMap = Int2IntArrayMap().also { + for (i in 0 .. 8) { + it.put(i, i) + } + } + + private val smallMap = Int2IntArrayMap().also { + var i = 0 + it.put(0, i++) + it.put(1, i++) + it.put(3, i++) + it.put(4, i) + } + + override fun transferRecipe( + container: ExopackInventoryMenu, + recipe: CraftingRecipe, + recipeSlots: IRecipeSlotsView, + player: Player, + maxTransfer: Boolean, + doTransfer: Boolean + ): IRecipeTransferError? { + val inputs = recipeSlots.getSlotViews(RecipeIngredientRole.INPUT) + + if (inputs.size != 9) { + return IRecipeTransferError { IRecipeTransferError.Type.INTERNAL } + } + + if (container.craftingSlots.size != 9) { + for ((i, ingredient) in inputs.withIndex()) { + if (!ingredient.isEmpty && i !in validSlots) { + return helper.createUserErrorWithTooltip(TranslatableComponent("jei.tooltip.error.recipe.transfer.too.large.player.inventory")) + } + } + + val filteredInputs = ArrayList() + + for ((i, ingredient) in inputs.withIndex()) { + if (i in validSlots) { + filteredInputs.add(ingredient) + } + } + + val outputs = recipeSlots.getSlotViews(RecipeIngredientRole.OUTPUT).toImmutableList() + val catalysts = recipeSlots.getSlotViews(RecipeIngredientRole.CATALYST).toImmutableList() + val render = recipeSlots.getSlotViews(RecipeIngredientRole.RENDER_ONLY).toImmutableList() + + val combine = ArrayList(filteredInputs.size + outputs.size + render.size) + + combine.addAll(filteredInputs) + combine.addAll(outputs) + combine.addAll(render) + + val combined = combine.toImmutableList() + + val newView = object : IRecipeSlotsView { + override fun getSlotViews(): List { + return combined + } + + override fun getSlotViews(role: RecipeIngredientRole): List { + return when (role) { + RecipeIngredientRole.INPUT -> filteredInputs + RecipeIngredientRole.OUTPUT -> outputs + RecipeIngredientRole.CATALYST -> catalysts + RecipeIngredientRole.RENDER_ONLY -> render + } + } + + override fun findSlotByName(slotName: String): Optional { + return combined.stream().filter { it.slotName.orElse(null) == slotName }.findAny() + } + } + + return this.transfer.transferRecipe(container, recipe, newView, player, maxTransfer, doTransfer) + } + + return this.transfer.transferRecipe(container, recipe, recipeSlots, player, maxTransfer, doTransfer) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/Gauges.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/Gauges.kt new file mode 100644 index 000000000..e6751c646 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/Gauges.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel +import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatMatter +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.systemTime + +fun renderMatterGauge( + guiGraphics: MGUIGraphics, + x: Float, + y: Float, + width: Float = MatterGaugePanel.GAUGE_BACKGROUND.width, + height: Float = MatterGaugePanel.GAUGE_BACKGROUND.height, + drainSpeed: Float = 1f, + wavesStrength: Float = drainSpeed * 2f, +) { + MatterGaugePanel.GAUGE_BACKGROUND.render(guiGraphics, x, y, width, height) + MatterGaugePanel.renderLevel(guiGraphics, x, y, width, height, 1f - ((systemTime.secondsF * drainSpeed) % 1f), wavesStrength) +} + +fun renderEnergyGauge( + guiGraphics: MGUIGraphics, + x: Float, + y: Float, + width: Float = PowerGaugePanel.GAUGE_BACKGROUND.width, + height: Float = PowerGaugePanel.GAUGE_BACKGROUND.height, + drainSpeed: Float = 1f +) { + val perc = 1f - ((systemTime.secondsF * drainSpeed) % 1f) + PowerGaugePanel.GAUGE_BACKGROUND.render(guiGraphics, x, y, width, height) + PowerGaugePanel.GAUGE_FOREGROUND.renderPartial(guiGraphics, x, y + height * (1f - perc) - 1f, width, height * perc) +} + +fun matterGaugeTooltips(target: MutableList, matter: Decimal, mouseX: Double, mouseY: Double, x: Float, y: Float, width: Float = MatterGaugePanel.GAUGE_BACKGROUND.width, height: Float = MatterGaugePanel.GAUGE_BACKGROUND.height) { + if (mouseX in x .. (x + width - 1) && mouseY in y .. (y + height - 1)) { + target.add(TranslatableComponent("otm.gui.matter_panel.matter_required", matter.formatMatter(formatAsReadable = ShiftPressedCond))) + } +} + +fun energyGaugeTooltips(target: MutableList, energy: Decimal, mouseX: Double, mouseY: Double, x: Float, y: Float, width: Float = PowerGaugePanel.GAUGE_BACKGROUND.width, height: Float = PowerGaugePanel.GAUGE_BACKGROUND.height) { + if (mouseX in x .. (x + width - 1) && mouseY in y .. (y + height - 1)) { + target.add(TranslatableComponent("otm.gui.energy_required", energy.formatPower(formatAsReadable = ShiftPressedCond))) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/IGUIRenderable2IDrawable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/IGUIRenderable2IDrawable.kt new file mode 100644 index 000000000..56ee12f89 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/IGUIRenderable2IDrawable.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import com.mojang.blaze3d.vertex.PoseStack +import mezz.jei.api.gui.drawable.IDrawable + +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable + +class IGUIRenderable2IDrawable(val parent: IGUIRenderable) : IDrawable { + override fun getWidth(): Int { + return parent.width.toInt() + } + + override fun getHeight(): Int { + return parent.height.toInt() + } + + override fun draw(stack: PoseStack, xOffset: Int, yOffset: Int) { + parent.render(MGUIGraphics(stack), xOffset.toFloat(), yOffset.toFloat()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/JEIPlugin.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/JEIPlugin.kt index 763dd099d..1a654f7c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/JEIPlugin.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/JEIPlugin.kt @@ -1,41 +1,41 @@ package ru.dbotthepony.mc.otm.compat.jei -import it.unimi.dsi.fastutil.ints.Int2IntArrayMap -import it.unimi.dsi.fastutil.ints.IntArraySet import mezz.jei.api.IModPlugin import mezz.jei.api.JeiPlugin import mezz.jei.api.constants.RecipeTypes +import mezz.jei.api.constants.VanillaTypes import mezz.jei.api.gui.handlers.IGuiContainerHandler -import mezz.jei.api.gui.ingredient.IRecipeSlotView -import mezz.jei.api.gui.ingredient.IRecipeSlotsView import mezz.jei.api.helpers.IJeiHelpers -import mezz.jei.api.recipe.RecipeIngredientRole -import mezz.jei.api.recipe.transfer.IRecipeTransferError -import mezz.jei.api.recipe.transfer.IRecipeTransferHandler -import mezz.jei.api.recipe.transfer.IRecipeTransferInfo +import mezz.jei.api.ingredients.ITypedIngredient import mezz.jei.api.registration.IGuiHandlerRegistration import mezz.jei.api.registration.IRecipeCatalystRegistration import mezz.jei.api.registration.IRecipeCategoryRegistration import mezz.jei.api.registration.IRecipeRegistration import mezz.jei.api.registration.IRecipeTransferRegistration +import mezz.jei.api.runtime.IClickableIngredient +import mezz.jei.api.runtime.IJeiRuntime import net.minecraft.client.renderer.Rect2i import net.minecraft.resources.ResourceLocation -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.MenuType -import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.crafting.CraftingRecipe import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.screen.MatteryScreen -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.toImmutableList -import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.filterIsInstance +import ru.dbotthepony.mc.otm.core.collect.filterNotNull +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu +import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu +import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu +import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu +import ru.dbotthepony.mc.otm.recipe.PainterRecipe import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRecipes import java.util.* import java.util.stream.Collectors -import kotlin.collections.ArrayList import kotlin.properties.Delegates var isJeiLoaded = false @@ -49,6 +49,11 @@ class JEIPlugin : IModPlugin { var helpers: IJeiHelpers by Delegates.notNull() private set + + private var _RUNTIME: IJeiRuntime? = null + + val RUNTIME: IJeiRuntime + get() = checkNotNull(_RUNTIME) { "JEI runtime is unavailable" } } init { @@ -61,155 +66,52 @@ class JEIPlugin : IModPlugin { override fun registerRecipeCatalysts(registration: IRecipeCatalystRegistration) { registration.addRecipeCatalyst(ItemStack(MItems.CHEMICAL_GENERATOR), RecipeTypes.FUELING) + registration.addRecipeCatalyst(ItemStack(MItems.POWERED_FURNACE), RecipeTypes.SMELTING) + registration.addRecipeCatalyst(ItemStack(MItems.ExopackUpgrades.SMELTING_UPGRADE), RecipeTypes.SMELTING) + registration.addRecipeCatalyst(ItemStack(MItems.POWERED_BLAST_FURNACE), RecipeTypes.BLASTING) + registration.addRecipeCatalyst(ItemStack(MItems.POWERED_SMOKER), RecipeTypes.SMOKING) + registration.addRecipeCatalyst(ItemStack(MItems.POWERED_SMOKER), MicrowaveRecipeCategory.recipeType) + registration.addRecipeCatalyst(ItemStack(MItems.ExopackUpgrades.CRAFTING_UPGRADE), RecipeTypes.CRAFTING) + registration.addRecipeCatalyst(ItemStack(MItems.ITEM_MONITOR), RecipeTypes.CRAFTING) registration.addRecipeCatalyst(ItemStack(MItems.PLATE_PRESS), PlatePressRecipeCategory.recipeType) + registration.addRecipeCatalyst(ItemStack(MItems.PAINTER), PainterRecipeCategory.recipeType) + registration.addRecipeCatalyst(ItemStack(MItems.MATTER_ENTANGLER), MatterEntanglerRecipeCategory.recipeType) + } + + override fun onRuntimeAvailable(jeiRuntime: IJeiRuntime) { + _RUNTIME = jeiRuntime + } + + override fun onRuntimeUnavailable() { + _RUNTIME = null } override fun registerCategories(registration: IRecipeCategoryRegistration) { helpers = registration.jeiHelpers registration.addRecipeCategories(PlatePressRecipeCategory) + registration.addRecipeCategories(PainterRecipeCategory) + registration.addRecipeCategories(MatterEntanglerRecipeCategory) + registration.addRecipeCategories(MicrowaveRecipeCategory) } override fun registerRecipes(registration: IRecipeRegistration) { val level = minecraft.level ?: throw NullPointerException("No ClientLevel. OLOLOLOLOLOLO") - registration.addRecipes(PlatePressRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PLATE_PRESS).filter { !it.isIncomplete }) + registration.addRecipes(PlatePressRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PLATE_PRESS).filter { !it.value.isIncomplete }.map { it.value }) + registration.addRecipes(PainterRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PAINTER).iterator().filter { !it.value.isIncomplete }.map { it.value }.filterIsInstance().toList()) + registration.addRecipes(MatterEntanglerRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.MATTER_ENTANGLER).filter { !it.value.isIncomplete }.map { it.value }) + registration.addRecipes(MicrowaveRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.MICROWAVE).filter { !it.value.isIncomplete }.map { it.value }) } override fun registerRecipeTransferHandlers(registration: IRecipeTransferRegistration) { val helper = registration.transferHelper - registration.addRecipeTransferHandler(object : IRecipeTransferHandler { - private val transfer = helper.createUnregisteredRecipeTransferHandler(object : IRecipeTransferInfo { - override fun getContainerClass(): Class { - return ExoPackInventoryMenu::class.java - } - - override fun getMenuType(): Optional> { - return Optional.empty() - } - - override fun getRecipeType(): mezz.jei.api.recipe.RecipeType { - return RecipeTypes.CRAFTING - } - - override fun canHandle(container: ExoPackInventoryMenu, recipe: CraftingRecipe): Boolean { - return true - } - - override fun getRecipeSlots( - container: ExoPackInventoryMenu, - recipe: CraftingRecipe - ): List { - return container.craftingSlots - } - - override fun getInventorySlots( - container: ExoPackInventoryMenu, - recipe: CraftingRecipe - ): List { - return container.playerInventorySlots - } - }) - - override fun getContainerClass(): Class { - return ExoPackInventoryMenu::class.java - } - - override fun getMenuType(): Optional> { - return Optional.empty() - } - - override fun getRecipeType(): mezz.jei.api.recipe.RecipeType { - return RecipeTypes.CRAFTING - } - - private val validSlots = IntArraySet().also { - it.add(0) - it.add(1) - it.add(3) - it.add(4) - } - - private val directMap = Int2IntArrayMap().also { - for (i in 0 .. 8) { - it.put(i, i) - } - } - - private val smallMap = Int2IntArrayMap().also { - var i = 0 - it.put(0, i++) - it.put(1, i++) - it.put(3, i++) - it.put(4, i) - } - - override fun transferRecipe( - container: ExoPackInventoryMenu, - recipe: CraftingRecipe, - recipeSlots: IRecipeSlotsView, - player: Player, - maxTransfer: Boolean, - doTransfer: Boolean - ): IRecipeTransferError? { - val inputs = recipeSlots.getSlotViews(RecipeIngredientRole.INPUT) - - if (inputs.size != 9) { - return IRecipeTransferError { IRecipeTransferError.Type.INTERNAL } - } - - if (container.craftingSlots.size != 9) { - for ((i, ingredient) in inputs.withIndex()) { - if (!ingredient.isEmpty && i !in validSlots) { - return helper.createUserErrorWithTooltip(TranslatableComponent("jei.tooltip.error.recipe.transfer.too.large.player.inventory")) - } - } - - val filteredInputs = ArrayList() - - for ((i, ingredient) in inputs.withIndex()) { - if (i in validSlots) { - filteredInputs.add(ingredient) - } - } - - val outputs = recipeSlots.getSlotViews(RecipeIngredientRole.OUTPUT).toImmutableList() - val catalysts = recipeSlots.getSlotViews(RecipeIngredientRole.CATALYST).toImmutableList() - val render = recipeSlots.getSlotViews(RecipeIngredientRole.RENDER_ONLY).toImmutableList() - - val combine = ArrayList(filteredInputs.size + outputs.size + render.size) - - combine.addAll(filteredInputs) - combine.addAll(outputs) - combine.addAll(render) - - val combined = combine.toImmutableList() - - val newView = object : IRecipeSlotsView { - override fun getSlotViews(): List { - return combined - } - - override fun getSlotViews(role: RecipeIngredientRole): List { - return when (role) { - RecipeIngredientRole.INPUT -> filteredInputs - RecipeIngredientRole.OUTPUT -> outputs - RecipeIngredientRole.CATALYST -> catalysts - RecipeIngredientRole.RENDER_ONLY -> render - } - } - - override fun findSlotByName(slotName: String): Optional { - return combined.stream().filter { it.slotName.orElse(null) == slotName }.findAny() - } - } - - return this.transfer.transferRecipe(container, recipe, newView, player, maxTransfer, doTransfer) - } - - return this.transfer.transferRecipe(container, recipe, recipeSlots, player, maxTransfer, doTransfer) - } - }, RecipeTypes.CRAFTING) + registration.addRecipeTransferHandler(ExopackInventoryTransferHandler(helper), RecipeTypes.CRAFTING) + registration.addRecipeTransferHandler(simpleTransferInfo(MatterEntanglerRecipeCategory.recipeType, MatterEntanglerMenu::inputs)) + registration.addRecipeTransferHandler(simpleTransferInfo0(PainterRecipeCategory.recipeType, PainterMenu::inputSlot)) + registration.addRecipeTransferHandler(simpleTransferInfo0(PlatePressRecipeCategory.recipeType, PlatePressMenu::inputSlot)) + registration.addRecipeTransferHandler(simpleTransferInfo(PlatePressRecipeCategory.recipeType, TwinPlatePressMenu::inputSlots)) + registration.addRecipeTransferHandler(simpleTransferInfo(MicrowaveRecipeCategory.recipeType, PoweredFurnaceMenu::inputSlots)) } override fun registerGuiHandlers(registration: IGuiHandlerRegistration) { @@ -226,12 +128,28 @@ class JEIPlugin : IModPlugin { .collect(Collectors.toList()) } - override fun getIngredientUnderMouse( + override fun getClickableIngredientUnderMouse( containerScreen: MatteryScreen<*>, mouseX: Double, mouseY: Double - ): Any? { - return containerScreen.panelsView.stream().map { it.findItemStack(mouseX.toFloat(), mouseY.toFloat(), ignoreMouseInputLock = true) }.filter { it.first }.findAny().orElse(null)?.second + ): Optional> { + return containerScreen.panelsView + .stream() + .map { it.findItemStack(mouseX.toFloat(), mouseY.toFloat(), ignoreMouseInputLock = true) } + .filter { it.first != null } + .findAny() + .flatMap { a -> helpers.ingredientManager.createTypedIngredient(VanillaTypes.ITEM_STACK, a.second).map { a.first to it } } + .map { + object : IClickableIngredient { + override fun getTypedIngredient(): ITypedIngredient { + return it.second + } + + override fun getArea(): Rect2i { + return it.first!!.calculateAbsoluteRectangle().toIntRect() + } + } + } } }) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MatterEntanglerRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MatterEntanglerRecipeCategory.kt new file mode 100644 index 000000000..ca75aeba9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MatterEntanglerRecipeCategory.kt @@ -0,0 +1,95 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import com.mojang.blaze3d.vertex.PoseStack +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder +import mezz.jei.api.gui.drawable.IDrawable +import mezz.jei.api.gui.ingredient.IRecipeSlotsView +import mezz.jei.api.recipe.IFocusGroup +import mezz.jei.api.recipe.RecipeIngredientRole +import mezz.jei.api.recipe.RecipeType +import mezz.jei.api.recipe.category.IRecipeCategory + +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.recipe.IMatterEntanglerRecipe +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MNames + +object MatterEntanglerRecipeCategory : IRecipeCategory, IDrawable { + private val recipeType = RecipeType.create(OverdriveThatMatters.MOD_ID, MNames.MATTER_ENTANGLER, IMatterEntanglerRecipe::class.java) + + override fun getRecipeType(): RecipeType { + return recipeType + } + + override fun getTitle(): Component { + return MItems.MATTER_ENTANGLER.description + } + + override fun getBackground(): IDrawable { + return this + } + + override fun getWidth(): Int { + return 140 + } + + override fun getHeight(): Int { + return 60 + } + + private val icon = IGUIRenderable2IDrawable(ItemStackIcon(ItemStack(MItems.MATTER_ENTANGLER))) + + override fun getIcon(): IDrawable { + return icon + } + + override fun setRecipe(builder: IRecipeLayoutBuilder, recipe: IMatterEntanglerRecipe, focuses: IFocusGroup) { + for (y in 0 until recipe.ingredients.height) { + for (x in 0 until recipe.ingredients.width) { + builder.addSlot(RecipeIngredientRole.INPUT, 30 + x * 18, 4 + y * 18).addIngredients(recipe.ingredients[x, y]) + } + } + + builder.addSlot(RecipeIngredientRole.OUTPUT, 116, 18 + 4).addItemStack(recipe.result) + } + + override fun draw(stack: PoseStack, xOffset: Int, yOffset: Int) { + val wrap = MGUIGraphics(stack) + + for (x in 0 until 3) { + for (y in 0 until 3) { + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, xOffset + x * 18f + 29f, yOffset + y * 18f + 3f) + } + } + + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, xOffset + 115f, yOffset + 18f + 3f) + ProgressGaugePanel.GAUGE_BACKGROUND.render(wrap, xOffset + 89f, yOffset + 18f + 4f) + } + + override fun draw(recipe: IMatterEntanglerRecipe, recipeSlotsView: IRecipeSlotsView, poseStack: PoseStack, mouseX: Double, mouseY: Double) { + val wrap = MGUIGraphics(poseStack) + + renderMatterGauge(wrap, 13f, 6f, drainSpeed = (recipe.matter / Decimal(300)).toFloat()) + renderEnergyGauge(wrap, 4f, 6f, drainSpeed = (recipe.ticks / 2000.0).toFloat()) + + wrap.draw(x = 85f, y = 45f, text = TranslatableComponent("otm.gui.recipe.ticks", recipe.ticks), drawShadow = true) + } + + override fun getTooltipStrings(recipe: IMatterEntanglerRecipe, recipeSlotsView: IRecipeSlotsView, mouseX: Double, mouseY: Double): MutableList { + val result = ArrayList() + + matterGaugeTooltips(result, recipe.matter, mouseX, mouseY, 13f, 6f) + energyGaugeTooltips(result, MachinesConfig.MATTER_ENTANGLER.energyConsumption * recipe.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier, mouseX, mouseY, 4f, 6f) + + return result + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt new file mode 100644 index 000000000..4e94fa4b5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/MicrowaveRecipeCategory.kt @@ -0,0 +1,118 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import com.mojang.blaze3d.vertex.PoseStack +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder +import mezz.jei.api.gui.drawable.IDrawable +import mezz.jei.api.gui.ingredient.IRecipeSlotsView +import mezz.jei.api.recipe.IFocusGroup +import mezz.jei.api.recipe.RecipeIngredientRole +import mezz.jei.api.recipe.RecipeType +import mezz.jei.api.recipe.category.IRecipeCategory + +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe +import ru.dbotthepony.mc.otm.registry.MItems +import kotlin.math.roundToInt + +object MicrowaveRecipeCategory : IRecipeCategory, IDrawable { + const val X_INPUT = 6f + const val Y_INPUT = 11f + + const val X_OUTPUT = 56f + const val Y_OUTPUT = 11f + + const val X_ARROW = 29f + const val Y_ARROW = 12f + + private val type = RecipeType(ResourceLocation(OverdriveThatMatters.MOD_ID, "microwave"), MicrowaveRecipe::class.java) + + override fun getRecipeType(): RecipeType { + return type + } + + override fun getTitle(): Component { + return MItems.POWERED_SMOKER.description + } + + override fun draw(stack: PoseStack, xOffset: Int, yOffset: Int) { + @Suppress("name_shadowing") + val xOffset = xOffset.toFloat() + + @Suppress("name_shadowing") + val yOffset = yOffset.toFloat() + + val wrap = MGUIGraphics(stack) + + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, X_INPUT + xOffset, Y_INPUT + yOffset) + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, X_OUTPUT + xOffset, Y_OUTPUT + yOffset) + ProgressGaugePanel.GAUGE_BACKGROUND.render(wrap, X_ARROW + xOffset, Y_ARROW + yOffset) + + ProgressGaugePanel.GAUGE_FOREGROUND.renderPartial( + wrap, + X_ARROW + xOffset, + Y_ARROW + yOffset, + width = (((System.currentTimeMillis() % 4000L) / 4000f) * ProgressGaugePanel.GAUGE_FOREGROUND.width).roundToInt().toFloat()) + } + + override fun draw( + recipe: MicrowaveRecipe, + recipeSlotsView: IRecipeSlotsView, + poseStack: PoseStack, + mouseX: Double, + mouseY: Double + ) { + val wrap = MGUIGraphics(poseStack) + wrap.draw(TranslatableComponent("otm.gui.recipe.ticks", recipe.workTime), 40f, 30f, gravity = RenderGravity.TOP_CENTER, color = RGBAColor.BLACK) + + val average = recipe.experience.toString() + + if (average != "0.0") { + wrap.draw(TranslatableComponent("gui.jei.category.smelting.experience", average), 40f, 1f, gravity = RenderGravity.TOP_CENTER, color = RGBAColor.BLACK) + } + } + + override fun getWidth(): Int { + return 80 + } + + override fun getHeight(): Int { + return 40 + } + + override fun getBackground(): IDrawable { + return this + } + + private val iconField by lazy { + JEIPlugin.helpers.guiHelper.createDrawableItemStack(ItemStack(MItems.POWERED_SMOKER)) + } + + override fun getIcon(): IDrawable { + return iconField + } + + override fun setRecipe(builder: IRecipeLayoutBuilder, recipe: MicrowaveRecipe, focuses: IFocusGroup) { + builder.addSlot(RecipeIngredientRole.INPUT, X_INPUT.toInt() + 1, Y_INPUT.toInt() + 1) + .addIngredients(recipe.input) + + val first = recipe.output.items.firstOrNull { it.item.registryName?.namespace == OverdriveThatMatters.MOD_ID } + + if (first == null) + builder.addSlot(RecipeIngredientRole.OUTPUT, X_OUTPUT.toInt() + 1, Y_OUTPUT.toInt() + 1) + .addIngredients(recipe.output) + else + builder.addSlot(RecipeIngredientRole.OUTPUT, X_OUTPUT.toInt() + 1, Y_OUTPUT.toInt() + 1) + .addIngredients(Ingredient.of(first)) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt new file mode 100644 index 000000000..440806e40 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt @@ -0,0 +1,89 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import com.mojang.blaze3d.vertex.PoseStack +import mezz.jei.api.constants.VanillaTypes +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder +import mezz.jei.api.gui.drawable.IDrawable +import mezz.jei.api.recipe.IFocusGroup +import mezz.jei.api.recipe.RecipeIngredientRole +import mezz.jei.api.recipe.RecipeType +import mezz.jei.api.recipe.category.IRecipeCategory + +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel +import ru.dbotthepony.mc.otm.recipe.PainterRecipe +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MNames + +object PainterRecipeCategory : IRecipeCategory, IDrawable { + private val type = RecipeType(ResourceLocation(OverdriveThatMatters.MOD_ID, MNames.PAINTER), PainterRecipe::class.java) + + override fun getRecipeType(): RecipeType { + return type + } + + override fun getTitle(): Component { + return MItems.PAINTER.description + } + + override fun getBackground(): IDrawable { + return this + } + + override fun getWidth(): Int { + return 120 + } + + override fun getHeight(): Int { + return 56 + } + + private val icon = IGUIRenderable2IDrawable(ItemStackIcon(ItemStack(MItems.PAINTER))) + + override fun getIcon(): IDrawable { + return icon + } + + override fun setRecipe(builder: IRecipeLayoutBuilder, recipe: PainterRecipe, focuses: IFocusGroup) { + var x = 0 + var y = 0 + + for ((dye, count) in recipe.dyes) { + builder.addSlot(RecipeIngredientRole.CATALYST, 1 + x * 18, 1 + y * 18) + .addIngredients(VanillaTypes.ITEM_STACK, dye?.let { Ingredient.of(it.tag).items.map { it.copyWithCount(count) } } ?: listOf(ItemStack(Items.WATER_BUCKET, count))) + + y++ + + if (y >= 3) { + x++ + y = 0 + } + } + + builder.addSlot(RecipeIngredientRole.INPUT, 50, 22).addIngredients(recipe.input) + builder.addSlot(RecipeIngredientRole.OUTPUT, 100, 22).addIngredients(VanillaTypes.ITEM_STACK, listOf(recipe.output)) + } + + override fun draw(stack: PoseStack, xOffset: Int, yOffset: Int) { + val wrap = MGUIGraphics(stack) + + for (x in 0 .. 1) { + for (y in 0 .. 2) { + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, xOffset.toFloat() + x * 18f, yOffset.toFloat() + y * 18f) + } + } + + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, xOffset.toFloat() + 49f, yOffset.toFloat() + 21f) + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, xOffset.toFloat() + 99f, yOffset.toFloat() + 21f) + + ProgressGaugePanel.GAUGE_BACKGROUND.render(wrap, xOffset.toFloat() + 73f, yOffset.toFloat() + 22f) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt index efd2869f0..452f2c025 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PlatePressRecipeCategory.kt @@ -8,17 +8,17 @@ import mezz.jei.api.recipe.IFocusGroup import mezz.jei.api.recipe.RecipeIngredientRole import mezz.jei.api.recipe.RecipeType import mezz.jei.api.recipe.category.IRecipeCategory + import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.Ingredient import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.TextAlign -import ru.dbotthepony.mc.otm.client.render.drawAligned -import ru.dbotthepony.mc.otm.client.screen.panels.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.render.MGUIGraphics +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.core.RGBAColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe @@ -45,19 +45,21 @@ object PlatePressRecipeCategory : IRecipeCategory, IDrawable { return MItems.PLATE_PRESS.description } - override fun draw(poseStack: PoseStack, xOffset: Int, yOffset: Int) { + override fun draw(stack: PoseStack, xOffset: Int, yOffset: Int) { @Suppress("name_shadowing") val xOffset = xOffset.toFloat() @Suppress("name_shadowing") val yOffset = yOffset.toFloat() - AbstractSlotPanel.SLOT_BACKGROUND.render(poseStack, X_INPUT + xOffset, Y_INPUT + yOffset) - AbstractSlotPanel.SLOT_BACKGROUND.render(poseStack, X_OUTPUT + xOffset, Y_OUTPUT + yOffset) - ProgressGaugePanel.GAUGE_BACKGROUND.render(poseStack, X_ARROW + xOffset, Y_ARROW + yOffset) + val wrap = MGUIGraphics(stack) + + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, X_INPUT + xOffset, Y_INPUT + yOffset) + AbstractSlotPanel.SLOT_BACKGROUND.render(wrap, X_OUTPUT + xOffset, Y_OUTPUT + yOffset) + ProgressGaugePanel.GAUGE_BACKGROUND.render(wrap, X_ARROW + xOffset, Y_ARROW + yOffset) ProgressGaugePanel.GAUGE_FOREGROUND.renderPartial( - poseStack, + wrap, X_ARROW + xOffset, Y_ARROW + yOffset, width = (((System.currentTimeMillis() % 4000L) / 4000f) * ProgressGaugePanel.GAUGE_FOREGROUND.width).roundToInt().toFloat()) @@ -70,7 +72,15 @@ object PlatePressRecipeCategory : IRecipeCategory, IDrawable { mouseX: Double, mouseY: Double ) { - minecraft.font.drawAligned(stack, TranslatableComponent("otm.gui.recipe.ticks", recipe.workTime), TextAlign.TOP_CENTER, 40f, 30f, RGBAColor.BLACK) + val wrap = MGUIGraphics(stack) + + wrap.draw(TranslatableComponent("otm.gui.recipe.ticks", recipe.workTime), 40f, 30f, gravity = RenderGravity.TOP_CENTER, color = RGBAColor.BLACK) + + val average = recipe.experience.toString() + + if (average != "0.0") { + wrap.draw(TranslatableComponent("gui.jei.category.smelting.experience", average), 40f, 1f, gravity = RenderGravity.TOP_CENTER, color = RGBAColor.BLACK) + } } override fun getWidth(): Int { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SimpleTransferInfo.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SimpleTransferInfo.kt new file mode 100644 index 000000000..1e6d6c465 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SimpleTransferInfo.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.compat.jei + +import mezz.jei.api.recipe.RecipeType +import mezz.jei.api.recipe.transfer.IRecipeTransferInfo +import net.minecraft.world.inventory.MenuType +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.crafting.Recipe +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.util.* + +inline fun > simpleTransferInfo(type: RecipeType, crossinline slots: (M) -> List): IRecipeTransferInfo { + return object : IRecipeTransferInfo { + override fun getContainerClass(): Class { + return M::class.java + } + + override fun getMenuType(): Optional> { + return Optional.empty() + } + + override fun getRecipeType(): RecipeType { + return type + } + + override fun canHandle(container: M, recipe: R): Boolean { + return true + } + + override fun getRecipeSlots(container: M, recipe: R): List { + return slots.invoke(container) + } + + override fun getInventorySlots(container: M, recipe: R): List { + return container.playerInventorySlots + } + } +} + +inline fun > simpleTransferInfo0(type: RecipeType, crossinline slot: (M) -> Slot): IRecipeTransferInfo { + return simpleTransferInfo(type) { listOf(slot.invoke(it)) } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SkinDrawable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SkinDrawable.kt deleted file mode 100644 index 8a7eaf3fd..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/SkinDrawable.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.jei - -import com.mojang.blaze3d.vertex.PoseStack -import mezz.jei.api.gui.drawable.IDrawable -import ru.dbotthepony.mc.otm.client.render.SkinElement - -class SkinDrawable(val element: SkinElement) : IDrawable { - override fun getWidth(): Int { - return element.width.toInt() - } - - override fun getHeight(): Int { - return element.height.toInt() - } - - override fun draw(poseStack: PoseStack, xOffset: Int, yOffset: Int) { - element.render(poseStack, xOffset.toFloat(), yOffset.toFloat()) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/StretchingDrawable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/StretchingDrawable.kt deleted file mode 100644 index da3566a38..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/StretchingDrawable.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.jei - -import com.mojang.blaze3d.vertex.PoseStack -import mezz.jei.api.gui.drawable.IDrawable -import ru.dbotthepony.mc.otm.client.render.StretchingRectangleElement - -class StretchingDrawable(val element: StretchingRectangleElement, val rectWidth: Int, val rectHeight: Int) : IDrawable { - override fun getWidth(): Int { - return rectWidth - } - - override fun getHeight(): Int { - return rectHeight - } - - override fun draw(poseStack: PoseStack, xOffset: Int, yOffset: Int) { - element.render(poseStack, xOffset.toFloat(), yOffset.toFloat(), rectWidth.toFloat(), rectHeight.toFloat()) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Conversions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Conversions.kt deleted file mode 100644 index ac3a4166d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Conversions.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.mekanism - -import mekanism.api.math.FloatingLong -import ru.dbotthepony.mc.otm.core.Decimal -import java.math.BigInteger - -private val LONG_OVERFLOW = BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.TWO -private val LONG_OVERFLOW1 = BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.ONE - -fun Decimal.toFloatingLong(): FloatingLong { - if (isNegative) { - // Floating long can't be negative - return FloatingLong.ZERO - } - - if (whole >= LONG_OVERFLOW1) { - return FloatingLong.MAX_VALUE - } - - return FloatingLong.create(whole.toLong(), (decimal * 10_000.0).toInt().toShort()) -} - -fun FloatingLong.toImpreciseFraction(): Decimal { - // Overflow - if (value < 0L) { - return Decimal(LONG_OVERFLOW + BigInteger.valueOf(value), decimal / 10_000.0) - } - - return Decimal(value, decimal / 10_000.0) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Power.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Power.kt deleted file mode 100644 index e0d67b117..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Power.kt +++ /dev/null @@ -1,233 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.mekanism - -import mekanism.api.Action -import mekanism.api.energy.IStrictEnergyHandler -import mekanism.api.math.FloatingLong -import mekanism.common.config.MekanismConfig -import net.minecraft.core.Direction -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.capabilities.ICapabilityProvider -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.energy.IEnergyStorage -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.isMekanismLoaded -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.orNull -import java.lang.ref.WeakReference -import java.util.WeakHashMap - -private val LOGGER = LogManager.getLogger() - -private class LazyCache(private val provider: () -> O, private val computer: (O) -> T) : Lazy { - private var cache: T? = null - private var observed: O? = null - - override fun isInitialized(): Boolean { - return cache != null - } - - override val value: T get() { - if (cache == null || observed == null || observed != provider.invoke()) { - observed = provider.invoke() - cache = computer.invoke(observed ?: throw ConcurrentModificationException()) - } - - return cache ?: throw ConcurrentModificationException() - } -} - -private class DoubleLazy(onion: () -> Lazy) : Lazy { - private val onion = lazy(onion) - override val value: T - get() = onion.value.value - - override fun isInitialized(): Boolean { - return onion.isInitialized() && onion.value.isInitialized() - } -} - -private val mekanism2MtJ by DoubleLazy lazy@{ - try { - val conf = MekanismConfig.general - return@lazy LazyCache(conf.forgeConversionRate::get) { Decimal.ONE / it.toImpreciseFraction() } - } catch(err: Throwable) { - LOGGER.error("Unable to get Forge Energy to Mekanism Joules's conversion rate! Expect issues", err) - } - - return@lazy lazyOf(Decimal.ONE) -} - -private val mtj2Mekanism by DoubleLazy lazy@{ - try { - val conf = MekanismConfig.general - return@lazy LazyCache(conf.forgeConversionRate::get) { it.toImpreciseFraction() } - } catch(err: Throwable) { - LOGGER.error("Unable to get Mekanism Joules's to Forge Energy conversion rate! Expect issues", err) - } - - return@lazy lazyOf(Decimal.ONE) -} - -class Mekanism2MatteryEnergyWrapper(private val power: IStrictEnergyHandler, private val forgePower: IEnergyStorage? = null) : IMatteryEnergyStorage { - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - val action = when (simulate) { - true -> Action.SIMULATE - false -> Action.EXECUTE - } - - return power.extractEnergy((howMuch * mtj2Mekanism).toFloatingLong(), action).toImpreciseFraction() * mekanism2MtJ - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return extractEnergyOuter(howMuch, simulate) - } - - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - val action = when (simulate) { - true -> Action.SIMULATE - false -> Action.EXECUTE - } - - return howMuch - power.insertEnergy((howMuch * mtj2Mekanism).toFloatingLong(), action).toImpreciseFraction() * mekanism2MtJ - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyOuter(howMuch, simulate) - } - - override val batteryLevel: Decimal - get() { - var sum = Decimal.ZERO - - for (i in 0 until power.energyContainerCount) { - sum += power.getEnergy(i).toImpreciseFraction() - } - - return sum * mekanism2MtJ - } - - override val maxBatteryLevel: Decimal - get() { - var sum = Decimal.ZERO - - for (i in 0 until power.energyContainerCount) { - sum += power.getMaxEnergy(i).toImpreciseFraction() - } - - return sum * mekanism2MtJ - } - - override fun canExtract(): Boolean { - if (forgePower != null) - return forgePower.canExtract() - - return super.canExtract() - } - - override fun canReceive(): Boolean { - if (forgePower != null) - return forgePower.canReceive() - - return super.canReceive() - } -} - -class Mattery2MekanismEnergyWrapper(private val power: IMatteryEnergyStorage) : IStrictEnergyHandler { - override fun getEnergyContainerCount(): Int = 1 - - override fun getEnergy(container: Int): FloatingLong { - if (container != 0) { - return FloatingLong.ZERO - } - - return (power.batteryLevel * mtj2Mekanism).toFloatingLong() - } - - override fun setEnergy(container: Int, value: FloatingLong) { - // no op - } - - override fun getMaxEnergy(container: Int): FloatingLong { - if (container != 0) { - return FloatingLong.ZERO - } - - return (power.maxBatteryLevel * mtj2Mekanism).toFloatingLong() - } - - override fun getNeededEnergy(container: Int): FloatingLong { - if (container != 0) { - return FloatingLong.ZERO - } - - return (power.missingPower * mtj2Mekanism).toFloatingLong() - } - - override fun insertEnergy(container: Int, howMuch: FloatingLong, action: Action): FloatingLong { - val copy = howMuch.copy() - return copy.minusEqual((power.receiveEnergyOuter(howMuch.toImpreciseFraction() * mekanism2MtJ, action.simulate()) * mtj2Mekanism).toFloatingLong()) - } - - override fun extractEnergy(container: Int, howMuch: FloatingLong, action: Action): FloatingLong { - return (power.extractEnergyOuter(howMuch.toImpreciseFraction() * mekanism2MtJ, action.simulate()) * mtj2Mekanism).toFloatingLong() - } -} - -val ICapabilityProvider.mekanismEnergy: IMatteryEnergyStorage? get() { - if (!isMekanismLoaded) { - return null - } - - val capability = getCapability(MatteryCapability.MEKANISM_ENERGY) - - if (!capability.isPresent) { - return null - } - - return Mekanism2MatteryEnergyWrapper(capability.orElseThrow(::IllegalStateException), getCapability(ForgeCapabilities.ENERGY).orNull()) -} - -private val lazyCache by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheDown by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheUp by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheSouth by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheNorth by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheEast by lazy { WeakHashMap, WeakReference>>() } -private val lazyCacheWest by lazy { WeakHashMap, WeakReference>>() } - -fun ICapabilityProvider.getMekanismEnergySided(side: Direction? = null): LazyOptional { - if (!isMekanismLoaded) return LazyOptional.empty() - val lazyOptional = getCapability(MatteryCapability.MEKANISM_ENERGY, side) - if (!lazyOptional.isPresent) return LazyOptional.empty() - - val cache = when (side) { - Direction.DOWN -> lazyCacheDown - Direction.UP -> lazyCacheUp - Direction.NORTH -> lazyCacheNorth - Direction.SOUTH -> lazyCacheSouth - Direction.WEST -> lazyCacheWest - Direction.EAST -> lazyCacheEast - null -> lazyCache - } - - val cached = cache[lazyOptional]?.get() - if (cached != null) return cached.cast() - - val forgeEnergy = getCapability(ForgeCapabilities.ENERGY, side) - - val resolver = LazyOptional.of { - Mekanism2MatteryEnergyWrapper(lazyOptional.orElseThrow(::IllegalStateException), forgeEnergy.orNull()) - } - - val ref = WeakReference(resolver) - cache[lazyOptional] = ref - - lazyOptional.addListener { - ref.get()?.invalidate() - } - - return resolver.cast() -} - diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt deleted file mode 100644 index 123bf5d3c..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt +++ /dev/null @@ -1,294 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.mekanism - -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap -import it.unimi.dsi.fastutil.objects.Object2ObjectFunction -import mekanism.common.content.qio.QIOFrequency -import mekanism.common.content.qio.QIOFrequency.QIOItemTypeData -import mekanism.common.lib.frequency.Frequency -import mekanism.common.lib.inventory.HashedItem -import mekanism.common.tile.qio.TileEntityQIODriveArray -import net.minecraft.core.Direction -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ICapabilityProvider -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.event.AttachCapabilitiesEvent -import net.minecraftforge.eventbus.api.SubscribeEvent -import ru.dbotthepony.mc.otm.core.ITickable -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.onceServer -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.isMekanismLoaded -import ru.dbotthepony.mc.otm.core.isPositive -import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode -import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph -import ru.dbotthepony.mc.otm.storage.* -import java.math.BigInteger -import java.util.UUID -import java.util.stream.Stream - -private val QIO_LOCATION = ResourceLocation(OverdriveThatMatters.MOD_ID, "item_storage") - -private class QIOTuple( - val mekanismItem: HashedItem, - override val stack: ItemStackWrapper, - override val id: UUID, - var mark: Long -) : IStorageTuple - -private class QIOFrequencyAccess(val parent: QIOFrequency) : IStorageComponent { - private var mark = 0L - - override val storageType: StorageStackType - get() = OverdriveThatMatters.INSTANCE.ITEM_STORAGE() - - private val index = HashMap() - private val tracked = HashMap() - private val listeners = ArrayList>() - - override fun get(id: UUID): ItemStackWrapper { - return index[id]?.stack ?: ItemStackWrapper.EMPTY - } - - override val stacks: Stream> get() { - return ArrayList>(index.size).also { it.addAll(index.values) }.stream() - } - - override fun insertStack(stack: ItemStackWrapper, simulate: Boolean): ItemStackWrapper { - // Because there is no simulate method on QIO array, we have to simulate it by ourselves. - val hash = HashedItem.create(stack.item) - - if (!simulate) { - //val used = TransporterManager.getToUse(stack.stack, parent.addItem(stack.stack)) - val used = parent.addItem(stack.stack) - - if (used.count == stack.count.toInt()) { - return stack - } - - scan(hash) - return ItemStackWrapper(used) - } - - if (parent.totalItemCount >= parent.totalItemCountCapacity) { - return stack - } - - if (!parent.itemDataMap.containsKey(hash) && parent.totalItemTypeCapacity <= parent.itemDataMap.size) { - return stack - } - - val inserted = stack.copy() - inserted.count = (parent.totalItemCountCapacity - parent.totalItemCount).toBigInteger().coerceAtMost(stack.count) - return inserted - } - - override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): ItemStackWrapper { - // Because there is no simulate method on QIO array, we have to simulate it by ourselves. - // ASSUMPTION: We can ALWAYS remove items from QIO grid. - - if (!amount.isPositive) - return ItemStackWrapper.EMPTY - - var local = index[id] ?: return ItemStackWrapper.EMPTY - scan(local.mekanismItem) - local = index[id] ?: return ItemStackWrapper.EMPTY // unexpected... - - if (simulate) { - return local.stack.copy().also { it.count = it.count.coerceAtMost(amount) } - } - - val removed = parent.removeByType(local.mekanismItem, amount.toInt()) - - if (removed.isEmpty) { - return ItemStackWrapper.EMPTY - } - - val copy = ItemStackWrapper(removed) - - if (local.stack.count > copy.count) { - // expecting stack to be still present in QIO storage grid - scan(local.mekanismItem) - } else { - // expecting stack to be removed from QIO storage grid - check(parent.itemDataMap[local.mekanismItem] == null) { - "${local.mekanismItem} is expected to be empty in $parent, got ${parent.itemDataMap[local.mekanismItem]}" - } - - for (listener in listeners) { - listener.removeStack(local) - } - - index.remove(local.id) - tracked.remove(local.mekanismItem) - } - - return copy - } - - override fun addListener(listener: IStorageEventConsumer): Boolean { - if (!listeners.contains(listener)) { - listeners.add(listener) - return true - } - - return false - } - - override fun removeListener(listener: IStorageEventConsumer): Boolean { - return listeners.remove(listener) - } - - private fun scan(at: HashedItem, value: QIOItemTypeData = parent.itemDataMap[at] ?: throw IllegalArgumentException("$parent does not have item $at (${at.stack})")) { - val local = tracked[at] - - if (local != null) { - local.mark = mark - - if (local.stack.count.toLong() != value.count) { - val oldCount = local.stack.count - local.stack.count = value.count.toBigInteger() - - for (listener in listeners) { - listener.changeStack(local, oldCount) - } - } - } else { - val tuple = QIOTuple(at, ItemStackWrapper(at.stack).also { it.count = value.count.toBigInteger() }, UUID.randomUUID(), mark) - index[tuple.id] = tuple - - for (listener in listeners) { - listener.addStack(tuple, this) - } - - tracked[at] = tuple - } - } - - fun scan() { - mark++ - - for ((key, value) in parent.itemDataMap) { - scan(key, value) - } - - var changed: ArrayList? = null - - for ((key, value) in tracked) { - if (value.mark != mark) { - if (changed == null) { - changed = ArrayList() - } - - changed.add(key) - } - } - - // sweep - if (changed != null) { - for (key in changed) { - val tuple = tracked.remove(key)!! - - for (listener in listeners) { - listener.removeStack(tuple) - } - - index.remove(tuple.id) - tracked.remove(tuple.mekanismItem) - } - } - } -} - -private class QIOStorage(private val tile: TileEntityQIODriveArray) : ICapabilityProvider { - private var lastFrequency: QIOFrequency? = null - private var frequencyAccess: QIOFrequencyAccess? = null - private var wasAttached = false - - val cell: BasicStorageGraphNode = object : BasicStorageGraphNode(), ITickable { - init { - manualAttaching = true - } - - override fun tick() { - if (tile.isRemoved) { - destroy(tile.level) - return - } - - val frequency = tile.qioFrequency - - if (frequency != lastFrequency) { - if (wasAttached) { - wasAttached = false - - val lastFrequency = lastFrequency ?: throw IllegalStateException("lastFrequency is null") - - checkNotNull(storageGraph) { - "Unexpected internal state (expected storage graph to be present, something detached $this from storage grid, but did not call removeComponents())" - }.userData.remove(key(lastFrequency)) - } - - frequencyAccess?.let(this::removeStorageComponent) - lastFrequency = frequency - - if (frequency != null) { - frequencyAccess = QIOFrequencyAccess(frequency).also(this::addStorageComponent) - } - } - - val frequencyAccess = frequencyAccess ?: return - frequencyAccess.scan() - val storageGraph = storageGraph ?: return - val key = key(frequencyAccess.parent) - - if (!storageGraph.userData.containsKey(key)) { - storageGraph.userData[key] = true - wasAttached = true - storageGraph.add(frequencyAccess) - } - } - - override fun removeComponents(from: StorageNetworkGraph) { - super.removeComponents(from) - - if (wasAttached) { - val lastFrequency = lastFrequency ?: throw IllegalStateException("lastFrequency is null") - from.userData.remove(key(lastFrequency)) - wasAttached = false - } - } - } - - override fun getCapability(cap: Capability, side: Direction?): LazyOptional { - if (cap === MatteryCapability.STORAGE_NODE) - return cell.get().cast() - - return LazyOptional.empty() - } - - companion object { - private fun key(frequency: Frequency) = keyMap.computeIfAbsent(frequency.name to frequency.owner, Object2ObjectFunction { UUID.randomUUID() }) - private val keyMap = Object2ObjectArrayMap, UUID>() - } -} - -@SubscribeEvent -@Suppress("unused") -fun attachCapabilities(event: AttachCapabilitiesEvent) { - if (!isMekanismLoaded) { - throw IllegalStateException("This event should never be called if Mekanism is not loaded.") - } - - if (event.`object` is TileEntityQIODriveArray) { - val capability = QIOStorage(event.`object` as TileEntityQIODriveArray) - event.addCapability(QIO_LOCATION, capability) - - onceServer { - if (!event.`object`.isRemoved && event.`object`.level?.isClientSide == false) { - StorageNetworkGraph.discoverFull(event.`object`, capability.cell.storageNode) - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Tooltips.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Tooltips.kt deleted file mode 100644 index 41bb89455..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/Tooltips.kt +++ /dev/null @@ -1,20 +0,0 @@ -package ru.dbotthepony.mc.otm.compat.mekanism - -import mekanism.common.registries.MekanismItems -import net.minecraft.ChatFormatting -import net.minecraftforge.event.entity.player.ItemTooltipEvent -import net.minecraftforge.eventbus.api.SubscribeEvent -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.capability.isMekanismLoaded - -private val BLACKHOLE_IMMUNITY = TranslatableComponent("otm.item.blackhole_immunity").withStyle(ChatFormatting.DARK_GRAY) - -fun tooltipEvent(event: ItemTooltipEvent) { - if (!isMekanismLoaded) { - throw IllegalStateException("Mekanism is not loaded!") - } - - if (event.itemStack.`is`(MekanismItems.MODULE_GRAVITATIONAL_MODULATING.get())) { - event.toolTip.add(BLACKHOLE_IMMUNITY) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/ExtendedInventoryHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt similarity index 90% rename from src/main/kotlin/ru/dbotthepony/mc/otm/compat/ExtendedInventoryHandler.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt index 2e1a2afdc..910a6933f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/ExtendedInventoryHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.compat +package ru.dbotthepony.mc.otm.compat.vanilla import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap @@ -13,17 +13,14 @@ import net.minecraft.world.inventory.InventoryMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import net.minecraftforge.network.NetworkEvent import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.packetHandled -import ru.dbotthepony.mc.otm.network.sender import java.util.* -import java.util.function.Supplier private val menuConfigurations = WeakHashMap() @@ -46,8 +43,8 @@ private class MenuConfiguration( val offset = (it - 3) * 9 for (i in 0 .. 8) { - if (matteryPlayer.exoPackContainer.containerSize > i + offset) { - row.add(MatterySlot(matteryPlayer.exoPackContainer, i + offset)) + if (matteryPlayer.exopackContainer.containerSize > i + offset) { + row.add(MatterySlot(matteryPlayer.exopackContainer, i + offset)) } else { row.add(FakeSlot()) } @@ -64,13 +61,13 @@ private class MenuConfiguration( val matteryPlayer = player.matteryPlayer ?: return - if (!matteryPlayer.hasExoPack || matteryPlayer.exoPackContainer.containerSize <= (value - 1) * 9) { + if (!matteryPlayer.hasExopack || matteryPlayer.exopackContainer.containerSize <= (value - 1) * 9) { return } for (rowIndex in 0 .. 2) { for (slot in getRow(rowIndex + field, matteryPlayer)) { - if (slot.container === matteryPlayer.exoPackContainer) { + if (slot.container === matteryPlayer.exopackContainer) { @Suppress("name_shadowing") val indexOf = matteryPlayer.exoPackMenu.slots.indexOfFirst { it.slotIndex == slot.slotIndex } @@ -186,6 +183,18 @@ private class FakeSlot : MatterySlot(SimpleContainer(1), 0, 0, 0) { override fun allowModification(p_150652_: Player): Boolean { return false } + + override fun isActive(): Boolean { + return false + } + + override fun getMaxStackSize(pStack: ItemStack): Int { + return 0 + } + + override fun getMaxStackSize(): Int { + return 0 + } } class InventoryScrollPacket(val scroll: Int) : MatteryPacket { @@ -214,8 +223,7 @@ class InventoryScrollPacket(val scroll: Int) : MatteryPacket { }.scroll = scroll } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { play(context.sender ?: throw IllegalStateException("Illegal side")) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt new file mode 100644 index 000000000..c726ff6d3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt @@ -0,0 +1,122 @@ +package ru.dbotthepony.mc.otm.compat.vanilla + +import net.minecraft.client.gui.screens.MenuScreens +import net.minecraft.core.registries.Registries +import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.flag.FeatureFlags +import net.minecraft.world.inventory.MenuType +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.makeSlots + +class MatteryChestMenu( + type: MenuType<*>, containerId: Int, + inventory: Inventory, val rows: Int, val columns: Int, + val container: Container = SimpleContainer(rows * columns) +) : MatteryMenu(type, containerId, inventory) { + val chestSlots = makeSlots(container, ::MatterySlot) + val sort = SortInput(container, playerSortSettings) + + init { + require(rows * columns == container.containerSize) { "Provided container $container has different dimensions than specified rows x columns: ${container.containerSize} vs $rows x $columns (${rows * columns})" } + container.startOpen(player) + addStorageSlot(chestSlots) + addInventorySlots() + } + + override fun stillValid(player: Player): Boolean { + return container.stillValid(player) + } + + override fun removed(player: Player) { + super.removed(player) + container.stopOpen(player) + } + + companion object { + private val registrar = DeferredRegister.create(Registries.MENU, OverdriveThatMatters.MOD_ID) + + private val GENERIC_9x1 by registrar.register("generic_9x1") { MenuType(::c9x1) } + private val GENERIC_9x2 by registrar.register("generic_9x2") { MenuType(::c9x2) } + private val GENERIC_9x3 by registrar.register("generic_9x3") { MenuType(::c9x3) } + private val GENERIC_9x4 by registrar.register("generic_9x4") { MenuType(::c9x4) } + private val GENERIC_9x5 by registrar.register("generic_9x5") { MenuType(::c9x5) } + private val GENERIC_9x6 by registrar.register("generic_9x6") { MenuType(::c9x6) } + private val GENERIC_3x3 by registrar.register("generic_3x3") { MenuType(::c3x3) } + private val HOPPER by registrar.register("hopper") { MenuType(::hopper) } + + @JvmStatic + @JvmOverloads + fun c9x1(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x1, containerId, inventory, 1, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c9x2(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 2)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x2, containerId, inventory, 2, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c9x3(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 3)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x3, containerId, inventory, 3, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c9x4(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 4)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x4, containerId, inventory, 4, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c9x5(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 5)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x5, containerId, inventory, 5, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c9x6(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 6)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_9x6, containerId, inventory, 6, 9, container) + } + + @JvmStatic + @JvmOverloads + fun c3x3(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(3 * 3)): MatteryChestMenu { + return MatteryChestMenu(GENERIC_3x3, containerId, inventory, 3, 3, container) + } + + @JvmStatic + @JvmOverloads + fun hopper(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(5)): MatteryChestMenu { + return MatteryChestMenu(HOPPER, containerId, inventory, 1, 5, container) + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + bus.addListener(this::registerClient) + } + + private fun registerClient(event: FMLClientSetupEvent) { + event.enqueueWork { + MenuScreens.register(GENERIC_9x1, ::MatteryChestScreen) + MenuScreens.register(GENERIC_9x2, ::MatteryChestScreen) + MenuScreens.register(GENERIC_9x3, ::MatteryChestScreen) + MenuScreens.register(GENERIC_9x4, ::MatteryChestScreen) + MenuScreens.register(GENERIC_9x5, ::MatteryChestScreen) + MenuScreens.register(GENERIC_9x6, ::MatteryChestScreen) + MenuScreens.register(GENERIC_3x3, ::MatteryChestScreen) + MenuScreens.register(HOPPER, ::MatteryChestScreen) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt new file mode 100644 index 000000000..316a948c9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.compat.vanilla + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.Dock +import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel + +class MatteryChestScreen(menu: MatteryChestMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { + override fun makeMainFrame(): FramePanel> { + val frame = FramePanel.padded(this, AbstractSlotPanel.SIZE * menu.columns.coerceAtLeast(9), AbstractSlotPanel.SIZE * menu.rows + 4f, title) + + frame.makeCloseButton() + frame.onClose { onClose() } + + val grid = GridPanel.slots(this, frame, menu.columns, menu.rows) + grid.dock = Dock.FILL + grid.gravity = RenderGravity.BOTTOM_CENTER + + for (slot in menu.chestSlots) + SlotPanel(this, grid, slot) + + val controls = DeviceControls(this, frame) + controls.sortingButtons(menu.sort) + + return frame + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt new file mode 100644 index 000000000..24b64492f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt @@ -0,0 +1,80 @@ +package ru.dbotthepony.mc.otm.config + +import net.minecraftforge.common.ForgeConfigSpec +import net.minecraftforge.fml.ModLoadingContext +import net.minecraftforge.fml.config.ModConfig +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal +import ru.dbotthepony.mc.otm.core.util.WriteOnce + +abstract class AbstractConfig(private val configName: String, private val type: ModConfig.Type = ModConfig.Type.SERVER) { + private var spec: ForgeConfigSpec by WriteOnce() + protected val builder = ForgeConfigSpec.Builder() + private var registered = false + + fun batteryValues(name: String, storage: Decimal, receive: Decimal, extract: Decimal = receive, initialBatteryLevel: Decimal = Decimal.ZERO): BatteryBalanceValues { + builder.push(name) + + val obj = object : BatteryBalanceValues { + override val energyCapacity: Decimal by builder.defineDecimal("ENERGY_CAPACITY", storage, minimum = Decimal.ONE) + override val maxEnergyReceive: Decimal by builder.defineDecimal("MAX_RECEIVE", receive, minimum = Decimal.ONE) + override val maxEnergyExtract: Decimal by builder.defineDecimal("MAX_EXTRACT", extract, minimum = Decimal.ONE) + override val initialBatteryLevel: Decimal by builder.defineDecimal("INITIAL_BATTERY_LEVEL", initialBatteryLevel, minimum = Decimal.ZERO) + } + + builder.pop() + + return obj + } + + fun conciseValues(name: String, storage: Decimal, throughput: Decimal, configurator: ForgeConfigSpec.Builder.() -> Unit = {}): EnergyBalanceValues { + builder.push(name) + + val obj = object : EnergyBalanceValues { + override val energyCapacity: Decimal by builder.defineDecimal("ENERGY_CAPACITY", storage, minimum = Decimal.ONE) + override val energyThroughput: Decimal by builder.defineDecimal("ENERGY_THROUGHPUT", throughput, minimum = Decimal.ONE) + } + + configurator.invoke(builder) + builder.pop() + + return obj + } + + fun workerValues( + name: String, + energyStorage: Decimal, + energyThroughput: Decimal, + workTimeMultiplier: Double? = 1.0, + energyConsumption: Decimal?, + matterCapacity: Decimal? = null, + maxExperience: Double? = null, + configurator: ForgeConfigSpec.Builder.() -> Unit = {} + ): WorkerBalanceValues { + builder.push(name) + + val obj = object : WorkerBalanceValues { + override val energyCapacity: Decimal by builder.defineDecimal("ENERGY_CAPACITY", energyStorage, minimum = Decimal.ONE) + override val energyThroughput: Decimal by builder.defineDecimal("ENERGY_THROUGHPUT", energyThroughput, minimum = Decimal.ONE) + override val energyConsumption: Decimal by (if (energyConsumption == null) GetterSetter.box(Decimal.ZERO) else builder.defineDecimal("ENERGY_CONSUMPTION", energyConsumption, minimum = Decimal.ONE)) + override val matterCapacity: Decimal by (if (matterCapacity == null) GetterSetter.box(Decimal.ZERO) else builder.defineDecimal("MATTER_CAPACITY", matterCapacity, minimum = Decimal.ONE)) + override val workTimeMultiplier: Double by (if (workTimeMultiplier == null) GetterSetter.box(1.0) else builder.defineInRange("WORK_TIME_MULTIPLIER", workTimeMultiplier, 0.0)) + override val maxExperienceStored: Double by (if (maxExperience == null) GetterSetter.box(Double.POSITIVE_INFINITY) else builder.defineInRange("MAX_EXPERIENCE_STORED", maxExperience, 0.0)) + } + + configurator.invoke(builder) + builder.pop() + + return obj + } + + fun register() { + check(!registered) { "Already registered config" } + registered = true + spec = builder.build() + ModLoadingContext.get().registerConfig(type, spec, "${OverdriveThatMatters.MOD_ID}-$configName.toml") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AndroidConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AndroidConfig.kt new file mode 100644 index 000000000..09bcdb1c5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AndroidConfig.kt @@ -0,0 +1,150 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal + +object AndroidConfig : AbstractConfig("androids") { + val REGENERATE_ENERGY: Boolean by builder + .comment("If (technically) hunger is above threshold, it turns into energy") + .comment("This setting controls whenever to regenerate small amount of energy while eating as Android") + .comment("And also whenever to regenerate energy while in peaceful") + .comment("If this is disabled, any (technically) excess hunger will be nullified, unless playing on peaceful difficulty.") + .define("REGENERATE_ENERGY", true) + + val TIME_BETWEEN_NATURAL_REGENERATION: Int by builder + .comment("Time in ticks between natural health regeneration ticks") + .comment("Default value is meant to be one of downsides of being an android,") + .comment("so please, don't blindly buff it, players have ability to research into Nanobots Regeneration,") + .comment("which provide superior regeneration on average than human players.") + .comment("---") + .comment("This regeneration obeys natural regeneration gamerule") + .comment("---") + .comment("Reason for this config option is that FoodData does not tick") + .comment("for android players, since 'hunger' (for compatibility) is managed by mod in such case") + .defineInRange("TIME_BETWEEN_NATURAL_REGENERATION", 120, 0, Int.MAX_VALUE) + + object NanobotsRegeneration { + init { + builder.push("NanobotsRegeneration") + } + + val COOLDOWN: List by builder + .comment("In ticks, time between heal ticks") + .comment("One heal tick restores 1 heart (2 health points) at most") + .comment("If not getting hurt in specified period of ticks, heal tick takes place, tick timer resets to zero and THIS array' index advances by 1") + .comment("Index inside this array can not exceed of one of ability's") + .comment("") + .comment("Wording in pseudocode:") + .comment("if (ticksSinceTakingDamage >= cooldownConfigOption[healTicks /* or config's biggest index, whichever is smaller */]) {") + .comment(" healTicks = min(healTicks + 1, level /* ability level */)") + .comment(" ticksSinceTakingDamage = 0") + .comment(" this.ply.heal(...)") + .comment("}") + .defineList("COOLDOWN", { mutableListOf(60, 50, 40, 30) }) { it is Int } + + val ENERGY_PER_HITPOINT by builder + .comment("Energy required to regenerate 1 health point (half a heart)") + .defineDecimal("ENERGY_PER_HITPOINT", Decimal(800)) + + init { + builder.pop() + } + } + + val ANDROID_ENERGY_PER_HUNGER_POINT by builder.defineDecimal("energyPerHunger", Decimal(2000), Decimal.ZERO) + val ANDROID_MAX_ENERGY by builder + .comment("Internal battery of every android has this much storage") + .comment("Keep in mind that already existing players won't get their value changed since it is", "stored inside their savedata") + .defineDecimal("capacity", Decimal(80_000), Decimal.ZERO) + val NIGHT_VISION_POWER_DRAW by builder.defineDecimal("nightVisionPowerDraw", Decimal(8), Decimal.ZERO) + + val FALL_DAMAGE_REDUCTION_PER_LEVEL_P: Double by builder + .comment("In percent. Level of feature is multiplied by this") + .comment("First, fall damage is reduced by flat resistance, then reduced by percentage resistance (this)") + .defineInRange("FALL_DAMAGE_REDUCTION_PER_LEVEL_P", 0.2, 0.01, 1.0) + + val FALL_DAMAGE_REDUCTION_PER_LEVEL_F: Double by builder + .comment("In flat half of hearts. Level of feature is multiplied by this") + .comment("First, fall damage is reduced by flat resistance (this), then reduced by percentage resistance") + .defineInRange("FALL_DAMAGE_REDUCTION_PER_LEVEL_F", 1.5, 0.0, Float.MAX_VALUE.toDouble()) + + val SWIM_BOOSTERS: Double by builder + .comment("Increase per level") + .defineInRange("SWIM_BOOSTERS", 0.25, 0.0, Float.MAX_VALUE.toDouble()) + + object EnderTeleporter { + init { + builder.comment("Ender Teleporter ability").push("EnderTeleporter") + } + + val ENERGY_COST by builder.defineDecimal("ENERGY_COST", Decimal(4096), Decimal.ZERO) + val COOLDOWN: Int by builder.comment("In ticks").defineInRange("COOLDOWN", 40, 0, Int.MAX_VALUE) + val MAX_PHASE_DISTANCE: Int by builder.comment("Determines how much blocks can we 'phase' through to teleport on solid surface").defineInRange("MAX_PHASE_DISTANCE", 6, 0, Int.MAX_VALUE) + val MAX_DISTANCE: Double by builder.comment("In blocks, euclidean distance").defineInRange("MAX_DISTANCE", 12.0, 2.0, Int.MAX_VALUE.toDouble()) + + init { + builder.pop() + } + } + + object JumpBoost { + init { + builder.comment("Jump boost ability").push("JumpBoost") + } + + val ENERGY_COST by builder.defineDecimal("ENERGY_COST", Decimal(1024), Decimal.ZERO) + val POWER: Double by builder.comment("The jump height on jump boost, as (level + 1) of feature, in meters per second").defineInRange("POWER", 6.0, 0.0, Double.MAX_VALUE) + val BASE_COOLDOWN: Int by builder.comment("In ticks").defineInRange("BASE_COOLDOWN", 40, 0, Int.MAX_VALUE) + val COOLDOWN_REDUCTION: Int by builder.comment("In ticks, per level of feature").defineInRange("COOLDOWN_REDUCTION", 20, 0, Int.MAX_VALUE) + + init { + builder.pop() + } + } + + object Magnet { + init { + builder.comment("Item magnet ability").push("Magnet") + } + + val POWER_DRAW by builder.comment("Per tick per stack").defineDecimal("POWER_DRAW", Decimal(8), Decimal.ZERO) + val RADIUS_HORIZONTAL: Double by builder.defineInRange("RADIUS_HORIZONTAL", 6.0, 0.0, Double.MAX_VALUE / 4.0) + val RADIUS_VERTICAL: Double by builder.defineInRange("RADIUS_VERTICAL", 3.0, 0.0, Double.MAX_VALUE / 4.0) + + init { + builder.pop() + } + } + + object Shockwave { + init { + builder.comment("Shockwave ability").push("Shockwave") + } + + val TERMINAL_VELOCITY: Double by builder.comment("In meters per second vertically").defineInRange("TERMINAL_VELOCITY", 5.6, 0.0) + val ACCELERATION: Double by builder.comment("In meters per second vertically").defineInRange("ACCELERATION", 4.0, 0.0) + val COOLDOWN: Int by builder.comment("In ticks").defineInRange("COOLDOWN", 30, 1) + val RADIUS_HORIZONTAL: Double by builder.comment("In meters").defineInRange("RADIUS_HORIZONTAL", 4.0, 0.0) + val RADIUS_VERTICAL: Double by builder.comment("In meters").defineInRange("RADIUS_VERTICAL", 1.0, 0.0) + + val RADIUS_HORIZONTAL_WARDEN: Double by builder.comment("In meters, when searching for Warden").defineInRange("RADIUS_HORIZONTAL_WARDEN", 16.0, 0.0) + val RADIUS_VERTICAL_WARDEN: Double by builder.comment("In meters, when searching for Warden").defineInRange("RADIUS_VERTICAL_WARDEN", 6.0, 0.0) + + val BREAK_BLOCKS: Boolean by builder.comment("Break blocks without any blast resistance").define("BREAK_BLOCKS", true) + val DAMAGE: Double by builder.comment("Max potential damage done by shockwave").defineInRange("DAMAGE", 12.0, 0.0, Float.MAX_VALUE.toDouble()) + val WARDEN_DAMAGE_MULT: Double by builder.defineInRange("WARDEN_DAMAGE_MULT", 4.0, 0.0, Float.MAX_VALUE.toDouble()) + val ENERGY_COST by builder.defineDecimal("ENERGY_COST", Decimal(2048), Decimal.ZERO) + + init { + builder.pop() + } + } + + init { + NanobotsRegeneration + EnderTeleporter + JumpBoost + Magnet + Shockwave + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/BalanceValues.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/BalanceValues.kt new file mode 100644 index 000000000..ea3cbac7d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/BalanceValues.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.core.math.Decimal + +interface EnergyBalanceValues { + val energyCapacity: Decimal + val energyThroughput: Decimal +} + +interface WorkerBalanceValues : EnergyBalanceValues { + val workTimeMultiplier: Double + val energyConsumption: Decimal + val matterCapacity: Decimal + val maxExperienceStored: Double +} + +interface VerboseEnergyBalanceValues { + val energyCapacity: Decimal + val maxEnergyReceive: Decimal + val maxEnergyExtract: Decimal +} + +interface BatteryBalanceValues : VerboseEnergyBalanceValues { + val initialBatteryLevel: Decimal +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt new file mode 100644 index 000000000..d9b13b56d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal + +object CablesConfig : AbstractConfig("cables") { + enum class E(throughput: Decimal) { + CRUDE(Decimal(160)), + REGULAR(Decimal(1024)), + ADVANCED(Decimal(8192)), + SUPERCONDUCTOR(Decimal.POSITIVE_INFINITY); + + init { + builder.push(name) + } + + var throughput by builder + .defineDecimal("THROUGHPUT", throughput, Decimal.ZERO) + + init { + builder.pop() + } + } + + init { + E.SUPERCONDUCTOR + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ClientConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ClientConfig.kt new file mode 100644 index 000000000..d4ce32b41 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ClientConfig.kt @@ -0,0 +1,74 @@ +package ru.dbotthepony.mc.otm.config + +import net.minecraftforge.fml.config.ModConfig + +object ClientConfig : AbstractConfig("client", ModConfig.Type.CLIENT) { + + var JUMP_BOOST_LOOK_ANGLE: Double by builder + .comment("If looking below this angle (actually, looking 'above' as you see in game, but not as you expect it, check with debug screen), Crouch + Jump will trigger jump boost android ability") + .defineInRange("JUMP_BOOST_LOOK_ANGLE", 30.0, -180.0, 180.0) + + object Tooltip { + init { + builder.push("Tooltip") + } + + var ALWAYS_DISPLAY_MATTER_VALUE: Boolean by builder + .comment("Always display matter value in tooltips without requiring holding Shift key") + .define("ALWAYS_DISPLAY_MATTER_VALUE", false) + + var DISPLAY_TAGS: Boolean by builder + .comment("Display block/item tags in advanced item tooltips") + .define("DISPLAY_TAGS", false) + + init { + builder.pop() + } + } + + object GUI { + init { + builder.comment("Inventory/GUI options").push("Gui") + } + + var EXOPACK_INVENTORY_ROWS: Int by builder + .comment("Amount of inventory rows to show when wearing Exosuit") + .defineInRange("EXOPACK_INVENTORY_ROWS", 3, 3, 6) + + var EXOPACK_FREE_SCROLL: Boolean by builder + .comment("Allow to scroll Exopack inventory in non OTM inventories when hovering just over inventory slots, not only scrollbar") + .define("EXOPACK_FREE_SCROLL", true) + + var REDSTONE_CONTROLS_ITEM_ICONS: Boolean by builder + .comment("Display redstone control button using items instead of custom sprites") + .comment("For those who want it.") + .define("REDSTONE_CONTROLS_ITEM_ICONS", false) + + init { + builder.pop() + } + } + + object HUD { + init { + builder.push("HUD") + } + + var ANDROID_HEALTH_BAR: Boolean by builder + .comment("Replace hearts with health bar on HUD when you are an android") + .define("ANDROID_HEALTH_BAR", true) + + var BAR_TEXT_SCALE: Double by builder + .defineInRange("BAR_TEXT_SCALE", 0.5, 0.5, 1.0) + + init { + builder.pop() + } + } + + init { + Tooltip + GUI + HUD + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt similarity index 94% rename from src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt index aedd29ba9..9febd526a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ConfigExt.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ConfigExt.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm +package ru.dbotthepony.mc.otm.config import net.minecraftforge.common.ForgeConfigSpec import kotlin.reflect.KProperty diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt new file mode 100644 index 000000000..ecc938e42 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal + +object ExopackConfig : AbstractConfig("exopack") { + val ENERGY_CAPACITY by builder + .comment("Internal battery capacity of Exopack") + .comment("Keep in mind that already existing players won't get their value changed since it is", "stored inside their savedata") + .defineDecimal("ENERGY_CAPACITY", Decimal(200_000), Decimal.ZERO) + + val FURNACE_POWER_CONSUMPTION by builder + .comment("Power consumed per tick by built in furnace") + .comment("Vanilla furnace consumes 10 MtU/t (1 tick = 10 MtU, 1 Coal = 16 000 MtU,", "this ratio is utilized by almost all mods)") + .defineDecimal("FURNACE_POWER_CONSUMPTION", Decimal(20), Decimal.ZERO) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt new file mode 100644 index 000000000..2bc71c21a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal +import ru.dbotthepony.mc.otm.registry.MNames + +object ItemsConfig : AbstractConfig("items") { + init { + builder.push("EnergyBatteries") + } + + object Batteries { + val CRUDE = batteryValues(MNames.BATTERY_CRUDE, Decimal(100_000), Decimal(160), Decimal(40), Decimal(80_000)) + val BASIC = batteryValues(MNames.BATTERY_BASIC, Decimal(400_000), Decimal(600)) + val NORMAL = batteryValues(MNames.BATTERY_NORMAL, Decimal(2_000_000), Decimal(1_000)) + val DENSE = batteryValues(MNames.BATTERY_DENSE, Decimal(10_000_000), Decimal(2_000)) + val CAPACITOR = batteryValues(MNames.BATTERY_CAPACITOR, Decimal(500_000), Decimal(50_000)) + + val QUANTUM_BATTERY = conciseValues(MNames.QUANTUM_BATTERY, Decimal(40_000_000), Decimal(10_000)) + val QUANTUM_CAPACITOR = conciseValues(MNames.QUANTUM_CAPACITOR, Decimal(1_000_000), Decimal(200_000)) + + val ZPM = conciseValues(MNames.ZPM_BATTERY, Decimal(200_000_000_000_000L), Decimal(200_000_000L)) + } + + init { + Batteries + builder.pop() + } + + val MATTER_DUST_CAPACITY by builder.comment("Maximal matter value one matter dust item can have").defineDecimal("matterDustCapacity", Decimal(2_000), minimum = Decimal.ONE_TENTH) + + init { + builder.push("MatterCapacitors") + } + + object Capacitors { + val BASIC by builder.defineDecimal(MNames.MATTER_CAPACITOR_BASIC, Decimal(2_500), minimum = Decimal.ONE_TENTH) + val NORMAL by builder.defineDecimal(MNames.MATTER_CAPACITOR_NORMAL, Decimal(10_000), minimum = Decimal.ONE_TENTH) + val DENSE by builder.defineDecimal(MNames.MATTER_CAPACITOR_DENSE, Decimal(25_000), minimum = Decimal.ONE_TENTH) + } + + init { + Capacitors + builder.pop() + } + + object PatternDrives { + val NORMAL: Int by builder.defineInRange(MNames.PATTERN_DRIVE_NORMAL, 4, 1, Int.MAX_VALUE) + } + + init { + builder.push("PatternDrives") + PatternDrives + builder.pop() + } + + val FLUID_CAPSULE_CAPACITY: Int by builder.defineInRange("FLUID_CAPSULE_CAPACITY", 1000, 1, Int.MAX_VALUE) + val FLUID_TANK_CAPACITY: Int by builder.defineInRange("FLUID_TANK_CAPACITY", 32_000, 1, Int.MAX_VALUE) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt new file mode 100644 index 000000000..3fc705cbd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt @@ -0,0 +1,225 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal +import ru.dbotthepony.mc.otm.registry.MNames + +object MachinesConfig : AbstractConfig("machines") { + val PLATE_PRESS = workerValues( + MNames.PLATE_PRESS, + energyStorage = Decimal(40_000), + energyThroughput = Decimal(200), + energyConsumption = Decimal(15), + maxExperience = 200.0, + ) + + val MATTER_ENTANGLER = workerValues( + MNames.MATTER_ENTANGLER, + energyStorage = Decimal(40_000), + energyThroughput = Decimal(400), + energyConsumption = Decimal(120), + matterCapacity = Decimal(60), + maxExperience = 400.0, + ) + + val MATTER_DECOMPOSER = workerValues( + MNames.MATTER_DECOMPOSER, + energyStorage = Decimal(100_000), + energyThroughput = Decimal(400), + energyConsumption = Decimal(240), + matterCapacity = Decimal(400), + ) + + val MATTER_SCANNER = workerValues( + MNames.MATTER_SCANNER, + energyStorage = Decimal(40_000), + energyThroughput = Decimal(200), + energyConsumption = Decimal(80), + ) + + val MATTER_REPLICATOR = workerValues( + MNames.MATTER_REPLICATOR, + energyStorage = Decimal(200_000), + energyThroughput = Decimal(4_000), + energyConsumption = Decimal(400), + matterCapacity = Decimal(200) + ) + + private val MATTER_RECYCLER = workerValues( + MNames.MATTER_RECYCLER, + energyStorage = Decimal(80_000), + energyThroughput = Decimal(400), + energyConsumption = Decimal(100), + matterCapacity = Decimal(400), + workTimeMultiplier = null + ) { + MatterRecycler + } + + private val MATTER_BOTTLER = workerValues( + MNames.MATTER_BOTTLER, + energyStorage = Decimal(40_000), + energyConsumption = Decimal(10), + energyThroughput = Decimal(200), + matterCapacity = Decimal(400), + workTimeMultiplier = null + ) { + MatterBottler + } + + private val CHEMICAL_GENERATOR = workerValues( + MNames.CHEMICAL_GENERATOR, + energyStorage = Decimal(24_000), + energyThroughput = Decimal(160), + energyConsumption = Decimal(40), + workTimeMultiplier = null + ) { + ChemicalGenerator + } + + object ChemicalGenerator { + val VALUES by ::CHEMICAL_GENERATOR + val RATIO: Int by builder.comment("This amount of burn ticks result in 1 generation tick").defineInRange("RATIO", 4, 0) + } + + object MatterBottler { + val VALUES by ::MATTER_BOTTLER + val RATE by builder.comment("Matter transferred per tick").defineDecimal("RATE", Decimal("2.0"), Decimal.ONE_TENTH) + } + + object MatterRecycler { + val VALUES by ::MATTER_RECYCLER + val MATTER_PER_TICK by builder.defineDecimal("MATTER_PER_TICK", Decimal("0.5")) + } + + private val ANDROID_STATION = workerValues( + MNames.ANDROID_STATION, + energyStorage = Decimal(40_000), + energyThroughput = Decimal(8_000), + energyConsumption = null, + workTimeMultiplier = null, + ) { + AndroidStation + } + + object AndroidStation { + val VALUES by ::ANDROID_STATION + val ENERGY_PER_OPERATION by builder.comment("Swapping parts").defineDecimal("ENERGY_PER_OPERATION", Decimal(2_000), Decimal.ZERO) + val ENERGY_PER_RESEARCH by builder.comment("Researching android stuff").defineDecimal("ENERGY_PER_RESEARCH", Decimal(16_000), Decimal.ZERO) + } + + private val MATTER_RECONSTRUCTOR = workerValues( + MNames.MATTER_RECONSTRUCTOR, + energyStorage = Decimal(100_000), + energyThroughput = Decimal(1000), + energyConsumption = Decimal(400), + matterCapacity = Decimal(200), + workTimeMultiplier = null, + ) { + MatterReconstructor + } + + object MatterReconstructor { + val VALUES by ::MATTER_RECONSTRUCTOR + + val ALLOW_TO_SKIP_ANVIL: Boolean by builder + .comment("Allow to repair tools without having their anvil-repair items researched") + .comment("Ignored if onlyAnvil is true") + .define("ALLOW_TO_SKIP_ANVIL", false) + + val ONLY_ANVIL: Boolean by builder + .comment("Force repairing only by matter value of anvil materials") + .comment("Doesn't make logical sense but might be good for balancing") + .define("ONLY_ANVIL", false) + + val FAILURE_CHANCE: Double by builder + .comment("Constant additional chance (over pattern research factor) that replication will fail at any given tick") + .comment("In event of failure repair tick is wasted, as well as resources") + .defineInRange("FAILURE_CHANCE", 0.01, 0.0, 0.99) + + val DIVISOR: Double by builder + .comment("Magnitute of **slowdown** of repairer.") + .comment("If this value is 1, repairer will repair item from 0% to 100% in COMPLEXITY (of tool) ticks") + .comment("(or twice the complexity of anvil repair ingredients if tool itself has no matter value)") + .comment("---") + .comment("If value is smaller than 1, repairer will repair items faster") + .comment("If value is bigger than 1, repairer will repair items slower") + .defineInRange("DIVISOR", 3.0, 0.1, Double.MAX_VALUE) + } + + val STORAGE_POWER_SUPPLIER = conciseValues(MNames.STORAGE_POWER_SUPPLIER, Decimal(80_000), Decimal(2_000)) + val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(10_000)) + val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(10_000)) + val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(10_000)) + val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(10_000)) + + object AndroidCharger { + val RADIUS_WIDTH: Double by builder + .comment("Effective charger range on horizontal plane") + .defineInRange("RADIUS_WIDTH", 16.0, 1.0, Double.MAX_VALUE) + + val RADIUS_HEIGHT: Double by builder + .comment("Effective charger range on vertical plane") + .defineInRange("RADIUS_HEIGHT", 4.0, 1.0, Double.MAX_VALUE) + } + + val ANDROID_CHARGER = conciseValues(MNames.ANDROID_CHARGER, Decimal(1_000_000), Decimal(8192)) { AndroidCharger } + + val POWERED_FURNACE = workerValues( + "POWERED_FURNACE", + energyStorage = Decimal(40_000), + energyThroughput = Decimal(200), + energyConsumption = Decimal(20), + workTimeMultiplier = 0.75, + maxExperience = 200.0 + ) + + val POWERED_BLAST_FURNACE = workerValues( + "POWERED_BLAST_FURNACE", + energyStorage = Decimal(40_000), + energyThroughput = Decimal(200), + energyConsumption = Decimal(20), + workTimeMultiplier = 0.75, + maxExperience = 200.0 + ) + + val POWERED_SMOKER = workerValues( + "POWERED_SMOKER", + energyStorage = Decimal(40_000), + energyThroughput = Decimal(200), + energyConsumption = Decimal(10), + workTimeMultiplier = 0.75, + maxExperience = 200.0 + ) + + object Upgrades { + init { + builder.push("UPGRADES") + } + + val MIN_SPEED: Double by builder + .comment("Minimal combined upgrade speed percentage (upgrades can't decrease machine speed below this)") + .defineInRange("MIN_SPEED", -0.8, -1.0, Double.MAX_VALUE) + + val MAX_SPEED: Double by builder + .comment("Maximal combined upgrade speed percentage") + .defineInRange("MAX_SPEED", Double.MAX_VALUE, 0.0, Double.MAX_VALUE) + + val MIN_ENERGY by builder + .comment("Minimal combined energy consumption percentage (upgrades can't decrease machine energy consumption below this)") + .defineDecimal("MIN_ENERGY", Decimal(-0.8), Decimal(-1.0)) + + val MAX_ENERGY by builder + .comment("Maximal combined energy consumption percentage") + .defineDecimal("MAX_ENERGY", Decimal.LONG_MAX_VALUE, Decimal.ZERO) + + init { + builder.pop() + } + } + + init { + Upgrades + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigValue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt similarity index 87% rename from src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigValue.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt index 4aa7953a8..a2a97bfa9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/ObservedConfigValue.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ObservedConfigValue.kt @@ -1,13 +1,14 @@ -package ru.dbotthepony.mc.otm +package ru.dbotthepony.mc.otm.config import net.minecraftforge.common.ForgeConfigSpec.ConfigValue import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.GetterSetter import java.util.function.Consumer import java.util.function.Supplier import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -abstract class ObservedConfigValue(val parent: ConfigValue) : ReadWriteProperty, Supplier, Consumer { +abstract class ObservedConfigValue(val parent: ConfigValue) : GetterSetter { var rawValue: String by parent private var observedValue: String? = null private var cachedValue: V? = null @@ -43,7 +44,7 @@ abstract class ObservedConfigValue(val parent: ConfigValue) : R return cachedValue ?: throw ConcurrentModificationException() } - override fun getValue(thisRef: Any, property: KProperty<*>): V { + override fun getValue(thisRef: Any?, property: KProperty<*>): V { return get() } @@ -64,7 +65,7 @@ abstract class ObservedConfigValue(val parent: ConfigValue) : R } } - override fun setValue(thisRef: Any, property: KProperty<*>, value: V) { + override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { accept(value) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerCompatConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerCompatConfig.kt new file mode 100644 index 000000000..268657160 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerCompatConfig.kt @@ -0,0 +1,40 @@ +package ru.dbotthepony.mc.otm.config + +object ServerCompatConfig : AbstractConfig("compat-server") { + object AdAstra { + init { + builder.push("AD_ASTRA") + } + + val ANDROIDS_DO_NOT_NEED_OXYGEN: Boolean by builder + .comment("Android do not need oxygen in space") + .define("ANDROIDS_DO_NOT_NEED_OXYGEN", true) + + val WHATS_UP_WITH_TEMPERATURE: Boolean by builder + .comment("Disables temperature mechanics on planets without atmosphere.") + .comment("I attended physics classes in middle school, and this kills me.") + .define("WHATS_UP_WITH_TEMPERATURE", true) + + val ANDROID_COSMIC_RAYS: Boolean by builder + .comment("Androids without spacesuit will get damaged over time by cosmic radiation") + .comment("on planets without atmosphere.") + .define("ANDROID_COSMIC_RAYS", true) + + val ANDROID_COSMIC_RAYS_CHANCE: Double by builder + .comment("Chance in percent android will be damaged this tick per missing piece of spacesuit") + .comment("Max chance in tick of damage is this value multiplied by 4 (spacesuit is completely missing)") + .defineInRange("ANDROID_COSMIC_RAYS_CHANCE", 0.015, 0.0, 1.0) + + val TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS: Boolean by builder + .comment("Should androids get 3/4th protection from cosmic rays per piece of tritanium armor") + .define("TRITANIUM_ARMOR_PROTECTS_AGAINST_COSMIC_RAYS", true) + + init { + builder.pop() + } + } + + init { + AdAstra + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt new file mode 100644 index 000000000..a4d6dc4bf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.mc.otm.config + +object ServerConfig : AbstractConfig("misc") { + val LABORATORY_LAMP_LIGHT_LENGTH: Int by builder.comment("In blocks").defineInRange("LABORATORY_LAMP_LIGHT_LENGTH", 6, 1, 128) + val INFINITE_EXOSUIT_UPGRADES: Boolean by builder.comment("Allows to apply the same upgrade over and over again.", "Obviously completely breaks balance.").define("INFINITE_EXOSUIT_UPGRADES", false) + + init { + builder.push("EXPERIENCE_CAPSULES") + } + + val DROP_EXPERIENCE_CAPSULES: Boolean by builder + .comment("Whenever to replace regular player experience drops with experience capsules") + .comment("These capsules can be collected by gravestone mods, and are not limited in experience they can contain") + .define("ENABLED", true) + + val MIN_EXPERIENCE_DROPPED: Double by builder + .comment("Minimal percent of experience dropped on death into capsules") + .defineInRange("MINIMAL_PERCENT", 0.4, 0.0, 1.0) + + val MAX_EXPERIENCE_DROPPED: Double by builder + .comment("Maximal percent of experience dropped on death into capsules") + .defineInRange("MAXIMAL_PERCENT", 0.8, 0.0, 1.0) + + init { + builder.pop() + builder.push("BLACKHOLES") + Blackhole + } + + object Blackhole { + val PLAYER_DAMAGE_SCALE: Double by builder + .comment("Percentage of damage received by players from Event Horizon of Singularities") + .defineInRange("PLAYER_DAMAGE_SCALE", 0.25, 0.0, Float.MAX_VALUE.toDouble()) + + val MOB_DAMAGE_SCALE: Double by builder + .comment("Percentage of damage received by non players from Event Horizon of Singularities") + .defineInRange("MOB_DAMAGE_SCALE", 1.0, 0.0, Float.MAX_VALUE.toDouble()) + + val BOSS_DAMAGE_SCALE: Double by builder + .comment("Percentage of damage received by bosses (forge:bosses tag) from Event Horizon of Singularities") + .defineInRange("BOSS_DAMAGE_SCALE", 0.25, 0.0, Float.MAX_VALUE.toDouble()) + + val PLAYER_FORCE_MULTIPLIER: Double by builder + .comment("Percentage of gravity force experienced by players from Singularities") + .defineInRange("PLAYER_FORCE_MULTIPLIER", 0.4, 0.0, Float.MAX_VALUE.toDouble()) + + val MOB_FORCE_MULTIPLIER: Double by builder + .comment("Percentage of gravity force experienced by non players from Singularities") + .defineInRange("MOB_FORCE_MULTIPLIER", 1.0, 0.0, Float.MAX_VALUE.toDouble()) + + val BOSS_FORCE_MULTIPLIER: Double by builder + .comment("Percentage of gravity force experienced by bosses (forge:bosses tag) from Singularities") + .defineInRange("BOSS_FORCE_MULTIPLIER", 0.4, 0.0, Float.MAX_VALUE.toDouble()) + + val DESTROY_BLOCKS: Boolean by builder + .comment("Whenever singularities should destroy blocks") + .comment("Keep in mind that mobs behind blocks can cause serious server lag") + .comment("when trying to reach singularity") + .define("DESTROY_BLOCKS", true) + } + + init { + builder.pop() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ToolsConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ToolsConfig.kt new file mode 100644 index 000000000..f6aadb631 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ToolsConfig.kt @@ -0,0 +1,83 @@ +package ru.dbotthepony.mc.otm.config + +import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem + +object ToolsConfig : AbstractConfig("tools") { + val AXES_BREAK_LEAVES_INSTANTLY: Boolean by builder.define("AXES_BREAK_LEAVES_INSTANTLY", true) + + init { + EnergySwordItem.registerConfig(builder) + } + + object ExplosiveHammer { + val MAX_TRAVEL: Double by builder + .comment("Max distance, in blocks, the nail can travel", "This also dictates potential max damage nail can deal (controlled by TRAVEL_DAMAGE_MULT)") + .comment("The nail penetrate through blocks and entities, losing power in process") + .comment("The nail can not travel into unloaded chunks and will stop immediately if it does so") + .comment("The nail travels its entire path instantaneously") + .defineInRange("MAX_TRAVEL", 64.0, 0.0, Double.MAX_VALUE) + + val TRAVEL_EXPLOSION_RESIST_MULT: Double by builder + .comment("Multiplier of explosion resistance of blocks on nail path") + .comment("If explosion resistance multiplied by this is bigger than nail current force (base of MAX_TRAVEL)") + .comment("then nail movement halts, otherwise nail force is reduced by explosion resistance multiplied by this") + .comment("and block is broken (if DAMAGE_BLOCKS is true, otherwise it only acts as stopping power)") + .comment("---") + .comment("Values over 1 will make blocks more resistant to be penetrated through (and broken)") + .comment("Values below 1 will make blocks less resistant to be penetrated though (and broken)") + .comment("Value of 0 means no matter how strong block resistance is, it will be broken by nail", "(this includes bedrock!!!)") + .comment("---") + .defineInRange("TRAVEL_EXPLOSION_RESIST_MULT", 1.75, 0.0, Double.MAX_VALUE) + + val TRAVEL_DAMAGE_MULT: Double by builder + .comment("Multiplier of current nail force (base of MAX_TRAVEL) for damage values") + .comment("That is, any being on path of nail will get damaged by nail power multiplied by this") + .defineInRange("TRAVEL_DAMAGE_MULT", 0.5, 0.0, Double.MAX_VALUE) + + val TRAVEL_MAX_HEALTH_MULT: Double by builder + .comment("Multiplier of being's max health in nail path subtracted from nail force when penetrating through") + .defineInRange("TRAVEL_MAX_HEALTH_MULT", 0.5, 0.0, Double.MAX_VALUE) + + val DAMAGE_BLOCKS: Boolean by builder + .comment("Should hammer (**not** explosion) damage blocks", "Blocks damaged this way will always be dropped") + .define("DAMAGE_BLOCKS", true) + + val EXPLOSION_DAMAGE_BLOCKS: Boolean by builder + .comment("Should hammer **explosion** damage blocks") + .define("EXPLOSION_DAMAGE_BLOCKS", false) + + val FLY_OFF_CHANCE: Double by builder + .comment("Chance that hammer will fly off hands of attacker upon impact") + .defineInRange("FLY_OFF_CHANCE", 0.4, 0.0, 1.0) + + val SELF_HARM_CHANCE: Double by builder + .comment("When hammer DOES NOT fly off hands, what is chance of it damaging the user?", "(this does not account for damage because of hitting things close)", "(idiot)") + .defineInRange("SELF_HARM_CHANCE", 0.25, 0.0, 1.0) + + val SELF_HARM_MIN_DAMAGE: Double by builder + .comment("When hammer DOES NOT fly off hands, what is minimal damage it deals to the user (in half of hearts)?") + .defineInRange("SELF_HARM_MIN_DAMAGE", 2.0, 0.0, Double.MAX_VALUE) + + val SELF_HARM_MAX_DAMAGE: Double by builder + .comment("When hammer DOES NOT fly off hands, what is maximal damage it deals to the user (in half of hearts)?") + .defineInRange("SELF_HARM_MAX_DAMAGE", 10.0, 0.0, Double.MAX_VALUE) + + val FLY_OFF_DAMAGE_CHANCE: Double by builder + .comment("When hammer flies off hands, what is chance of it damaging the user?") + .defineInRange("FLY_OFF_DAMAGE_CHANCE", 0.4, 0.0, 1.0) + + val FLY_OFF_MIN_DAMAGE: Double by builder + .comment("When hammer flies off hands, what is minimal damage it deals to the user (in half of hearts)?") + .defineInRange("FLY_OFF_MIN_DAMAGE", 4.0, 0.0, Double.MAX_VALUE) + + val FLY_OFF_MAX_DAMAGE: Double by builder + .comment("When hammer flies off hands, what is maximal damage it deals to the user (in half of hearts)?") + .defineInRange("FLY_OFF_MAX_DAMAGE", 20.0, 0.0, Double.MAX_VALUE) + } + + init { + builder.push("ExplosiveHammer") + ExplosiveHammer + builder.pop() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt new file mode 100644 index 000000000..36e2aa9d8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt @@ -0,0 +1,230 @@ +package ru.dbotthepony.mc.otm.container + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.util.IContainerSlot +import ru.dbotthepony.mc.otm.container.util.containerSlot +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.concatIterators +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.flatMap +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.stream +import java.util.LinkedList +import java.util.stream.Stream + +class CombinedContainer(containers: Stream>>) : IMatteryContainer { + constructor(vararg containers: Container) : this(containers.stream().map { it to (0 until it.containerSize) }) + constructor(containers: Collection) : this(containers.stream().map { it to (0 until it.containerSize) }) + + private inner class Slot(override val slot: Int, val outer: IContainerSlot) : IContainerSlot by outer { + override val container: Container + get() = this@CombinedContainer + + override fun component1(): Int { + return super.component1() + } + + override fun component2(): ItemStack { + return super.component2() + } + } + + private val slots: List + private val slotsMap: Map> + private val containers: Set + private val fullCoverage: List + private val notFullCoverage: Map> + + init { + val list = ImmutableList.Builder() + var i = 0 + val validationMap = Reference2ObjectOpenHashMap() + val slotsMap = Reference2ObjectOpenHashMap>() + + for ((container, slots) in containers) { + val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntAVLTreeSet() }) + val slotList = slotsMap.computeIfAbsent(container, Object2ObjectFunction { ArrayList() }) + + for (slot in slots) { + if (validator.add(slot)) { + val slotObj = container.containerSlot(slot) + list.add(Slot(i++, slotObj)) + slotList.add(slotObj) + } else { + throw IllegalArgumentException("Duplicate mapping for $container at $i for slot $slot") + } + } + } + + slots = list.build() + this.containers = ImmutableSet.copyOf(validationMap.keys) + + this.slotsMap = slotsMap.entries + .stream() + .map { it.key to ImmutableList.copyOf(it.value) } + .collect(ImmutableMap.toImmutableMap({ it.first }, { it.second })) + + this.fullCoverage = this.slotsMap.entries + .stream() + .filter { it.value.size == it.key.containerSize } + .map { it.key } + .collect(ImmutableList.toImmutableList()) + + this.notFullCoverage = this.slotsMap.entries + .stream() + .filter { it.key !in this.fullCoverage } + .collect(ImmutableMap.toImmutableMap({ it.key }, { it.value })) + } + + override fun clearContent() { + for (container in fullCoverage) { + container.clearContent() + } + + for (slots in notFullCoverage.values) { + for (slot in slots) { + slot.item = ItemStack.EMPTY + } + } + } + + override fun getContainerSize(): Int { + return slots.size + } + + override fun isEmpty(): Boolean { + if (fullCoverage.isNotEmpty()) + for (container in fullCoverage) + if (!container.isEmpty) + return false + + if (notFullCoverage.isNotEmpty()) + for (slots in notFullCoverage.values) + for (slot in slots) + if (!slot.isEmpty) + return false + + return true + } + + override fun getItem(slot: Int): ItemStack { + // do not violate contract of getItem not throwing exceptions when index is invalid + return slots.getOrNull(slot)?.item ?: ItemStack.EMPTY + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + val data = slots.getOrNull(slot) ?: return ItemStack.EMPTY + return data.outer.container.removeItem(data.outer.slot, amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + val data = slots.getOrNull(slot) ?: return ItemStack.EMPTY + return data.outer.container.removeItemNoUpdate(data.outer.slot) + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + slots.getOrNull(slot)?.item = itemStack + } + + override fun setChanged() { + for (container in containers) { + container.setChanged() + } + } + + override fun stillValid(player: Player): Boolean { + for (container in containers) + if (!container.stillValid(player)) + return false + + return true + } + + override fun iterator(nonEmpty: Boolean): Iterator { + return concatIterators( + fullCoverage.iterator().flatMap { it.iterator(nonEmpty) }, + notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.let { if (nonEmpty) it.filter { it.isNotEmpty } else it } + ) + } + + override fun slotIterator(nonEmpty: Boolean): Iterator { + if (nonEmpty) + return slots.iterator().filter { it.isNotEmpty } + + return slots.iterator() + } + + override fun containerSlot(slot: Int): IContainerSlot { + return slots[slot] + } + + override fun getSlotFilter(slot: Int): Item? { + return slots[slot].getFilter() + } + + override fun setChanged(slot: Int) { + slots[slot].setChanged() + } + + override fun setSlotFilter(slot: Int, filter: Item?): Boolean { + return slots[slot].setFilter(filter) + } + + class Builder { + private val values = ArrayList>>() + + fun add(container: Container): Builder { + values.add(container to container.slotRange) + return this + } + + fun add(container: Container, slots: Iterator): Builder { + values.add(container to IntArrayList(slots)) + return this + } + + fun add(container: Container, slot: Int): Builder { + values.add(container to intArrayOf(slot).asIterable()) + return this + } + + fun add(container: Container, from: Int, to: Int): Builder { + values.add(container to (from .. to)) + return this + } + + fun add(container: Container, slots: Iterable): Builder { + values.add(container to slots) + return this + } + + fun build(): CombinedContainer { + return CombinedContainer(values.stream()) + } + } + + companion object { + fun fromMenuSlots(slots: Iterator): CombinedContainer { + val builder = Builder() + + for (slot in slots) { + builder.add(slot.container, slot.slotIndex) + } + + return builder.build() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt new file mode 100644 index 000000000..eefa76b79 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt @@ -0,0 +1,89 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.items.IItemHandler + +class ContainerHandler( + private val container: IMatteryContainer, + private val filter: HandlerFilter = HandlerFilter.Both, +) : IItemHandler { + override fun getSlots() = container.containerSize + override fun getStackInSlot(slot: Int) = container[slot] + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + if (!container.testSlotFilter(slot, stack) || !filter.canInsert(slot, stack)) + return stack + + filter.preInsert(slot, stack, simulate) + + val localStack = container[slot] + var amount = filter.modifyInsertCount(slot, stack, localStack, simulate) + + if (amount <= 0) + return stack + + if (localStack.isEmpty) { + amount = stack.count.coerceAtMost(container.getMaxStackSize(slot, stack)).coerceAtMost(amount) + + if (!simulate) { + container.setItem(slot, stack.copyWithCount(amount)) + } + + if (stack.count <= amount) { + return ItemStack.EMPTY + } else { + return stack.copyWithCount(stack.count - amount) + } + } else if (localStack.isStackable && container.getMaxStackSize(slot, localStack) > localStack.count && ItemStack.isSameItemSameTags(localStack, stack)) { + val newCount = container.getMaxStackSize(slot, localStack).coerceAtMost(localStack.count + stack.count.coerceAtMost(amount)) + val diff = newCount - localStack.count + + if (diff != 0) { + if (!simulate) { + localStack.grow(diff) + container.setChanged(slot) + } + + val copy = stack.copy() + copy.shrink(diff) + return copy + } + } + + return stack + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + if (amount <= 0 || container.isSlotForbiddenForAutomation(slot)) + return ItemStack.EMPTY + + val localStack = container.getItem(slot) + if (localStack.isEmpty) return ItemStack.EMPTY + + @Suppress("name_shadowing") + val amount = filter.modifyExtractCount(slot, amount, simulate) + if (amount <= 0) return ItemStack.EMPTY + if (!filter.canExtract(slot, amount, localStack)) return ItemStack.EMPTY + + filter.preExtract(slot, amount, simulate) + + val minimal = amount.coerceAtMost(localStack.count) + val copy = localStack.copy() + copy.count = minimal + + if (!simulate) { + localStack.shrink(minimal) + container.setChanged(slot) + } + + return copy + } + + override fun getSlotLimit(slot: Int): Int { + return container.maxStackSize + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return container.testSlotFilter(slot, stack) && filter.canInsert(slot, stack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt new file mode 100644 index 000000000..c50cd7b5c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt @@ -0,0 +1,381 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntArraySet +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.ints.IntIterable +import it.unimi.dsi.fastutil.ints.IntIterator +import it.unimi.dsi.fastutil.ints.IntList +import it.unimi.dsi.fastutil.ints.IntOpenHashSet +import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import net.minecraft.world.Container +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.container.util.IContainerSlot +import ru.dbotthepony.mc.otm.container.util.ItemStackHashStrategy +import ru.dbotthepony.mc.otm.container.util.containerSlot +import ru.dbotthepony.mc.otm.container.util.slotIterator +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.map +import ru.dbotthepony.mc.otm.core.util.ItemStackSorter +import kotlin.math.roundToInt + +@Suppress("nothing_to_inline") +inline operator fun Container.set(index: Int, value: ItemStack) = setItem(index, value) + +@Suppress("nothing_to_inline") +inline operator fun Container.get(index: Int): ItemStack = getItem(index) + +@Suppress("nothing_to_inline") +inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index) + +val Container.slotRange: IntIterable get() { + return IntIterable { + val i = (0 until containerSize).iterator() + + object : IntIterator { + override fun hasNext(): Boolean { + return i.hasNext() + } + + override fun remove() { + throw UnsupportedOperationException() + } + + override fun nextInt(): Int { + return i.nextInt() + } + } + } +} + +fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange): ItemStack { + if (this is IMatteryContainer) { + return this.addItem(stack, simulate, slots) + } + + if (stack.isEmpty) + return stack + + val copy = stack.copy() + + // двигаем в одинаковые слоты + var i = slots.intIterator() + + while (i.hasNext()) { + val slot = i.nextInt() + + if (ItemStack.isSameItemSameTags(this[slot], copy)) { + val slotStack = this[slot] + val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize) + + if (slotStack.count < slotLimit) { + val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) + val diff = newCount - slotStack.count + + if (!simulate) { + slotStack.count = newCount + setChanged() + } + + copy.shrink(diff) + + if (copy.isEmpty) { + return copy + } + } + } + } + + // двигаем в пустые слоты + i = slots.intIterator() + + while (i.hasNext()) { + val slot = i.nextInt() + + if (this[slot].isEmpty) { + val diff = copy.count.coerceAtMost(maxStackSize.coerceAtMost(copy.maxStackSize)) + + if (!simulate) { + val copyToPut = copy.copy() + copyToPut.count = diff + this[slot] = copyToPut + setChanged() + } + + copy.shrink(diff) + + if (copy.isEmpty) { + return copy + } + } + } + + return copy +} + +fun Container.vanishCursedItems() { + for (slot in slotIterator()) { + if (hasVanishingCurse(slot.item)) { + slot.remove() + } + } +} + +fun Container.balance(slots: IntSet, checkForEmpty: Boolean = true) { + if (slots.isEmpty() || checkForEmpty && !slots.any { getItem(it).isNotEmpty }) return + + val empty = IntArrayList() + val itemTypes = Object2ObjectOpenCustomHashMap(ItemStackHashStrategy) + + for (i in slots.intIterator()) { + val item = getItem(i) + + if (item.isEmpty) { + empty.add(i) + } else { + itemTypes.computeIfAbsent(item, Object2ObjectFunction { IntAVLTreeSet() }).add(i) + } + } + + if (itemTypes.isEmpty()) + return + + // только один вид предмета, просто балансируем его во все слоты + if (itemTypes.size == 1) { + val (item, list) = itemTypes.entries.first() + var count = 0 + for (i in list.intIterator()) count += getItem(i).count + var ghostChanges = false + + // всего предметов меньше, чем слотов + if (count < slots.size) { + for (slot in list.intIterator()) { + getItem(slot).count = 1 + ghostChanges = true + } + + count -= list.size + + while (count > 0 && empty.isNotEmpty()) { + setItem(empty.removeInt(0), item.copyWithCount(1)) + count-- + } + + if (count > 0) { + getItem(list.firstInt()).count += count + ghostChanges = true + } + } else { + // всего предметов больше, чем слотов + val perSlot = count / slots.size + var leftover = count - perSlot * slots.size + + for (i in slots.intIterator()) { + val target = perSlot + leftover.coerceAtMost(1) + + if (getItem(i).count != target) { + setItem(i, item.copyWithCount(target)) + } + + if (leftover > 0) leftover-- + } + } + + if (ghostChanges) setChanged() + return + } + + // а вот тут уже проблемы + // для упрощения задачи, выполним рекурсивное разбитие задачи на более простые, + // где балансировка будет происходить только между пустыми слотами и предметами одного типа + + // если у нас нет пустых слотов, просто балансируем между заполненными слотами + if (empty.isEmpty) { + for (set in itemTypes.values) { + balance(set) + } + + return + } else if (empty.size == 1) { + // только один пустой слот, отдадим самому "жирному" предмету + val type = itemTypes.entries.stream().max { a, b -> b.value.intStream().sum().compareTo(a.value.intStream().sum()) }.orElseThrow() + type.value.add(empty.getInt(0)) + balance(type.value) + + for ((a, set) in itemTypes) { + if (a !== type.key) { + balance(set) + } + } + + return + } + + // определяем общее количество предметов + val totalCount = itemTypes.values.stream().mapToInt { it.stream().mapToInt { getItem(it).count }.sum() }.sum().toDouble() + val totalEmpty = empty.size + + // определяем доли предметов по их количеству к общему количеству, + // что позволит нам выделить претендентов на пустые слоты + for (list in itemTypes.values) { + if (empty.isEmpty) break // ошибка округления + + val perc = list.stream().mapToInt { getItem(it).count }.sum() / totalCount + + for (i in 0 until (perc * totalEmpty).roundToInt()) { + list.add(empty.removeInt(0)) + if (empty.isEmpty) break // ошибка округления + } + } + + if (empty.isNotEmpty()) { + // ошибка округления + itemTypes.values.stream().max { a, b -> b.intStream().sum().compareTo(a.intStream().sum()) }.orElseThrow().add(empty.removeInt(0)) + } + + for (list in itemTypes.values) { + balance(list) + } +} + +fun Container.balance(slots: Iterator) { + balance(IntArraySet().also { it.addAll(slots) }) +} + +fun Container.balance(slots: Iterable) { + balance(IntArraySet().also { it.addAll(slots) }) +} + +fun Container.balance(slots: IntRange) { + balance(IntArraySet().also { it.addAll(slots) }) +} + +fun Container.balance(startSlot: Int, endSlot: Int) { + require(startSlot <= endSlot) { "Invalid slot range: $startSlot .. $endSlot" } + var any = false + + for (i in startSlot .. endSlot) { + if (getItem(i).isNotEmpty) { + any = true + break + } + } + + if (!any) return + balance(IntArraySet(endSlot - startSlot + 1).also { for (i in startSlot .. endSlot) it.add(i) }, false) +} + +fun Container.balance() { + if (isEmpty) + return + + balance(IntArraySet(containerSize).also { for (i in 0 until containerSize) it.add(i) }, false) +} + +operator fun CraftingContainer.get(column: Int, row: Int): ItemStack { + return getItem(column + row * width) +} + +operator fun CraftingContainer.get(column: Int, row: Int, flop: Boolean): ItemStack { + return if (flop) + get(width - column - 1, row) + else + get(column, row) +} + +fun Container.sort(comparator: Comparator = ItemStackSorter.DEFAULT) { + if (isEmpty) + return + + val slots = slotIterator().filter { !it.isForbiddenForAutomation && it.getMaxStackSize() >= it.item.count }.toList() + + if (slots.isEmpty()) + return + + val items = Object2ObjectOpenCustomHashMap(ItemStackHashStrategy) + + slots.forEach { + val get = items[it.item] + + if (get == null) { + items[it.item] = it.item.copy() + } else { + get.count += it.item.count + } + } + + val sortedItems = ObjectArrayList(items.values) + sortedItems.sortWith(comparator) + + slots.forEach { it.remove() } + sortedItems.forEach { addItem(it, false) } +} + +fun Container.sortWithIndices(sortedSlots: IntCollection) { + if (sortedSlots.isEmpty() || isEmpty) + return + + val seen = IntOpenHashSet() + val valid = ArrayList() + + val iterator = sortedSlots.intIterator() + + while (iterator.hasNext()) { + val value = iterator.nextInt() + + if (value in 0 until containerSize && seen.add(value)) { + val slot = containerSlot(value) + + if (slot.isNotEmpty && !slot.isForbiddenForAutomation && slot.item.count <= slot.getMaxStackSize()) { + valid.add(slot) + } + } + } + + if (valid.isEmpty()) + return + + val items = valid.map { it.item.copy() } + valid.forEach { it.remove() } + items.forEach { addItem(it, false) } +} + +fun Container.computeSortedIndices(comparator: Comparator = ItemStackSorter.DEFAULT): IntList { + if (isEmpty) + return IntList.of() + + val slots = slotIterator().filter { !it.isForbiddenForAutomation && it.getMaxStackSize() >= it.item.count }.toList() + + if (slots.isEmpty()) + return IntList.of() + + val items = Object2ObjectOpenCustomHashMap>(ItemStackHashStrategy) + + slots.forEach { + val get = items[it.item] + + if (get == null) { + items[it.item] = it.item.copy() to IntArrayList().also { s -> s.add(it.slot) } + } else { + get.first.count += it.item.count + get.second.add(it.slot) + } + } + + val sortedItems = ObjectArrayList(items.values) + sortedItems.sortWith(comparator.map { it.first }) + + val result = IntArrayList() + sortedItems.forEach { result.addAll(it.second) } + return result +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt deleted file mode 100644 index 710757ef0..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt +++ /dev/null @@ -1,59 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import it.unimi.dsi.fastutil.objects.ObjectIterators -import net.minecraft.world.Container -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry -import ru.dbotthepony.mc.otm.core.AwareItemStack - -class ContainerIterator(private val container: Container, initialPosition: Int = 0) : ObjectIterators.AbstractIndexBasedListIterator(0, initialPosition), MutableListIterator { - init { - require(initialPosition in 0 .. container.containerSize) { "Invalid initial position: $initialPosition" } - } - - override fun add(location: Int, k: ItemStack) { - throw UnsupportedOperationException() - } - - override fun set(location: Int, k: ItemStack) { - container[location] = k - } - - override fun remove(location: Int) { - pos++ - container[location] = ItemStack.EMPTY - } - - override fun get(location: Int): ItemStack { - return container[location] - } - - override fun getMaxPos(): Int { - return container.containerSize - } -} - -class AwareContainerIterator(private val container: Container, initialPosition: Int = 0) : ObjectIterators.AbstractIndexBasedIterator(0, initialPosition), Iterator { - init { - require(initialPosition in 0 .. container.containerSize) { "Invalid initial position: $initialPosition" } - } - - override fun remove(location: Int) { - pos++ - container[location] = ItemStack.EMPTY - } - - override fun get(location: Int): AwareItemStack { - return ContainerItemStackEntry(location, container) - } - - override fun getMaxPos(): Int { - return container.containerSize - } -} - -@JvmOverloads -fun Container.iterator(initialPosition: Int = 0) = ContainerIterator(this, initialPosition) - -@JvmOverloads -fun Container.awareIterator(initialPosition: Int = 0) = AwareContainerIterator(this, initialPosition) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt deleted file mode 100644 index b8cb0a3ed..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerProxy.kt +++ /dev/null @@ -1,187 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap -import com.google.common.collect.ImmutableSet -import it.unimi.dsi.fastutil.ints.IntAVLTreeSet -import it.unimi.dsi.fastutil.ints.IntOpenHashSet -import it.unimi.dsi.fastutil.objects.Object2ObjectFunction -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import java.util.LinkedList -import java.util.function.Consumer -import java.util.function.Supplier -import java.util.stream.Stream -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -open class ContainerProxy(containers: Stream>>) : Container { - data class ContainerSlot(val container: Container, val outerIndex: Int, val index: Int) : ReadWriteProperty, Supplier, Consumer { - var item: ItemStack - get() = container[index] - set(value) { container[index] = value } - - override fun getValue(thisRef: Any, property: KProperty<*>): ItemStack { - return item - } - - override fun setValue(thisRef: Any, property: KProperty<*>, value: ItemStack) { - this.item = value - } - - override fun get(): ItemStack { - return item - } - - override fun accept(t: ItemStack) { - item = t - } - - val isEmpty: Boolean get() = item.isEmpty - } - - protected val slots: List - protected val slotsMap: Map> - protected val containers: Set - protected val fullCoverage: List - protected val notFullCoverage: Map> - - init { - val list = ImmutableList.Builder() - var i = 0 - val validationMap = Reference2ObjectOpenHashMap() - val slotsMap = Reference2ObjectOpenHashMap>() - - for ((container, slots) in containers) { - val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntOpenHashSet() }) - val slotList = slotsMap.computeIfAbsent(container, Object2ObjectFunction { ArrayList() }) - - for (slot in slots) { - if (validator.add(slot)) { - val slotObj = ContainerSlot(container, i++, slot) - list.add(slotObj) - slotList.add(slotObj) - } else { - throw IllegalArgumentException("Duplicate mapping for $container at $i for slot $slot") - } - } - } - - slots = list.build() - this.containers = ImmutableSet.copyOf(validationMap.keys) - - this.slotsMap = slotsMap.entries - .stream() - .map { it.key to ImmutableList.copyOf(it.value) } - .collect(ImmutableMap.toImmutableMap({ it.first }, { it.second })) - - this.fullCoverage = this.slotsMap.entries - .stream() - .filter { it.value.size == it.key.containerSize } - .map { it.key } - .collect(ImmutableList.toImmutableList()) - - this.notFullCoverage = this.slotsMap.entries - .stream() - .filter { it.key !in this.fullCoverage } - .collect(ImmutableMap.toImmutableMap({ it.key }, { it.value })) - } - - override fun clearContent() { - for (container in fullCoverage) { - container.clearContent() - } - - for (slots in notFullCoverage.values) { - for (slot in slots) { - slot.item = ItemStack.EMPTY - } - } - } - - override fun getContainerSize(): Int { - return slots.size - } - - override fun isEmpty(): Boolean { - for (container in fullCoverage) - if (!container.isEmpty) - return false - - for (slots in notFullCoverage.values) - for (slot in slots) - if (!slot.isEmpty) - return false - - return true - } - - fun slotAt(index: Int): ContainerSlot { - return slots[index] - } - - override fun getItem(index: Int): ItemStack { - // do not violate contract of getItem not throwing exceptions when index is invalid - return slots.getOrNull(index)?.item ?: ItemStack.EMPTY - } - - override fun removeItem(index: Int, count: Int): ItemStack { - val data = slots.getOrNull(index) ?: return ItemStack.EMPTY - return data.container.removeItem(data.index, count) - } - - override fun removeItemNoUpdate(index: Int): ItemStack { - val data = slots[index] - return data.container.removeItemNoUpdate(data.index) - } - - override fun setItem(index: Int, value: ItemStack) { - slots[index].item = value - } - - override fun setChanged() { - for (container in containers) { - container.setChanged() - } - } - - override fun stillValid(player: Player): Boolean { - for (container in containers) - if (!container.stillValid(player)) - return false - - return true - } - - class Builder { - private var built = false - private val values = LinkedList>>() - - fun add(container: Container): Builder { - check(!built) { "Already built!" } - values.add(container to (0 until container.containerSize).iterator()) - return this - } - - fun add(container: Container, slots: Iterator): Builder { - check(!built) { "Already built!" } - values.add(container to slots) - return this - } - - fun add(container: Container, slots: Iterable): Builder { - check(!built) { "Already built!" } - values.add(container to slots.iterator()) - return this - } - - fun build(): ContainerProxy { - check(!built) { "Already built!" } - val value = ContainerProxy(values.stream()) - values.clear() - return value - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt new file mode 100644 index 000000000..26b258970 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt @@ -0,0 +1,77 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import java.util.function.Predicate +import java.util.function.Supplier + +/** + * because mods tend to do crazy shit + */ +class DynamicallyProxiedContainer(private val toProxy: Supplier) : IContainer { + override fun clearContent() { + return toProxy.get().clearContent() + } + + override fun getContainerSize(): Int { + return toProxy.get().containerSize + } + + override fun isEmpty(): Boolean { + return toProxy.get().isEmpty + } + + override fun getItem(slot: Int): ItemStack { + return toProxy.get().getItem(slot) + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return toProxy.get().removeItem(slot, amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + return toProxy.get().removeItemNoUpdate(slot) + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + return toProxy.get().setItem(slot, itemStack) + } + + override fun setChanged() { + return toProxy.get().setChanged() + } + + override fun stillValid(player: Player): Boolean { + return toProxy.get().stillValid(player) + } + + override fun getMaxStackSize(): Int { + return toProxy.get().getMaxStackSize() + } + + override fun startOpen(player: Player) { + toProxy.get().startOpen(player) + } + + override fun stopOpen(player: Player) { + toProxy.get().stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return toProxy.get().canPlaceItem(slot, itemStack) + } + + override fun countItem(item: Item): Int { + return toProxy.get().countItem(item) + } + + override fun hasAnyOf(items: Set): Boolean { + return toProxy.get().hasAnyOf(items) + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + return toProxy.get().hasAnyMatching(predicate) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/Ext.kt deleted file mode 100644 index bc2b1a738..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/Ext.kt +++ /dev/null @@ -1,108 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.Container -import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.capabilities.Capability -import ru.dbotthepony.mc.otm.core.iterator -import ru.dbotthepony.mc.otm.core.nonEmpty - -operator fun Container.set(index: Int, value: ItemStack) = setItem(index, value) -operator fun Container.get(index: Int): ItemStack = getItem(index) - -fun Container.addItem(stack: ItemStack, range: IntRange, simulate: Boolean = false): ItemStack { - if (this is MatteryContainer) { - return this.addItem(stack, range, simulate) - } - - if (range.last >= containerSize || range.first < 0) - throw IllegalArgumentException("Invalid range: $range") - - if (stack.isEmpty) - return stack - - val copy = stack.copy() - - // двигаем в одинаковые слоты - for (slot in range) { - if (ItemStack.isSameItemSameTags(this[slot], copy)) { - val slotStack = this[slot] - val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize) - - if (slotStack.count < slotLimit) { - val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) - val diff = newCount - slotStack.count - - if (!simulate) { - slotStack.count = newCount - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - } - - // двигаем в пустые слоты - for (slot in range) { - if (this[slot].isEmpty) { - val diff = copy.count.coerceAtMost(maxStackSize.coerceAtMost(copy.maxStackSize)) - - if (!simulate) { - val copyToPut = copy.copy() - copyToPut.count = diff - this[slot] = copyToPut - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - - return copy -} - -fun Container.addItem(stack: ItemStack, simulate: Boolean): ItemStack = addItem(stack, 0 until containerSize, simulate) - -inline fun Container.forEach(cap: Capability, consumer: (Pair) -> Unit) { - for (value in iterator(cap)) { - consumer(value) - } -} - -inline fun Container.forEach(cap: Capability, consumer: (ItemStack, T) -> Unit) { - for ((a, b) in iterator(cap)) { - consumer(a, b) - } -} - -inline fun Container.forEachItem(cap: Capability, consumer: (ItemStack) -> Unit) { - for (pair in iterator(cap)) { - consumer(pair.first) - } -} - -inline fun Container.forEachCapability(cap: Capability, consumer: (T) -> Unit) { - for (pair in iterator(cap)) { - consumer(pair.second) - } -} - -inline fun Container.forEach(lambda: (ItemStack) -> Unit) { - for (value in iterator()) { - lambda(value) - } -} - -inline fun Container.forEachNonEmpty(lambda: (ItemStack) -> Unit) { - for (value in iterator().nonEmpty()) { - lambda(value) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt new file mode 100644 index 000000000..12647683a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt @@ -0,0 +1,125 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.ForgeHooks +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.fluid.stream +import ru.dbotthepony.mc.otm.core.isNotEmpty + +interface HandlerFilter { + fun canInsert(slot: Int, stack: ItemStack): Boolean { + return true + } + + fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return true + } + + fun preInsert(slot: Int, stack: ItemStack, simulate: Boolean) {} + fun preExtract(slot: Int, amount: Int, simulate: Boolean) {} + + fun modifyInsertCount(slot: Int, stack: ItemStack, existing: ItemStack, simulate: Boolean): Int { + return stack.count + } + + fun modifyExtractCount(slot: Int, amount: Int, simulate: Boolean): Int { + return amount + } + + fun and(other: HandlerFilter): HandlerFilter { + return object : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return this@HandlerFilter.canInsert(slot, stack) && other.canInsert(slot, stack) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return this@HandlerFilter.canExtract(slot, amount, stack) && other.canExtract(slot, amount, stack) + } + + override fun preInsert(slot: Int, stack: ItemStack, simulate: Boolean) { + this@HandlerFilter.preInsert(slot, stack, simulate) + other.preInsert(slot, stack, simulate) + } + + override fun preExtract(slot: Int, amount: Int, simulate: Boolean) { + this@HandlerFilter.preExtract(slot, amount, simulate) + other.preExtract(slot, amount, simulate) + } + } + } + + object FluidContainers : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.tanks > 0 }.orElse(false) + } + } + + object DrainableFluidContainers : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).map { it.stream().anyMatch { it.isNotEmpty } }.orElse(false) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return !canInsert(slot, stack) + } + } + + object OnlyIn : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return true + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return false + } + } + + object OnlyOut : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return false + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return true + } + } + + object Both : HandlerFilter + + object Dischargeable : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.ENERGY).map { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 }.orElse(false) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.ENERGY).map { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 }.orElse(true) + } + } + + object Chargeable : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.ENERGY).map { it.canReceive() && it.receiveEnergy(Int.MAX_VALUE, true) > 0 }.orElse(false) + } + + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return stack.getCapability(ForgeCapabilities.ENERGY).map { !it.canReceive() || it.receiveEnergy(Int.MAX_VALUE, true) <= 0 }.orElse(true) + } + } + + object ChemicalFuel : HandlerFilter { + override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { + return ForgeHooks.getBurnTime(stack, null) <= 0 + } + + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return ForgeHooks.getBurnTime(stack, null) > 0 + } + } + + object IsPattern : HandlerFilter { + override fun canInsert(slot: Int, stack: ItemStack): Boolean { + return stack.getCapability(MatteryCapability.PATTERN).isPresent + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt new file mode 100644 index 000000000..152b969e5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt @@ -0,0 +1,87 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import java.util.function.Predicate + +// passthrough all default methods to fix Kotlin bug related to implementation delegation not properly working on Java interfaces +// and also to give params proper names +// https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods +interface IContainer : Container { + override fun getMaxStackSize(): Int { + return super.getMaxStackSize() + } + + override fun startOpen(player: Player) { + super.startOpen(player) + } + + override fun stopOpen(player: Player) { + super.stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return super.canPlaceItem(slot, itemStack) + } + + override fun countItem(item: Item): Int { + return super.countItem(item) + } + + override fun hasAnyOf(items: Set): Boolean { + return super.hasAnyOf(items) + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + return super.hasAnyMatching(predicate) + } + + override fun clearContent() + override fun getContainerSize(): Int + override fun isEmpty(): Boolean + override fun getItem(slot: Int): ItemStack + override fun removeItem(slot: Int, amount: Int): ItemStack + override fun removeItemNoUpdate(slot: Int): ItemStack + override fun setItem(slot: Int, itemStack: ItemStack) + override fun setChanged() + + override fun stillValid(player: Player): Boolean + companion object { + fun wrap(container: Container): IContainer { + if (container is IContainer) + return container + else + return object : IContainer, Container by container { + override fun getMaxStackSize(): Int { + return container.getMaxStackSize() + } + + override fun startOpen(player: Player) { + container.startOpen(player) + } + + override fun stopOpen(player: Player) { + container.stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return container.canPlaceItem(slot, itemStack) + } + + override fun countItem(item: Item): Int { + return container.countItem(item) + } + + override fun hasAnyOf(items: Set): Boolean { + return container.hasAnyOf(items) + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + return container.hasAnyMatching(predicate) + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt new file mode 100644 index 000000000..b1d30a73f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt @@ -0,0 +1,217 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntIterable +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.container.util.IContainerSlot +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty + +interface IMatteryContainer : IContainer, Iterable { + fun getSlotFilter(slot: Int): Item? + + /** + * @return whenever the filter was set. Returns false only if container can't be filtered. + */ + fun setSlotFilter(slot: Int, filter: Item? = null): Boolean { + return false + } + + fun setChanged(slot: Int) + + /** + * Iterates over non-empty itemstacks of this container + */ + override fun iterator(): Iterator { + return iterator(true) + } + + /** + * Iterates non-empty slots of this container + */ + fun slotIterator(): Iterator { + return slotIterator(true) + } + + fun iterator(nonEmpty: Boolean): Iterator { + if (nonEmpty) { + return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } + } else { + return (0 until containerSize).iterator().map { this[it] } + } + } + + open class ContainerSlot(override val slot: Int, override val container: IMatteryContainer) : IContainerSlot { + override val isForbiddenForAutomation: Boolean + get() = container.isSlotForbiddenForAutomation(slot) + + override fun getFilter(): Item? { + return container.getSlotFilter(slot) + } + + override fun setFilter(filter: Item?): Boolean { + return container.setSlotFilter(slot, filter) + } + + override fun getMaxStackSize(item: ItemStack): Int { + return container.getMaxStackSize(slot, item) + } + + override fun setChanged() { + container.setChanged(slot) + } + } + + fun slotIterator(nonEmpty: Boolean): Iterator { + if (nonEmpty) { + return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { containerSlot(it) } + } else { + return (0 until containerSize).iterator().map { containerSlot(it) } + } + } + + fun containerSlot(slot: Int): IContainerSlot { + return ContainerSlot(slot, this) + } + + fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null + fun isSlotForbiddenForAutomation(slot: Int) = getSlotFilter(slot) === Items.AIR + + fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean { + return testSlotFilter(slot, itemStack.item) + } + + fun testSlotFilter(slot: Int, item: Item): Boolean { + if (getSlotFilter(slot) == null) { + return true + } else { + return getSlotFilter(slot) === item + } + } + + fun getMaxStackSize(slot: Int, itemStack: ItemStack) = maxStackSize.coerceAtMost(itemStack.maxStackSize) + + private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntIterable, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { + if (stack.isEmpty) + return stack + + // двигаем в одинаковые слоты + var i = slots.intIterator() + + while (i.hasNext()) { + val slot = i.nextInt() + + if ( + (ignoreFilters || !isSlotForbiddenForAutomation(slot)) && + ItemStack.isSameItemSameTags(getItem(slot), stack) && + (ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack)) + ) { + val slotStack = getItem(slot) + val slotLimit = getMaxStackSize(slot, slotStack) + + if (slotStack.count < slotLimit) { + val newCount = (slotStack.count + stack.count).coerceAtMost(slotLimit) + val diff = newCount - slotStack.count + + if (!simulate) { + slotStack.count = newCount + setChanged(slot) + + if (popTime != null) { + slotStack.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) { + return stack + } + } + } + } + + if (!onlyIntoExisting) { + i = slots.intIterator() + + // двигаем в пустые слоты + while (i.hasNext()) { + val slot = i.nextInt() + + if ( + getItem(slot).isEmpty && + (ignoreFilters || !isSlotForbiddenForAutomation(slot)) && + (ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack)) + ) { + val diff = stack.count.coerceAtMost(getMaxStackSize(slot, stack)) + + if (!simulate) { + val copyToPut = stack.copy() + copyToPut.count = diff + setItem(slot, copyToPut) + + if (popTime != null) { + copyToPut.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) { + return stack + } + } + } + } + + return stack + } + + fun addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack { + if (stack.isEmpty) + return stack + + if (ignoreFilters) { + return addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, true) + } else { + var copy = addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, false) + copy = addItem(copy, simulate, false, slots, onlyIntoExisting, popTime, false) + return copy + } + } + + fun handler(filter: HandlerFilter = HandlerFilter.Both): ContainerHandler { + return ContainerHandler(this, filter) + } + + /** + * Unlike [addItem], modifies original [stack] + * + * @return Whenever [stack] was modified + */ + fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean { + if (stack.isEmpty) + return false + + val result = addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters) + + if (result.count != stack.count) { + if (!simulate) { + stack.count = result.count + } + + return true + } + + return false + } + + fun fullyAddItem(stack: ItemStack, slots: IntIterable = slotRange, ignoreFilters: Boolean = false): Boolean { + if (!addItem(stack, true, slots, ignoreFilters).isEmpty) + return false + + return addItem(stack, false, slots, ignoreFilters).isEmpty + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt index 25e41c726..5bd791cb2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt @@ -2,114 +2,19 @@ package ru.dbotthepony.mc.otm.container import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.server.level.ServerPlayer import net.minecraft.tags.TagKey import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraftforge.common.util.INBTSerializable -import net.minecraftforge.network.NetworkEvent -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.ifHas -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.set -import java.util.Arrays -import java.util.LinkedList -import java.util.function.Supplier - -data class ItemFilterSlotPacket(val slotID: Int, val newValue: ItemStack) : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - buff.writeInt(slotID) - buff.writeItemStack(newValue, true) - } - - override fun play(context: Supplier) { - context.get().packetHandled = true - - if (context.get().sender != null) { - context.get().enqueueWork { - playServer(context.get().sender!!) - } - } else { - context.get().enqueueWork { - playClient() - } - } - } - - fun playClient() { - val menu = minecraft.player?.containerMenu as? MatteryMenu ?: throw IllegalStateException("No MatteryMenu is open right now, can't handle ItemFilterSlotReplicaPacket") - menu.filterSlots[slotID].set(newValue) - } - - fun playServer(player: ServerPlayer) { - if (player.isSpectator) { - return - } - - val menu = player.containerMenu as? MatteryMenu ?: return LOGGER.error("No MatteryMenu is open right now, can't handle ItemFilterSlotReplicaPacket from $player") - val slot = menu.filterSlots.getOrNull(slotID) ?: return LOGGER.error("ItemFilterSlotReplicaPacket: unknown slot $slotID from $player in $menu!") - - if (slot.filter?.isLocked == true) - return - - slot.set(newValue) - } - - companion object { - fun read(buff: FriendlyByteBuf): ItemFilterSlotPacket { - return ItemFilterSlotPacket(buff.readInt(), buff.readItem()) - } - - private val LOGGER = LogManager.getLogger() - } -} - -data class ItemFilterNetworkSlot(val slotID: Int, val networkID: Int, var filter: ItemFilter?) { - fun get() = filter?.get(slotID) ?: remote - fun set(value: ItemStack) { - if (filter != null) - filter!![slotID] = value - else - remote = value - } - - private var remote: ItemStack = ItemStack.EMPTY - - fun sendChanges(full: Boolean = false): ItemFilterSlotPacket? { - requireNotNull(filter) { "Invalid side" } - - if (full || !ItemStack.isSameItemSameTags(remote, get())) { - remote = get() - return ItemFilterSlotPacket(networkID, get()) - } - - return null - } -} - -fun interface ItemFilterCallback { - fun invoke(slot: Int?, oldValue: ItemStack?, newValue: ItemStack?) -} - -fun interface ItemFilterFullCallback { - fun invoke(self: ItemFilter, slot: Int?, oldValue: ItemStack?, newValue: ItemStack?) -} +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import java.util.* +import java.util.function.Consumer class ItemFilter( val size: Int, - private val modified: ItemFilterFullCallback? = null -) : INBTSerializable { - constructor(size: Int, modified: ItemFilterCallback?) : - this(size, - modified?.let { - fn -> - return@let ItemFilterFullCallback { _: ItemFilter, slot: Int?, oldValue: ItemStack?, newValue: ItemStack? -> fn.invoke(slot, oldValue, newValue) } - }) - + private val modified: Consumer? = null +) : INBTSerializable { private val filter = Array(size) { ItemStack.EMPTY } private val linkedFilter = LinkedList() @@ -119,7 +24,7 @@ class ItemFilter( set(value) { if (value != field) { field = value - modified?.invoke(this, null, null, null) + modified?.accept(this) } } @@ -127,7 +32,7 @@ class ItemFilter( set(value) { if (value != field) { field = value - modified?.invoke(this, null, null, null) + modified?.accept(this) } } @@ -135,7 +40,7 @@ class ItemFilter( set(value) { if (value != field) { field = value - modified?.invoke(this, null, null, null) + modified?.accept(this) } } @@ -147,27 +52,7 @@ class ItemFilter( Arrays.fill(filter, ItemStack.EMPTY) linkedFilter.clear() - modified?.invoke(this, null, null, null) - } - - fun copyFrom(other: ItemFilter) { - require(other.size == size) { "Size differs (this filter has size of $size, other has size of ${other.size})" } - - linkedFilter.clear() - - for (i in 0 until size) { - filter[i] = other.filter[i] - - if (!filter[i].isEmpty) { - linkedFilter.add(filter[i]) - } - } - - isWhitelist = other.isWhitelist - matchTag = other.matchTag - matchNBT = other.matchNBT - - modified?.invoke(this, null, null, null) + modified?.accept(this) } operator fun set(index: Int, value: ItemStack) { @@ -188,7 +73,7 @@ class ItemFilter( if (!filter[index].isEmpty) linkedFilter.add(filter[index]) - modified?.invoke(this, index, old, filter[index]) + modified?.accept(this) } operator fun get(index: Int): ItemStack = filter[index].copy() @@ -267,7 +152,7 @@ class ItemFilter( if (nbt == null) return - nbt.ifHas("items", ListTag::class.java) { + nbt.map("items") { it: ListTag -> for ((i, value) in it.withIndex()) { if (value is CompoundTag) { filter[i] = ItemStack.of(value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index 54ac9226f..688d803cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -1,84 +1,206 @@ package ru.dbotthepony.mc.otm.container +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntComparators +import it.unimi.dsi.fastutil.ints.IntSpliterator +import it.unimi.dsi.fastutil.objects.ObjectSpliterators import net.minecraft.world.item.ItemStack import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag +import net.minecraft.resources.ResourceLocation import net.minecraft.world.Container -import kotlin.jvm.JvmOverloads import net.minecraft.world.entity.player.Player -import net.minecraft.world.level.block.entity.BlockEntity -import ru.dbotthepony.mc.otm.core.ifHas -import ru.dbotthepony.mc.otm.core.set +import net.minecraft.world.entity.player.StackedContents +import net.minecraft.world.inventory.StackedContentsCompatible +import net.minecraft.world.item.Item +import net.minecraft.world.item.Items +import net.minecraftforge.common.util.INBTSerializable +import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.container.util.IContainerSlot +import ru.dbotthepony.mc.otm.core.addSorted +import ru.dbotthepony.mc.otm.core.collect.any +import ru.dbotthepony.mc.otm.core.collect.count +import ru.dbotthepony.mc.otm.core.collect.emptyIterator +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.util.ItemValueCodec +import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import ru.dbotthepony.mc.otm.network.synchronizer.IField +import java.lang.ref.WeakReference import java.util.* +import java.util.function.Consumer +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.stream.Stream +import java.util.stream.StreamSupport +import kotlin.collections.ArrayList @Suppress("UNUSED") -open class MatteryContainer(val watcher: Runnable, private val size: Int) : Container, Iterable { - constructor(watcher: BlockEntity, size: Int) : this(watcher::setChanged, size) - constructor(size: Int) : this({}, size) +open class MatteryContainer(var listener: ContainerListener, private val size: Int) : IMatteryContainer, INBTSerializable, StackedContentsCompatible { + constructor(watcher: () -> Unit, size: Int) : this({ _, _, _ -> watcher.invoke() }, size) + constructor(size: Int) : this(EmptyListener, size) + + fun interface ContainerListener { + fun setChanged(slot: Int, new: ItemStack, old: ItemStack) + } + + object EmptyListener : ContainerListener { + override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {} + } init { require(size >= 0) { "Invalid container size $size" } } - private var ignoreChangeNotifications = 0 - protected val slots: Array = Array(size) { ItemStack.EMPTY } + private val slots = Array(size) { ItemStack.EMPTY } + private val nonEmptyFlags = BitSet() + private var nonEmptyIndices = IntArrayList() + private var indicesReferenced = false + private data class Update(val slot: Int, val new: ItemStack, val old: ItemStack) + private val queuedUpdates = ArrayList() + private var queueUpdates = false + + private fun cowIndices() { + if (indicesReferenced) { + nonEmptyIndices = IntArrayList(nonEmptyIndices) + indicesReferenced = false + } + } + private val trackedSlots: Array = Array(size) { ItemStack.EMPTY } + private val filters: Array = arrayOfNulls(size) + private var filterSynchronizer: WeakReference>>? = null + + var changeset = 0 + private set + + inner class Cache(private val supplier: (MatteryContainer) -> T) : Supplier { + private var thisChangeset = -1 + private var value: T? = null + private var isDisabled = false + + fun disable() { + isDisabled = true + } + + fun enable() { + isDisabled = false + } + + override fun get(): T { + if (thisChangeset == changeset && !isDisabled) { + return value as T + } + + value = supplier.invoke(this@MatteryContainer) + thisChangeset = changeset + return value as T + } + } + + fun clearSlotFilters() { + Arrays.fill(filters, null) + filterSynchronizer?.get()?.value?.clear() + } + + fun removeFilterSynchronizer() { + filterSynchronizer?.get()?.remove() + filterSynchronizer = null + } + + fun addFilterSynchronizer(synchronizer: FieldSynchronizer): IField> { + check(filterSynchronizer?.get() == null) { "Already has filter synchronizer" } + + val field = synchronizer.Map( + keyCodec = VarIntValueCodec, + valueCodec = ItemValueCodec, + backingMap = Int2ObjectOpenHashMap(), + callback = { + for (change in it) { + change.map({ k, v -> filters[k] = v }, { k -> filters[k] = null }, { Arrays.fill(filters, null) }) + } + } + ) + + for ((i, filter) in filters.withIndex()) { + if (filter != null) { + field.value[i] = filter + } + } + + filterSynchronizer = WeakReference(field) + return field + } + + final override fun setSlotFilter(slot: Int, filter: Item?): Boolean { + if (filters[slot] !== filter) { + filters[slot] = filter + + filterSynchronizer?.get()?.let { + if (filter == null) { + it.value.remove(slot) + } else { + it.value[slot] = filter + } + } + } + + return true + } + + final override fun getSlotFilter(slot: Int) = filters[slot] + final override fun hasSlotFilter(slot: Int) = filters[slot] !== null + final override fun isSlotForbiddenForAutomation(slot: Int) = filters[slot] === Items.AIR + + final override fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean { + return testSlotFilter(slot, itemStack.item) + } + + final override fun testSlotFilter(slot: Int, item: Item): Boolean { + if (filters[slot] == null) { + return true + } else { + return filters[slot] === item + } + } final override fun getContainerSize() = size - - fun startIgnoreUpdates() { - if (++ignoreChangeNotifications == 1) { - startedIgnoringUpdates() - } - } - - fun stopIgnoreUpdates() { - if (--ignoreChangeNotifications == 0) { - stoppedIgnoringUpdates() - } - } - protected open fun startedIgnoringUpdates() {} protected open fun stoppedIgnoringUpdates() {} - private fun deserializeNBT(tag: CompoundTag?) { - Arrays.fill(slots, ItemStack.EMPTY) - - if (tag == null) { - setChanged() - return + private fun deserializeNBT(tag: CompoundTag) { + // нам не интересен размер + tag.map("items") { it: ListTag -> + deserializeNBT(it) } - // нам не интересен размер - tag.ifHas("items", ListTag::class.java) { + tag.map("filters") { it: ListTag -> + val map = filterSynchronizer?.get() + for (i in 0 until it.size.coerceAtMost(size)) { - slots[i] = ItemStack.of(it[i] as CompoundTag) + val nbt = it[i] as CompoundTag + val index = nbt.getInt("slotIndex") + filters[index] = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(nbt.getString("filter")) ?: continue) + + if (filters[index] != null) { + map?.value?.put(index, filters[index]!!) + } } } setChanged() } - private fun deserializeNBT(tag: ListTag?) { - Arrays.fill(slots, ItemStack.EMPTY) - - if (tag == null) { - setChanged() - return - } - - var isIndexed = true - - for (i in 0 until tag.size.coerceAtMost(size)) { - if (!(tag[i] as CompoundTag).contains("slotIndex")) { - isIndexed = false - break - } - } - - if (isIndexed) { + private fun deserializeNBT(tag: ListTag) { + if (tag.all { (it as CompoundTag).contains("slotIndex") }) { val freeSlots = IntAVLTreeSet() for (i in 0 until size) @@ -103,180 +225,81 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont slots[i] = ItemStack.of(tag[i] as CompoundTag) } } - - setChanged() } - fun deserializeNBT(tag: Tag?) { + override fun deserializeNBT(tag: Tag?) { + Arrays.fill(slots, ItemStack.EMPTY) + Arrays.fill(filters, null) + nonEmptyFlags.clear() + nonEmptyIndices = IntArrayList() + + filterSynchronizer?.get()?.value?.clear() + when (tag) { is CompoundTag -> deserializeNBT(tag) - is ListTag -> deserializeNBT(tag) - else -> { - Arrays.fill(slots, ItemStack.EMPTY) + is ListTag -> { + deserializeNBT(tag) setChanged() } + + else -> setChanged() } } - open fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - if (ignoreChangeNotifications == 0) watcher.run() + private fun internalSetChanged(slot: Int, new: ItemStack, old: ItemStack) { + if (queueUpdates) { + queuedUpdates.add(Update(slot, new, old)) + } else { + setChanged(slot, new, old) + } } - fun serializeNBT(): ListTag { - return ListTag().also { - for ((i, item) in slots.withIndex()) { - if (!item.isEmpty) { - it.add(item.serializeNBT().also { - it["slotIndex"] = i - }) + private fun runUpdates() { + for ((slot, new, old) in queuedUpdates) { + setChanged(slot, new, old) + } + + queuedUpdates.clear() + } + + protected open fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { + listener.setChanged(slot, new, old) + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["items"] = ListTag().also { + for ((i, item) in slots.withIndex()) { + if (!item.isEmpty) { + it.add(item.serializeNBT().also { + it["slotIndex"] = i + }) + } + } + } + + it["filters"] = ListTag().also { + for ((i, filter) in filters.withIndex()) { + if (filter != null) { + it.add(CompoundTag().also { + it["filter"] = filter.registryName!!.toString() + it["slotIndex"] = i + }) + } } } } } - fun hasEmptySlot(): Boolean { - for (i in 0 until size) { - if (this[i].isEmpty) { - return true - } - } - - return false + final override fun isEmpty(): Boolean { + return nonEmptyIndices.isEmpty } - fun handler( - insert_validator: (slot: Int, stack: ItemStack) -> Boolean, - extract_validator: (slot: Int, amount: Int, stack: ItemStack) -> Boolean - ): MatteryContainerHandler { - return MatteryContainerHandler(this, object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack) = insert_validator(slot, stack) - override fun canExtract(slot: Int, amount: Int, stack: ItemStack) = extract_validator(slot, amount, stack) - }) - } + @Suppress("nothing_to_inline") + inline operator fun get(slot: Int) = getItem(slot) - fun handler( - filter: MatteryContainerHooks - ): MatteryContainerHandler { - return MatteryContainerHandler(this, filter) - } - - fun handler(insert_validator: (Int, ItemStack) -> Boolean): MatteryContainerHandler { - return MatteryContainerHandler(this, object : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack) = insert_validator(slot, stack) - override fun canExtract(slot: Int, amount: Int, stack: ItemStack) = false - }) - } - - fun handler(): MatteryContainerHandler { - return MatteryContainerHandler(this) - } - - open fun getMaxStackSize(slot: Int) = maxStackSize - - open fun getMaxStackSizeWithItem(slot: Int): Int { - val item = this[slot] - - if (!item.isEmpty) { - return getMaxStackSize(slot).coerceAtMost(item.maxStackSize) - } - - return getMaxStackSize(slot) - } - - @JvmOverloads - fun addItem(stack: ItemStack, range: IntRange, simulate: Boolean = false, onlyIntoExisting: Boolean = false, popTime: Int? = null): ItemStack { - if (range.last >= size || range.first < 0) - throw IllegalArgumentException("Invalid range: $range") - - if (stack.isEmpty) - return stack - - val copy = stack.copy() - - // двигаем в одинаковые слоты - for (slot in range) { - if (ItemStack.isSameItemSameTags(slots[slot], copy)) { - val slotStack = slots[slot] - val slotLimit = getMaxStackSize(slot).coerceAtMost(slotStack.maxStackSize) - - if (slotStack.count < slotLimit) { - val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) - val diff = newCount - slotStack.count - - if (!simulate) { - val old = slotStack.copy() - slotStack.count = newCount - trackedSlots[slot] = slotStack.copy() - setChanged(slot, slotStack, old) - - if (popTime != null) { - slotStack.popTime = popTime - } - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - } - - if (!onlyIntoExisting) { - // двигаем в пустые слоты - for (slot in range) { - if (slots[slot].isEmpty) { - val diff = copy.count.coerceAtMost(getMaxStackSize(slot).coerceAtMost(copy.maxStackSize)) - - if (!simulate) { - val copyToPut = copy.copy() - copyToPut.count = diff - this[slot] = copyToPut - - if (popTime != null) { - copyToPut.popTime = popTime - } - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - } - - return copy - } - - fun addItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null): ItemStack { - return addItem(stack, 0 until size, simulate, onlyIntoExisting = onlyIntoExisting, popTime = popTime) - } - - @JvmOverloads - fun fullyAddItem(stack: ItemStack, start: Int = 0, end: Int = size - 1): Boolean { - return fullyAddItem(stack, start .. end) - } - - fun fullyAddItem(stack: ItemStack, range: IntRange): Boolean { - if (!addItem(stack, range, true).isEmpty) - return false - - return addItem(stack, range, false).isEmpty - } - - override fun isEmpty(): Boolean { - for (stack in slots) - if (!stack.isEmpty) - return false - - return true - } - - operator fun get(slot: Int) = getItem(slot) - operator fun set(slot: Int, stack: ItemStack) = setItem(slot, stack) + @Suppress("nothing_to_inline") + inline operator fun set(slot: Int, stack: ItemStack) = setItem(slot, stack) operator fun contains(other: ItemStack): Boolean { for (i in 0 until size) { @@ -291,10 +314,25 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont final override fun getItem(slot: Int): ItemStack { val item = slots[slot] - if (item.isEmpty) + if (item.isEmpty) { + if (nonEmptyFlags[slot]) { + setChanged(slot) + } + return ItemStack.EMPTY - else + } else { + if (!nonEmptyFlags[slot]) { + setChanged(slot) + } + return item + } + } + + override fun fillStackedContents(contents: StackedContents) { + for (item in iterator()) { + contents.accountStack(item) + } } final override fun removeItem(slot: Int, amount: Int): ItemStack { @@ -304,7 +342,9 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont val old = slots[slot].copy() val split = slots[slot].split(amount) trackedSlots[slot] = slots[slot].copy() - setChanged(slot, if (slots[slot].isEmpty) ItemStack.EMPTY else slots[slot], old) + changeset++ + updateEmptyFlag(slot) + internalSetChanged(slot, if (slots[slot].isEmpty) ItemStack.EMPTY else slots[slot], old) return split } @@ -313,50 +353,210 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont val old = slots[slot] slots[slot] = ItemStack.EMPTY trackedSlots[slot] = ItemStack.EMPTY + + if (old.isNotEmpty) { + updateEmptyFlag(slot) + changeset++ + } + return old } - final override fun setItem(slot: Int, stack: ItemStack) { - if (slots[slot].isEmpty && stack.isEmpty || stack === slots[slot]) + final override fun setItem(slot: Int, itemStack: ItemStack) { + if (slots[slot].isEmpty && itemStack.isEmpty || itemStack === slots[slot]) return val old = slots[slot] - slots[slot] = stack - trackedSlots[slot] = stack.copy() - setChanged(slot, stack, old) + slots[slot] = if (itemStack.isEmpty) ItemStack.EMPTY else itemStack + trackedSlots[slot] = if (itemStack.isEmpty) ItemStack.EMPTY else itemStack.copy() + + updateEmptyFlag(slot) + changeset++ + internalSetChanged(slot, itemStack, old) } final override fun setChanged() { - for (slot in 0 until size) { - setChanged(slot) + queueUpdates = true + + try { + for (slot in 0 until size) { + setChanged(slot) + } + + runUpdates() + } finally { + queuedUpdates.clear() + queueUpdates = false } } - fun setChanged(slot: Int) { + final override fun setChanged(slot: Int) { if (!slots[slot].equals(trackedSlots[slot], false)) { - setChanged(slot, slots[slot], trackedSlots[slot]) trackedSlots[slot] = slots[slot].copy() + updateEmptyFlag(slot) + changeset++ + internalSetChanged(slot, slots[slot], trackedSlots[slot]) // mojang соси))0)0))0)))))0) } } + private fun updateEmptyFlag(slot: Int) { + if (slots[slot].isEmpty) { + if (nonEmptyFlags[slot]) { + nonEmptyFlags[slot] = false + cowIndices() + nonEmptyIndices.rem(slot) + } + } else { + if (!nonEmptyFlags[slot]) { + nonEmptyFlags[slot] = true + cowIndices() + nonEmptyIndices.addSorted(slot, IntComparators.NATURAL_COMPARATOR) + } + } + } + override fun stillValid(player: Player): Boolean { return true } final override fun clearContent() { + nonEmptyFlags.clear() + nonEmptyIndices = IntArrayList() + + Arrays.fill(trackedSlots, ItemStack.EMPTY) + for (slot in 0 until size) { if (!slots[slot].isEmpty) { val old = slots[slot] slots[slot] = ItemStack.EMPTY - setChanged(slot, ItemStack.EMPTY, old) + internalSetChanged(slot, ItemStack.EMPTY, old) + } + } + } + + private inner class Iterator : kotlin.collections.Iterator { + init { + indicesReferenced = true + } + + private val parent = nonEmptyIndices.intIterator() + private var lastIndex = -1 + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): ItemStack { + lastIndex = parent.nextInt() + return getItem(lastIndex) + } + } + + private inner class Spliterator(private val parent: IntSpliterator) : java.util.Spliterator { + override fun tryAdvance(action: Consumer): Boolean { + return parent.tryAdvance { + action.accept(getItem(it)) } } - Arrays.fill(trackedSlots, ItemStack.EMPTY) + override fun trySplit(): java.util.Spliterator? { + return parent.trySplit()?.let(::Spliterator) + } + + override fun estimateSize(): Long { + return parent.estimateSize() + } + + override fun characteristics(): Int { + return parent.characteristics() + } } - final override fun iterator(): MutableIterator { - return ContainerIterator(this) + final override fun iterator(): kotlin.collections.Iterator { + if (isEmpty) { + return emptyIterator() + } + + return Iterator() + } + + final override fun iterator(nonEmpty: Boolean): kotlin.collections.Iterator { + if (!nonEmpty) { + return (0 until size).iterator().map { slots[it] } + } else if (isEmpty) { + return emptyIterator() + } else { + return Iterator() + } + } + + private inner class Slot(override val slot: Int) : IContainerSlot { + override val container: Container + get() = this@MatteryContainer + + override val isForbiddenForAutomation: Boolean + get() = isSlotForbiddenForAutomation(slot) + + override fun getFilter(): Item? { + return getSlotFilter(slot) + } + + override fun setFilter(filter: Item?): Boolean { + return setSlotFilter(slot, filter) + } + + override fun getMaxStackSize(item: ItemStack): Int { + return getMaxStackSize(slot, item) + } + + override fun setChanged() { + setChanged(slot) + } + } + + final override fun slotIterator(): kotlin.collections.Iterator { + indicesReferenced = true + return nonEmptyIndices.iterator().map { Slot(it) } + } + + final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator { + if (!nonEmpty) { + return (0 until size).iterator().map { Slot(it) } + } else if (isEmpty) { + return emptyIterator() + } else { + indicesReferenced = true + return nonEmptyIndices.iterator().map { Slot(it) } + } + } + + final override fun containerSlot(slot: Int): IContainerSlot { + return Slot(slot) + } + + final override fun countItem(item: Item): Int { + return iterator().filter { it.item == item }.count().toInt() + } + + final override fun hasAnyOf(items: Set): Boolean { + return iterator().any { it.item in items } + } + + final override fun hasAnyMatching(predicate: Predicate): Boolean { + return iterator().any(predicate) + } + + final override fun spliterator(): java.util.Spliterator { + if (isEmpty) { + return ObjectSpliterators.emptySpliterator() + } + + indicesReferenced = true + return Spliterator(nonEmptyIndices.intSpliterator()) + } + + fun stream(): Stream { + return StreamSupport.stream(spliterator(), false) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.kt deleted file mode 100644 index f37e9a581..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.kt +++ /dev/null @@ -1,130 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.util.LazyOptional -import net.minecraftforge.items.IItemHandler - -interface MatteryContainerHooks { - fun canInsert(slot: Int, stack: ItemStack): Boolean { - return true - } - - fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return true - } - - fun preInsert(slot: Int, stack: ItemStack, simulate: Boolean) {} - fun preExtract(slot: Int, amount: Int, simulate: Boolean) {} -} - -class MatteryContainerHandler @JvmOverloads internal constructor( - private val container: MatteryContainer, - private val hooks: MatteryContainerHooks = Both, -) : IItemHandler { - object OnlyIn : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return true - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - } - - object OnlyOut : MatteryContainerHooks { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return true - } - } - - object Both : MatteryContainerHooks - - private var handler = LazyOptional.of { this } - - fun get(): LazyOptional { - return handler - } - - fun invalidate() { - handler.invalidate() - } - - fun revive() { - handler = LazyOptional.of { this } - } - - override fun getSlots() = container.containerSize - override fun getStackInSlot(slot: Int) = container[slot] - - override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { - if (!hooks.canInsert(slot, stack)) - return stack - - hooks.preInsert(slot, stack, simulate) - val localStack = container[slot] - - if (localStack.isEmpty) { - if (!simulate) { - val copy = stack.copy() - container.setChanged(slot, copy, ItemStack.EMPTY) - container.setItem(slot, copy) - } - - return ItemStack.EMPTY - } else if (localStack.isStackable && localStack.maxStackSize > localStack.count && ItemStack.isSameItemSameTags(localStack, stack)) { - val newCount = Math.min(localStack.maxStackSize, localStack.count + stack.count) - val diff = newCount - localStack.count - - if (diff != 0) { - if (!simulate) { - val copy = localStack.copy() - localStack.grow(diff) - container.setChanged(slot, localStack, copy) - } - - val copy = stack.copy() - copy.shrink(diff) - return copy - } - } - - return stack - } - - override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { - if (amount == 0) - return ItemStack.EMPTY - - require(amount >= 0) { "Can not extract negative amount of items" } - - hooks.preExtract(slot, amount, simulate) - - val localStack = container.getItem(slot) - if (localStack.isEmpty) return ItemStack.EMPTY - if (!hooks.canExtract(slot, amount, localStack)) return ItemStack.EMPTY - - val minimal = Math.min(amount, localStack.count) - val copy = localStack.copy() - copy.count = minimal - - if (!simulate) { - val copy = localStack.copy() - localStack.shrink(minimal) - container.setChanged(slot, localStack, copy) - } - - return copy - } - - override fun getSlotLimit(slot: Int): Int { - return container.maxStackSize - } - - override fun isItemValid(slot: Int, stack: ItemStack): Boolean { - return hooks.canInsert(slot, stack) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt new file mode 100644 index 000000000..27baab0d0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt @@ -0,0 +1,114 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.Tag +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.entity.player.StackedContents +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.util.INBTSerializable +import java.util.function.Predicate + +object DummyMenu : AbstractContainerMenu(null, 0) { + override fun quickMoveStack(pPlayer: Player, pIndex: Int): ItemStack { + return ItemStack.EMPTY + } + + override fun stillValid(pPlayer: Player): Boolean { + return false + } +} + +// урод +open class MatteryCraftingContainer private constructor(private val listener: MatteryContainer.ContainerListener, width: Int, height: Int, private val parent: MatteryContainer) : CraftingContainer(DummyMenu, width, height), IMatteryContainer by parent, INBTSerializable { + constructor(width: Int, height: Int) : this(MatteryContainer.EmptyListener, width, height) + constructor(listener: () -> Unit, width: Int, height: Int) : this({ _, _, _ -> listener.invoke() }, width, height, MatteryContainer(width * height)) + constructor(listener: MatteryContainer.ContainerListener, width: Int, height: Int) : this(listener, width, height, MatteryContainer(width * height)) + + init { + parent.listener = MatteryContainer.ContainerListener { slot, new, old -> this@MatteryCraftingContainer.setChanged(slot, new, old) } + } + + protected open fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { + listener.setChanged(slot, new, old) + } + + override fun serializeNBT(): CompoundTag { + return parent.serializeNBT() + } + + override fun deserializeNBT(nbt: CompoundTag?) { + parent.deserializeNBT(nbt) + } + + override fun clearContent() { + parent.clearContent() + } + + override fun getContainerSize(): Int { + return parent.getContainerSize() + } + + override fun isEmpty(): Boolean { + return parent.isEmpty() + } + + override fun getItem(p_39332_: Int): ItemStack { + return parent.getItem(p_39332_) + } + + override fun removeItem(p_39334_: Int, p_39335_: Int): ItemStack { + return parent.removeItem(p_39334_, p_39335_) + } + + override fun removeItemNoUpdate(p_39344_: Int): ItemStack { + return parent.removeItemNoUpdate(p_39344_) + } + + override fun setItem(p_39337_: Int, p_39338_: ItemStack) { + parent.setItem(p_39337_, p_39338_) + } + + override fun setChanged() { + parent.setChanged() + } + + override fun stillValid(p_39340_: Player): Boolean { + return parent.stillValid(p_39340_) + } + + override fun fillStackedContents(p_39342_: StackedContents) { + parent.fillStackedContents(p_39342_) + } + + override fun getMaxStackSize(): Int { + return parent.getMaxStackSize() + } + + override fun startOpen(p_18955_: Player) { + parent.startOpen(p_18955_) + } + + override fun stopOpen(p_18954_: Player) { + parent.stopOpen(p_18954_) + } + + override fun canPlaceItem(p_18952_: Int, p_18953_: ItemStack): Boolean { + return parent.canPlaceItem(p_18952_, p_18953_) + } + + override fun countItem(p_18948_: Item): Int { + return parent.countItem(p_18948_) + } + + override fun hasAnyOf(p_18950_: Set): Boolean { + return parent.hasAnyOf(p_18950_) + } + + override fun hasAnyMatching(p_216875_: Predicate): Boolean { + return parent.hasAnyMatching(p_216875_) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt new file mode 100644 index 000000000..93c85355e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack + +class ShadowContainer(private val parent: Container) : IContainer by IContainer.wrap(parent) { + private val shadowed = Int2ObjectArrayMap(0) + + override fun clearContent() { + shadowed.clear() + parent.clearContent() + } + + override fun isEmpty(): Boolean { + return parent.isEmpty && shadowed.isEmpty() + } + + override fun getItem(slot: Int): ItemStack { + return shadowed[slot] ?: parent.getItem(slot) + } + + override fun removeItem(slot: Int, count: Int): ItemStack { + val shadow = shadowed[slot] ?: return parent.removeItem(slot, count) + val copy = shadow.copyWithCount(shadow.count.coerceAtLeast(count)) + shadow.split(count) + if (shadow.isEmpty) shadowed[slot] = ItemStack.EMPTY + return copy + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + shadowed[slot] ?: return parent.removeItemNoUpdate(slot) + val old = shadowed[slot] + shadowed[slot] = ItemStack.EMPTY + return old!! + } + + override fun setItem(slot: Int, item: ItemStack) { + shadowed[slot] = item + } + + companion object { + fun shadow(container: Container, slot: Int, itemStack: ItemStack): Container { + return ShadowContainer(container).also { it[slot] = itemStack } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt new file mode 100644 index 000000000..cd8c011c6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt @@ -0,0 +1,68 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.entity.player.Player +import net.minecraft.world.entity.player.StackedContents +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.toList + +class ShadowCraftingContainer private constructor(private val parent: CraftingContainer, private val shadow: ShadowContainer) : CraftingContainer(DummyMenu, parent.width, parent.height), IContainer by shadow { + constructor(parent: CraftingContainer) : this(parent, ShadowContainer(parent)) + + override fun fillStackedContents(contents: StackedContents) { + for (item in iterator()) { + contents.accountStack(item) + } + } + + override fun getWidth(): Int { + return parent.width + } + + override fun getHeight(): Int { + return parent.height + } + + override fun clearContent() { + shadow.clearContent() + } + + override fun getContainerSize(): Int { + return shadow.getContainerSize() + } + + override fun isEmpty(): Boolean { + return shadow.isEmpty() + } + + override fun getItem(p_39332_: Int): ItemStack { + return shadow.getItem(p_39332_) + } + + override fun removeItem(p_39334_: Int, p_39335_: Int): ItemStack { + return shadow.removeItem(p_39334_, p_39335_) + } + + override fun removeItemNoUpdate(p_39344_: Int): ItemStack { + return shadow.removeItemNoUpdate(p_39344_) + } + + override fun setItem(p_39337_: Int, p_39338_: ItemStack) { + shadow.setItem(p_39337_, p_39338_) + } + + override fun setChanged() { + shadow.setChanged() + } + + override fun stillValid(p_39340_: Player): Boolean { + return shadow.stillValid(p_39340_) + } + + companion object { + fun shadow(container: CraftingContainer, slot: Int, itemStack: ItemStack): CraftingContainer { + return ShadowCraftingContainer(container).also { it[slot] = itemStack } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt new file mode 100644 index 000000000..35abc5839 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt @@ -0,0 +1,69 @@ +package ru.dbotthepony.mc.otm.container + +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues +import ru.dbotthepony.mc.otm.config.VerboseEnergyBalanceValues +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.math.Decimal +import kotlin.math.pow + +open class UpgradeContainer(slotCount: Int, open val allowedUpgrades: Set = UpgradeType.ALL, listener: ContainerListener = EmptyListener) : MatteryContainer(listener, slotCount), IMatteryUpgrade { + constructor(listener: () -> Unit, slotCount: Int, allowedUpgrades: Set) : this(slotCount, allowedUpgrades, { _, _, _ -> listener.invoke() }) + + final override val upgradeTypes: Set + get() = setOf() + + protected fun decimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal { + return iterator() + .map { it.getCapability(MatteryCapability.UPGRADE).map(fn).orElse(Decimal.ZERO).moreThanZero() * it.count } + .reduce(Decimal.ZERO, reducer) + } + + override val speedBonus: Double + get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.speedBonus }.orElse(0.0) * it.count }.reduce(0.0) { a, b -> a + b } + override val processingItems: Int + get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.processingItems }.orElse(0).coerceAtLeast(0) * it.count }.reduce(0) { a, b -> a + b } + override val energyStorageFlat: Decimal + get() = decimals(IMatteryUpgrade::energyStorageFlat, Decimal::plus) + override val energyStorage: Decimal + get() = decimals(IMatteryUpgrade::energyStorage, Decimal::plus) + override val matterStorageFlat: Decimal + get() = decimals(IMatteryUpgrade::matterStorageFlat, Decimal::plus) + override val matterStorage: Decimal + get() = decimals(IMatteryUpgrade::matterStorage, Decimal::plus) + override val energyConsumed: Decimal + get() = decimals(IMatteryUpgrade::energyConsumed, Decimal::plus) + override val failureMultiplier: Double + get() = iterator().map { it.getCapability(MatteryCapability.UPGRADE).map { it.failureMultiplier }.orElse(1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b } + override val energyThroughputFlat: Decimal + get() = decimals(IMatteryUpgrade::energyThroughputFlat, Decimal::plus) + override val energyThroughput: Decimal + get() = decimals(IMatteryUpgrade::energyThroughput, Decimal::plus) + + fun transform(values: EnergyBalanceValues): EnergyBalanceValues { + return object : EnergyBalanceValues { + override val energyCapacity: Decimal + get() = values.energyCapacity * (energyStorage + Decimal.ONE) + energyStorageFlat + override val energyThroughput: Decimal + get() = values.energyThroughput * (this@UpgradeContainer.energyThroughput + Decimal.ONE) + energyThroughputFlat + } + } + + fun transform(values: VerboseEnergyBalanceValues): VerboseEnergyBalanceValues { + return object : VerboseEnergyBalanceValues { + override val energyCapacity: Decimal + get() = values.energyCapacity * (energyStorage + Decimal.ONE) + energyStorageFlat + override val maxEnergyReceive: Decimal + get() = values.maxEnergyReceive * (energyThroughput + Decimal.ONE) + energyThroughputFlat + override val maxEnergyExtract: Decimal + get() = values.maxEnergyExtract * (energyThroughput + Decimal.ONE) + energyThroughputFlat + } + } + + fun matterCapacity(value: () -> Decimal): () -> Decimal { + return { value.invoke() * (matterStorage + Decimal.ONE) + matterStorageFlat } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ContainerSpliterator.kt similarity index 72% rename from src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ContainerSpliterator.kt index 305792410..e3f7c6216 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ContainerSpliterator.kt @@ -1,11 +1,12 @@ -package ru.dbotthepony.mc.otm.container +package ru.dbotthepony.mc.otm.container.util import it.unimi.dsi.fastutil.objects.ObjectSpliterator import it.unimi.dsi.fastutil.objects.ObjectSpliterators import net.minecraft.world.Container import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.AwareItemStack -import ru.dbotthepony.mc.otm.core.ContainerItemStackEntry +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.collect.AwareItemStack +import ru.dbotthepony.mc.otm.core.collect.ContainerItemStackEntry import java.util.stream.Stream import java.util.stream.StreamSupport @@ -47,7 +48,7 @@ class AwareContainerSpliterator(private val container: Container, offset: Int = } } -fun Container.spliterator() = ContainerSpliterator(this) -fun Container.awareSpliterator() = AwareContainerSpliterator(this) -fun Container.stream(): Stream = StreamSupport.stream(spliterator(), false) -fun Container.awareStream(): Stream = StreamSupport.stream(awareSpliterator(), false) +fun Container.spliterator(offset: Int = 0) = ContainerSpliterator(this, offset) +fun Container.awareSpliterator(offset: Int = 0) = AwareContainerSpliterator(this, offset) +fun Container.stream(offset: Int = 0): Stream = StreamSupport.stream(spliterator(offset), false) +fun Container.awareStream(offset: Int = 0): Stream = StreamSupport.stream(awareSpliterator(offset), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt similarity index 87% rename from src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt index 4482d0efc..9d7c27669 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemHandlerSpliterator.kt @@ -1,11 +1,13 @@ -package ru.dbotthepony.mc.otm.container +package ru.dbotthepony.mc.otm.container.util import it.unimi.dsi.fastutil.objects.ObjectSpliterator import it.unimi.dsi.fastutil.objects.ObjectSpliterators import net.minecraft.world.item.ItemStack import net.minecraftforge.items.IItemHandler -import ru.dbotthepony.mc.otm.core.AwareItemStack -import ru.dbotthepony.mc.otm.core.ItemHandlerItemStackEntry +import ru.dbotthepony.mc.otm.core.collect.AwareItemStack +import ru.dbotthepony.mc.otm.core.collect.ItemHandlerItemStackEntry +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.isNotEmpty import java.util.* import java.util.stream.Stream import java.util.stream.StreamSupport @@ -48,7 +50,7 @@ class ItemHandlerAwareSpliterator(private val handler: IItemHandler, offset: Int } } -fun IItemHandler.iterator(): Iterator = Spliterators.iterator(spliterator()) +fun IItemHandler.iterator(): Iterator = Spliterators.iterator(spliterator()).filter { it.isNotEmpty } fun IItemHandler.spliterator(): Spliterator = ItemHandlerSpliterator(this) fun IItemHandler.awareSpliterator(): Spliterator = ItemHandlerAwareSpliterator(this) fun IItemHandler.stream(): Stream = StreamSupport.stream(spliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt new file mode 100644 index 000000000..5d635e5af --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/ItemStackHashStrategy.kt @@ -0,0 +1,15 @@ +package ru.dbotthepony.mc.otm.container.util + +import it.unimi.dsi.fastutil.Hash +import net.minecraft.world.item.ItemStack + +object ItemStackHashStrategy : Hash.Strategy { + override fun equals(a: ItemStack?, b: ItemStack?): Boolean { + return a === b || a != null && b != null && ItemStack.isSameItemSameTags(a, b) + } + + override fun hashCode(o: ItemStack?): Int { + o ?: return 0 + return o.item.hashCode().xor(o.tag.hashCode()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt new file mode 100644 index 000000000..e2cb1ed31 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt @@ -0,0 +1,118 @@ +package ru.dbotthepony.mc.otm.container.util + +import net.minecraft.world.Container +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.container.IMatteryContainer +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty + +/** + * While this somewhat similar to [net.minecraft.world.inventory.Slot], this slot is not meant + * for Player interaction. + */ +interface IContainerSlot : GetterSetter { + val slot: Int + val container: Container + + operator fun component1() = slot + operator fun component2() = item + + fun getMaxStackSize(item: ItemStack = this.item): Int { + return container.maxStackSize + } + + val isForbiddenForAutomation: Boolean get() { + return getFilter() === Items.AIR + } + + fun getFilter(): Item? + + /** + * @return whenever the filter was set. Returns false only if container can't be filtered. + */ + fun setFilter(filter: Item? = null): Boolean + + val hasFilter: Boolean + get() = getFilter() != null + + fun remove() { + container[slot] = ItemStack.EMPTY + } + + fun remove(count: Int): ItemStack { + return container.removeItem(slot, count) + } + + override fun get(): ItemStack { + return container[slot] + } + + override fun accept(t: ItemStack) { + container[slot] = t + } + + fun setChanged() { + container.setChanged() + } + + var item: ItemStack + get() = container[slot] + set(value) { container[slot] = value } + + val isEmpty: Boolean + get() = container[slot].isEmpty + + val isNotEmpty: Boolean + get() = container[slot].isNotEmpty +} + +class ContainerSlot(override val slot: Int, override val container: Container) : IContainerSlot { + init { + require(slot in 0 until container.containerSize) { "Slot out of bounds: $slot (container: $container with size ${container.containerSize})" } + } + + override fun getFilter(): Item? { + return null + } + + override fun setFilter(filter: Item?): Boolean { + return false + } +} + +fun Container.containerSlot(slot: Int): IContainerSlot { + if (this is IMatteryContainer) { + return containerSlot(slot) + } else { + return ContainerSlot(slot, this) + } +} + +operator fun Container.iterator() = iterator(true) + +fun Container.iterator(nonEmpty: Boolean): Iterator { + if (this is IMatteryContainer) { + return iterator(nonEmpty) + } else if (nonEmpty) { + return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } + } else { + return (0 until containerSize).iterator().map { this[it] } + } +} + +fun Container.slotIterator(nonEmpty: Boolean = true): Iterator { + if (this is IMatteryContainer) { + return slotIterator(nonEmpty) + } else if (nonEmpty) { + return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { ContainerSlot(it, this) } + } else { + return (0 until containerSize).iterator().map { ContainerSlot(it, this) } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ByteSupplier.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ByteSupplier.kt new file mode 100644 index 000000000..77714b552 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ByteSupplier.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.core + +interface ByteSupplier { + fun getAsByte(): Byte +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CapabilityIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/CapabilityIterators.kt deleted file mode 100644 index a04c6358b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CapabilityIterators.kt +++ /dev/null @@ -1,59 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.world.Container -import net.minecraftforge.common.capabilities.Capability -import net.minecraftforge.common.capabilities.ICapabilityProvider -import ru.dbotthepony.mc.otm.container.iterator - -open class CapabilityIterator

(protected open val parent: Iterator

, private val cap: Capability) : Iterator> { - private var provider: P? = null - private var capability: T? = null - private var searched = false - - private fun search() { - searched = true - - if (provider != null) { - return - } - - for (provider in parent) { - provider.getCapability(cap).ifPresentK { - this.provider = provider - capability = it - return - } - } - } - - override fun hasNext(): Boolean { - if (!searched) { - search() - } - - return provider != null - } - - override fun next(): Pair { - if (!searched) { - search() - } - - val provider = provider ?: throw IllegalStateException("No next element") - val capability = capability ?: throw IllegalStateException("No next element") - this.provider = null - this.capability = null - this.searched = false - return provider to capability - } -} - -class MutableCapabilityIterator

(override val parent: MutableIterator

, cap: Capability) : CapabilityIterator(parent, cap), MutableIterator> { - override fun remove() { - parent.remove() - } -} - -fun Container.iterator(cap: Capability) = CapabilityIterator(iterator().nonEmpty(), cap) -fun

Iterator

.filtered(cap: Capability) = CapabilityIterator(this, cap) -fun

MutableIterator

.filtered(cap: Capability) = MutableCapabilityIterator(this, cap) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSet.kt deleted file mode 100644 index 0b75642e9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSet.kt +++ /dev/null @@ -1,62 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -class ConditionalSet : AbstractSet { - // method without boxing - fun interface Condition { - fun check(): Boolean - } - - private val getters: Array> - - constructor(vararg getters: Pair) : super() { - this.getters = Array(getters.size) { getters[it] } - } - - constructor(getters: List>) : super() { - this.getters = Array(getters.size) { getters[it] } - } - - override val size: Int get() { - var i = 0 - - for (pair in getters) { - if (pair.first.check()) { - i++ - } - } - - return i - } - - override fun iterator(): Iterator { - return object : Iterator { - val parent = getters.iterator() - private var pair: Pair? = null - - private fun search() { - for (pair in parent) { - if (pair.first.check()) { - this.pair = pair - return - } - } - - this.pair = null - } - - init { - search() - } - - override fun hasNext(): Boolean { - return pair != null - } - - override fun next(): T { - val pair = pair ?: throw NoSuchElementException() - search() - return pair.second - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/DataStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/DataStreams.kt deleted file mode 100644 index 77d78a59d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/DataStreams.kt +++ /dev/null @@ -1,315 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.world.item.ItemStack -import java.io.DataInputStream -import java.io.DataOutputStream -import java.io.InputStream -import java.io.OutputStream -import java.util.* -import java.util.function.Predicate -import kotlin.NoSuchElementException -import kotlin.math.absoluteValue - -/** - * Represents value which can be encoded onto or decoded from stream. - * - * Also provides [copy] and [compare] methods - */ -interface IStreamCodec { - fun read(stream: DataInputStream): V - fun write(stream: DataOutputStream, value: V) - - /** - * if value is immutable, return it right away - */ - fun copy(value: V): V - - /** - * Optional equality check override. Utilized to determine whenever e.g. network value is different from new value - * - * By default uses [Any.equals] - */ - fun compare(a: V, b: V): Boolean { - return a == b - } -} - -class StreamCodec( - private val reader: (stream: DataInputStream) -> V, - private val writer: (stream: DataOutputStream, value: V) -> Unit, - private val copier: ((value: V) -> V) = { it }, - private val comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } -) : IStreamCodec { - override fun read(stream: DataInputStream): V { - return reader.invoke(stream) - } - - override fun write(stream: DataOutputStream, value: V) { - writer.invoke(stream, value) - } - - override fun copy(value: V): V { - return copier.invoke(value) - } - - override fun compare(a: V, b: V): Boolean { - return comparator.invoke(a, b) - } -} - -val NullValueCodec = StreamCodec({ null }, { _, _ -> }) -val BooleanValueCodec = StreamCodec(DataInputStream::readBoolean, DataOutputStream::writeBoolean) -val ByteValueCodec = StreamCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) }) -val ShortValueCodec = StreamCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) }) -val IntValueCodec = StreamCodec(DataInputStream::readInt, DataOutputStream::writeInt) -val LongValueCodec = StreamCodec(DataInputStream::readLong, DataOutputStream::writeLong) -val FloatValueCodec = StreamCodec(DataInputStream::readFloat, DataOutputStream::writeFloat) -val DoubleValueCodec = StreamCodec(DataInputStream::readDouble, DataOutputStream::writeDouble) -val ItemStackValueCodec = StreamCodec(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) } -val ImpreciseFractionValueCodec = StreamCodec(DataInputStream::readDecimal, DataOutputStream::writeDecimal) -val BigDecimalValueCodec = StreamCodec(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal) -val UUIDValueCodec = StreamCodec({ s -> UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) }) -val VarIntValueCodec = StreamCodec(DataInputStream::readVarIntLE, DataOutputStream::writeVarIntLE) -val VarLongValueCodec = StreamCodec(DataInputStream::readVarLongLE, DataOutputStream::writeVarLongLE) - -class EnumValueCodec>(private val clazz: Class) : IStreamCodec { - private val values = clazz.enumConstants - - override fun read(stream: DataInputStream): V { - val id = stream.readVarIntLE() - - if (id >= values.size) { - throw IndexOutOfBoundsException("Unable to read enum $clazz, no such enum at index $id") - } - - return values[id] - } - - override fun write(stream: DataOutputStream, value: V) { - stream.writeVarIntLE(value.ordinal) - } - - override fun copy(value: V): V { - return value - } - - override fun compare(a: V, b: V): Boolean { - return a === b - } -} - -fun OutputStream.writeInt(value: Int) { - if (this is DataOutputStream) { - writeInt(value) - return - } - - write(value ushr 24) - write(value ushr 16) - write(value ushr 8) - write(value) -} - -fun InputStream.readInt(): Int { - if (this is DataInputStream) { - return readInt() - } - - return (read() shl 24) or (read() shl 16) or (read() shl 8) or read() -} - -fun OutputStream.writeLong(value: Long) { - if (this is DataOutputStream) { - writeLong(value) - return - } - - write((value ushr 48).toInt()) - write((value ushr 40).toInt()) - write((value ushr 32).toInt()) - write((value ushr 24).toInt()) - write((value ushr 16).toInt()) - write((value ushr 8).toInt()) - write(value.toInt()) -} - -fun InputStream.readLong(): Long { - if (this is DataInputStream) { - return readLong() - } - - return (read().toLong() shl 48) or - (read().toLong() shl 40) or - (read().toLong() shl 32) or - (read().toLong() shl 24) or - (read().toLong() shl 16) or - (read().toLong() shl 8) or - read().toLong() -} - -fun OutputStream.writeFloat(value: Float) = writeInt(value.toBits()) -fun InputStream.readFloat() = Float.fromBits(readInt()) -fun OutputStream.writeDouble(value: Double) = writeLong(value.toBits()) -fun InputStream.readDouble() = Double.fromBits(readLong()) - -fun InputStream.readVarIntLE(): Int { - val readFirst = read() - - if (readFirst < 0) { - throw NoSuchElementException("Reached end of stream") - } - - if (readFirst and 64 == 0) { - return if (readFirst and 128 != 0) -(readFirst and 63) else readFirst and 63 - } - - var result = 0 - var nextBit = readFirst and 64 - var read = readFirst and 63 - var i = 0 - - while (nextBit != 0) { - result = result or (read shl i) - read = read() - - if (read < 0) { - throw NoSuchElementException("Reached end of stream") - } - - nextBit = read and 128 - read = read and 127 - - if (i == 0) - i = 6 - else - i += 7 - } - - result = result or (read shl i) - return if (readFirst and 128 != 0) -result else result -} - -fun OutputStream.writeVarIntLE(value: Int) { - write((if (value < 0) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63)) - var written = value.absoluteValue ushr 6 - - while (written != 0) { - write((written and 127) or (if (written >= 128) 128 else 0)) - written = written ushr 7 - } -} - -fun InputStream.readVarLongLE(): Long { - val readFirst = read() - - if (readFirst < 0) { - throw NoSuchElementException("Reached end of stream") - } - - if (readFirst and 64 == 0) { - return if (readFirst and 128 != 0) -(readFirst and 63).toLong() else (readFirst and 63).toLong() - } - - var result = 0L - var nextBit = readFirst and 64 - var read = readFirst and 63 - var i = 0 - - while (nextBit != 0) { - result = result or (read shl i).toLong() - read = read() - - if (read < 0) { - throw NoSuchElementException("Reached end of stream") - } - - nextBit = read and 128 - read = read and 127 - - if (i == 0) - i = 6 - else - i += 7 - } - - result = result or (read shl i).toLong() - return if (readFirst and 128 != 0) -result else result -} - -fun OutputStream.writeVarLongLE(value: Long) { - write((if (value < 0L) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63).toInt()) - var written = value.absoluteValue ushr 6 - - while (written != 0L) { - write((written and 127).toInt() or (if (written >= 128) 128 else 0)) - written = written ushr 7 - } -} - -private data class IndexedStreamCodec( - val condition: Predicate, - val id: Int, - val codec: StreamCodec -) { - fun read(stream: DataInputStream): T { - return codec.read(stream) - } - - fun write(stream: DataOutputStream, value: Any?) { - codec.write(stream, value as T) - } -} - -private inline fun IndexedStreamCodec(id: Int, codec: StreamCodec): IndexedStreamCodec { - return IndexedStreamCodec({ it is T }, id, codec) -} - -private val codecs: List> = com.google.common.collect.ImmutableList.builder>().let { - var codecID = 0 - - it.add(IndexedStreamCodec({ it == null }, codecID++, NullValueCodec)) - - it.add(IndexedStreamCodec(codecID++, BooleanValueCodec)) - it.add(IndexedStreamCodec(codecID++, ByteValueCodec)) - it.add(IndexedStreamCodec(codecID++, ShortValueCodec)) - it.add(IndexedStreamCodec(codecID++, VarIntValueCodec)) - it.add(IndexedStreamCodec(codecID++, VarLongValueCodec)) - it.add(IndexedStreamCodec(codecID++, FloatValueCodec)) - it.add(IndexedStreamCodec(codecID++, DoubleValueCodec)) - it.add(IndexedStreamCodec(codecID++, ItemStackValueCodec)) - it.add(IndexedStreamCodec(codecID++, ImpreciseFractionValueCodec)) - it.add(IndexedStreamCodec(codecID++, BigDecimalValueCodec)) - it.add(IndexedStreamCodec(codecID++, UUIDValueCodec)) - - it.build() -} - -class NotSerializableValueException(message: String) : Exception(message) - -/** - * Write arbitrary data to this stream, in exploit-free way - */ -fun DataOutputStream.writeType(value: Any?) { - for (codec in codecs) { - if (codec.condition.test(value)) { - write(codec.id) - codec.write(this, value) - return - } - } - - throw NotSerializableValueException("Value $value <${value?.let { it::class.qualifiedName }}> can not be serialized") -} - -/** - * Read arbitrary data from this stream, in exploit-free way - */ -fun DataInputStream.readType(): Any? { - val id = read() - - if (id >= codecs.size) { - throw IndexOutOfBoundsException("No codec for network type $id") - } - - return codecs[id].read(this) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Decimal.kt deleted file mode 100644 index 68d8ab1d7..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Decimal.kt +++ /dev/null @@ -1,938 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.nbt.ByteArrayTag -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.StringTag -import net.minecraft.nbt.Tag -import net.minecraft.network.FriendlyByteBuf -import net.minecraftforge.common.ForgeConfigSpec -import ru.dbotthepony.mc.otm.ObservedConfigValue -import java.io.InputStream -import java.io.OutputStream -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import kotlin.math.absoluteValue - -//private fun isZero(value: BigInteger) = value == BigInteger.ZERO -private fun isZero(value: BigInteger) = value.signum() == 0 - -private fun unsignedInt(value: Byte): Int { - return value.toInt() and 0xFF -} - -private fun usignedLong(value: Byte): Long { - return value.toLong() and 0xFFL -} - -private fun longToBytesBE(value: Long): ByteArray { - return byteArrayOf( - (value ushr 56).toByte(), - (value ushr 48).toByte(), - (value ushr 40).toByte(), - (value ushr 32).toByte(), - (value ushr 24).toByte(), - (value ushr 16).toByte(), - (value ushr 8).toByte(), - (value).toByte(), - ) -} - -private fun bytesToLongBE(value: ByteArray, from: Int = 0): Long { - return ( - (usignedLong(value[from + 7])) or - (usignedLong(value[from + 6]) shl 8) or - (usignedLong(value[from + 5]) shl 16) or - (usignedLong(value[from + 4]) shl 24) or - (usignedLong(value[from + 3]) shl 32) or - (usignedLong(value[from + 2]) shl 40) or - (usignedLong(value[from + 1]) shl 48) or - (usignedLong(value[from]) shl 56) - ) -} - -private fun bytesToLongBE( - value7: Byte, - value6: Byte, - value5: Byte, - value4: Byte, - value3: Byte, - value2: Byte, - value1: Byte, - value0: Byte, -): Long { - return ( - (usignedLong(value0)) or - (usignedLong(value1) shl 8) or - (usignedLong(value2) shl 16) or - (usignedLong(value3) shl 24) or - (usignedLong(value4) shl 32) or - (usignedLong(value5) shl 40) or - (usignedLong(value6) shl 48) or - (usignedLong(value7) shl 56) - ) -} - -/** - * Constant value which represent edge of meaningful bits. - * - * Equals to 0.000000000001 - */ -const val EPSILON = 0.000000000001 - -private val zeroes = Array(16) { "0".repeat(it).intern() } -private val nums = Array(10) { it.toString()[0] } - -private fun decimals(input: Double, places: Int): String { - if (weakEqualDoubles(input, 0.0)) { - return zeroes.elementAtOrElse(places) { "0".repeat(places) } - } - - val buffer = CharArray(places) - - @Suppress("NAME_SHADOWING") - var input = input.absoluteValue - - for (i in 0 until places) { - input *= 10.0 - buffer[i] = nums[(input % 10.0).toInt()] - } - - return String(buffer) -} - -fun weakEqualDoubles(a: Double, b: Double): Boolean { - if (a == b) - return true - - return (a - b).absoluteValue <= EPSILON -} - -fun weakCompareDoubles(a: Double, b: Double): Int { - if (weakEqualDoubles(a, b)) - return 0 - - if (a > b) - return 1 - - return -1 -} - -fun weakLessThan(a: Double, b: Double) = weakCompareDoubles(a, b) < 0 -fun weakGreaterThan(a: Double, b: Double) = weakCompareDoubles(a, b) > 0 -fun weakLessOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) <= 0 -fun weakGreaterOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) >= 0 - -private val PERCENTAGE_CONTEXT = MathContext(6) - -private const val MEANINGFUL_BITS_LONG = 1000000000000000000L -private const val MEANINGFUL_BITS_DOUBLE = MEANINGFUL_BITS_LONG.toDouble() -private val MEANINGFUL_BITS_BI = BigInteger.valueOf(MEANINGFUL_BITS_LONG) - -private val BI_MINUS_ONE = -BigInteger.ONE - -fun Decimal(value: Byte) = Decimal.valueOf(value) -fun Decimal(value: Short) = Decimal.valueOf(value) -fun Decimal(value: Int) = Decimal.valueOf(value) -fun Decimal(value: Long) = Decimal.valueOf(value) - -/** - * Decimal class for having infinitely precise [whole] part, while having fixed precision [decimal] part. - * - * In essence, this class is pretty much like [BigDecimal], except decimal part of this number is - * not guaranteed to be precise (stored as [Double]). The reason behind creation of this class, however, - * is to allow infinite precision of [whole] part, while leaving [decimal] part to be rounded in inexact operations. - * - * This class is value based, however, [equals] and [compareTo] are not doing *exact* comparison. Whole part is always compared - * exactly, but decimal part is considered equal if their difference is less than [EPSILON]. - */ -@Suppress("unused") -class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Number(), Comparable, java.io.Serializable { - constructor(whole: Byte, decimal: Double) : this(BigInteger.valueOf(whole.toLong()), decimal) - constructor(whole: Short, decimal: Double) : this(BigInteger.valueOf(whole.toLong()), decimal) - constructor(whole: Int, decimal: Double) : this(BigInteger.valueOf(whole.toLong()), decimal) - constructor(whole: Long, decimal: Double) : this(BigInteger.valueOf(whole), decimal) - constructor(value: BigDecimal) : this(value.toBigInteger(), value.remainder(BigDecimal.ONE).toDouble()) // TODO - constructor(value: Float) : this(BigInteger.valueOf((value - value % 1.0).toLong()), value % 1.0) - constructor(value: Double) : this(BigInteger.valueOf((value - value % 1.0).toLong()), value % 1.0) - constructor(value: String) : this(BigDecimal(value)) - - /** - * Inexact fractional part of this fraction - */ - val decimal: Double - - /** - * Exact whole part of this fraction - */ - val whole: BigInteger - - init { - @Suppress("name_shadowing") - var decimal = decimal - @Suppress("name_shadowing") - var whole = whole - - if (decimal <= -1f || decimal >= 1f) { - val frac = decimal % 1f - whole += BigInteger.valueOf((decimal - frac).toLong()) - decimal = frac - } - - // дробная часть равна или очень близка к нулю - if (weakEqualDoubles(decimal, 0.0)) { - if (!isZero(whole)) { - this.decimal = 0.0 - this.whole = whole - } else { // сохраняет минус ноль - this.decimal = decimal - this.whole = whole - } - // целая часть и дробная часть имеют одинаковые знаки - } else if (decimal > 0.0 && whole.signum() >= 0 || decimal < 0.0 && whole.signum() <= 0) { - this.decimal = decimal - this.whole = whole - } else { - // целая часть и дробная часть имеют разные знаки - - if (decimal > 0.0) { - this.decimal = decimal - 1f - this.whole = whole + BigInteger.ONE - //} else if (double < 0.0 && whole.signum() > 0) { - } else { - this.decimal = decimal + 1f - this.whole = whole - BigInteger.ONE - } - - //throw InternalError("This should never happen, but it happened. Input was: $whole $double") - } - } - - val isNaN get() = decimal.isNaN() - - operator fun plus(other: Decimal) = Decimal(whole + other.whole, decimal + other.decimal) - operator fun minus(other: Decimal) = Decimal(whole - other.whole, decimal - other.decimal) - - operator fun times(other: Decimal): Decimal { - if (other == ONE) - return this - else if (other == MINUS_ONE) - return -this - - val a = whole - val c = other.whole - - val bZero = weakEqualDoubles(decimal, 0.0) - val dZero = weakEqualDoubles(other.decimal, 0.0) - - if (bZero && dZero) { - if (isZero(a) || isZero(c)) - return ZERO - - return Decimal(a * c) - } else if (isZero(a) && isZero(c)) { - if (bZero || dZero) - return ZERO - - return Decimal(0, decimal * other.decimal) - } else if (bZero) { - val d = other.decimal * MEANINGFUL_BITS_DOUBLE - val dL = d.toLong() - val adInflated = a * BigInteger.valueOf(dL) - val ad = adInflated.divideAndRemainder(MEANINGFUL_BITS_BI) - - if (isZero(c)) { - return Decimal( - ad[0], - ad[1].toDouble() / MEANINGFUL_BITS_DOUBLE - ) - } - - return Decimal( - a * c + ad[0], - ad[1].toDouble() / MEANINGFUL_BITS_DOUBLE - ) - } else if (dZero) { - val b = decimal * MEANINGFUL_BITS_DOUBLE - val bL = b.toLong() - val bcInflated = c * BigInteger.valueOf(bL) - val bc = bcInflated.divideAndRemainder(MEANINGFUL_BITS_BI) - - if (isZero(a)) { - return Decimal( - bc[0], - bc[1].toDouble() / MEANINGFUL_BITS_DOUBLE - ) - } - - return Decimal( - a * c + bc[0], - bc[1].toDouble() / MEANINGFUL_BITS_DOUBLE - ) - } - - val b = decimal * MEANINGFUL_BITS_DOUBLE - val d = other.decimal * MEANINGFUL_BITS_DOUBLE - - val bL = b.toLong() - val dL = d.toLong() - - val bcInflated = c * BigInteger.valueOf(bL) - val adInflated = a * BigInteger.valueOf(dL) - - val bc = bcInflated.divideAndRemainder(MEANINGFUL_BITS_BI) - val ad = adInflated.divideAndRemainder(MEANINGFUL_BITS_BI) - - return Decimal( - a * c + bc[0] + ad[0], - decimal * other.decimal + bc[1].toDouble() / MEANINGFUL_BITS_DOUBLE + ad[1].toDouble() / MEANINGFUL_BITS_DOUBLE - ) - } - - operator fun div(other: Decimal): Decimal { - if (other == ONE) - return this - else if (other == MINUS_ONE) - return -this - - if (isZero && other.isZero) - return NaN - - if (!isZero && other.isZero) - throw ArithmeticException("Divide by zero") - - //if (isZero(whole) && isZero(other.whole)) - // return ImpreciseFraction(BigInteger.ZERO, decimal / other.decimal) - - val a = toBigDecmial() - val b = other.toBigDecmial() - val div = a.divideAndRemainder(b, MathContext(a.precision() + b.precision())) - - val bD = b.toDouble() - - if (bD.isInfinite() || bD == 0.0) { - return Decimal(div[0].toBigInteger()) - } - - return Decimal(div[0].toBigInteger(), div[1].toDouble() / bD) - } - - operator fun plus(other: Float): Decimal { - if (other == 0f) { - return this - } - - return plus(Decimal(other)) - } - - operator fun minus(other: Float): Decimal { - if (other == 0f) { - return this - } - - return minus(Decimal(other)) - } - - operator fun times(other: Float): Decimal { - if (other == 1f) { - return this - } else if (other == 0f) { - return ZERO - } else if (other == -1f) { - return -this - } - - return times(Decimal(other)) - } - - operator fun div(other: Float): Decimal { - if (other == 0f) { - if (isZero) - return NaN - else - throw ArithmeticException("Divide by zero") - } else if (other == 1f) { - return this - } else if (other == -1f) { - return -this - } - - return div(Decimal(other)) - } - - operator fun plus(other: Double): Decimal { - if (other == 0.0) { - return this - } - - return plus(Decimal(other)) - } - - operator fun minus(other: Double): Decimal { - if (other == 0.0) { - return this - } - - return minus(Decimal(other)) - } - - operator fun times(other: Double): Decimal { - if (other == 1.0) { - return this - } else if (other == 0.0) { - return ZERO - } else if (other == -1.0) { - return -this - } - - return times(Decimal(other)) - } - - operator fun div(other: Double): Decimal { - if (other == 0.0) { - if (isZero) - return NaN - else - throw ArithmeticException("Divide by zero") - } else if (other == 1.0) { - return this - } else if (other == -1.0) { - return -this - } - - return div(Decimal(other)) - } - - operator fun plus(other: Int): Decimal { - if (other == 0) { - return this - } - - return plus(valueOf(other)) - } - - operator fun minus(other: Int): Decimal { - if (other == 0) { - return this - } - - return minus(valueOf(other)) - } - - operator fun times(other: Int): Decimal { - if (other == 1) { - return this - } else if (other == 0) { - return ZERO - } else if (other == -1) { - return -this - } - - return times(valueOf(other)) - } - - operator fun div(other: Int): Decimal { - if (other == 0) { - if (isZero) - return NaN - else - throw ArithmeticException("Divide by zero") - } else if (other == 1) { - return this - } else if (other == -1) { - return -this - } - - return div(valueOf(other)) - } - - operator fun plus(other: Long): Decimal { - if (other == 0L) { - return this - } - - return plus(valueOf(other)) - } - - operator fun minus(other: Long): Decimal { - if (other == 0L) { - return this - } - - return minus(valueOf(other)) - } - - operator fun times(other: Long): Decimal { - if (other == 1L) { - return this - } else if (other == 0L) { - return ZERO - } else if (other == -1L) { - return -this - } - - return times(valueOf(other)) - } - - operator fun div(other: Long): Decimal { - if (other == 0L) { - if (isZero) - return NaN - else - throw ArithmeticException("Divide by zero") - } else if (other == 1L) { - return this - } else if (other == -1L) { - return -this - } - - return div(valueOf(other)) - } - - operator fun plus(other: BigInteger): Decimal { - if (other == BigInteger.ZERO) { - return this - } - - return plus(Decimal(other)) - } - - operator fun minus(other: BigInteger): Decimal { - if (other == BigInteger.ZERO) { - return this - } - - return minus(Decimal(other)) - } - - operator fun times(other: BigInteger): Decimal { - if (other == BigInteger.ONE) { - return this - } else if (other == BigInteger.ZERO) { - return ZERO - } else if (other == BI_MINUS_ONE) { - return -this - } - - return times(Decimal(other)) - } - - operator fun div(other: BigInteger): Decimal { - if (other == BigInteger.ZERO) { - return NaN - } else if (other == BigInteger.ONE) { - return this - } else if (other == BI_MINUS_ONE) { - return -this - } - - return div(Decimal(other)) - } - - operator fun unaryMinus(): Decimal { - return Decimal(-whole, -decimal) - } - - override fun equals(other: Any?): Boolean { - if (isNaN) - return false - - if (other is Decimal) { - return other.whole == whole && weakEqualDoubles(decimal, other.decimal) - } - - return super.equals(other) - } - - fun equalsStrict(other: Any?): Boolean { - if (isNaN) - return other is Decimal && other.isNaN // ибо hashCode() так требует - - if (other is Decimal) { - return other.whole == whole && other.decimal == decimal - } - - return super.equals(other) - } - - override fun hashCode(): Int { - if (isNaN) - return Double.NaN.hashCode() - - return 31 * (decimal - decimal % EPSILON).hashCode() + whole.hashCode() - } - - fun floor(): Decimal { - if (decimal == 0.0) - return this - - return Decimal(whole) - } - - fun toString(decimals: Int): String { - if (isNaN) - return "NaN" - - if (decimals == 0) { - return whole.toString() - } else if (decimals > 0) { - return "$whole.${decimals(decimal, decimals)}" - } - - return when (signum()) { - 1, -1 -> "$whole.${decimals(decimal, 11)}" - 0 -> "0.0" - else -> throw IllegalArgumentException("invalid signum") - } - } - - override fun toString() = toString(-1) - - fun toBigDecmial(): BigDecimal { - return BigDecimal(whole) + BigDecimal(decimal) - } - - fun signum(): Int { - val sign = whole.signum() - - if (sign != 0) { - return sign - } - - // exactly zero - if (decimal == 0.0) - return if (1.0 / decimal > 0) 1 else -1 - - // inexactly zero - if (weakEqualDoubles(decimal, 0.0)) - return 0 - - return if (decimal > 0.0) 1 else -1 - } - - override fun compareTo(other: Decimal): Int { - if (isNaN) - return 1 - else if (other.isNaN) - return -1 - - val a = signum() - val b = other.signum() - - if (a < b) - return -1 - else if (a > b) - return 1 - - if (other.whole == whole) { - return weakCompareDoubles(decimal, other.decimal) - } - - return whole.compareTo(other.whole) - } - - fun compareToStrict(other: Decimal): Int { - if (isNaN) - return 1 - else if (other.isNaN) - return -1 - - val a = signum() - val b = other.signum() - - if (a < b) - return -1 - else if (a > b) - return 1 - - if (other.whole == whole) { - if (other.decimal == decimal) { - return 0 - } - - return if (other.decimal < decimal) 1 else -1 - } - - return whole.compareTo(other.whole) - } - - fun toByteArray(): ByteArray { - val whole = whole.toByteArray() - return byteArrayOf(whole.size.toByte(), *whole, *longToBytesBE(decimal.toBits())) - } - - fun serializeNBT(): Tag { - return ByteArrayTag(toByteArray()) - } - - val isZero get() = weakEqualDoubles(decimal, 0.0) && isZero(whole) - val isPositive get() = this > ZERO - val isNegative get() = this < ZERO - - fun moreThanZero(): Decimal { - if (signum() >= 0) - return this - - return ZERO - } - - fun lessOrZero(): Decimal { - if (signum() <= 0) - return this - - return ZERO - } - - override fun toInt(): Int { - if (whole > BI_INT_MAX) { - return Int.MAX_VALUE - } else if (whole < BI_INT_MIN) { - return Int.MIN_VALUE - } - - return whole.toInt() - } - - fun write(buff: FriendlyByteBuf) { - val whole = whole.toByteArray() - buff.writeByteArray(whole) - buff.writeDouble(decimal) - } - - override fun toFloat(): Float { - return whole.toFloat() + decimal.toFloat() - } - - override fun toDouble(): Double { - return whole.toDouble() + decimal - } - - override fun toByte(): Byte { - return toInt().toByte() - } - - override fun toChar(): Char { - return toInt().toChar() - } - - override fun toLong(): Long { - if (whole > BI_LONG_MAX) { - return Long.MAX_VALUE - } else if (whole < BI_LONG_MIN) { - return Long.MIN_VALUE - } - - return whole.toLong() - } - - override fun toShort(): Short { - return toInt().toShort() - } - - fun toFraction(): Fraction { - if (isZero) { - return Fraction.ZERO - } - - if (weakEqualDoubles(decimal, 0.0)) { - return Fraction(whole) - } - - return Fraction(toBigDecmial()) - } - - fun percentage(divisor: Decimal, zeroing: Boolean = true): Float { - if ((isZero || divisor.isZero) && zeroing) return 0f - if (isNaN || divisor.isNaN || divisor.isZero) return Float.NaN - - if (this >= divisor) - return 1f - - if (this <= FLOAT_MAX_VALUE && divisor <= FLOAT_MAX_VALUE) - return (toDouble() / divisor.toDouble()).toFloat() - - return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() - } - - operator fun rem(divisor: Decimal): Decimal { - TODO("Not yet implemented") - } - - companion object { - @JvmStatic - val serialVersionUID: Long = 287354739494574838L - - @JvmField val MINUS_ZERO = Decimal(0, -0.0) - @JvmField val ZERO = Decimal(BigInteger.ZERO) - @JvmField val ONE = Decimal(BigInteger.ONE) - @JvmField val MINUS_ONE = Decimal(-BigInteger.ONE) - @JvmField val TWO = Decimal(BigInteger.TWO) - @JvmField val TEN = Decimal(BigInteger.TEN) - - @JvmField val INT_MAX_VALUE = Decimal(BI_INT_MAX) - @JvmField val INT_MIN_VALUE = Decimal(BI_INT_MIN) - @JvmField val LONG_MAX_VALUE = Decimal(BI_LONG_MAX) - @JvmField val LONG_MIN_VALUE = Decimal(BI_LONG_MIN) - - @JvmField val FLOAT_MAX_VALUE = Decimal(BI_FLOAT_MAX) - @JvmField val FLOAT_MIN_VALUE = Decimal(BI_FLOAT_MIN) - @JvmField val DOUBLE_MAX_VALUE = Decimal(BI_DOUBLE_MAX) - @JvmField val DOUBLE_MIN_VALUE = Decimal(BI_DOUBLE_MIN) - - @JvmField val ONE_TENTH = Decimal("0.1") - - private val cache = Array(2048) { Decimal(it - 1024, 0.0) } - - /** - * Returns pooled value if present, otherwise constructs new object - */ - fun valueOf(value: Int): Decimal { - if (value in -1024 .. 1023) { - return cache[value + 1024] - } - - return Decimal(value, 0.0) - } - - /** - * Returns pooled value if present, otherwise constructs new object - */ - fun valueOf(value: Long): Decimal { - if (value in -1024L .. 1023L) { - return cache[value.toInt() + 1024] - } - - return Decimal(value, 0.0) - } - - /** - * Returns pooled value if present, otherwise constructs new object - */ - fun valueOf(value: Short) = valueOf(value.toInt()) - - /** - * Returns pooled value if present, otherwise constructs new object - */ - fun valueOf(value: Byte) = valueOf(value.toInt()) - - @JvmField - val NaN = Decimal(0, Double.NaN) - - @JvmStatic - fun fromByteArray(input: ByteArray): Decimal { - val size = unsignedInt(input[0]) - - if (size > 0) { - val slice = input.copyOfRange(1, 1 + size) - val bits = bytesToLongBE(input, 1 + size) - return Decimal(BigInteger(slice), Double.fromBits(bits)) - } else { - return Decimal(BigInteger.ZERO, Double.fromBits(bytesToLongBE(input, 1))) - } - } - - @JvmStatic - fun deserializeNBT(input: Tag?): Decimal { - if (input is ByteArrayTag) { - return fromByteArray(input.asByteArray) - } else if (input is StringTag) { - return Decimal(input.asString) - } - - return ZERO - } - - @JvmStatic - fun deserializeNBT(input: ByteArrayTag): Decimal { - return fromByteArray(input.asByteArray) - } - - @JvmStatic - fun read(buff: FriendlyByteBuf): Decimal { - return Decimal(BigInteger(buff.readByteArray()), buff.readDouble()) - } - } -} - -fun FriendlyByteBuf.readDecimal() = Decimal.read(this) -fun FriendlyByteBuf.writeDecimal(value: Decimal) = value.write(this) - -fun InputStream.readDecimal(): Decimal { - val bytes = ByteArray(readVarIntLE()) - read(bytes) - return Decimal(BigInteger(bytes), readDouble()) -} - -fun OutputStream.writeDecimal(value: Decimal) { - val bytes = value.whole.toByteArray() - writeVarIntLE(bytes.size) - write(bytes) - writeDouble(value.decimal) -} - -fun CompoundTag.getDecimal(key: String) = Decimal.deserializeNBT(this[key]) -fun CompoundTag.putDecimal(key: String, value: Decimal) = put(key, value.serializeNBT()) - -operator fun CompoundTag.set(key: String, value: Decimal) = putDecimal(key, value) - -fun Float.toDecimal() = Decimal(this) -fun Double.toDecimal() = Decimal(this) -fun Int.toDecimal() = Decimal(this) -fun Byte.toDecimal() = Decimal(this) -fun Short.toDecimal() = Decimal(this) -fun Long.toDecimal() = Decimal(this) -fun Decimal.toDecimal() = this - -class DecimalConfigValue( - parent: ForgeConfigSpec.ConfigValue, - val minimum: Decimal? = null, - val maximum: Decimal? = null, -) : ObservedConfigValue(parent) { - override fun fromString(value: String): Decimal? { - try { - val parsed = Decimal(value) - - if (minimum != null && minimum > parsed) { - return minimum - } else if (maximum != null && maximum < parsed) { - return maximum - } - - return parsed - } catch (err: java.lang.NumberFormatException) { - return null - } - } - - override fun toString(value: Decimal): Pair { - if (minimum != null && minimum > value) { - return minimum.toString() to minimum - } else if (maximum != null && maximum < value) { - return maximum.toString() to maximum - } - - return value.toString() to value - } -} - -private fun ForgeConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) { - if (minimum != null && maximum != null) { - comment("Range: $minimum ~ $maximum") - } else if (minimum != null) { - comment("Range: >= $minimum") - } else if (maximum != null) { - comment("Range: <= $maximum") - } -} - -fun ForgeConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { - commentRange(minimum, maximum) - comment("Default: $defaultValue") - return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) -} - -fun ForgeConfigSpec.Builder.defineDecimal(path: List, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { - commentRange(minimum, maximum) - comment("Default: $defaultValue") - return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt deleted file mode 100644 index b673ac9c3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EmptyMutableIterator.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -object EmptyMutableIterator : MutableIterator<@UnsafeVariance Nothing>, MutableListIterator<@UnsafeVariance Nothing> { - override fun hasPrevious(): Boolean { - return false - } - - override fun nextIndex(): Int { - return 0 - } - - override fun previous(): Nothing { - throw NoSuchElementException() - } - - override fun previousIndex(): Int { - return -1 - } - - override fun add(element: Nothing) { - throw UnsupportedOperationException() - } - - override fun hasNext(): Boolean { - return false - } - - override fun next(): Nothing { - throw NoSuchElementException() - } - - override fun remove() { - throw NoSuchElementException() - } - - override fun set(element: Nothing) { - throw UnsupportedOperationException() - } - - @Suppress("unchecked_cast") - fun cast(): MutableListIterator { - return this as MutableListIterator - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index 5466189c0..1a3e5ce02 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -4,236 +4,66 @@ package ru.dbotthepony.mc.otm.core import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableMultimap +import com.google.common.collect.ImmutableSet import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import com.mojang.serialization.DataResult +import it.unimi.dsi.fastutil.objects.ObjectComparators import net.minecraft.core.BlockPos -import net.minecraft.nbt.ByteArrayTag import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.Tag +import net.minecraft.nbt.NbtAccounter import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.chat.ComponentContents +import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.resources.ResourceLocation +import net.minecraft.world.Container import net.minecraft.world.entity.Entity import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.StateHolder import net.minecraft.world.level.block.state.properties.Property +import net.minecraft.world.level.saveddata.SavedData +import net.minecraft.world.level.storage.DimensionDataStorage import net.minecraft.world.phys.Vec3 import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.util.LazyOptional +import net.minecraftforge.fluids.FluidStack import net.minecraftforge.items.IItemHandler import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.ForgeRegistry import net.minecraftforge.registries.IForgeRegistry +import ru.dbotthepony.mc.otm.core.math.BlockRotation +import ru.dbotthepony.mc.otm.core.math.BlockRotationFreedom +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.util.readInt +import ru.dbotthepony.mc.otm.core.util.readVarIntLE +import ru.dbotthepony.mc.otm.core.util.writeInt +import ru.dbotthepony.mc.otm.core.util.writeVarIntLE +import java.io.InputStream +import java.io.OutputStream import java.lang.ref.Reference -import java.lang.ref.WeakReference import java.math.BigInteger import java.util.Arrays import java.util.Spliterators import java.util.UUID +import java.util.function.Consumer +import java.util.function.Function +import java.util.function.Supplier import java.util.stream.Stream import java.util.stream.StreamSupport import kotlin.reflect.KProperty -/** - * Performs type check+cast and sums two numbers. - * - * Returns the same type as on input (adding Double to Float will not return Double, etc.) - */ -fun Number.dynPlus(other: Number): Number { - return when (this) { - is Float -> this + other.toFloat() - is Double -> this + other.toDouble() - is Int -> this + other.toInt() - is Long -> this + other.toLong() - is Short -> this + other.toShort() - is Byte -> this + other.toByte() - - is Decimal -> { - when (other) { - is Float -> this + other.toFloat() - is Double -> this + other.toDouble() - is Int, is Byte, is Short -> this + other.toInt() - is Long -> this + other.toLong() - is Decimal -> this + other - is Fraction -> this + other.toImpreciseFraction() - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - is Fraction -> { - when (other) { - is Float -> this + other.toFloat() - is Double -> this + other.toDouble() - is Int, is Byte, is Short -> this + other.toInt() - is Long -> this + other.toLong() - is Decimal -> this + other.toFraction() - is Fraction -> this + other - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") - } -} - -/** - * Performs type check+cast and subtracts two numbers. - * - * Returns the same type as on input (adding Double to Float will not return Double, etc.) - */ -fun Number.dynMinus(other: Number): Number { - return when (this) { - is Float -> this - other.toFloat() - is Double -> this - other.toDouble() - is Int -> this - other.toInt() - is Long -> this - other.toLong() - is Short -> this - other.toShort() - is Byte -> this - other.toByte() - - is Decimal -> { - when (other) { - is Float -> this - other.toFloat() - is Double -> this - other.toDouble() - is Int, is Byte, is Short -> this - other.toInt() - is Long -> this - other.toLong() - is Decimal -> this - other - is Fraction -> this - other.toImpreciseFraction() - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - is Fraction -> { - when (other) { - is Float -> this - other.toFloat() - is Double -> this - other.toDouble() - is Int, is Byte, is Short -> this - other.toInt() - is Long -> this - other.toLong() - is Decimal -> this - other.toFraction() - is Fraction -> this - other - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") - } -} - -/** - * Performs type check+cast and multiplies two numbers. - * - * Returns the same type as on input (adding Double to Float will not return Double, etc.) - */ -fun Number.dynTimes(other: Number): Number { - return when (this) { - is Float -> this * other.toFloat() - is Double -> this * other.toDouble() - is Int -> this * other.toInt() - is Long -> this * other.toLong() - is Short -> this * other.toShort() - is Byte -> this * other.toByte() - - is Decimal -> { - when (other) { - is Float -> this * other.toFloat() - is Double -> this * other.toDouble() - is Int, is Byte, is Short -> this * other.toInt() - is Long -> this * other.toLong() - is Decimal -> this * other - is Fraction -> this * other.toImpreciseFraction() - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - is Fraction -> { - when (other) { - is Float -> this * other.toFloat() - is Double -> this * other.toDouble() - is Int, is Byte, is Short -> this * other.toInt() - is Long -> this * other.toLong() - is Decimal -> this * other.toFraction() - is Fraction -> this * other - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") - } -} - -/** - * Performs type check+cast and divides two numbers. - * - * Returns the same type as on input (adding Double to Float will not return Double, etc.) - */ -fun Number.dynDiv(other: Number): Number { - return when (this) { - is Float -> this / other.toFloat() - is Double -> this / other.toDouble() - is Int -> this / other.toInt() - is Long -> this / other.toLong() - is Short -> this / other.toShort() - is Byte -> this / other.toByte() - - is Decimal -> { - when (other) { - is Float -> this / other.toFloat() - is Double -> this / other.toDouble() - is Int, is Byte, is Short -> this / other.toInt() - is Long -> this / other.toLong() - is Decimal -> this / other - is Fraction -> this / other.toImpreciseFraction() - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - is Fraction -> { - when (other) { - is Float -> this / other.toFloat() - is Double -> this / other.toDouble() - is Int, is Byte, is Short -> this / other.toInt() - is Long -> this / other.toLong() - is Decimal -> this / other.toFraction() - is Fraction -> this / other - else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") - } - } - - else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") - } -} - -val Number.isFractional get() = this is Float || this is Double || this is Decimal || this is Fraction -val Number.isWhole get() = !isFractional - -fun Number.toDecimal(): Decimal { - return when (this) { - is Decimal -> this - is Float -> Decimal(this) - is Double -> Decimal(this) - is Int -> Decimal(this) - is Byte -> Decimal(this) - is Short -> Decimal(this) - is Long -> Decimal(this) - is Fraction -> this.toImpreciseFraction() - else -> throw ClassCastException("Can not turn $this into ImpreciseFraction") - } -} - -fun BigInteger(tag: Tag?): BigInteger { - if (tag !is ByteArrayTag) - return BigInteger.ZERO - - return BigInteger(tag.asByteArray) -} - -fun BigInteger.serializeNBT(): ByteArrayTag { - return ByteArrayTag(toByteArray()) -} - fun FriendlyByteBuf.writeBigInteger(value: BigInteger) { writeByteArray(value.toByteArray()) } @@ -272,6 +102,9 @@ inline fun LazyOptional.ifPresentK(lambda: (T) -> Unit) { val ItemStack.tagNotNull: CompoundTag get() = orCreateTag +inline val FluidStack.isNotEmpty get() = !isEmpty +inline val ItemStack.isNotEmpty get() = !isEmpty + inline var Entity.position: Vec3 get() = position() set(value) { setPos(value) } @@ -308,7 +141,7 @@ fun > T.prev(values: Array): T { return values[next] } -inline fun ImmutableList(size: Int, initializer: (index: Int) -> T): ImmutableList { +inline fun immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList { require(size >= 0) { "Invalid list size $size" } return when (size) { @@ -324,6 +157,37 @@ inline fun ImmutableList(size: Int, initializer: (index: Int) -> T): Immutab } } +inline fun immutableMap(initializer: ImmutableMap.Builder.() -> Unit): ImmutableMap { + val builder = ImmutableMap.Builder() + initializer.invoke(builder) + return builder.build() +} + +inline fun immutableMultimap(initializer: ImmutableMultimap.Builder.() -> Unit): ImmutableMultimap { + val builder = ImmutableMultimap.Builder() + initializer.invoke(builder) + return builder.build() +} + +inline fun immutableSet(initializer: Consumer.() -> Unit): ImmutableSet { + val builder = ImmutableSet.Builder() + initializer.invoke(builder::add) + return builder.build() +} + +inline fun immutableList(initializer: Consumer.() -> Unit): ImmutableList { + val builder = ImmutableList.Builder() + initializer.invoke(builder::add) + return builder.build() +} + +fun immutableList(a: V, vararg values: V): ImmutableList { + val builder = ImmutableList.Builder() + builder.add(a) + builder.addAll(values.iterator()) + return builder.build() +} + fun IForgeRegistry.getID(value: T): Int { return (this as ForgeRegistry).getID(value) } @@ -340,14 +204,26 @@ fun FriendlyByteBuf.writeItemType(value: Item) { writeInt(ForgeRegistries.ITEMS.getID(value)) } -fun FriendlyByteBuf.readItemType(): Item? { - return ForgeRegistries.ITEMS.getValue(readInt()) +fun OutputStream.writeItemType(value: Item) { + writeVarIntLE(ForgeRegistries.ITEMS.getID(value)) +} + +fun FriendlyByteBuf.readItemType(): Item { + return ForgeRegistries.ITEMS.getValue(readInt()) ?: Items.AIR +} + +fun InputStream.readItemType(): Item { + return ForgeRegistries.ITEMS.getValue(readVarIntLE()) ?: Items.AIR } operator fun > StateHolder<*, *>.get(property: Property): T { return getValue(property) } +operator fun StateHolder<*, *>.get(property: BlockRotationFreedom): BlockRotation { + return getValue(property.property) +} + operator fun , T : Comparable> S.set(property: Property, value: T): S { setValue(property, value) return this @@ -375,6 +251,15 @@ fun BlockState.getExplosionResistance(level: BlockGetter, pos: BlockPos): Float } } +fun Level.gracefulBlockBreak(blockPos: BlockPos, block: BlockState = getBlockState(blockPos)) { + val tile = getBlockEntity(blockPos) + val state = block.fluidState.createLegacyBlock() + block.onRemove(this, blockPos, state, false) + Block.dropResources(block, this, blockPos, tile) + block.block.destroy(this, blockPos, block) + setBlock(blockPos, state, Block.UPDATE_ALL) +} + fun MutableCollection.addAll(elements: Iterator) { for (item in elements) { add(item) @@ -415,7 +300,8 @@ fun String.toUUID(): UUID { fun Collection.probablyParallelStream(): Stream { if (size >= 400) { - return parallelStream() + // TODO: https://github.com/MinecraftForge/EventBus/issues/44 + return stream() } return stream() @@ -446,3 +332,146 @@ inline fun MutableList>.forValidRefs(fn: (T) -> Unit) { } } } + +inline fun MutableList>.forValidRefsBreak(fn: (T) -> Boolean) { + val iterator = listIterator() + + for (value in iterator) { + val get = value.get() + + if (get == null) { + iterator.remove() + } else { + if (fn.invoke(get)) { + break + } + } + } +} + +val ComponentContents.key: String + get() = (this as? TranslatableContents ?: throw ClassCastException("$this is not a TranslatableContents")).key + +fun > BlockState.getValueNullable(prop: Property): T? { + if (hasProperty(prop)) { + return getValue(prop) + } + + return null +} + +fun Stream.asIterable(): Iterable { + return object : Iterable { + override fun iterator(): Iterator { + return this@asIterable.iterator() + } + } +} + +fun Comparator.nullsFirst(): Comparator { + return Comparator.nullsFirst(this) +} + +fun Comparator.nullsLast(): Comparator { + return Comparator.nullsLast(this) +} + +class MappedComparator(private val parent: Comparator, private val mapper: (T) -> O) : Comparator { + override fun compare(o1: T, o2: T): Int { + return parent.compare(mapper.invoke(o1), mapper.invoke(o2)) + } + + override fun equals(other: Any?): Boolean { + return other is MappedComparator<*, *> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "MappedComparator[$parent]" + } +} + +fun Comparator.map(mapper: (B) -> A): Comparator { + return MappedComparator(this, mapper) +} + +fun Comparator.suppliers(): Comparator> { + return MappedComparator(this) { it.get() } +} + +/** + * Returns applicable index to put [element] into [List] determined by [comparator], optionally specifying ranges as [fromIndex] and [toIndex] + * + * If [List] is not sorted, result of this function is undefined + */ +fun List.searchInsertionIndex(element: E, comparator: Comparator, fromIndex: Int = 0, toIndex: Int = size): Int { + require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" } + require(fromIndex >= 0) { "Invalid from index: $fromIndex" } + require(toIndex >= 0) { "Invalid to index: $toIndex" } + require(fromIndex <= size) { "Invalid from index: $fromIndex (list size $size)" } + require(toIndex <= size) { "Invalid to index: $toIndex (list size $size)" } + + if (fromIndex == size || fromIndex == toIndex || comparator.compare(element, this[fromIndex]) <= 0) { + return fromIndex + } + + // линейный поиск если границы маленькие + if (toIndex - fromIndex <= 10) { + for (i in fromIndex + 1 until toIndex) { + val compare = comparator.compare(element, this[i]) + + if (compare <= 0) { + return i + } + } + + return size + } else { + // двоичный поиск + var lower = fromIndex + var upper = toIndex - 1 + + while (upper - lower >= 10) { + val middle = (upper + lower) / 2 + val compare = comparator.compare(element, this[middle]) + + if (compare == 0) { + return middle + } else if (compare < 0) { + upper = middle + } else { + lower = middle + } + } + + return searchInsertionIndex(element, comparator, lower, upper + 1) + } +} + +/** + * Inserts [element] into [MutableList] at index determined by [comparator] + * + * If [MutableList] is not sorted, result of this function is undefined + */ +fun MutableList.addSorted(element: E, comparator: Comparator) { + add(searchInsertionIndex(element, comparator), element) +} + +/** + * Inserts [element] into [MutableList] at index determined by comparing values themselves + * + * If [MutableList] is not sorted, result of this function is undefined + */ +fun > MutableList.addSorted(element: E) { + add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element) +} + +fun lazy2(a: () -> A, b: A.() -> B): Supplier { + val first = lazy(a) + return Supplier { b.invoke(first.value) } +} + +inline val , T : Container> R.value get() = this diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/FloatSupplier.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/FloatSupplier.kt new file mode 100644 index 000000000..79c5bdd98 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/FloatSupplier.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.core + +fun interface FloatSupplier { + fun getAsFloat(): Float +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Formatting.kt deleted file mode 100644 index 70dbdba68..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Formatting.kt +++ /dev/null @@ -1,453 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import com.google.common.collect.ImmutableList -import net.minecraft.network.chat.Component -import java.math.BigDecimal -import java.math.BigInteger - -fun BigInteger.formatReadableNumber(): String { - if (isZero) { - return "0" - } - - val strValue = toString() - - val absLength = strValue.length - (if (isNegative) 1 else 0) - val remainder = absLength % 3 - var groups = absLength / 3 - - if (remainder == 0) { - groups-- - } - - val buffer = CharArray((if (remainder == 0) 3 else remainder) + groups * 4 + if (isNegative) 1 else 0) - var c = 0 - var index = buffer.size - 1 - - for (i in strValue.length - 1 downTo (if (isNegative) 1 else 0)) { - c++ - - if (c == 4) { - buffer[index] = ' ' - index-- - c = 1 - } - - buffer[index] = strValue[i] - index-- - } - - if (isNegative) { - buffer[index] = '-' - } - - return String(buffer) -} - -enum class SiPrefix( - val power: Int, - fractional: Boolean, - val symbol: Char, -) { - // multiplies - KILO (3, false, 'k'), - MEGA (6, false, 'M'), - GIGA (9, false, 'G'), - TERA (12, false, 'T'), - PETA (15, false, 'P'), - EXA (18, false, 'E'), - ZETTA(21, false, 'Z'), - YOTTA(24, false, 'Y'), - - // decimals - // DECI (1, true, 'd'), - // CENTI(2, true, 'c'), - MILLI(3, true, 'm'), - MICRO(6, true, 'μ'), - NANO (9, true, 'n'), - PICO (12, true, 'p'), - FEMTO(15, true, 'f'), - ATTO (18, true, 'a'), - ZEPTO(21, true, 'z'), - YOCTO(24, true, 'y'); - - val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern() - val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern() - - val string = if (fractional) "0." + "0".repeat(power - 1) + "1" else "1" + "0".repeat(power) - - fun paddedIndex(input: String, index: Int): Char { - val finalIndex = input.length - power + index - - if (finalIndex >= 0) { - return input[finalIndex] - } - - return '0' - } - - val decimal = BigDecimal(string) - val fraction = Fraction(decimal) - val impreciseFraction = Decimal(string) - val integer = if (!fractional) BigInteger(string) else null - - val long = if (!fractional) string.toLongOrNull() else null - val int = if (!fractional) string.toIntOrNull() else null - val double = string.toDouble() - - companion object { - val MULTIPLIES: List = ImmutableList.builder() - .add(KILO) - .add(MEGA) - .add(GIGA) - .add(TERA) - .add(PETA) - .add(EXA) - .add(ZETTA) - .add(YOTTA) - .build() - - val DECIMALS: List = ImmutableList.builder() - //.add(DECI) - //.add(CENTI) - .add(MILLI) - .add(MICRO) - .add(NANO) - .add(PICO) - .add(FEMTO) - .add(ATTO) - .add(ZEPTO) - .add(YOCTO) - .build() - - val DECIMALS_IMPRECISE: List = ImmutableList.builder() - //.add(DECI) - //.add(CENTI) - .add(MILLI) - .add(MICRO) - .build() - } -} - -fun BigDecimal.determineSiPrefix(): SiPrefix? { - if (isZero) { - return null - } - - var num = this - - if (isNegative) { - num = -this - } - - var prev: SiPrefix? = null - - if (num >= BigDecimal.ONE) { - for (value in SiPrefix.MULTIPLIES) { - if (value.decimal <= num) { - prev = value - } else { - break - } - } - } else { - for (value in SiPrefix.DECIMALS) { - if (value.decimal >= num) { - prev = value - } else { - break - } - } - } - - return prev -} - -fun BigDecimal.formatSiTranslatable(): Component { - if (isZero) { - return TextComponent("0.00") - } else if (this == BigDecimal.ONE) { - return TextComponent("1.00") - } - - return TextComponent("1.00") -} - -fun BigInteger.determineSiPrefix(): SiPrefix? { - if (isZero) { - return null - } - - var num = this - - if (isNegative) { - num = -this - } - - var prev: SiPrefix? = null - - if (num >= BigInteger.ONE) { - for (value in SiPrefix.MULTIPLIES) { - if (value.integer!! <= num) { - prev = value - } else { - break - } - } - } - - return prev -} - -fun BigInteger.formatSi(decimalPlaces: Int = 2): String { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return toString() // + "." + "0".repeat(decimalPlaces) - val isNegative = isNegative - val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.integer) - val divided = arr[0].toString() - val remainder = arr[1].toString() - - if (decimalPlaces == 0) { - if (isNegative) { - return "-" + divided + prefix.symbol - } else { - return divided + prefix.symbol - } - } - - @Suppress("NAME_SHADOWING") - val decimalPlaces = decimalPlaces.coerceAtMost(prefix.power) - - val add = (if (isNegative) 1 else 0) - - val buffer = CharArray(divided.length + 2 + decimalPlaces + add) - buffer[buffer.size - 1] = prefix.symbol - - if (isNegative) { - buffer[0] = '-' - } - - for (i in divided.indices) { - buffer[add + i] = divided[i] - } - - buffer[add + divided.length] = '.' - - for (i in 0 until decimalPlaces) { - buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i) - } - - return String(buffer) -} - -fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2): Component { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return if (suffix == "") TextComponent(toString(decimalPlaces)) else if (suffix is Component) TextComponent(toString(decimalPlaces) + suffix.string) else TextComponent(toString(decimalPlaces) + suffix) - val isNegative = isNegative - val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.integer) - val divided = arr[0].toString() - val remainder = arr[1].toString() - - if (decimalPlaces == 0) { - if (isNegative) { - return TranslatableComponent(prefix.formatLocaleKey, "-$divided", suffix) - } else { - return TranslatableComponent(prefix.formatLocaleKey, divided + prefix.symbol, suffix) - } - } - - @Suppress("NAME_SHADOWING") - val decimalPlaces = decimalPlaces.coerceAtMost(prefix.power) - - val add = (if (isNegative) 1 else 0) - - val buffer = CharArray(divided.length + 1 + decimalPlaces + add) - - if (isNegative) { - buffer[0] = '-' - } - - for (i in divided.indices) { - buffer[add + i] = divided[i] - } - - buffer[add + divided.length] = '.' - - for (i in 0 until decimalPlaces) { - buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i) - } - - return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix) -} - -fun Decimal.determineSiPrefix(): SiPrefix? { - if (isZero) { - return null - } - - var num = this - - if (isNegative) { - num = -this - } - - var prev: SiPrefix? = null - - if (num >= Decimal.ONE) { - for (value in SiPrefix.MULTIPLIES) { - if (value.impreciseFraction <= num) { - prev = value - } else { - break - } - } - } else { - for (value in SiPrefix.DECIMALS_IMPRECISE) { - if (value.impreciseFraction >= num) { - prev = value - } else { - break - } - } - } - - return prev -} - -fun Int.determineSiPrefix(): SiPrefix? { - if (this == 0) { - return null - } - - var num = this - - if (this < 0) { - num = -this - } - - var prev: SiPrefix? = null - - if (num >= 1) { - for (value in SiPrefix.MULTIPLIES) { - if (value.int != null && value.int <= num) { - prev = value - } else { - break - } - } - } - - return prev -} - -fun Double.determineSiPrefix(): SiPrefix? { - if (this == 0.0) { - return null - } - - var num = this - - if (this < 0) { - num = -this - } - - var prev: SiPrefix? = null - - if (num >= 1) { - for (value in SiPrefix.MULTIPLIES) { - if (value.double <= num) { - prev = value - } else { - break - } - } - } - - return prev -} - -fun Decimal.formatSi(decimalPlaces: Int = 2): String { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return toString(decimalPlaces) - return (this / prefix.impreciseFraction).toString(decimalPlaces) + prefix.symbol -} - -fun Int.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2): Component { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return if (suffix == "") TextComponent(toString()) else if (suffix is Component) TextComponent(toString() + " " + suffix.string) else TextComponent(toString() + " " + suffix) - return TranslatableComponent(prefix.formatLocaleKey, "%.${decimalPlaces}f".format(this.toFloat() / prefix.int!!.toFloat()), suffix) -} - -fun Double.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2): Component { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return if (suffix == "") TextComponent("%.${decimalPlaces}f".format(this)) else if (suffix is Component) TextComponent("%.${decimalPlaces}f".format(this) + " " + suffix.string) else TextComponent("%.${decimalPlaces}f".format(this) + " " + suffix) - return TranslatableComponent(prefix.formatLocaleKey, "%.${decimalPlaces}f".format(this / prefix.double), suffix) -} - -fun Decimal.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 2): Component { - require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } - val prefix = determineSiPrefix() ?: return if (suffix == "") TextComponent(toString(decimalPlaces)) else if (suffix is Component) TextComponent(toString(decimalPlaces) + " " + suffix.string) else TextComponent(toString(decimalPlaces) + " " + suffix) - return TranslatableComponent(prefix.formatLocaleKey, (this / prefix.impreciseFraction).toString(decimalPlaces), suffix) -} - -val POWER_NAME = TranslatableComponent("otm.gui.power.name") -val MATTER_NAME = TranslatableComponent("otm.gui.matter.name") - -fun Int.formatPower(decimalPlaces: Int = 2) = formatSiComponent(POWER_NAME, decimalPlaces) - -fun Decimal.formatPower(decimalPlaces: Int = 2) = formatSiComponent(POWER_NAME, decimalPlaces) -fun Decimal.formatMatter(decimalPlaces: Int = 2) = formatSiComponent(MATTER_NAME, decimalPlaces) -fun Decimal.formatMatterFull(decimalPlaces: Int = 2) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(MATTER_NAME, decimalPlaces)) - -fun BigInteger.formatPower(decimalPlaces: Int = 2) = formatSiComponent(POWER_NAME, decimalPlaces) -fun BigInteger.formatMatter(decimalPlaces: Int = 2) = formatSiComponent(MATTER_NAME, decimalPlaces) -fun BigInteger.formatMatterFull(decimalPlaces: Int = 2) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(MATTER_NAME, decimalPlaces)) - -fun formatPowerLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 2) = TranslatableComponent("otm.gui.level", a.formatPower(decimalPlaces), b.formatPower(decimalPlaces)) -fun formatMatterLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 2) = TranslatableComponent("otm.gui.level", a.formatMatter(decimalPlaces), b.formatMatter(decimalPlaces)) - -private fun padded(num: Int): String { - if (num in 0 .. 9) { - return "0$num" - } - - return num.toString() -} - -fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String { - @Suppress("name_shadowing") - var leftTicks = ticks - - // val mTicks = leftTicks % 20 - leftTicks /= 20 - - val seconds = padded(leftTicks % 60) - leftTicks /= 60 - - if (longFormat) { - if (ticks <= 0) { - return "00:00:00" - } else if (ticks <= 20) { - return ".00000${padded(ticks)}" - } - - val minutes = padded(leftTicks % 60) - leftTicks /= 60 - val hours = padded(leftTicks) - return "$hours:$minutes:$seconds" - } else { - if (ticks <= 0) { - return "00:00" - } else if (ticks <= 20) { - return ".00${padded(ticks)}" - } - - val minutes = leftTicks - - if (minutes > 99) { - return "**:**" - } - - return "${padded(minutes)}:$seconds" - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt deleted file mode 100644 index 4733891a5..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt +++ /dev/null @@ -1,931 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.nbt.ByteArrayTag -import net.minecraft.nbt.StringTag -import net.minecraft.nbt.Tag -import net.minecraft.network.FriendlyByteBuf -import org.apache.logging.log4j.LogManager -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode - -private fun powScale(int: Int): BigInteger { - if (int <= 0) - return BigInteger.ONE - - var result = BigInteger.TEN - - for (i in 2 .. int) - result *= BigInteger.TEN - - return result -} - -private fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger { - if (scale >= 0) - return unscaled - - var result = unscaled - - for (i in 2 .. -scale) - result *= BigInteger.TEN - - return result -} - -val DEFAULT_MATH_CONTEXT = MathContext(64, RoundingMode.HALF_UP) - -@Suppress("NOTHING_TO_INLINE") -private inline fun invertCompare(int: Int): Int { - if (int == 0) - return 0 - - return if (int < 0) 1 else -1 -} - -private val BI_MINUS_ZERO = BigInteger("-0") -private val BI_MINUS_ONE = BigInteger("-1") -private val BI_DIV = BigInteger.valueOf(1_000_000_000) -private val BI_DIV2 = BigInteger.valueOf(10) -private val BI_MINUS_DIV = BigInteger.valueOf(-1_000_000_000) - -private fun isZero(value: BigInteger): Boolean { - return when (value.signum()) { - 0 -> true - 1 -> value == BigInteger.ZERO - -1 -> value == BI_MINUS_ZERO - else -> false - } -} - -private fun isOne(value: BigInteger): Boolean { - return when (value.signum()) { - 0 -> false - 1 -> value == BigInteger.ONE - -1 -> value == BI_MINUS_ONE - else -> false - } -} - -private fun compactTwoMod(value1: BigInteger, value2: BigInteger, compact: Boolean = true): Fraction { - val mod = value1.divideAndRemainder(value2) - - if (isZero(mod[1])) - return Fraction(mod[0], compact = compact) - - return Fraction(value1, value2, compact = compact) -} - -private fun positive(value: BigInteger): BigInteger { - return when (value.signum()) { - 0 -> value - 1 -> value - -1 -> -value - else -> throw InternalError() - } -} - -private val GCDs = arrayOf( - BigInteger.valueOf(2), BigInteger.valueOf(3), BigInteger.valueOf(5), BigInteger.valueOf(7), BigInteger.valueOf(11), BigInteger.valueOf(13), BigInteger.valueOf(17), BigInteger.valueOf(19), BigInteger.valueOf(23), BigInteger.valueOf(29), BigInteger.valueOf(31), BigInteger.valueOf(37), BigInteger.valueOf(41), BigInteger.valueOf(43), BigInteger.valueOf(47), BigInteger.valueOf(53), BigInteger.valueOf(59), BigInteger.valueOf(61), BigInteger.valueOf(67), BigInteger.valueOf(71), - BigInteger.valueOf(73), BigInteger.valueOf(79), BigInteger.valueOf(83), BigInteger.valueOf(89), BigInteger.valueOf(97), BigInteger.valueOf(101), BigInteger.valueOf(103), BigInteger.valueOf(107), BigInteger.valueOf(109), BigInteger.valueOf(113), BigInteger.valueOf(127), BigInteger.valueOf(131), BigInteger.valueOf(137), BigInteger.valueOf(139), BigInteger.valueOf(149), BigInteger.valueOf(151), BigInteger.valueOf(157), BigInteger.valueOf(163), BigInteger.valueOf(167), BigInteger.valueOf(173), - BigInteger.valueOf(179), BigInteger.valueOf(181), BigInteger.valueOf(191), BigInteger.valueOf(193), BigInteger.valueOf(197), BigInteger.valueOf(199), BigInteger.valueOf(211), BigInteger.valueOf(223), BigInteger.valueOf(227), BigInteger.valueOf(229), BigInteger.valueOf(233), BigInteger.valueOf(239), BigInteger.valueOf(241), BigInteger.valueOf(251), BigInteger.valueOf(257), BigInteger.valueOf(263), BigInteger.valueOf(269), BigInteger.valueOf(271), BigInteger.valueOf(277), BigInteger.valueOf(281), - BigInteger.valueOf(283), BigInteger.valueOf(293), BigInteger.valueOf(307), BigInteger.valueOf(311), BigInteger.valueOf(313), BigInteger.valueOf(317), BigInteger.valueOf(331), BigInteger.valueOf(337), BigInteger.valueOf(347), BigInteger.valueOf(349), BigInteger.valueOf(353), BigInteger.valueOf(359), BigInteger.valueOf(367), BigInteger.valueOf(373), BigInteger.valueOf(379), BigInteger.valueOf(383), BigInteger.valueOf(389), BigInteger.valueOf(397), BigInteger.valueOf(401), BigInteger.valueOf(409), - BigInteger.valueOf(419), BigInteger.valueOf(421), BigInteger.valueOf(431), BigInteger.valueOf(433), BigInteger.valueOf(439), BigInteger.valueOf(443), BigInteger.valueOf(449), BigInteger.valueOf(457), BigInteger.valueOf(461), BigInteger.valueOf(463), BigInteger.valueOf(467), BigInteger.valueOf(479), BigInteger.valueOf(487), BigInteger.valueOf(491), BigInteger.valueOf(499), BigInteger.valueOf(503), BigInteger.valueOf(509), BigInteger.valueOf(521), BigInteger.valueOf(523), BigInteger.valueOf(541), - BigInteger.valueOf(547), BigInteger.valueOf(557), BigInteger.valueOf(563), BigInteger.valueOf(569), BigInteger.valueOf(571), BigInteger.valueOf(577), BigInteger.valueOf(587), BigInteger.valueOf(593), BigInteger.valueOf(599), BigInteger.valueOf(601), BigInteger.valueOf(607), BigInteger.valueOf(613), BigInteger.valueOf(617), BigInteger.valueOf(619), BigInteger.valueOf(631), BigInteger.valueOf(641), BigInteger.valueOf(643), BigInteger.valueOf(647), BigInteger.valueOf(653), BigInteger.valueOf(659), - BigInteger.valueOf(661), BigInteger.valueOf(673), BigInteger.valueOf(677), BigInteger.valueOf(683), BigInteger.valueOf(691), BigInteger.valueOf(701), BigInteger.valueOf(709), BigInteger.valueOf(719), BigInteger.valueOf(727), BigInteger.valueOf(733), BigInteger.valueOf(739), BigInteger.valueOf(743), BigInteger.valueOf(751), BigInteger.valueOf(757), BigInteger.valueOf(761), BigInteger.valueOf(769), BigInteger.valueOf(773), BigInteger.valueOf(787), BigInteger.valueOf(797), BigInteger.valueOf(809), - BigInteger.valueOf(811), BigInteger.valueOf(821), BigInteger.valueOf(823), BigInteger.valueOf(827), BigInteger.valueOf(829), BigInteger.valueOf(839), BigInteger.valueOf(853), BigInteger.valueOf(857), BigInteger.valueOf(859), BigInteger.valueOf(863), BigInteger.valueOf(877), BigInteger.valueOf(881), BigInteger.valueOf(883), BigInteger.valueOf(887), BigInteger.valueOf(907), BigInteger.valueOf(911), BigInteger.valueOf(919), BigInteger.valueOf(929), BigInteger.valueOf(937), BigInteger.valueOf(941), - BigInteger.valueOf(947), BigInteger.valueOf(953), BigInteger.valueOf(967), BigInteger.valueOf(971), BigInteger.valueOf(977), BigInteger.valueOf(983), BigInteger.valueOf(991), BigInteger.valueOf(997), BigInteger.valueOf(1009), BigInteger.valueOf(1013), BigInteger.valueOf(1019), BigInteger.valueOf(1021), BigInteger.valueOf(1031), BigInteger.valueOf(1033), BigInteger.valueOf(1039), BigInteger.valueOf(1049), BigInteger.valueOf(1051), BigInteger.valueOf(1061), BigInteger.valueOf(1063), BigInteger.valueOf(1069), - BigInteger.valueOf(1087), BigInteger.valueOf(1091), BigInteger.valueOf(1093), BigInteger.valueOf(1097), BigInteger.valueOf(1103), BigInteger.valueOf(1109), BigInteger.valueOf(1117), BigInteger.valueOf(1123), BigInteger.valueOf(1129), BigInteger.valueOf(1151), BigInteger.valueOf(1153), BigInteger.valueOf(1163), BigInteger.valueOf(1171), BigInteger.valueOf(1181), BigInteger.valueOf(1187), BigInteger.valueOf(1193), BigInteger.valueOf(1201), BigInteger.valueOf(1213), BigInteger.valueOf(1217), BigInteger.valueOf(1223), - BigInteger.valueOf(1229), BigInteger.valueOf(1231), BigInteger.valueOf(1237), BigInteger.valueOf(1249), BigInteger.valueOf(1259), BigInteger.valueOf(1277), BigInteger.valueOf(1279), BigInteger.valueOf(1283), BigInteger.valueOf(1289), BigInteger.valueOf(1291), BigInteger.valueOf(1297), BigInteger.valueOf(1301), BigInteger.valueOf(1303), BigInteger.valueOf(1307), BigInteger.valueOf(1319), BigInteger.valueOf(1321), BigInteger.valueOf(1327), BigInteger.valueOf(1361), BigInteger.valueOf(1367), BigInteger.valueOf(1373), - BigInteger.valueOf(1381), BigInteger.valueOf(1399), BigInteger.valueOf(1409), BigInteger.valueOf(1423), BigInteger.valueOf(1427), BigInteger.valueOf(1429), BigInteger.valueOf(1433), BigInteger.valueOf(1439), BigInteger.valueOf(1447), BigInteger.valueOf(1451), BigInteger.valueOf(1453), BigInteger.valueOf(1459), BigInteger.valueOf(1471), BigInteger.valueOf(1481), BigInteger.valueOf(1483), BigInteger.valueOf(1487), BigInteger.valueOf(1489), BigInteger.valueOf(1493), BigInteger.valueOf(1499), BigInteger.valueOf(1511), - BigInteger.valueOf(1523), BigInteger.valueOf(1531), BigInteger.valueOf(1543), BigInteger.valueOf(1549), BigInteger.valueOf(1553), BigInteger.valueOf(1559), BigInteger.valueOf(1567), BigInteger.valueOf(1571), BigInteger.valueOf(1579), BigInteger.valueOf(1583), BigInteger.valueOf(1597), BigInteger.valueOf(1601), BigInteger.valueOf(1607), BigInteger.valueOf(1609), BigInteger.valueOf(1613), BigInteger.valueOf(1619), BigInteger.valueOf(1621), BigInteger.valueOf(1627), BigInteger.valueOf(1637), BigInteger.valueOf(1657), - BigInteger.valueOf(1663), BigInteger.valueOf(1667), BigInteger.valueOf(1669), BigInteger.valueOf(1693), BigInteger.valueOf(1697), BigInteger.valueOf(1699), BigInteger.valueOf(1709), BigInteger.valueOf(1721), BigInteger.valueOf(1723), BigInteger.valueOf(1733), BigInteger.valueOf(1741), BigInteger.valueOf(1747), BigInteger.valueOf(1753), BigInteger.valueOf(1759), BigInteger.valueOf(1777), BigInteger.valueOf(1783), BigInteger.valueOf(1787), BigInteger.valueOf(1789), BigInteger.valueOf(1801), BigInteger.valueOf(1811), - BigInteger.valueOf(1823), BigInteger.valueOf(1831), BigInteger.valueOf(1847), BigInteger.valueOf(1861), BigInteger.valueOf(1867), BigInteger.valueOf(1871), BigInteger.valueOf(1873), BigInteger.valueOf(1877), BigInteger.valueOf(1879), BigInteger.valueOf(1889), BigInteger.valueOf(1901), BigInteger.valueOf(1907), BigInteger.valueOf(1913), BigInteger.valueOf(1931), BigInteger.valueOf(1933), BigInteger.valueOf(1949), BigInteger.valueOf(1951), BigInteger.valueOf(1973), BigInteger.valueOf(1979), BigInteger.valueOf(1987), - BigInteger.valueOf(1993), BigInteger.valueOf(1997), BigInteger.valueOf(1999), BigInteger.valueOf(2003), BigInteger.valueOf(2011), BigInteger.valueOf(2017), BigInteger.valueOf(2027), BigInteger.valueOf(2029), BigInteger.valueOf(2039), BigInteger.valueOf(2053), BigInteger.valueOf(2063), BigInteger.valueOf(2069), BigInteger.valueOf(2081), BigInteger.valueOf(2083), BigInteger.valueOf(2087), BigInteger.valueOf(2089), BigInteger.valueOf(2099), BigInteger.valueOf(2111), BigInteger.valueOf(2113), BigInteger.valueOf(2129), - BigInteger.valueOf(2131), BigInteger.valueOf(2137), BigInteger.valueOf(2141), BigInteger.valueOf(2143), BigInteger.valueOf(2153), BigInteger.valueOf(2161), BigInteger.valueOf(2179), BigInteger.valueOf(2203), BigInteger.valueOf(2207), BigInteger.valueOf(2213), BigInteger.valueOf(2221), BigInteger.valueOf(2237), BigInteger.valueOf(2239), BigInteger.valueOf(2243), BigInteger.valueOf(2251), BigInteger.valueOf(2267), BigInteger.valueOf(2269), BigInteger.valueOf(2273), BigInteger.valueOf(2281), BigInteger.valueOf(2287), - BigInteger.valueOf(2293), BigInteger.valueOf(2297), BigInteger.valueOf(2309), BigInteger.valueOf(2311), BigInteger.valueOf(2333), BigInteger.valueOf(2339), BigInteger.valueOf(2341), BigInteger.valueOf(2347), BigInteger.valueOf(2351), BigInteger.valueOf(2357), BigInteger.valueOf(2371), BigInteger.valueOf(2377), BigInteger.valueOf(2381), BigInteger.valueOf(2383), BigInteger.valueOf(2389), BigInteger.valueOf(2393), BigInteger.valueOf(2399), BigInteger.valueOf(2411), BigInteger.valueOf(2417), BigInteger.valueOf(2423), - BigInteger.valueOf(2437), BigInteger.valueOf(2441), BigInteger.valueOf(2447), BigInteger.valueOf(2459), BigInteger.valueOf(2467), BigInteger.valueOf(2473), BigInteger.valueOf(2477), BigInteger.valueOf(2503), BigInteger.valueOf(2521), BigInteger.valueOf(2531), BigInteger.valueOf(2539), BigInteger.valueOf(2543), BigInteger.valueOf(2549), BigInteger.valueOf(2551), BigInteger.valueOf(2557), BigInteger.valueOf(2579), BigInteger.valueOf(2591), BigInteger.valueOf(2593), BigInteger.valueOf(2609), BigInteger.valueOf(2617), - BigInteger.valueOf(2621), BigInteger.valueOf(2633), BigInteger.valueOf(2647), BigInteger.valueOf(2657), BigInteger.valueOf(2659), BigInteger.valueOf(2663), BigInteger.valueOf(2671), BigInteger.valueOf(2677), BigInteger.valueOf(2683), BigInteger.valueOf(2687), BigInteger.valueOf(2689), BigInteger.valueOf(2693), BigInteger.valueOf(2699), BigInteger.valueOf(2707), BigInteger.valueOf(2711), BigInteger.valueOf(2713), BigInteger.valueOf(2719), BigInteger.valueOf(2729), BigInteger.valueOf(2731), BigInteger.valueOf(2741), - BigInteger.valueOf(2749), BigInteger.valueOf(2753), BigInteger.valueOf(2767), BigInteger.valueOf(2777), BigInteger.valueOf(2789), BigInteger.valueOf(2791), BigInteger.valueOf(2797), BigInteger.valueOf(2801), BigInteger.valueOf(2803), BigInteger.valueOf(2819), BigInteger.valueOf(2833), BigInteger.valueOf(2837), BigInteger.valueOf(2843), BigInteger.valueOf(2851), BigInteger.valueOf(2857), BigInteger.valueOf(2861), BigInteger.valueOf(2879), BigInteger.valueOf(2887), BigInteger.valueOf(2897), BigInteger.valueOf(2903), - BigInteger.valueOf(2909), BigInteger.valueOf(2917), BigInteger.valueOf(2927), BigInteger.valueOf(2939), BigInteger.valueOf(2953), BigInteger.valueOf(2957), BigInteger.valueOf(2963), BigInteger.valueOf(2969), BigInteger.valueOf(2971), BigInteger.valueOf(2999), BigInteger.valueOf(3001), BigInteger.valueOf(3011), BigInteger.valueOf(3019), BigInteger.valueOf(3023), BigInteger.valueOf(3037), BigInteger.valueOf(3041), BigInteger.valueOf(3049), BigInteger.valueOf(3061), BigInteger.valueOf(3067), BigInteger.valueOf(3079), - BigInteger.valueOf(3083), BigInteger.valueOf(3089), BigInteger.valueOf(3109), BigInteger.valueOf(3119), BigInteger.valueOf(3121), BigInteger.valueOf(3137), BigInteger.valueOf(3163), BigInteger.valueOf(3167), BigInteger.valueOf(3169), BigInteger.valueOf(3181), BigInteger.valueOf(3187), BigInteger.valueOf(3191), BigInteger.valueOf(3203), BigInteger.valueOf(3209), BigInteger.valueOf(3217), BigInteger.valueOf(3221), BigInteger.valueOf(3229), BigInteger.valueOf(3251), BigInteger.valueOf(3253), BigInteger.valueOf(3257), - BigInteger.valueOf(3259), BigInteger.valueOf(3271), BigInteger.valueOf(3299), BigInteger.valueOf(3301), BigInteger.valueOf(3307), BigInteger.valueOf(3313), BigInteger.valueOf(3319), BigInteger.valueOf(3323), BigInteger.valueOf(3329), BigInteger.valueOf(3331), BigInteger.valueOf(3343), BigInteger.valueOf(3347), BigInteger.valueOf(3359), BigInteger.valueOf(3361), BigInteger.valueOf(3371), BigInteger.valueOf(3373), BigInteger.valueOf(3389), BigInteger.valueOf(3391), BigInteger.valueOf(3407), BigInteger.valueOf(3413), - BigInteger.valueOf(3433), BigInteger.valueOf(3449), BigInteger.valueOf(3457), BigInteger.valueOf(3461), BigInteger.valueOf(3463), BigInteger.valueOf(3467), BigInteger.valueOf(3469), BigInteger.valueOf(3491), BigInteger.valueOf(3499), BigInteger.valueOf(3511), BigInteger.valueOf(3517), BigInteger.valueOf(3527), BigInteger.valueOf(3529), BigInteger.valueOf(3533), BigInteger.valueOf(3539), BigInteger.valueOf(3541), BigInteger.valueOf(3547), BigInteger.valueOf(3557), BigInteger.valueOf(3559), BigInteger.valueOf(3571), -) - -private val COMPACT_THRESHOLD = BigInteger("1000000000000000000000") - -@Suppress("NAME_SHADOWING") -private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean = true): Fraction { - when (value1.signum()) { - 0 -> return Fraction(value1, value2, compact = compact) - 1 -> if (value1 < BI_DIV) return compactTwoMod(value1, value2, compact) - -1 -> if (value1 > BI_MINUS_DIV) return compactTwoMod(value1, value2, compact) - } - - when (value2.signum()) { - 0 -> return Fraction(value1, value2, compact = compact) - 1 -> if (value2 < BI_DIV) return compactTwoMod(value1, value2, compact) - -1 -> if (value2 > BI_MINUS_DIV) return compactTwoMod(value1, value2, compact) - } - - var value1 = value1 - var value2 = value2 - - while (true) { - val _a = value1.divideAndRemainder(BI_DIV) - val _b = value2.divideAndRemainder(BI_DIV) - - if (!isZero(_a[1]) || !isZero(_b[1])) - break - - value1 = _a[0] - value2 = _b[0] - } - - while (true) { - val _a = value1.divideAndRemainder(BI_DIV2) - val _b = value2.divideAndRemainder(BI_DIV2) - - if (!isZero(_a[1]) || !isZero(_b[1])) - break - - value1 = _a[0] - value2 = _b[0] - } - - val p1 = positive(value1) - val p2 = positive(value2) - - if (p1 > COMPACT_THRESHOLD || p2 > COMPACT_THRESHOLD) { - var hit = true - - while (hit) { - hit = false - - for (i in GCDs.size - 1 downTo 0) { - val div = GCDs[i] - - while (true) { - val _a = value1.divideAndRemainder(div) - val _b = value2.divideAndRemainder(div) - - if (!isZero(_a[1]) || !isZero(_b[1])) - break - - value1 = _a[0] - value2 = _b[0] - hit = true - } - } - } - } else if (p1 > GCDs[0] && p2 > GCDs[0]) { - var hit = true - - while (hit) { - hit = false - - for (i in 4 downTo 0) { - val div = GCDs[i] - - while (true) { - val _a = value1.divideAndRemainder(div) - val _b = value2.divideAndRemainder(div) - - if (!isZero(_a[1]) || !isZero(_b[1])) - break - - value1 = _a[0] - value2 = _b[0] - hit = true - } - } - } - } - - return compactTwoMod(value1, value2, compact) -} - -private fun unsignedInt(value: Byte): Int { - if (value < 0) { - return 256 + value - } - - return value.toInt() -} - -private data class MagnitudeCrunchResult(val value: BigInteger, val mag: Int) - -@Suppress("NAME_SHADOWING") -private fun magnitude(value: BigInteger): MagnitudeCrunchResult { - if (isZero(value)) - return MagnitudeCrunchResult(value, 0) - - var mag = 0 - var value = value - - while (true) { - val c = value.divideAndRemainder(BI_DIV2) - - if (!isZero(c[1])) - break - - value = c[0] - mag++ - } - - return MagnitudeCrunchResult(value, mag) -} - -@Suppress("unused") -class Fraction @JvmOverloads constructor( - val value: BigInteger, - val divisor: BigInteger = BigInteger.ONE, - val compact: Boolean = true -) : Number(), Comparable { - @JvmOverloads constructor(value: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), compact = compact) - @JvmOverloads constructor(value: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), compact = compact) - @JvmOverloads constructor(value: Float, compact: Boolean = true) : this(BigDecimal(value.toString()), compact = compact) - @JvmOverloads constructor(value: Double, compact: Boolean = true) : this(BigDecimal(value.toString()), compact = compact) - @JvmOverloads constructor(value: String, compact: Boolean = true) : this(BigDecimal(value), compact = compact) - - @JvmOverloads constructor(value: Long, div: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), BigInteger.valueOf(div), compact = compact) - @JvmOverloads constructor(value: Int, div: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), BigInteger.valueOf(div.toLong()), compact = compact) - @JvmOverloads constructor(value: Float, div: Float, compact: Boolean = true) : this(BigDecimal(value.toString()), BigDecimal(div.toString()), compact = compact) - @JvmOverloads constructor(value: Double, div: Double, compact: Boolean = true) : this(BigDecimal(value.toString()), BigDecimal(div.toString()), compact = compact) - @JvmOverloads constructor(value: String, div: String, compact: Boolean = true) : this(BigDecimal(value), BigDecimal(div), compact = compact) - - @JvmOverloads constructor(value: BigDecimal, compact: Boolean = true) : this(powUnscaled(value.unscaledValue(), value.scale()), powScale(value.scale()), compact = compact) - @JvmOverloads constructor(value: BigDecimal, div: BigDecimal, compact: Boolean = true) : this(powUnscaled(value.unscaledValue(), value.scale()).multiply(powScale(div.scale())), powScale(value.scale()).multiply(powUnscaled(div.unscaledValue(), div.scale())), compact = compact) - - override fun equals(other: Any?): Boolean { - if (other is Fraction) { - if (other.divisor == divisor) - return other.value == value - - val a = value * other.divisor - val b = other.value * divisor - - return a == b - } - - return false - } - - override fun hashCode(): Int { - return 31 * value.hashCode() + divisor.hashCode() - } - - fun compactAndCanonize(): Fraction { - if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE) - return this - - val a = signnum() - val b = value.signum() - val c = divisor.signum() - - if (a != b && a != c) { - if (isZero(divisor)) - return Fraction(-value, -divisor) - - val mod = value.divideAndRemainder(divisor) - - if (isZero(mod[1])) - return Fraction(mod[0], compact = compact) - - return Fraction(-value, -divisor) - } - - if (isZero(divisor)) - return this - - return compactTwo(value, divisor, compact) - } - - fun isZero(): Boolean { - if (isNaN()) return false - return value == BigInteger.ZERO - } - - fun isOne(): Boolean { - if (isNaN()) return false - return value != BigInteger.ZERO && value == divisor - } - - fun compact(): Fraction { - if (isNaN()) return this - - if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE) - return this - - if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO) - return this - - return compactTwo(value, divisor, compact) - } - - fun canonize(): Fraction { - if (isNaN()) return this - - if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE || divisor == BigInteger.ZERO) - return this - - val a = signnum() - val b = value.signum() - val c = divisor.signum() - - if (a != b && a != c) - return Fraction(-value, -divisor) - - return this - } - - // Операторы - fun equalsCompact(other: Fraction?): Boolean { - if (other == null) - return false - - if (isNaN() || other.isNaN()) return false - - val a = compact() - val b = other.compact() - - return a.value == b.value && a.divisor == b.divisor - } - - override operator fun compareTo(other: Fraction): Int { - if (isNaN() || other.isNaN()) return 0 - - if (divisor == other.divisor) - return value.compareTo(other.value) - - val a = signnum() - val b = other.signnum() - - if (a == b && a == 0) - return 0 - - if (a < b) - return -1 - else if (a > b) - return 1 - - val cmp = (value * other.divisor).compareTo(other.value * divisor) - - if (a != value.signum() && a != divisor.signum()) { - if (b != other.value.signum() && b != other.divisor.signum()) { - return cmp - } - - return invertCompare(cmp) - } else if (b != other.value.signum() && b != other.divisor.signum()) { - return invertCompare(cmp) - } - - return cmp - } - - private fun plusCompact(other: Fraction): Fraction { - if (isNaN()) return this - - if (divisor == other.divisor) { - if (isOne(divisor)) - return Fraction(value + other.value, divisor) - - val new = value + other.value - - if (new.compareTo(BigInteger.ZERO) == 0) - return ZERO - - if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO) - return Fraction(new, divisor) - - return compactTwo(new, divisor) - } - - val new = value * other.divisor + other.value * divisor - val div = divisor * other.divisor - - if (div == BigInteger.ZERO || div == BI_MINUS_ZERO) - return Fraction(new, div) - - return compactTwo(new, div) - } - - operator fun plus(other: Fraction): Fraction { - if (isNaN()) return this - if (other.isNaN()) return other - - if (compact) - return plusCompact(other) - - if (divisor == other.divisor) { - return Fraction(value + other.value, divisor, compact = false) - } - - return Fraction(value * other.divisor + other.value * divisor, divisor * other.divisor, compact = false) - } - - private fun minusCompact(other: Fraction): Fraction { - if (divisor == other.divisor) { - if (isOne(divisor)) - return Fraction(value - other.value, divisor) - - val new = value - other.value - - if (new.compareTo(BigInteger.ZERO) == 0) - return ZERO - - if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO) - return Fraction(new, divisor) - - return compactTwo(new, divisor) - } - - val new = value * other.divisor - other.value * divisor - val div = divisor * other.divisor - - if (div == BigInteger.ZERO || div == BI_MINUS_ZERO) - return Fraction(new, div) - - return compactTwo(new, div) - } - - operator fun minus(other: Fraction): Fraction { - if (isNaN()) return this - if (other.isNaN()) return other - - if (compact) - return minusCompact(other) - - if (divisor == other.divisor) { - return Fraction(value - other.value, divisor, compact = false) - } - - return Fraction(value * other.divisor - other.value * divisor, divisor * other.divisor, compact = false) - } - - private fun timesCompact(other: Fraction): Fraction { - val new = value * other.value - - if (new.compareTo(BigInteger.ZERO) == 0) - return ZERO - - val div = divisor * other.divisor - - if (div == BigInteger.ZERO || div == BI_MINUS_ZERO) - return Fraction(new, div) - - return compactTwo(new, div) - } - - operator fun times(other: Fraction): Fraction { - if (isNaN()) return this - if (other.isNaN()) return other - if (other.isOne()) return this - - if (compact) - return timesCompact(other) - - return Fraction(value * other.value, divisor * other.divisor, compact = false) - } - - private fun divCompact(other: Fraction): Fraction { - val new = value * other.divisor - - if (new.compareTo(BigInteger.ZERO) == 0) - return ZERO - - val div = divisor * other.value - - if (div == BigInteger.ZERO || div == BI_MINUS_ZERO) - return Fraction(new, div) - - return compactTwo(new, div) - } - - operator fun div(other: Fraction): Fraction { - if (isNaN()) return this - if (other.isNaN()) return other - if (other.isOne()) return this - - if (compact) - return divCompact(other) - - return Fraction(value * other.divisor, divisor * other.value, compact = false) - } - - operator fun unaryMinus(): Fraction { - if (isNaN()) return this - return Fraction(-value, divisor) - } - - operator fun unaryPlus(): Fraction { - return this - } - - operator fun plus(other: Float): Fraction = plus(Fraction(other)) - operator fun minus(other: Float): Fraction = minus(Fraction(other)) - operator fun times(other: Float): Fraction = times(Fraction(other)) - operator fun div(other: Float): Fraction = div(Fraction(other)) - - operator fun plus(other: Double): Fraction = plus(Fraction(other)) - operator fun minus(other: Double): Fraction = minus(Fraction(other)) - operator fun times(other: Double): Fraction = times(Fraction(other)) - operator fun div(other: Double): Fraction = div(Fraction(other)) - - // может вызвать путаницу - /* - operator fun plus(other: BigDecimal): Fraction = plus(Fraction(other)) - operator fun minus(other: BigDecimal): Fraction = minus(Fraction(other)) - operator fun times(other: BigDecimal): Fraction = times(Fraction(other)) - operator fun div(other: BigDecimal): Fraction = div(Fraction(other)) - - operator fun plus(other: BigInteger): Fraction = plus(Fraction(other)) - operator fun minus(other: BigInteger): Fraction = minus(Fraction(other)) - operator fun times(other: BigInteger): Fraction = times(Fraction(other)) - operator fun div(other: BigInteger): Fraction = div(Fraction(other)) - */ - - operator fun plus(other: Int): Fraction = plus(Fraction(other)) - operator fun minus(other: Int): Fraction = minus(Fraction(other)) - operator fun times(other: Int): Fraction = times(Fraction(other)) - operator fun div(other: Int): Fraction = div(Fraction(other)) - - operator fun plus(other: Long): Fraction = plus(Fraction(other)) - operator fun minus(other: Long): Fraction = minus(Fraction(other)) - operator fun times(other: Long): Fraction = times(Fraction(other)) - operator fun div(other: Long): Fraction = div(Fraction(other)) - - /* - fun add(other: Float): Fraction = plus(Fraction(other)) - fun subtract(other: Float): Fraction = minus(Fraction(other)) - fun multiply(other: Float): Fraction = times(Fraction(other)) - fun divide(other: Float): Fraction = div(Fraction(other)) - - fun add(other: Double): Fraction = plus(Fraction(other)) - fun subtract(other: Double): Fraction = minus(Fraction(other)) - fun multiply(other: Double): Fraction = times(Fraction(other)) - fun divide(other: Double): Fraction = div(Fraction(other)) - - fun add(other: Int): Fraction = plus(Fraction(other)) - fun subtract(other: Int): Fraction = minus(Fraction(other)) - fun multiply(other: Int): Fraction = times(Fraction(other)) - fun divide(other: Int): Fraction = div(Fraction(other)) - - fun add(other: Long): Fraction = plus(Fraction(other)) - fun subtract(other: Long): Fraction = minus(Fraction(other)) - fun multiply(other: Long): Fraction = times(Fraction(other)) - fun divide(other: Long): Fraction = div(Fraction(other)) - - fun add(other: Fraction): Fraction = plus(other) - fun subtract(other: Fraction): Fraction = minus(other) - fun multiply(other: Fraction): Fraction = times(other) - fun divide(other: Fraction): Fraction = div(other) - */ - - operator fun rem(other: Fraction): Fraction { - if (isNaN()) return this - if (other.isNaN()) return other - return Fraction((this / other).wholePart()) - } - - // Преобразования - override fun toFloat(): Float { - if (isNaN()) return Float.NaN - return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat()) - } - - override fun toDouble(): Double { - if (isNaN()) return Double.NaN - return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble()) - } - - fun toImpreciseFraction(): Decimal { - if (isNaN()) - return Decimal.NaN - - val div = value.divideAndRemainder(divisor) - val a = divisor.toDouble() - val b = div[1].toDouble() - - if (b == 0.0 || !a.isFinite() || !b.isFinite()) { - return Decimal(div[0]) - } - - val div2 = a / b - - if (div2.isFinite() && !div2.isNaN()) { - return Decimal(div[0], div2) - } - - return Decimal(div[0]) - } - - fun toByteArray(): ByteArray { - if (isNaN()) { - return byteArrayOf(1, 0, 0, 0, 0, 1, 0, 0, 0, 0) - } - - val magValue = magnitude(value) - val magDiv = magnitude(divisor) - - val bytesA = magValue.value.toByteArray() - val bytesB = magDiv.value.toByteArray() - - return byteArrayOf( - (bytesA.size and 0xFF).toByte(), - (bytesA.size ushr 8).toByte(), - - (magValue.mag and 0xFF).toByte(), - (magValue.mag ushr 8).toByte(), - - *bytesA, - - (bytesB.size and 0xFF).toByte(), - (bytesB.size ushr 8).toByte(), - - (magDiv.mag and 0xFF).toByte(), - (magDiv.mag ushr 8).toByte(), - - *bytesB - ) - } - - override fun toInt(): Int { - if (isNaN()) throw ArithmeticException("Fraction is not a number") - return (value / divisor).toInt() - } - - override fun toLong(): Long { - if (isNaN()) throw ArithmeticException("Fraction is not a number") - return (value / divisor).toLong() - } - - override fun toByte(): Byte { - return toInt().toByte() - } - - override fun toChar(): Char { - return toInt().toChar() - } - - override fun toShort(): Short { - return toInt().toShort() - } - - @JvmOverloads - fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal { - if (isNaN()) throw ArithmeticException("Fraction is not a number") - return BigDecimal(value).divide(BigDecimal(divisor), context) - } - - // Утилиты - fun wholePart(): BigInteger { - if (isNaN()) throw ArithmeticException("Fraction is not a number") - return value / divisor - } - - fun fractionPart(): Fraction { - if (isNaN()) return this - return Fraction(value % divisor, divisor) - } - - fun modPart(): BigInteger { - if (isNaN()) throw ArithmeticException("Fraction is not a number") - return value % divisor - } - - override fun toString(): String { - if (isNaN()) return "NaN" - - return "$value/$divisor" - } - - fun formattedString(): String { - if (isNaN()) return "NaN" - return "${wholePart()} ${modPart()}/$divisor" - } - - @JvmOverloads - fun decimalString(nums: Int = 2, strict: Boolean = false): String { - if (isNaN()) return "NaN" - - val whole = wholePart() - val fraction = modPart() - - if (fraction == BigInteger.ZERO) { - if (!strict) - return whole.toString() - - return "${whole}.${"0".repeat(nums)}" - } - - var strdec = (fraction.toDouble() / divisor.toDouble()).toString() - - if (strdec.length == 1) { - strdec = "" - - if (strict) { - strdec = "0".repeat(nums) - } - } else { - strdec = strdec.substring(2) - - if (strict && strdec.length < nums) { - strdec += "0".repeat(nums - strdec.length) - } else if (strdec.length > nums) { - strdec = strdec.substring(0, nums) - } - } - - if (strdec == "") { - return whole.toString() - } - - return "${whole}.$strdec" - } - - fun signnum(): Int { - if (isNaN()) return 0 - - val a = value.signum() - val b = divisor.signum() - - if (a == b) { - if (a == 0) - return 0 - - return 1 - } - - return -1 - } - - fun serializeNBT(): ByteArrayTag { - return ByteArrayTag(toByteArray()) - } - - fun isNaN(): Boolean { - return divisor == BigInteger.ZERO - } - - fun write(buff: FriendlyByteBuf) { - buff.writeByteArray(toByteArray()) - } - - fun max(vararg others: Fraction): Fraction { - var max = this - - for (other in others) { - if (max < other) { - max = other - } - } - - return max - } - - fun min(vararg others: Fraction): Fraction { - var min = this - - for (other in others) { - if (min > other) { - min = other - } - } - - return min - } - - fun moreThanZero(): Fraction { - if (signnum() >= 0) - return this - - return ZERO - } - - fun lessOrZero(): Fraction { - if (signnum() <= 0) - return this - - return ZERO - } - - // Позволяет получить процент от деления данного на divisor с точностью до 5 цифр - fun percentage(divisor: Fraction, zeroing: Boolean = true): Float { - if (divisor.isZero() && zeroing) return 0f - if (isNaN() || divisor.isNaN()) return Float.NaN - - val mul = (this * TEN_THOUSAND) / divisor - if (mul.isNaN()) return Float.NaN - - return mul.wholePart().toFloat() / 10_000f - } - - companion object { - private val LOGGER = LogManager.getLogger() - - @JvmField - val ZERO = Fraction(BigInteger.ZERO) - - @JvmField - val NaN = Fraction(BigInteger.ZERO, BigInteger.ZERO) - - @JvmField - val ONE_HUNDRED = Fraction(BigInteger.valueOf(100)) - - @JvmField - val TEN_THOUSAND = Fraction(BigInteger.valueOf(10_000)) - - @JvmField - val HALF = Fraction(BigInteger.ONE, BigInteger.TWO) - - @JvmField - val THIRD = Fraction(BigInteger.ONE, BigInteger.valueOf(3)) - - @JvmField - val ONE = Fraction(BigInteger.ONE) - - @JvmField - val TWO = Fraction(BigInteger.TWO) - - @JvmField - val MINUS_ONE = Fraction(-1) - - @JvmField - val TEN = Fraction(BigInteger.TEN) - - @JvmField - val INT_MAX_VALUE = Fraction(Int.MAX_VALUE) - - @JvmField - val LONG_MAX_VALUE = Fraction(Long.MAX_VALUE) - - @JvmStatic - fun read(buff: FriendlyByteBuf): Fraction { - return fromByteArray(buff.readByteArray()) - } - - @JvmStatic - fun fromByteArray(bytes: ByteArray): Fraction { - try { - val rangeA = unsignedInt(bytes[0]) or (unsignedInt(bytes[1]) shl 8) - val magA = unsignedInt(bytes[2]) or (unsignedInt(bytes[3]) shl 8) - - val bytesA = bytes.copyOfRange(4, 4 + rangeA) - - val offsetB = 4 + rangeA - - val rangeB = unsignedInt(bytes[offsetB]) or (unsignedInt(bytes[offsetB + 1]) shl 8) - val magB = unsignedInt(bytes[offsetB + 2]) or (unsignedInt(bytes[offsetB + 3]) shl 8) - - val bytesB = bytes.copyOfRange(offsetB + 4, offsetB + 4 + rangeB) - - if (bytesB.isEmpty()) - return NaN - - return Fraction(if (bytesA.isNotEmpty()) (BigInteger(bytesA) * BigInteger("1" + "0".repeat(magA))) else BigInteger.ZERO, BigInteger(bytesB) * BigInteger("1" + "0".repeat(magB))) - } catch(err: Throwable) { - LOGGER.error("Unable to load Fraction from byte array", err) - return NaN - } - } - - @JvmStatic - fun deserializeNBT(bytesTag: ByteArrayTag): Fraction { - val bytes = bytesTag.asByteArray - return fromByteArray(bytes) - } - - // Преобразование строки в дробь с подавлением ошибок форматирования - @JvmStatic - fun fromString(str: String): Fraction { - try { - return Fraction(BigDecimal(str)) - } catch(err: Throwable) { - return ZERO - } - } - - @JvmStatic - fun deserializeNBT(tag: Tag?): Fraction { - if (tag == null) - return ZERO - - if (tag is ByteArrayTag) - return deserializeNBT(tag) - - if (tag is StringTag) - return try { - fromString(tag.asString) - } catch (anything: Throwable) { - ZERO - } - - return ZERO - } - } -} - diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/FriendlyStreams.kt deleted file mode 100644 index 53769f924..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/FriendlyStreams.kt +++ /dev/null @@ -1,90 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import io.netty.handler.codec.EncoderException -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.NbtAccounter -import net.minecraft.nbt.NbtIo -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraftforge.registries.ForgeRegistries -import net.minecraftforge.registries.ForgeRegistry -import java.io.* -import java.math.BigDecimal -import java.math.BigInteger - -// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement -// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway? - -// netty ByteBuf -> netty ByteBufInputStream -> Minecraft FriendlyInputStream - -fun OutputStream.writeNbt(value: CompoundTag) { - try { - NbtIo.write(value, if (this is DataOutputStream) this else DataOutputStream(this)) - } catch (ioexception: IOException) { - throw EncoderException(ioexception) - } -} - -fun InputStream.readNbt(accounter: NbtAccounter = NbtAccounter.UNLIMITED): CompoundTag { - return try { - NbtIo.read(if (this is DataInputStream) this else DataInputStream(this), accounter) - } catch (ioexception: IOException) { - throw EncoderException(ioexception) - } -} - -fun OutputStream.writeItem(itemStack: ItemStack, limitedTag: Boolean = true) { - if (itemStack.isEmpty) { - write(0) - } else { - write(1) - val id = (ForgeRegistries.ITEMS as ForgeRegistry).getID(itemStack.item) - - writeInt(id) - writeInt(itemStack.count) - - var compoundtag: CompoundTag? = null - - if (itemStack.item.isDamageable(itemStack) || itemStack.item.shouldOverrideMultiplayerNbt()) { - compoundtag = if (limitedTag) itemStack.shareTag else itemStack.tag - } - - write(if (compoundtag != null) 1 else 0) - - if (compoundtag != null) { - writeNbt(compoundtag) - } - } -} - -fun InputStream.readItem(): ItemStack { - if (read() == 0) { - return ItemStack.EMPTY - } - - - val item = (ForgeRegistries.ITEMS as ForgeRegistry).getValue(readInt()) - val itemStack = ItemStack(item, readInt()) - - if (read() != 0) { - itemStack.readShareTag(readNbt()) - } - - return itemStack -} - -fun OutputStream.writeBigDecimal(value: BigDecimal) { - writeInt(value.scale()) - val bytes = value.unscaledValue().toByteArray() - writeVarIntLE(bytes.size) - write(bytes) -} - -fun InputStream.readBigDecimal(): BigDecimal { - val scale = readInt() - val size = readVarIntLE() - val bytes = ByteArray(size) - read(bytes) - return BigDecimal(BigInteger(bytes), scale) -} - diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt index 10de8c854..a0bac0048 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.core import java.util.function.Consumer import java.util.function.Supplier +import kotlin.properties.ReadWriteProperty import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KProperty @@ -9,17 +10,48 @@ inline var GetterSetter.value: V get() = get() set(value) { accept(value) } -@Suppress("nothing_to_inline") -inline operator fun GetterSetter.getValue(thisRef: Any, property: KProperty<*>): V { - return get() -} +interface GetterSetter : Supplier, Consumer, ReadWriteProperty { + override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { + accept(value) + } -@Suppress("nothing_to_inline") -inline operator fun GetterSetter.setValue(thisRef: Any, property: KProperty<*>, value: V) { - accept(value) -} + override fun getValue(thisRef: Any?, property: KProperty<*>): V { + return get() + } + + operator fun invoke(): V { + return get() + } + + operator fun invoke(value: V) { + accept(value) + } + + fun asGetterOnly(): GetterSetter { + val self = this + + return object : GetterSetter { + override fun get(): V { + return self.get() + } + + override fun accept(t: V) { + } + } + } + + fun watch(watch: (old: V, new: V) -> Unit): GetterSetter { + val self = this + + return object : GetterSetter by self { + override fun accept(t: V) { + val old = get() + self.accept(t) + watch.invoke(old, t) + } + } + } -interface GetterSetter : Supplier, Consumer { companion object { fun of(getter: Supplier, setter: Consumer): GetterSetter { return object : GetterSetter { @@ -56,8 +88,61 @@ interface GetterSetter : Supplier, Consumer { } } } + + fun box(value: V): SentientGetterSetter { + return object : SentientGetterSetter { + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var value = value + + override fun get(): V { + return value + } + + override fun accept(t: V) { + this.value = t + subs.accept(t) + } + } + } + } +} + +interface SentientGetterSetter : GetterSetter, ISubscriptable { + override fun watch(watch: (old: V, new: V) -> Unit): SentientGetterSetter { + val self = this + + return object : SentientGetterSetter by self { + override fun accept(t: V) { + val old = get() + self.accept(t) + watch.invoke(old, t) + } + } + } + +} + +operator fun Supplier.getValue(thisRef: Any?, property: KProperty<*>): T { + return get() +} + +operator fun Consumer.setValue(thisRef: Any?, property: KProperty<*>, value: T) { + accept(value) +} + +fun KMutableProperty0.asGetterSetter(watch: ((old: V, new: V) -> Unit)? = null): GetterSetter { + return GetterSetter.of(this).let { + if (watch != null) { + it.watch(watch) + } else { + it + } } } -fun KMutableProperty0.asGetterSetter() = GetterSetter.of(this) fun KMutableProperty0.asGetterOnly() = GetterSetter.of(Supplier { this.get() }, Consumer { /* do nothing */ }) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt new file mode 100644 index 000000000..5bbd7b52e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt @@ -0,0 +1,262 @@ +package ru.dbotthepony.mc.otm.core + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer +import it.unimi.dsi.fastutil.floats.FloatConsumer +import it.unimi.dsi.fastutil.objects.ReferenceArraySet +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet +import java.util.function.Consumer +import java.util.function.DoubleConsumer +import java.util.function.IntConsumer +import java.util.function.LongConsumer + +interface ISubscriptable { + /** + * Listener token, allows to remove listener from subscriber list + */ + fun interface L { + /** + * Removes this listener + */ + fun remove() + } + + fun addListener(listener: Consumer): L + + class Impl : ISubscriptable, Consumer { + private inner class L(val callback: Consumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: Consumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: V) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } + + companion object : ISubscriptable, L { + @Suppress("unchecked_cast") + fun empty(): ISubscriptable { + return this as ISubscriptable + } + + override fun remove() {} + + override fun addListener(listener: Consumer): L { + return this + } + } +} + +interface IFloatSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(listener::accept) + } + + fun addListener(listener: FloatConsumer): ISubscriptable.L + + class Impl : IFloatSubcripable, Consumer, FloatConsumer { + private inner class L(val callback: FloatConsumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Float) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IDoubleSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(DoubleConsumer { listener.accept(it) }) + } + + fun addListener(listener: DoubleConsumer): ISubscriptable.L + + class Impl : IDoubleSubcripable, Consumer, DoubleConsumer { + private inner class L(val callback: DoubleConsumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Double) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IIntSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(IntConsumer { listener.accept(it) }) + } + + fun addListener(listener: IntConsumer): ISubscriptable.L + + class Impl : IIntSubcripable, Consumer, IntConsumer { + private inner class L(val callback: IntConsumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Int) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface ILongSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(LongConsumer { listener.accept(it) }) + } + + fun addListener(listener: LongConsumer): ISubscriptable.L + + class Impl : ILongSubcripable, Consumer, LongConsumer { + private inner class L(val callback: LongConsumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Long) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IBooleanSubscriptable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(listener::accept) + } + + fun addListener(listener: BooleanConsumer): ISubscriptable.L + + class Impl : IBooleanSubscriptable, Consumer, BooleanConsumer { + private inner class L(val callback: BooleanConsumer) : ISubscriptable.L { + private var isRemoved = false + + init { + subscribers.add(this) + } + + override fun remove() { + if (!isRemoved) { + isRemoved = true + queue.add(this) + } + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + private val queue = ReferenceArraySet(0) + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Boolean) { + queue.forEach { subscribers.remove(it) } + queue.clear() + subscribers.forEach { it.callback.accept(t) } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt deleted file mode 100644 index e64500e50..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ItemStackIterators.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.world.item.ItemStack - -fun Iterator.nonEmpty() = PredicateIterator(this) { !it.isEmpty } -fun MutableIterator.nonEmpty() = MutablePredicateIterator(this) { !it.isEmpty } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt index 3342aefe9..6e03b8bff 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/LevelExt.kt @@ -3,14 +3,22 @@ package ru.dbotthepony.mc.otm.core import net.minecraft.world.entity.Entity import net.minecraft.world.level.Level import net.minecraft.world.phys.AABB +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.minus import java.util.LinkedList import java.util.function.Predicate import kotlin.math.pow +data class EntityDistance(val entity: T, val distance: Double) : Comparable> { + override fun compareTo(other: EntityDistance<*>): Int { + return distance.compareTo(other.distance) + } +} + /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity?, predicate: Predicate): List> { +fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity?, predicate: Predicate): MutableList> { val entities = getEntities(except, AABB( pos.x - dimensions.x, pos.y - dimensions.y, @@ -21,7 +29,7 @@ fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity pos.z + dimensions.z, ), predicate) - val result = LinkedList>() + val result = ArrayList>() for (it in entities) { val a = (it.position - pos).let { vec -> @@ -39,7 +47,7 @@ fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity val min = a.coerceAtMost(b) if (min <= 1.0) { - result.add(it to min) + result.add(EntityDistance(it, min)) } } @@ -49,7 +57,7 @@ fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, except: Entity /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInEllipsoid(type: Class, pos: Vector, dimensions: Vector, predicate: Predicate): List> { +fun Level.getEntitiesInEllipsoid(type: Class, pos: Vector, dimensions: Vector, predicate: Predicate): MutableList> { val entities = getEntitiesOfClass(type, AABB( pos.x - dimensions.x, pos.y - dimensions.y, @@ -60,7 +68,7 @@ fun Level.getEntitiesInEllipsoid(type: Class, pos: Vector, d pos.z + dimensions.z, ), predicate) - val result = LinkedList>() + val result = ArrayList>() for (it in entities) { val a = (it.position - pos).let { vec -> @@ -78,7 +86,7 @@ fun Level.getEntitiesInEllipsoid(type: Class, pos: Vector, d val min = a.coerceAtMost(b) if (min <= 1.0) { - result.add(it to min) + result.add(EntityDistance(it, min)) } } @@ -88,27 +96,27 @@ fun Level.getEntitiesInEllipsoid(type: Class, pos: Vector, d /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, predicate: Predicate): List> { +fun Level.getEntitiesInEllipsoid(pos: Vector, dimensions: Vector, predicate: Predicate): MutableList> { return getEntitiesInEllipsoid(pos, dimensions, null, predicate) } /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInSphere(pos: Vector, radius: Double, except: Entity?, predicate: Predicate): List> { +fun Level.getEntitiesInSphere(pos: Vector, radius: Double, except: Entity?, predicate: Predicate): MutableList> { return getEntitiesInEllipsoid(pos, Vector(radius, radius, radius), except, predicate) } /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInSphere(pos: Vector, radius: Double, predicate: Predicate): List> { +fun Level.getEntitiesInSphere(pos: Vector, radius: Double, predicate: Predicate): MutableList> { return getEntitiesInEllipsoid(pos, Vector(radius, radius, radius), null, predicate) } /** * Pair of entity and distance fraction to center of ellipsoid */ -fun Level.getEntitiesInSphere(type: Class, pos: Vector, radius: Double, predicate: Predicate): List> { +fun Level.getEntitiesInSphere(type: Class, pos: Vector, radius: Double, predicate: Predicate): MutableList> { return getEntitiesInEllipsoid(type, pos, Vector(radius, radius, radius), predicate) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/MatrixExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/MatrixExt.kt deleted file mode 100644 index 3e8b1d6d3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/MatrixExt.kt +++ /dev/null @@ -1,209 +0,0 @@ - -@file:Suppress("unused") - -package ru.dbotthepony.mc.otm.core - -import com.mojang.math.Matrix3f -import com.mojang.math.Matrix4f -import com.mojang.math.Vector3f -import com.mojang.math.Vector4f - -fun Matrix4f.rotate(angle: IAngle): Matrix4f { - multiply(angle.rotationXYZW()) - return this -} - -fun Matrix4f.translate(vector: Vector) { - translate(-vector.asVector3f()) -} - -fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { - val p = point.asVector3f() - translate(-p) - multiply(axis.rotateAroundThis(rotation, isDegrees)) - translate(-p) -} - -fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { - val p = point.asVector3f() - translate(-p) - multiply(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) - translate(-p) -} - -fun Matrix4f.rotateAroundPoint(point: Vector, rotation: IAngle) { - val p = point.asVector3f() - translate(-p) - multiply(rotation.rotationXYZW()) - translate(-p) -} - -fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { - translate(-point) - multiply(axis.rotateAroundThis(rotation, isDegrees)) - translate(-point) -} - -fun Matrix4f.rotateAroundPoint(point: Vector3f, rotation: IAngle) { - translate(-point) - multiply(rotation.rotationXYZW()) - translate(-point) -} - -fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { - translate(-point) - multiply(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) - translate(-point) -} - -operator fun Matrix4f.get(row: Int, column: Int): Float { - require(row in 0 .. 3) { "Invalid row: $row" } - require(column in 0 .. 3) { "Invalid column: $column" } - - return when (val index = row or (column shl 2)) { - 0 or (0 shl 2) -> m00 - 1 or (0 shl 2) -> m10 - 2 or (0 shl 2) -> m20 - 3 or (0 shl 2) -> m30 - 0 or (1 shl 2) -> m01 - 1 or (1 shl 2) -> m11 - 2 or (1 shl 2) -> m21 - 3 or (1 shl 2) -> m31 - 0 or (2 shl 2) -> m02 - 1 or (2 shl 2) -> m12 - 2 or (2 shl 2) -> m22 - 3 or (2 shl 2) -> m32 - 0 or (3 shl 2) -> m03 - 1 or (3 shl 2) -> m13 - 2 or (3 shl 2) -> m23 - 3 or (3 shl 2) -> m33 - else -> throw IllegalStateException("$index") - } -} - -operator fun Matrix4f.set(row: Int, column: Int, value: Float) { - require(row in 0 .. 3) { "Invalid row: $row" } - require(column in 0 .. 3) { "Invalid column: $column" } - - when (val index = row or (column shl 2)) { - 0 or (0 shl 2) -> m00 = value - 1 or (0 shl 2) -> m10 = value - 2 or (0 shl 2) -> m20 = value - 3 or (0 shl 2) -> m30 = value - 0 or (1 shl 2) -> m01 = value - 1 or (1 shl 2) -> m11 = value - 2 or (1 shl 2) -> m21 = value - 3 or (1 shl 2) -> m31 = value - 0 or (2 shl 2) -> m02 = value - 1 or (2 shl 2) -> m12 = value - 2 or (2 shl 2) -> m22 = value - 3 or (2 shl 2) -> m32 = value - 0 or (3 shl 2) -> m03 = value - 1 or (3 shl 2) -> m13 = value - 2 or (3 shl 2) -> m23 = value - 3 or (3 shl 2) -> m33 = value - else -> throw IllegalStateException("$index") - } -} - -operator fun Matrix3f.get(row: Int, column: Int): Float { - require(row in 0 .. 2) { "Invalid row: $row" } - require(column in 0 .. 2) { "Invalid column: $column" } - - return when (val index = row or (column shl 2)) { - 0 or (0 shl 2) -> m00 - 1 or (0 shl 2) -> m10 - 2 or (0 shl 2) -> m20 - 0 or (1 shl 2) -> m01 - 1 or (1 shl 2) -> m11 - 2 or (1 shl 2) -> m21 - 0 or (2 shl 2) -> m02 - 1 or (2 shl 2) -> m12 - 2 or (2 shl 2) -> m22 - else -> throw IllegalStateException("$index") - } -} - -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") -operator fun Matrix3f.set(row: Int, column: Int, value: Float) { - require(row in 0 .. 2) { "Invalid row: $row" } - require(column in 0 .. 2) { "Invalid column: $column" } - - when (val index = row or (column shl 2)) { - 0 or (0 shl 2) -> m00 = value - 1 or (0 shl 2) -> m10 = value - 2 or (0 shl 2) -> m20 = value - 0 or (1 shl 2) -> m01 = value - 1 or (1 shl 2) -> m11 = value - 2 or (1 shl 2) -> m21 = value - 0 or (2 shl 2) -> m02 = value - 1 or (2 shl 2) -> m12 = value - 2 or (2 shl 2) -> m22 = value - else -> throw IllegalStateException("$index") - } -} - -fun Matrix4f.identity() = setIdentity() -fun Matrix3f.identity() = setIdentity() - -fun Matrix4f.identityFast() { - m00 = 1.0F - m11 = 1.0F - m22 = 1.0F - m33 = 1.0F -} - -fun Matrix3f.identityFast() { - m00 = 1.0F - m11 = 1.0F - m22 = 1.0F -} - -fun Matrix3f.toMatrix4f(): Matrix4f { - val result = Matrix4f() - - result.m00 = this.m00 - result.m10 = this.m10 - result.m20 = this.m20 - result.m01 = this.m01 - result.m11 = this.m11 - result.m21 = this.m21 - result.m02 = this.m02 - result.m12 = this.m12 - result.m22 = this.m22 - - return result -} - -var Matrix4f.translation: Vector3f - get() { - return Vector3f(this[0, 3], this[1, 3], this[2, 3]) - } - - set(value) { - this[0, 0] = 1f - this[1, 1] = 1f - this[2, 2] = 1f - this[3, 3] = 1f - - this[0, 3] = value.x() - this[1, 3] = value.y() - this[2, 3] = value.z() - } - -var Matrix4f.translation4: Vector4f - get() { - return Vector4f(this[0, 3], this[1, 3], this[2, 3], this[3, 3]) - } - - set(value) { - this[0, 0] = 1f - this[1, 1] = 1f - this[2, 2] = 1f - this[3, 3] = 1f - - this[0, 3] = value.x() - this[1, 3] = value.y() - this[2, 3] = value.z() - this[3, 3] = value.w() - } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt deleted file mode 100644 index 92ba48e04..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/PredicateIterator.kt +++ /dev/null @@ -1,91 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import java.util.function.Consumer -import java.util.function.Predicate - -class PredicateIterator : Iterator { - private val parent: Iterator - private val predicate: Predicate - private val consumer: Consumer? - - constructor(parent: Iterator, predicate: Predicate) { - this.parent = parent - this.predicate = predicate - this.consumer = null - } - - constructor(parent: Iterator, predicate: Predicate, consumer: Consumer) { - this.parent = parent - this.predicate = predicate - this.consumer = consumer - } - - private var foundValue: Any? = Companion - - override fun hasNext(): Boolean { - if (foundValue === Companion) { - while (parent.hasNext()) { - val next = parent.next() - - if (predicate.test(next)) { - foundValue = next - return true - } - } - - return false - } - - return true - } - - @Suppress("unchecked_cast") - override fun next(): T { - if (!hasNext()) { - throw NoSuchElementException() - } - - val foundValue = foundValue - - if (foundValue === Companion) { - throw ConcurrentModificationException() - } - - this.foundValue = Companion - consumer?.accept(foundValue as T) - return foundValue as T - } - - private companion object -} - -fun Iterator.filter(condition: Predicate) = PredicateIterator(this, condition) - -class MutablePredicateIterator : MutableIterator { - private val parent: MutableIterator - private val predicateParent: PredicateIterator - - constructor(parent: MutableIterator, predicate: Predicate) { - this.parent = parent - this.predicateParent = PredicateIterator(parent, predicate) - } - - constructor(parent: MutableIterator, predicate: Predicate, consumer: Consumer) { - this.parent = parent - this.predicateParent = PredicateIterator(parent, predicate, consumer) - } - - override fun hasNext(): Boolean { - return predicateParent.hasNext() - } - - override fun next(): T { - return predicateParent.next() - } - - override fun remove() { - return parent.remove() - } -} - -fun MutableIterator.filter(condition: Predicate) = MutablePredicateIterator(this, condition) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/RGBAColor.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/RGBAColor.kt deleted file mode 100644 index 44e049b2a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/RGBAColor.kt +++ /dev/null @@ -1,126 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import com.mojang.blaze3d.systems.RenderSystem -import net.minecraft.ChatFormatting -import kotlin.math.roundToInt - -data class RGBAColor(val red: Float, val green: Float, val blue: Float, val alpha: Float = 1f) { - constructor(r: Int, g: Int, b: Int) : this((r / 255f), (g / 255f), (b / 255f), 1f) - constructor(r: Int, g: Int, b: Int, a: Int) : this((r / 255f), (g / 255f), (b / 255f), (a / 255f)) - constructor(r: Int, g: Int, b: Int, a: Float) : this((r / 255f), (g / 255f), (b / 255f), a) - - constructor(color: Long) : this( - (((color and -0x1000000) ushr 24) / 255f), - (((color and 0xFF0000) ushr 16) / 255f), - (((color and 0xFF00) ushr 8) / 255f), - (((color and 0xFF)) / 255f) - ) - - fun toInt(): Int { - val r = (this.red * 255).roundToInt() - val g = (this.green * 255).roundToInt() - val b = (this.blue * 255).roundToInt() - return (r shl 16) or (g shl 8) or b - } - - fun toRGBA(): Int { - val r = (this.red * 255).roundToInt() - val g = (this.green * 255).roundToInt() - val b = (this.blue * 255).roundToInt() - val a = (this.alpha * 255).roundToInt() - return (r shl 24) or (g shl 16) or (b shl 8) or a - } - - fun toARGB(): Int { - val r = (this.red * 255).roundToInt() - val g = (this.green * 255).roundToInt() - val b = (this.blue * 255).roundToInt() - val a = (this.alpha * 255).roundToInt() - return (a shl 24) or (r shl 16) or (g shl 8) or b - } - - fun toBGRA(): Int { - val r = (this.red * 255).roundToInt() - val g = (this.green * 255).roundToInt() - val b = (this.blue * 255).roundToInt() - val a = (this.alpha * 255).roundToInt() - return (r shl 24) or (g shl 16) or (r shl 8) or a - } - - fun toIntInv(): Int { - val r = (this.red * 255).roundToInt() - val g = (this.green * 255).roundToInt() - val b = (this.blue * 255).roundToInt() - return (b shl 16) or (g shl 8) or r - } - - fun setSystemColor() { - setShaderColor() - setDrawColor() - } - - fun setShaderColor() { - RenderSystem.setShaderColor(red, green, blue, alpha) - } - - fun setDrawColor() { - ru.dbotthepony.mc.otm.client.render.setDrawColor(this) - } - - fun linearInterpolation(t: Float, other: RGBAColor): RGBAColor { - return RGBAColor( - linearInterpolation(t, red, other.red), - linearInterpolation(t, green, other.green), - linearInterpolation(t, blue, other.blue), - linearInterpolation(t, alpha, other.alpha), - ) - } - - companion object { - val BLACK = RGBAColor(0f, 0f, 0f, 1f) - val WHITE = RGBAColor(1f, 1f, 1f, 1f) - val RED = RGBAColor(1f, 0f, 0f) - val GREEN = RGBAColor(0f, 1f, 0f, 1f) - val LIGHT_GREEN = RGBAColor(136, 255, 124) - val SLATE_GRAY = RGBAColor(64, 64, 64) - val GRAY = RGBAColor(0x2C2C2CFFL) - - val DARK_BLUE = rgb(ChatFormatting.DARK_BLUE.color!!) - val DARK_GREEN = rgb(ChatFormatting.DARK_GREEN.color!!) - val DARK_AQUA = rgb(ChatFormatting.DARK_AQUA.color!!) - val DARK_RED = rgb(ChatFormatting.DARK_RED.color!!) - val DARK_PURPLE = rgb(ChatFormatting.DARK_PURPLE.color!!) - val GOLD = rgb(ChatFormatting.GOLD.color!!) - val DARK_GRAY = rgb(ChatFormatting.DARK_GRAY.color!!) - val BLUE = rgb(ChatFormatting.BLUE.color!!) - val AQUA = rgb(ChatFormatting.AQUA.color!!) - val LIGHT_PURPLE = rgb(ChatFormatting.LIGHT_PURPLE.color!!) - val YELLOW = rgb(ChatFormatting.YELLOW.color!!) - - val LOW_POWER = RGBAColor(173, 41, 41) - val FULL_POWER = RGBAColor(255, 242, 40) - val LOW_MATTER = RGBAColor(0, 24, 148) - val FULL_MATTER = RGBAColor(72, 90, 255) - val LOW_PATTERNS = RGBAColor(44, 104, 57) - val FULL_PATTERNS = RGBAColor(65, 255, 87) - - fun inv(color: Int): RGBAColor { - val r = (color and -0x1000000 ushr 24) / 255f - val g = (color and 0xFF0000 ushr 16) / 255f - val b = (color and 0xFF00 ushr 8) / 255f - val a = (color and 0xFF) / 255f - return RGBAColor(a, b, g, r) - } - - fun rgb(color: Int): RGBAColor { - val r = (color and 0xFF0000 ushr 16) / 255f - val g = (color and 0xFF00 ushr 8) / 255f - val b = (color and 0xFF) / 255f - return RGBAColor(r, g, b) - } - } -} - -fun linearInterpolation(t: Float, a: RGBAColor, b: RGBAColor): RGBAColor { - return a.linearInterpolation(t, b) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ShortSupplier.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ShortSupplier.kt new file mode 100644 index 000000000..e8952d63a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ShortSupplier.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.core + +interface ShortSupplier { + fun getAsShort(): Short +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SlotIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/SlotIterator.kt deleted file mode 100644 index e8bcb616c..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SlotIterator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack - -private class SlotIterator(private val parent: Iterator) : MutableIterator { - private var last: Slot? = null - - override fun hasNext(): Boolean { - return parent.hasNext() - } - - override fun next(): ItemStack { - return parent.next().also { last = it }.item - } - - override fun remove() { - val last = last ?: throw IllegalStateException("Never called next()") - last.set(ItemStack.EMPTY) - } -} - -fun AbstractContainerMenu.itemStackIterator() : MutableIterator = SlotIterator(slots.iterator()) -fun Iterable.itemStackIterator() : MutableIterator = SlotIterator(iterator()) -fun Iterator.asItemStackIterator() : MutableIterator = SlotIterator(this) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt deleted file mode 100644 index 8b1d635f9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt +++ /dev/null @@ -1,135 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import org.apache.logging.log4j.LogManager -import kotlin.ConcurrentModificationException - -class TickList { - private val conditional = ArrayDeque() - private val once = ArrayDeque() - - private val conditionalValveTime = ArrayList() - private val onceValveTime = ArrayList() - - private var inTicker = false - - fun add(ticker: IConditionalTickable) { - if (inTicker) { - conditionalValveTime.add(ticker) - } else { - conditional.addFirst(ticker) - } - } - - fun add(ticker: ITickable) { - if (inTicker) { - onceValveTime.add(ticker) - } else { - once.addFirst(ticker) - } - } - - fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) { - if (!condition) { - LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) - return - } - - return add(ticker) - } - - fun add(ticker: ITickable, condition: Boolean, reason: String) { - if (!condition) { - LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) - return - } - - return add(ticker) - } - - fun until(ticker: () -> Boolean) = add(IConditionalTickable.wrap(ticker)) - fun `while`(tickerCondition: () -> Boolean, ticker: () -> Unit) = add(IConditionalTickable.wrap(tickerCondition, ticker)) - - fun until(ticker: () -> Boolean, condition: Boolean, reason: String) = add(IConditionalTickable.wrap(ticker), condition, reason) - fun `while`(tickerCondition: () -> Boolean, ticker: () -> Unit, condition: Boolean, reason: String) = add(IConditionalTickable.wrap(tickerCondition, ticker), condition, reason) - - fun tick() { - if (inTicker) { - throw ConcurrentModificationException("Already ticking") - } - - inTicker = true - val iterator = conditional.iterator() - - for (ticker in iterator) { - if (!ticker.canTick) { - iterator.remove() - } else { - ticker.tick() - } - } - - for (ticker in once) { - ticker.tick() - } - - once.clear() - - for (ticker in conditionalValveTime) { - conditional.addFirst(ticker) - } - - for (ticker in onceValveTime) { - once.addFirst(ticker) - } - - conditionalValveTime.clear() - onceValveTime.clear() - - inTicker = false - } - - fun clear() { - conditional.clear() - once.clear() - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} - -fun interface ITickable { - fun tick() -} - -interface IConditionalTickable : ITickable { - /** - * Once this returns false, it should stay false. - * - * If it suddenly turns true after being false, result is undefined. - */ - val canTick: Boolean - - companion object { - fun wrap(ticker: () -> Boolean): IConditionalTickable { - return object : IConditionalTickable { - override var canTick: Boolean = true - private set - - override fun tick() { - canTick = !ticker.invoke() - } - } - } - - fun wrap(condition: () -> Boolean, ticker: () -> Unit): IConditionalTickable { - return object : IConditionalTickable { - override val canTick: Boolean get() = condition.invoke() - - override fun tick() { - ticker.invoke() - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt deleted file mode 100644 index c4747cc94..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt +++ /dev/null @@ -1,85 +0,0 @@ -package ru.dbotthepony.mc.otm.core - -import org.apache.logging.log4j.LogManager - -class TimerQueue { - private var ticks = 0 - private val list = ArrayDeque() - - inner class Timer(val timerTicks: Int, val runnable: Runnable) { - val ringAt = ticks + timerTicks - - var finished = false - private set - - init { - if (list.isEmpty()) { - list.addLast(this) - } else { - val iterator = list.listIterator() - var hit = false - - for (value in iterator) { - if (value.ringAt == ringAt) { - hit = true - iterator.add(this) - break - } else if (value.ringAt > ringAt) { - if (iterator.hasPrevious()) { - iterator.previous() - iterator.add(this) - } else { - list.addFirst(this) - } - - hit = true - break - } - } - - if (!hit) { - list.addLast(this) - } - } - } - - fun execute() { - if (finished) return - runnable.run() - finished = true - } - } - - fun add(timerTicks: Int, ticker: Runnable, condition: Boolean, reason: String): Timer? { - if (!condition) { - LOGGER.error("Refusing to add timer $ticker in $timerTicks ticks because we $reason", IllegalStateException(reason)) - return null - } - - return Timer(timerTicks, ticker) - } - - fun tick() { - ticks++ - - while (list.isNotEmpty()) { - val head = list.first() - - if (head.ringAt <= ticks) { - head.execute() - list.removeFirst() - } else { - break - } - } - } - - fun clear() { - ticks = 0 - list.clear() - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt index 60f4e7329..83122b2c1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/UnOverengineering.kt @@ -1,14 +1,65 @@ package ru.dbotthepony.mc.otm.core +import com.google.gson.JsonElement +import com.google.gson.JsonSyntaxException +import com.mojang.serialization.Codec +import com.mojang.serialization.JsonOps +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.contents.LiteralContents import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.resources.ResourceLocation +import net.minecraft.sounds.SoundEvent import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block +import net.minecraft.world.level.material.Fluid import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.IForgeRegistry +import ru.dbotthepony.mc.otm.core.util.readBinaryJson +import ru.dbotthepony.mc.otm.core.util.writeBinaryJson + +// because doing it inline is ugly +fun Codec.fromJson(value: JsonElement): V? { + return decode(JsonOps.INSTANCE, value).get().map({ left -> left.first }, { null }) +} + +fun Codec.fromJsonStrict(value: JsonElement): V { + return decode(JsonOps.INSTANCE, value).get().map({ left -> left.first }, { throw JsonSyntaxException("Error decoding element: ${it.message()}") }) +} + +fun Codec.toJson(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement? { + return encode(value, JsonOps.INSTANCE, prefix).get().map({ it }, { null }) +} + +fun Codec.toJsonStrict(value: V, prefix: JsonElement = JsonOps.INSTANCE.empty()): JsonElement { + return encode(value, JsonOps.INSTANCE, prefix).get().map({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") }) +} + +fun Codec.fromNbt(value: Tag): V? { + return decode(NbtOps.INSTANCE, value).get().map({ left -> left.first }, { null }) +} + +fun Codec.fromNbtStrict(value: Tag): V { + return decode(NbtOps.INSTANCE, value).get().map({ left -> left.first }, { throw RuntimeException("Error decoding element: ${it.message()}") }) +} + +fun Codec.toNbt(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag? { + return encode(value, NbtOps.INSTANCE, prefix).get().map({ it }, { null }) +} + +fun Codec.toNbtStrict(value: V, prefix: Tag = NbtOps.INSTANCE.empty()): Tag { + return encode(value, NbtOps.INSTANCE, prefix).get().map({ it }, { throw RuntimeException("Error encoding element: ${it.message()}") }) +} + +fun Codec.toNetwork(buff: FriendlyByteBuf, value: V) { + buff.writeBinaryJson(toJsonStrict(value)) +} + +fun Codec.fromNetwork(buff: FriendlyByteBuf): V { + return fromJsonStrict(buff.readBinaryJson()) +} // 1.19 being 1.19 fun TranslatableComponent(key: String, vararg values: Any): MutableComponent = MutableComponent.create(TranslatableContents(key, *values)) @@ -25,6 +76,10 @@ fun IForgeRegistry.getKeyNullable(value: T): ResourceLocation? { } val Item.registryName get() = ForgeRegistries.ITEMS.getKeyNullable(this) +val Fluid.registryName get() = ForgeRegistries.FLUIDS.getKeyNullable(this) val Block.registryName get() = ForgeRegistries.BLOCKS.getKeyNullable(this) fun FriendlyByteBuf.writeRegistryId(value: Item) = writeRegistryId(ForgeRegistries.ITEMS, value) + +// 1.19.3 lol +inline val SoundEvent.holder get() = ForgeRegistries.SOUND_EVENTS.getHolder(this).orElse(null) ?: throw NoSuchElementException("$this is missing from ${ForgeRegistries.SOUND_EVENTS}") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt similarity index 95% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt index 3222c9e15..cc60cfe9d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/AwareItemStack.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/AwareItemStack.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory @@ -7,6 +7,7 @@ import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse import net.minecraftforge.items.IItemHandler import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.core.get /** * Allows to see the contents of container and extract the item from it diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt new file mode 100644 index 000000000..9dbbf12ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.core.collect + +import java.util.EnumMap +import java.util.function.BooleanSupplier +import java.util.stream.Stream + +class ConditionalEnumSet>private constructor(private val backing: EnumMap, marker: Unit) : Set { + constructor(clazz: Class) : this(EnumMap(clazz), Unit) + constructor(map: Map) : this(EnumMap(map), Unit) + + override val size: Int + get() = backing.values.stream().filter { it.asBoolean }.count().toInt() + + override fun contains(element: T): Boolean { + return backing[element]?.asBoolean ?: false + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } + } + + override fun isEmpty(): Boolean { + return backing.isEmpty() || backing.values.none { it.asBoolean } + } + + override fun stream(): Stream { + return backing.entries.stream().filter { it.value.asBoolean }.map { it.key } + } + + override fun iterator(): Iterator { + return backing.entries.iterator().filter { it.value.asBoolean }.map { it.key } + } + + fun add(value: T, condition: BooleanSupplier) { + backing[value] = condition + } + + fun remove(value: T): Boolean { + return backing.remove(value) != null + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt new file mode 100644 index 000000000..7b70a3f0a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt @@ -0,0 +1,61 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap +import java.util.function.BooleanSupplier +import java.util.stream.Stream + +class ConditionalSet : Set { + private val backing = Object2ObjectLinkedOpenHashMap() + + override val size: Int + get() = backing.values.stream().filter { it.asBoolean }.count().toInt() + + override fun contains(element: E): Boolean { + return backing[element]?.asBoolean ?: false + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } + } + + override fun isEmpty(): Boolean { + return backing.isEmpty() || backing.values.stream().noneMatch { it.asBoolean } + } + + override fun stream(): Stream { + return backing.entries.stream().filter { it.value.asBoolean }.map { it.key } + } + + override fun iterator(): Iterator { + return backing.entries.iterator().filter { it.value.asBoolean }.map { it.key } + } + + fun actuallyContains(element: E): Boolean { + return element in backing + } + + fun add(element: E, condition: BooleanSupplier): Boolean { + if (element in backing) return false + backing[element] = condition + return true + } + + fun addFirst(element: E, condition: BooleanSupplier): Boolean { + if (element in backing) return false + backing.putAndMoveToFirst(element, condition) + return true + } + + fun replace(element: E, condition: BooleanSupplier) { + backing[element] = condition + } + + fun replaceFirst(element: E, condition: BooleanSupplier) { + backing.remove(element) + backing.putAndMoveToFirst(element, condition) + } + + fun remove(element: E): Boolean { + return backing.remove(element) != null + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSupplierSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSupplierSet.kt similarity index 96% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSupplierSet.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSupplierSet.kt index f3454ceb0..9e55865fd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ConditionalSupplierSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSupplierSet.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect class ConditionalSupplierSet : AbstractSet { // method without boxing diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt new file mode 100644 index 000000000..469b95915 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.ints.IntIterable +import it.unimi.dsi.fastutil.ints.IntIterator + +fun IntRange.asIterable(): IntIterable { + return IntIterable { + val i = this@asIterable.iterator() + + object : IntIterator { + override fun hasNext(): Boolean { + return i.hasNext() + } + + override fun remove() { + throw UnsupportedOperationException() + } + + override fun nextInt(): Int { + return i.nextInt() + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArrayCollector.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArrayCollector.kt new file mode 100644 index 000000000..2922a2270 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArrayCollector.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.core.collect + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import java.util.function.BiConsumer +import java.util.function.BinaryOperator +import java.util.function.Function +import java.util.function.Supplier +import java.util.stream.Collector + +object JsonArrayCollector : Collector { + override fun supplier(): Supplier { + return Supplier { JsonArray() } + } + + override fun accumulator(): BiConsumer { + return BiConsumer { t, u -> t.add(u) } + } + + override fun combiner(): BinaryOperator { + return BinaryOperator { t, u -> t.addAll(u); t } + } + + override fun finisher(): Function { + return Function.identity() + } + + override fun characteristics(): Set { + return setOf(Collector.Characteristics.IDENTITY_FINISH) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArraySpliterator.kt similarity index 87% rename from src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArraySpliterator.kt index 9e4b2ed30..86eda1eb5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonArraySpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/JsonArraySpliterator.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.data +package ru.dbotthepony.mc.otm.core.collect import com.google.gson.JsonArray import com.google.gson.JsonElement @@ -27,4 +27,4 @@ class JsonArraySpliterator(private val obj: JsonArray, offset: Int = 0, private } fun JsonArray.elementSpliterator() = JsonArraySpliterator(this) -fun JsonArray.stream(): Stream = StreamSupport.stream(elementSpliterator(), false) +fun JsonArray.stream(): Stream = StreamSupport.stream(elementSpliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ListSet.kt similarity index 96% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ListSet.kt index 449e5b585..fd8cdb104 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ListSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ListSet.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet @@ -56,4 +56,4 @@ class ListSet(private val list: ImmutableList) : List, Set, RandomAc override fun subList(fromIndex: Int, toIndex: Int): List { return list.subList(fromIndex, toIndex) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ProxiedMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ProxiedMap.kt similarity index 99% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/ProxiedMap.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ProxiedMap.kt index 2c4840fd7..eae95106b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ProxiedMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ProxiedMap.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect abstract class ProxiedMap(protected val backingMap: MutableMap = HashMap()) : MutableMap { protected abstract fun onClear() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt new file mode 100644 index 000000000..a8ed01173 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.Hash +import java.lang.ref.Reference + +object ReferenceHashStrategy : Hash.Strategy { + override fun equals(a: Any?, b: Any?): Boolean { + if (a === b) return true + if (a == null || b == null) return false + + if (a is Reference<*>) { + val ref = a.get() + if (b is Reference<*>) return ref != null && ref == b.get() + return ref == b + } else if (b is Reference<*>) { + return b.get() == a + } else { + return a == b + } + } + + override fun hashCode(o: Any?): Int { + return o.hashCode() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt new file mode 100644 index 000000000..cc7aa3289 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt @@ -0,0 +1,407 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.ints.Int2IntFunction +import it.unimi.dsi.fastutil.ints.IntIterator +import it.unimi.dsi.fastutil.ints.IntIterators +import it.unimi.dsi.fastutil.objects.ObjectIterators +import ru.dbotthepony.mc.otm.core.addAll +import java.util.Optional +import java.util.Spliterator +import java.util.Spliterators +import java.util.function.BinaryOperator +import java.util.function.IntPredicate +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.stream.Collector +import java.util.stream.Stream +import java.util.stream.StreamSupport + +// Purpose of Stream API over Iterators is that it is simple enough for JIT to inline most of it, +// unlike actual Streams. + +// We lose only one (actual) element of Stream API here: ability to parallelize work, +// except it doesn't (properly) work in Minecraft (Forge) either, due to classloader bug: +// https://github.com/MinecraftForge/EventBus/issues/44 + +// Aside parallel work, unimplemented Stream API elements can be easily implemented when required + +private class FilteringIterator(private val parent: Iterator, private val predicate: Predicate, private var value: T) : MutableIterator { + private var hasValue = true + private var returned = false + + override fun hasNext(): Boolean { + return hasValue + } + + override fun next(): T { + if (!hasValue) throw NoSuchElementException() + hasValue = false + returned = true + + val value = this.value + + while (parent.hasNext()) { + val next = parent.next() + + if (predicate.test(next)) { + hasValue = true + this.value = next + break + } + } + + return value + } + + override fun remove() { + if (!returned) throw NoSuchElementException() + returned = false + (parent as MutableIterator).remove() + } +} + +private class MappingIterator(private val parent: Iterator, private val mapper: (T) -> R) : MutableIterator { + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): R { + return mapper.invoke(parent.next()) + } + + override fun remove() { + (parent as MutableIterator).remove() + } +} + +private class FlatMappingIterator(private val parent: Iterator, private val mapper: (T) -> Iterator) : MutableIterator { + private var current: Iterator = mapper.invoke(parent.next()) + private var last: Iterator? = null + + init { + while (!current.hasNext() && parent.hasNext()) { + current = mapper.invoke(parent.next()) + } + } + + override fun hasNext(): Boolean { + return current.hasNext() + } + + override fun next(): R { + if (!current.hasNext()) + throw NoSuchElementException() + + val v = current.next() + last = current + + while (!current.hasNext() && parent.hasNext()) { + current = mapper.invoke(parent.next()) + } + + return v + } + + override fun remove() { + (last as MutableIterator? ?: throw NoSuchElementException()).remove() + last = null + } +} + +private class LimitingIterator(private val parent: Iterator, private val limit: Long) : Iterator { + init { + require(limit > 0) { "Invalid limit $limit" } + } + + private var found = 0L + + override fun hasNext(): Boolean { + return found < limit && parent.hasNext() + } + + override fun next(): T { + if (found >= limit) + throw NoSuchElementException() + + return parent.next() + } +} + +private class SkippingIterator(private val parent: Iterator, skip: Long) : MutableIterator { + init { + require(skip >= 0) { "Invalid skip amount $skip" } + } + + private var found = skip + private var returned = false + + override fun hasNext(): Boolean { + while (parent.hasNext() && found > 0L) { + found-- + parent.next() + } + + return parent.hasNext() + } + + override fun next(): T { + if (!hasNext()) + throw NoSuchElementException() + + val v = parent.next() + returned = true + return v + } + + override fun remove() { + if (!returned) { + throw NoSuchElementException() + } + + returned = false + (parent as MutableIterator).remove() + } +} + +fun concatIterators(): MutableIterator { + return ObjectIterators.EMPTY_ITERATOR as MutableIterator +} + +fun concatIterators(a: Iterator): MutableIterator { + return a as MutableIterator +} + +fun concatIterators(iterators: Iterable>): MutableIterator { + return iterators.iterator().flatMap { it } +} + +fun concatIterators(vararg iterators: Iterator): MutableIterator { + return iterators.iterator().flatMap { it } +} + +/** + * Filters elements of [this] iterator + * + * Resulting [Iterator] is [MutableIterator] if [this] is + */ +fun Iterator.filter(condition: Predicate): MutableIterator { + while (hasNext()) { + val v = next() + + if (condition.test(v)) { + return FilteringIterator(this, condition, v) + } + } + + return emptyIterator() +} + +/** + * Maps elements of [this] iterator from values of [T] to [R] using function [mapper] + * + * Resulting [Iterator] is [MutableIterator] if [this] is + */ +fun Iterator.map(mapper: (T) -> R): MutableIterator { + if (!hasNext()) { + return emptyIterator() + } + + return MappingIterator(this, mapper) +} + +/** + * Maps elements of [this] iterator from type [T] to other iterators of type [R] using function [mapper] + * + * Resulting [Iterator] is [MutableIterator] if [this] is + */ +fun Iterator.flatMap(mapper: (T) -> Iterator): MutableIterator { + if (!hasNext()) { + return emptyIterator() + } + + return FlatMappingIterator(this, mapper) +} + +inline fun Iterator.reduce(identity: T, reducer: (T, T) -> T): T { + var result = identity + while (hasNext()) result = reducer.invoke(result, next()) + return result +} + +fun Iterator.filterNotNull(): MutableIterator = filter { it != null } as MutableIterator + +inline fun Iterator<*>.filterIsInstance(): MutableIterator = filter { it is T } as MutableIterator + +fun Iterator.any(predicate: Predicate): Boolean { + while (hasNext()) + if (predicate.test(next())) + return true + + return false +} + +inline fun Iterator.any(predicate: (T) -> Boolean): Boolean { + while (hasNext()) + if (predicate.invoke(next())) + return true + + return false +} + +fun Iterator.all(predicate: Predicate): Boolean { + while (hasNext()) + if (!predicate.test(next())) + return false + + return true +} + +inline fun Iterator.all(predicate: (T) -> Boolean): Boolean { + while (hasNext()) + if (!predicate.invoke(next())) + return false + + return true +} + +fun Iterator.none(predicate: Predicate): Boolean { + while (hasNext()) + if (predicate.test(next())) + return false + + return true +} + +inline fun Iterator.none(predicate: (T) -> Boolean): Boolean { + while (hasNext()) + if (predicate.invoke(next())) + return false + + return true +} + +fun Iterator.collect(collector: Collector): R { + val accumulator = collector.accumulator() + val instance = collector.supplier().get() + + for (value in this) { + accumulator.accept(instance, value) + } + + return collector.finisher().apply(instance) +} + +fun Iterator.toList(expectedSize: Int = 16): MutableList { + val result = ArrayList(expectedSize) + result.addAll(this) + return result +} + +fun Iterator.toImmutableList(expectedSize: Int = 16): List { + if (!hasNext()) + return emptyList() + + return toList(expectedSize) +} + +fun Iterator.find(): Optional { + if (hasNext()) { + return Optional.of(next()) + } + + return Optional.empty() +} + +fun Iterator.limit(limit: Long): Iterator = LimitingIterator(this, limit) +fun Iterator.skip(skip: Long): Iterator = if (skip == 0L) this else SkippingIterator(this, skip) + +inline fun Iterator.forEach(action: (T) -> Unit) { + for (value in this) { + action.invoke(value) + } +} + +fun Iterator.min(comparator: Comparator): Optional { + if (!hasNext()) { + return Optional.empty() + } + + var min = next() + + for (value in this) { + if (comparator.compare(min, value) > 0) { + min = value + } + } + + return Optional.of(min) +} + +fun Iterator.max(comparator: Comparator): Optional { + if (!hasNext()) { + return Optional.empty() + } + + var max = next() + + for (value in this) { + if (comparator.compare(max, value) < 0) { + max = value + } + } + + return Optional.of(max) +} + +fun Iterator.peek(peeker: (T) -> Unit): MutableIterator { + return object : MutableIterator { + override fun hasNext(): Boolean { + return this@peek.hasNext() + } + + override fun next(): T { + return this@peek.next().also(peeker) + } + + override fun remove() { + (this@peek as MutableIterator).remove() + } + } +} + +fun Iterator.maybe(): T? { + return if (hasNext()) + next() + else + null +} + +fun emptyIterator(): MutableIterator { + return ObjectIterators.emptyIterator() +} + +fun Iterator.toStream(): Stream { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false) +} + +fun Iterator.allEqual(): Boolean { + if (hasNext()) { + val v = next() + + while (hasNext()) { + if (v != next()) { + return false + } + } + + return true + } + + return false +} + +fun Iterator.count(): Long { + var count = 0L + while (hasNext()) count++ + return count +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierList.kt similarity index 95% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierList.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierList.kt index aa47888fc..82c637fcf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierList.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect import java.util.function.Supplier import java.util.stream.Stream diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierMap.kt similarity index 95% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierMap.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierMap.kt index 3b15b9c08..da61f08b2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/SupplierMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/SupplierMap.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.collect import com.google.common.collect.ImmutableSet import java.util.stream.Stream diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt similarity index 82% rename from src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt index 8ddafb76a..9895d02c8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/UUIDIntModifiersMap.kt @@ -1,13 +1,17 @@ -package ru.dbotthepony.mc.otm.capability +package ru.dbotthepony.mc.otm.core.collect import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag -import net.minecraft.nbt.Tag import net.minecraftforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.core.contains +import ru.dbotthepony.mc.otm.core.nbt.contains import java.util.UUID -class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap = HashMap()) : INBTSerializable { +/** + * This class represents an `UUID -> Int` map, with each `Int` value being added to [value] property + * + * This is like Minecraft's Attribute map, except it operate only on whole numbers + */ +class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap = HashMap()) : INBTSerializable { var value: Int = 0 private set @@ -74,8 +78,9 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi } } - override fun deserializeNBT(nbt: ListTag) { + override fun deserializeNBT(nbt: ListTag?) { backingMap.clear() + nbt ?: return val old = this.value this.value = 0 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt new file mode 100644 index 000000000..6464b7d87 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt @@ -0,0 +1,78 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenCustomHashSet +import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet +import it.unimi.dsi.fastutil.objects.ObjectSet +import ru.dbotthepony.mc.otm.core.util.HashedWeakReference +import java.lang.ref.ReferenceQueue + +class WeakHashSet(initialCapacity: Int = 16, loadFactor: Float = 0.75f, linked: Boolean = false) : MutableSet { + private val queue = ReferenceQueue() + private val backing: ObjectSet = if (linked) ObjectLinkedOpenCustomHashSet(initialCapacity, loadFactor, ReferenceHashStrategy) else ObjectOpenCustomHashSet(initialCapacity, loadFactor, ReferenceHashStrategy) + + private fun purge() { + var next = queue.poll() + + while (next != null) { + backing.remove(next) + next = queue.poll() + } + } + + override fun add(element: E): Boolean { + purge() + if (element in backing) return false + return backing.add(HashedWeakReference(element, queue)) + } + + override fun addAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = add(it) || any } + return any + } + + override fun clear() { + backing.clear() + while (queue.poll() != null) {} + } + + override fun iterator(): MutableIterator { + purge() + return backing.iterator().map { (it as HashedWeakReference).get() }.filterNotNull() + } + + override fun remove(element: E): Boolean { + purge() + return backing.remove(element) + } + + override fun removeAll(elements: Collection): Boolean { + purge() + return backing.removeAll(elements) + } + + override fun retainAll(elements: Collection): Boolean { + purge() + return backing.retainAll(elements) + } + + override val size: Int get() { + purge() + return backing.size + } + + override fun contains(element: E): Boolean { + purge() + return backing.contains(element) + } + + override fun containsAll(elements: Collection): Boolean { + purge() + return backing.containsAll(elements) + } + + override fun isEmpty(): Boolean { + purge() + return backing.isEmpty() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt new file mode 100644 index 000000000..1985f0925 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotation.kt @@ -0,0 +1,195 @@ +package ru.dbotthepony.mc.otm.core.math + +import com.google.common.collect.ImmutableMap +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.core.Vec3i +import net.minecraft.util.StringRepresentable +import net.minecraft.world.level.block.Rotation +import net.minecraftforge.common.capabilities.Capability +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.common.util.LazyOptional +import java.util.Collections +import java.util.EnumMap + +internal inline val Direction.blockRotation + get() = BlockRotation.of(this) + +fun ICapabilityProvider.getCapability(capability: Capability, side: BlockRotation?): LazyOptional { + return getCapability(capability, side?.front) +} + +operator fun Vec3i.plus(other: BlockRotation): Vec3i { + return this + other.normal +} + +operator fun BlockPos.plus(other: BlockRotation): BlockPos { + return this + other.normal +} + +enum class RelativeSide { + FRONT, BACK, LEFT, RIGHT, TOP, BOTTOM +} + +/** + * Represents unique block orientation in space, by providing [front] and [top] directions + * + * Allows to get relative faces through [left], [right], [bottom] and [back] + * + * @see BlockRotationFreedom + */ +enum class BlockRotation( + val front: Direction, + val top: Direction, +) : StringRepresentable { + DOWN(Direction.DOWN, Direction.NORTH), + UP(Direction.UP, Direction.NORTH), + NORTH(Direction.NORTH, Direction.UP), + SOUTH(Direction.SOUTH, Direction.UP), + WEST(Direction.WEST, Direction.UP), + EAST(Direction.EAST, Direction.UP), + + DOWN_SOUTH(Direction.DOWN, Direction.SOUTH), + DOWN_WEST(Direction.DOWN, Direction.WEST), + DOWN_EAST(Direction.DOWN, Direction.EAST), + + UP_SOUTH(Direction.UP, Direction.SOUTH), + UP_WEST(Direction.UP, Direction.WEST), + UP_EAST(Direction.UP, Direction.EAST), + + /** + * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis) + */ + NORTH_DOWN(Direction.NORTH, Direction.DOWN), + + /** + * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis) + */ + SOUTH_DOWN(Direction.SOUTH, Direction.DOWN), + + /** + * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis) + */ + WEST_DOWN(Direction.WEST, Direction.DOWN), + + /** + * This rotation is impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis) + */ + EAST_DOWN(Direction.EAST, Direction.DOWN), + ; + + val right: Direction + + init { + val crossproduct = front.normal.cross(top.normal) + right = Direction.values().first { it.normal == crossproduct } + } + + val left: Direction = right.opposite + val bottom: Direction = top.opposite + val back: Direction = front.opposite + + private val _dir2Side = EnumMap(Direction::class.java) + private val _side2Dir = EnumMap(RelativeSide::class.java) + + val dir2Side: Map = Collections.unmodifiableMap(_dir2Side) + val side2Dir: Map = Collections.unmodifiableMap(_side2Dir) + + init { + _side2Dir[RelativeSide.FRONT] = front + _side2Dir[RelativeSide.BACK] = back + _side2Dir[RelativeSide.LEFT] = left + _side2Dir[RelativeSide.RIGHT] = right + _side2Dir[RelativeSide.TOP] = top + _side2Dir[RelativeSide.BOTTOM] = bottom + + _dir2Side[front] = RelativeSide.FRONT + _dir2Side[back] = RelativeSide.BACK + _dir2Side[left] = RelativeSide.LEFT + _dir2Side[right] = RelativeSide.RIGHT + _dir2Side[top] = RelativeSide.TOP + _dir2Side[bottom] = RelativeSide.BOTTOM + } + + fun dir2Side(direction: Direction): RelativeSide { + return _dir2Side[direction]!! + } + + fun side2Dir(side: RelativeSide): Direction { + return _side2Dir[side]!! + } + + operator fun component1() = front + operator fun component2() = top + operator fun component3() = left + + val lowercaseName = name.lowercase() + + override fun getSerializedName(): String { + return lowercaseName + } + + fun rotate(by: Rotation): BlockRotation { + return of(by.rotate(front), top) + } + + operator fun times(other: Int): Vec3i { + return normal * other + } + + operator fun plus(other: Vec3i): Vec3i { + return normal + other + } + + operator fun plus(other: Direction): Vec3i { + return normal + other + } + + operator fun plus(other: BlockRotation): Vec3i { + return normal + other.normal + } + + operator fun unaryMinus() = opposite + + // more performant + val opposite by lazy { of(front.opposite, top.opposite) } + val normal: Vec3i get() = front.normal + + val oppositeFront by lazy { of(front.opposite, top) } + + companion object { + @JvmStatic + fun of(direction: Direction): BlockRotation { + return when (direction) { + Direction.DOWN -> DOWN + Direction.UP -> UP + Direction.NORTH -> NORTH + Direction.SOUTH -> SOUTH + Direction.WEST -> WEST + Direction.EAST -> EAST + } + } + + private val mapped = EnumMap>(Direction::class.java) + + init { + for (value in values()) { + val (front, top) = value + mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value) + } + } + + @JvmStatic + fun of(front: Direction, top: Direction?): BlockRotation { + val f = mapped[front]!! + return f[top ?: (if (front == Direction.DOWN || front == Direction.UP) Direction.NORTH else Direction.UP)] ?: throw IllegalArgumentException("Impossible block rotation: $front - $top") + } + + @JvmStatic + fun ofSafe(front: Direction, top: Direction?): BlockRotation { + val f = mapped[front]!! + val computeTop = if (front == Direction.DOWN || front == Direction.UP) Direction.NORTH else Direction.UP + return f[top ?: computeTop] ?: f[computeTop] ?: f.values.first() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotationFreedom.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotationFreedom.kt new file mode 100644 index 000000000..8a7328ca5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/BlockRotationFreedom.kt @@ -0,0 +1,91 @@ +package ru.dbotthepony.mc.otm.core.math + +import net.minecraft.core.Direction +import net.minecraft.world.level.block.state.properties.EnumProperty +import java.util.EnumMap + +/** + * Controls the rotational freedom of block in space, employing [property] and [of] to get valid value from set of possible values + */ +enum class BlockRotationFreedom(vararg values: BlockRotation) { + HORIZONTAL( + BlockRotation.NORTH, + BlockRotation.SOUTH, + BlockRotation.WEST, + BlockRotation.EAST, + ), + DIRECTIONAL( + BlockRotation.DOWN, + BlockRotation.UP, + BlockRotation.NORTH, + BlockRotation.SOUTH, + BlockRotation.WEST, + BlockRotation.EAST, + ), + DIRECTIONAL_WITH_ROTATION( + BlockRotation.DOWN, + BlockRotation.UP, + BlockRotation.NORTH, + BlockRotation.SOUTH, + BlockRotation.WEST, + BlockRotation.EAST, + BlockRotation.DOWN_SOUTH, + BlockRotation.DOWN_WEST, + BlockRotation.DOWN_EAST, + BlockRotation.UP_SOUTH, + BlockRotation.UP_WEST, + BlockRotation.UP_EAST, + ), + + /** + * These rotations are impossible to achieve using blockstates because blockstate def can't rotate models in XY plane (z axis) + */ + FULL( + BlockRotation.DOWN, + BlockRotation.UP, + BlockRotation.NORTH, + BlockRotation.SOUTH, + BlockRotation.WEST, + BlockRotation.EAST, + BlockRotation.DOWN_SOUTH, + BlockRotation.DOWN_WEST, + BlockRotation.DOWN_EAST, + BlockRotation.UP_SOUTH, + BlockRotation.UP_WEST, + BlockRotation.UP_EAST, + BlockRotation.NORTH_DOWN, + BlockRotation.SOUTH_DOWN, + BlockRotation.WEST_DOWN, + BlockRotation.EAST_DOWN, + ); + + val possibleValues: Collection get() = property.possibleValues + val property: EnumProperty = EnumProperty.create("facing", BlockRotation::class.java, *values) + + private val oneDirection = EnumMap(Direction::class.java) + private val twoDirection = EnumMap>(Direction::class.java) + + init { + for (direction in Direction.values()) { + oneDirection[direction] = possibleValues.firstOrNull { it.front == direction } + ?: possibleValues.first() + + val second = EnumMap(Direction::class.java) + twoDirection[direction] = second + + for (direction2 in Direction.values()) { + second[direction2] = possibleValues.firstOrNull { it.front == direction && it.top == direction2 } + ?: possibleValues.firstOrNull { it.front == direction } + ?: possibleValues.first() + } + } + } + + fun of(front: Direction): BlockRotation { + return oneDirection[front]!! + } + + fun of(front: Direction, top: Direction = Direction.UP): BlockRotation { + return twoDirection[front]!![top]!! + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Colors.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Colors.kt new file mode 100644 index 000000000..9c57692b1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Colors.kt @@ -0,0 +1,390 @@ +package ru.dbotthepony.mc.otm.core.math + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.chars.CharAVLTreeSet +import net.minecraft.ChatFormatting +import kotlin.math.roundToInt + +class HSVColor(hue: Float, saturation: Float, value: Float) : Comparable { + val hue = (hue % 360f).let { if (it < 0f) it + 360f else it } + val saturation = saturation.coerceIn(0f, 1f) + val value = value.coerceIn(0f, 1f) + + override fun equals(other: Any?): Boolean { + return other === this || other is HSVColor && other.hue == hue && other.saturation == saturation && other.value == value + } + + override fun hashCode(): Int { + return hue.hashCode() + saturation.hashCode() * 31 + value.hashCode() * 31 * 31 + } + + fun copy(hue: Float = this.hue, saturation: Float = this.saturation, value: Float = this.value): HSVColor { + return HSVColor(hue, saturation, value) + } + + operator fun component1() = hue + operator fun component2() = saturation + operator fun component3() = value + + fun toRGBA(alpha: Float = 1f): RGBAColor { + val valueMin = (1f - saturation) * value + val delta = (value - valueMin) * (hue % 60f) / 60f + val valueInc = valueMin + delta + val valueDec = value - delta + + return when ((hue / 60f).toInt()) { + 0 -> RGBAColor(value, valueInc, valueMin, alpha) + 1 -> RGBAColor(valueDec, value, valueMin, alpha) + 2 -> RGBAColor(valueMin, value, valueInc, alpha) + 3 -> RGBAColor(valueMin, valueDec, value, alpha) + 4 -> RGBAColor(valueInc, valueMin, value, alpha) + 5 -> RGBAColor(value, valueMin, valueDec, alpha) + else -> throw IllegalStateException("whut") + } + } + + override fun compareTo(other: HSVColor): Int { + return comparator.compare(this, other) + } + + companion object { + @JvmField val WHITE = HSVColor(0f, 1f, 1f) + + private val comparator = Comparator + .comparing(HSVColor::hue) + .thenComparing(HSVColor::saturation) + .thenComparing(HSVColor::value) + } +} + +private fun hex(value: Int): String { + require(value in 0 .. 255) + val v = value.toString(16) + + if (v.length == 1) + return "0$v" + else + return v +} + +class RGBAColor(red: Float, green: Float, blue: Float, alpha: Float = 1f) : Comparable { + constructor(r: Int, g: Int, b: Int) : this((r / 255f), (g / 255f), (b / 255f), 1f) + constructor(r: Int, g: Int, b: Int, a: Int) : this((r / 255f), (g / 255f), (b / 255f), (a / 255f)) + constructor(r: Int, g: Int, b: Int, a: Float) : this((r / 255f), (g / 255f), (b / 255f), a) + + val red = red.coerceIn(0f, 1f) + val green = green.coerceIn(0f, 1f) + val blue = blue.coerceIn(0f, 1f) + val alpha = alpha.coerceIn(0f, 1f) + + val redInt get() = (red * 255f).roundToInt() + val greenInt get() = (green * 255f).roundToInt() + val blueInt get() = (blue * 255f).roundToInt() + val alphaInt get() = (alpha * 255f).roundToInt() + + fun toRGBA(): Int { + return (redInt shl 24) or (greenInt shl 16) or (blueInt shl 8) or alphaInt + } + + fun toARGB(): Int { + return (alphaInt shl 24) or (redInt shl 16) or (greenInt shl 8) or blueInt + } + + fun toBGRA(): Int { + return (blueInt shl 24) or (greenInt shl 16) or (redInt shl 8) or alphaInt + } + + val isFullyTransparent get() = alpha <= 0f + val isWhite: Boolean get() = red >= 1f && green >= 1f && blue >= 1f && alpha >= 1f + + fun canRepresentHue(): Boolean { + val min = red.coerceAtMost(green).coerceAtMost(blue) + val max = red.coerceAtLeast(green).coerceAtLeast(blue) + return min != max + } + + fun hue(ifNoHue: Float = 0f): Float { + val min = red.coerceAtMost(green).coerceAtMost(blue) + val max = red.coerceAtLeast(green).coerceAtLeast(blue) + + if (min == max) { + return ifNoHue + } + + val diff = max - min + + return if (max == red && green >= blue) { + 60f * (green - blue) / diff + } else if (max == red) { + 60f * (green - blue) / diff + 360f + } else if (max == green) { + 60f * (blue - red) / diff + 120f + } else if (max == blue) { + 60f * (red - green) / diff + 240f + } else { + throw IllegalStateException("Whut $red $green $blue ($min / $max)") + } + } + + fun toHSV(): HSVColor { + val min = red.coerceAtMost(green).coerceAtMost(blue) + val max = red.coerceAtLeast(green).coerceAtLeast(blue) + + if (min == max) { + return HSVColor(0f, if (max == 0f) 0f else 1f - min / max, max) + } + + val diff = max - min + + val hue = if (max == red && green >= blue) { + 60f * (green - blue) / diff + } else if (max == red) { + 60f * (green - blue) / diff + 360f + } else if (max == green) { + 60f * (blue - red) / diff + 120f + } else if (max == blue) { + 60f * (red - green) / diff + 240f + } else { + throw IllegalStateException("Whut $red $green $blue ($min / $max)") + } + + return HSVColor(hue, 1f - min / max, max) + } + + fun toHexStringRGB(): String { + return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}" + } + + fun toHexStringRGBA(): String { + return "#${hex(redInt)}${hex(greenInt)}${hex(blueInt)}${hex(alphaInt)}" + } + + fun toHexStringARGB(): String { + return "#${hex(alphaInt)}${hex(redInt)}${hex(greenInt)}${hex(blueInt)}" + } + + override fun toString(): String { + return "RGBAColor[$red $green $blue $alpha]" + } + + operator fun component1() = red + operator fun component2() = green + operator fun component3() = blue + operator fun component4() = alpha + + fun toIntInv(): Int { + return (blueInt shl 16) or (greenInt shl 8) or redInt + } + + fun toRGB(): Int { + return (redInt shl 16) or (greenInt shl 8) or blueInt + } + + fun copy(red: Float = this.red, green: Float = this.green, blue: Float = this.blue, alpha: Float = this.alpha): RGBAColor { + return RGBAColor(red, green, blue, alpha) + } + + override fun compareTo(other: RGBAColor): Int { + if (canRepresentHue() && other.canRepresentHue()) + return hue().compareTo(other.hue()).let { + if (it != 0) + it + else + toHSV().compareTo(other.toHSV()) + } + + return comparator.compare(this, other) + } + + override fun equals(other: Any?): Boolean { + return other === this || other is RGBAColor && comparator.compare(this, other) == 0 + } + + override fun hashCode(): Int { + return red.hashCode() + green.hashCode() * 31 + blue.hashCode() * 31 * 31 + alpha.hashCode() * 31 * 31 * 31 + } + + fun linearInterpolation(t: Float, other: RGBAColor, interpolateAlpha: Boolean = true): RGBAColor { + return RGBAColor( + linearInterpolation(t, red, other.red), + linearInterpolation(t, green, other.green), + linearInterpolation(t, blue, other.blue), + if (interpolateAlpha) linearInterpolation(t, alpha, other.alpha) else alpha, + ) + } + + operator fun times(other: RGBAColor): RGBAColor { + if (isWhite) + return other + else if (other.isWhite) + return this + + return RGBAColor(red * other.red, green * other.green, blue * other.blue, alpha * other.alpha) + } + + @Suppress("unused") + companion object { + private val comparator = Comparator + .comparing(RGBAColor::red) + .thenComparing(RGBAColor::green) + .thenComparing(RGBAColor::blue) + .thenComparing(RGBAColor::alpha) + + @JvmField val TRANSPARENT_BLACK = RGBAColor(0f, 0f, 0f, 0f) + @JvmField val TRANSPARENT_WHITE = RGBAColor(1f, 1f, 1f, 0f) + + @JvmField val BLACK = RGBAColor(0f, 0f, 0f) + @JvmField val WHITE = RGBAColor(1f, 1f, 1f) + @JvmField val RED = RGBAColor(1f, 0f, 0f) + @JvmField val GREEN = RGBAColor(0f, 1f, 0f) + @JvmField val LIGHT_GREEN = RGBAColor(136, 255, 124) + @JvmField val SLATE_GRAY = RGBAColor(64, 64, 64) + @JvmField val GRAY = rgb(0x2C2C2CL) + @JvmField val LIGHT_GRAY = rgb(0x7F7F7FL) + @JvmField val HEADING_TEXT = rgb(0x404040L) + + @JvmField val DARK_BLUE = rgb(ChatFormatting.DARK_BLUE.color!!) + @JvmField val DARK_GREEN = rgb(ChatFormatting.DARK_GREEN.color!!) + @JvmField val DARK_AQUA = rgb(ChatFormatting.DARK_AQUA.color!!) + @JvmField val DARK_RED = rgb(ChatFormatting.DARK_RED.color!!) + @JvmField val DARK_PURPLE = rgb(ChatFormatting.DARK_PURPLE.color!!) + @JvmField val GOLD = rgb(ChatFormatting.GOLD.color!!) + @JvmField val DARK_GRAY = rgb(ChatFormatting.DARK_GRAY.color!!) + @JvmField val BLUE = rgb(ChatFormatting.BLUE.color!!) + @JvmField val AQUA = rgb(ChatFormatting.AQUA.color!!) + @JvmField val LIGHT_PURPLE = rgb(ChatFormatting.LIGHT_PURPLE.color!!) + @JvmField val YELLOW = rgb(ChatFormatting.YELLOW.color!!) + + @JvmField val LOW_POWER = RGBAColor(173, 41, 41) + @JvmField val FULL_POWER = RGBAColor(255, 242, 40) + @JvmField val LOW_MATTER = RGBAColor(0, 24, 148) + @JvmField val FULL_MATTER = RGBAColor(72, 90, 255) + @JvmField val LOW_PATTERNS = RGBAColor(44, 104, 57) + @JvmField val FULL_PATTERNS = RGBAColor(65, 255, 87) + + @JvmField val HALF_TRANSPARENT = RGBAColor(1f, 1f, 1f, 0.5f) + @JvmField val REDDISH = RGBAColor(1f, 0.4f, 0.4f) + + @JvmField + val CODECRGBA: Codec = RecordCodecBuilder.create { + it.group( + Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red), + Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green), + Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue), + Codec.floatRange(0f, 1f).optionalFieldOf("alpha", 1f).forGetter(RGBAColor::alpha), + ).apply(it, ::RGBAColor) + } + + @JvmField + val CODECRGB: Codec = RecordCodecBuilder.create { + it.group( + Codec.floatRange(0f, 1f).fieldOf("red").forGetter(RGBAColor::red), + Codec.floatRange(0f, 1f).fieldOf("green").forGetter(RGBAColor::green), + Codec.floatRange(0f, 1f).fieldOf("blue").forGetter(RGBAColor::blue), + ).apply(it, ::RGBAColor) + } + + fun rgb(color: Int): RGBAColor { + val r = (color and 0xFF0000 ushr 16) / 255f + val g = (color and 0xFF00 ushr 8) / 255f + val b = (color and 0xFF) / 255f + return RGBAColor(r, g, b) + } + + fun rgb(color: Long): RGBAColor { + val r = (color and 0xFF0000 ushr 16) / 255f + val g = (color and 0xFF00 ushr 8) / 255f + val b = (color and 0xFF) / 255f + return RGBAColor(r, g, b) + } + + fun bgr(color: Int): RGBAColor { + val r = (color and 0xFF0000 ushr 16) / 255f + val g = (color and 0xFF00 ushr 8) / 255f + val b = (color and 0xFF) / 255f + return RGBAColor(b, g, r) + } + + fun bgr(color: Long): RGBAColor { + val r = (color and 0xFF0000 ushr 16) / 255f + val g = (color and 0xFF00 ushr 8) / 255f + val b = (color and 0xFF) / 255f + return RGBAColor(b, g, r) + } + + fun abgr(color: Int): RGBAColor { + val r = (color and -0x1000000 ushr 24) / 255f + val g = (color and 0xFF0000 ushr 16) / 255f + val b = (color and 0xFF00 ushr 8) / 255f + val a = (color and 0xFF) / 255f + return RGBAColor(a, b, g, r) + } + + fun argb(color: Int): RGBAColor { + val a = (color and -0x1000000 ushr 24) / 255f + val r = (color and 0xFF0000 ushr 16) / 255f + val g = (color and 0xFF00 ushr 8) / 255f + val b = (color and 0xFF) / 255f + return RGBAColor(r, g, b, a) + } + + private val hexChars = CharAVLTreeSet() + + init { + "#0123456789abcdefABCDEF".forEach { hexChars.add(it) } + } + + fun isHexCharacter(char: Char): Boolean { + return char in hexChars + } + + private val shorthandRGBHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE) + private val longhandRGBHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE) + + private val shorthandRGBAHex = Regex("#?([0-9abcdef])([0-9abcdef])([0-9abcdef])([0-9abcdef])", RegexOption.IGNORE_CASE) + private val longhandRGBAHex = Regex("#?([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})([0-9abcdef]{2})", RegexOption.IGNORE_CASE) + + fun fromHexStringRGB(value: String): RGBAColor? { + if (value.length == 3 || value.length == 4) { + val match = shorthandRGBHex.find(value) ?: return null + val red = match.groupValues[1].toIntOrNull(16) ?: return null + val green = match.groupValues[2].toIntOrNull(16) ?: return null + val blue = match.groupValues[3].toIntOrNull(16) ?: return null + return RGBAColor(red * 16, green * 16, blue * 16) + } else if (value.length == 6 || value.length == 7) { + val match = longhandRGBHex.find(value) ?: return null + val red = match.groupValues[1].toIntOrNull(16) ?: return null + val green = match.groupValues[2].toIntOrNull(16) ?: return null + val blue = match.groupValues[3].toIntOrNull(16) ?: return null + return RGBAColor(red, green, blue) + } else { + return null + } + } + + fun fromHexStringRGBA(value: String): RGBAColor? { + if (value.length == 4 || value.length == 5) { + val match = shorthandRGBAHex.find(value) ?: return null + val red = match.groupValues[1].toIntOrNull(16) ?: return null + val green = match.groupValues[2].toIntOrNull(16) ?: return null + val blue = match.groupValues[3].toIntOrNull(16) ?: return null + val alpha = match.groupValues[4].toIntOrNull(16) ?: return null + return RGBAColor(red * 16, green * 16, blue * 16, alpha * 16) + } else if (value.length == 8 || value.length == 9) { + val match = longhandRGBAHex.find(value) ?: return null + val red = match.groupValues[1].toIntOrNull(16) ?: return null + val green = match.groupValues[2].toIntOrNull(16) ?: return null + val blue = match.groupValues[3].toIntOrNull(16) ?: return null + val alpha = match.groupValues[4].toIntOrNull(16) ?: return null + return RGBAColor(red, green, blue, alpha) + } else { + return null + } + } + } +} + +fun linearInterpolation(t: Float, a: RGBAColor, b: RGBAColor): RGBAColor { + return a.linearInterpolation(t, b) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt new file mode 100644 index 000000000..69e20e0c1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt @@ -0,0 +1,1663 @@ +package ru.dbotthepony.mc.otm.core.math + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import net.minecraft.nbt.ByteArrayTag +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtAccounter +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.Tag +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.util.RandomSource +import net.minecraftforge.common.ForgeConfigSpec +import ru.dbotthepony.mc.otm.config.ObservedConfigValue +import ru.dbotthepony.mc.otm.core.util.readVarIntLE +import ru.dbotthepony.mc.otm.core.util.writeVarIntLE +import java.io.InputStream +import java.io.OutputStream +import java.math.BigDecimal +import java.math.BigInteger +import java.math.MathContext +import java.math.RoundingMode + +private val BI_MINUS_ONE = -BigInteger.ONE +private val PERCENTAGE_CONTEXT = MathContext(6) // 6 ибо это число знаков для вычисления процента + +fun Decimal(value: Byte) = Decimal.valueOf(value) +fun Decimal(value: Short) = Decimal.valueOf(value) +fun Decimal(value: Int) = Decimal.valueOf(value) +fun Decimal(value: Long) = Decimal.valueOf(value) +fun Decimal(value: Float) = Decimal.valueOf(value) +fun Decimal(value: Double) = Decimal.valueOf(value) +fun Decimal(value: BigDecimal) = Decimal.valueOf(value) +fun Decimal(value: BigInteger) = Decimal.valueOf(value) +fun Decimal(value: String) = Decimal.valueOf(value) + +/** + * Fixed point arbitrary precision Decimal value. Values of this class embed [BigInteger] unscaled value, scale + * is defined at compile time by [PRECISION]. + */ +sealed class Decimal : Number(), Comparable { + /** + * Whole part of this Decimal + */ + abstract val whole: BigInteger + + /** + * Arbitrary fractional part of this Decimal, as [BigInteger] + * + * Makes sense only when utilized along [PRECISION], [PRECISION_POW], [PRECISION_POW_BI] + */ + abstract val fractional: BigInteger + + /** + * *Signed* normalized (-1,1) fractional part of this Decimal, as [Float] + */ + inline val fractionalFloat: Float get() { + return fractional.toFloat() / PRECISION_FLOAT + } + + /** + * *Signed* normalized (-1,1) fractional part of this Decimal, as [Double] + */ + inline val fractionalDouble: Double get() { + return fractional.toDouble() / PRECISION_DOUBLE + } + + abstract operator fun plus(other: Decimal): Decimal + abstract operator fun minus(other: Decimal): Decimal + abstract operator fun times(other: Decimal): Decimal + abstract operator fun div(other: Decimal): Decimal + + abstract operator fun rem(other: Decimal): Decimal + + // Primitive operators + abstract operator fun plus(other: Float): Decimal + abstract operator fun minus(other: Float): Decimal + abstract operator fun times(other: Float): Decimal + abstract operator fun div(other: Float): Decimal + + abstract operator fun plus(other: Double): Decimal + abstract operator fun minus(other: Double): Decimal + abstract operator fun times(other: Double): Decimal + abstract operator fun div(other: Double): Decimal + + abstract operator fun plus(other: Int): Decimal + abstract operator fun minus(other: Int): Decimal + abstract operator fun times(other: Int): Decimal + abstract operator fun div(other: Int): Decimal + + abstract operator fun plus(other: Long): Decimal + abstract operator fun minus(other: Long): Decimal + abstract operator fun times(other: Long): Decimal + abstract operator fun div(other: Long): Decimal + + abstract operator fun plus(other: BigInteger): Decimal + abstract operator fun minus(other: BigInteger): Decimal + abstract operator fun times(other: BigInteger): Decimal + abstract operator fun div(other: BigInteger): Decimal + // /Primitive operators + + // "de-virtualize" generic method + abstract override fun compareTo(other: Decimal): Int + + abstract operator fun unaryMinus(): Decimal + operator fun unaryPlus() = this + + /** + * Sign number of this Decimal, as defined by [BigInteger.signum] + */ + abstract fun signum(): Int + + /** + * Whenever this Decimal is negative (less than zero) + */ + inline val isNegative get() = signum() < 0 + + /** + * Whenever this Decimal is positive (bigger than zero) + */ + inline val isPositive get() = signum() > 0 + + /** + * Whenever this Decimal is zero + */ + inline val isZero get() = signum() == 0 + + abstract val isInfinite: Boolean + abstract val isFinite: Boolean + + /** + * Alias for [coerceAtLeast] with [ZERO] constant, except this method might + * perform faster. + */ + fun moreThanZero(): Decimal { + if (signum() >= 0) + return this + + return Zero + } + + /** + * Alias for [coerceAtMost] with [ZERO] constant, except this method might + * perform faster. + */ + fun lessOrZero(): Decimal { + if (signum() <= 0) + return this + + return Zero + } + + abstract fun toByteArray(): ByteArray + + fun serializeNBT(): Tag { + return ByteArrayTag(toByteArray()) + } + + fun write(buff: FriendlyByteBuf) { + buff.writeByteArray(toByteArray()) + } + + val absoluteValue: Decimal + get() { + return if (isNegative) { + -this + } else { + this + } + } + + /** + * Truncates fractional part of this Decimal + */ + abstract fun floor(): Decimal + + abstract fun toString(decimals: Int): String + override fun toString(): String = toString(-1) + + abstract fun toBigDecmial(): BigDecimal + + fun percentage(divisor: Decimal): Float { + if (isZero || divisor.isZero) return 0f + + if (this >= divisor) + return 1f + else if (this <= FLOAT_MAX_VALUE && divisor <= FLOAT_MAX_VALUE) + return (toDouble() / divisor.toDouble()).toFloat() + else if (divisor === PositiveInfinity || divisor === NegativeInfinity) + return 0f + + return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() + } + + private class Regular(val mag: BigInteger, marker: Nothing?) : Decimal() { + constructor(value: BigInteger) : this(value * PRECISION_POW_BI, null) + constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null) + constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble())) + constructor(value: Double) : this(BigDecimal(value)) + constructor(value: String) : this(BigDecimal(value)) + + override val isInfinite: Boolean + get() = false + override val isFinite: Boolean + get() = true + + private var _whole: BigInteger? = null + private var _fractional: BigInteger? = null + + private fun computeWholeFractional(): BigInteger { + val (a, b) = mag.divideAndRemainder(PRECISION_POW_BI) + _whole = a + _fractional = b + return a + } + + override val whole: BigInteger get() { + return _whole ?: computeWholeFractional() + } + + override val fractional: BigInteger get() { + if (_fractional == null) computeWholeFractional() + return _fractional!! + } + + override fun toDouble(): Double { + return mag.toDouble() / PRECISION_DOUBLE + } + + override fun toFloat(): Float { + return mag.toFloat() / PRECISION_FLOAT + } + + override fun toByte(): Byte { + return whole.toByte() + } + + override fun toShort(): Short { + return whole.toShort() + } + + override fun signum(): Int { + return mag.signum() + } + + override fun toInt(): Int { + return if (whole > BI_INT_MAX) { + Int.MAX_VALUE + } else if (whole < BI_INT_MIN) { + Int.MIN_VALUE + } else { + whole.toInt() + } + } + + override fun toLong(): Long { + return if (whole > BI_LONG_MAX) { + Long.MAX_VALUE + } else if (whole < BI_LONG_MIN) { + Long.MIN_VALUE + } else { + whole.toLong() + } + } + + override fun compareTo(other: Decimal): Int { + return if (other === Zero) + signum() + else if (other is Regular) + mag.compareTo(other.mag) + else if (other === PositiveInfinity) + -1 + else if (other === NegativeInfinity) + 1 + else + throw RuntimeException("unreachable code") + } + + + override fun plus(other: Decimal): Decimal { + return if (other is Regular) { + raw(mag + other.mag) + } else if (other === Zero) { + this + } else if (other === PositiveInfinity) { + PositiveInfinity + } else if (other === NegativeInfinity) { + NegativeInfinity + } else { + throw RuntimeException("Unreachable code") + } + } + + override fun minus(other: Decimal): Decimal { + return if (other is Regular) { + raw(mag - other.mag) + } else if (other === Zero) { + this + } else if (other === PositiveInfinity) { + NegativeInfinity + } else if (other === NegativeInfinity) { + PositiveInfinity + } else { + throw RuntimeException("Unreachable code") + } + } + + override fun times(other: Decimal): Decimal { + if (other is Regular) { + if (other == ONE) { + return this + } else if (other == MINUS_ONE) { + return raw(-mag) + } + + val result = mag * other.mag + val (a, b) = result.divideAndRemainder(PRECISION_POW_BI) + + return if (b >= PRECISION_POW_BI_HIGH) { + raw(a + BigInteger.ONE) + } else { + raw(a) + } + } else if (other === Zero) { + return Zero + } else if (other === PositiveInfinity) { + return PositiveInfinity + } else if (other === NegativeInfinity) { + return NegativeInfinity + } else { + throw RuntimeException("Unreachable code") + } + } + + override fun div(other: Decimal): Decimal { + if (other is Regular) { + if (other == ONE) { + return this + } else if (other == MINUS_ONE) { + return raw(-mag) + } + + return raw((mag * PRECISION_POW_BI) / other.mag) + } else if (other === Zero) { + throw ArithmeticException("$this / 0") + } else if (other === PositiveInfinity || other === NegativeInfinity) { + return Zero + } else { + throw RuntimeException("Unreachable code") + } + } + + override fun rem(other: Decimal): Decimal { + return if (other is Regular) { + raw(mag % other.mag) + } else if (other === Zero) { + throw ArithmeticException("$this % 0") + } else if (other === PositiveInfinity) { + this + } else if (other === NegativeInfinity) { + -this + } else { + throw RuntimeException("Unreachable code") + } + } + + // Primitive operators + override fun plus(other: Float): Decimal { + if (other == 0f) { + return this + } else if (other.isNaN()) { + throw ArithmeticException("$this + NaN") + } else if (other == Float.POSITIVE_INFINITY) { + return PositiveInfinity + } else if (other == Float.NEGATIVE_INFINITY) { + return NegativeInfinity + } + + return plus(valueOf(other)) + } + + override fun minus(other: Float): Decimal { + if (other == 0f) { + return this + } else if (other.isNaN()) { + throw ArithmeticException("$this - NaN") + } else if (other == Float.POSITIVE_INFINITY) { + return NegativeInfinity + } else if (other == Float.NEGATIVE_INFINITY) { + return PositiveInfinity + } + + return minus(valueOf(other)) + } + + override fun times(other: Float): Decimal { + if (other == 1f) { + return this + } else if (other == 0f) { + return Zero + } else if (other == -1f) { + return -this + } else if (other.isNaN()) { + throw ArithmeticException("$this * NaN") + } else if (other == Float.POSITIVE_INFINITY) { + return if (signum() < 0) NegativeInfinity else PositiveInfinity + } else if (other == Float.NEGATIVE_INFINITY) { + return if (signum() < 0) PositiveInfinity else NegativeInfinity + } + + return times(valueOf(other)) + } + + override fun div(other: Float): Decimal { + if (other == 0f) { + throw ArithmeticException("$this / 0") + } else if (other == 1f) { + return this + } else if (other == -1f) { + return -this + } else if (other.isNaN()) { + throw ArithmeticException("$this / NaN") + } else if (other.isInfinite()) { + return Zero + } + + return div(valueOf(other)) + } + + override fun plus(other: Double): Decimal { + if (other == 0.0) { + return this + } else if (other.isNaN()) { + throw ArithmeticException("$this + NaN") + } else if (other == Double.POSITIVE_INFINITY) { + return PositiveInfinity + } else if (other == Double.NEGATIVE_INFINITY) { + return NegativeInfinity + } + + return plus(valueOf(other)) + } + + override fun minus(other: Double): Decimal { + if (other == 0.0) { + return this + } else if (other.isNaN()) { + throw ArithmeticException("$this - NaN") + } else if (other == Double.POSITIVE_INFINITY) { + return NegativeInfinity + } else if (other == Double.NEGATIVE_INFINITY) { + return PositiveInfinity + } + + return minus(valueOf(other)) + } + + override fun times(other: Double): Decimal { + if (other == 1.0) { + return this + } else if (other == 0.0) { + return Zero + } else if (other == -1.0) { + return -this + } else if (other.isNaN()) { + throw ArithmeticException("$this * NaN") + } else if (other == Double.POSITIVE_INFINITY) { + return if (signum() < 0) NegativeInfinity else PositiveInfinity + } else if (other == Double.NEGATIVE_INFINITY) { + return if (signum() < 0) PositiveInfinity else NegativeInfinity + } + + return times(valueOf(other)) + } + + override fun div(other: Double): Decimal { + if (other == 0.0) { + throw ArithmeticException("$this / zero") + } else if (other == 1.0) { + return this + } else if (other == -1.0) { + return -this + } else if (other.isNaN()) { + throw ArithmeticException("$this / NaN") + } else if (other.isInfinite()) { + return Zero + } + + return div(valueOf(other)) + } + + override fun plus(other: Int): Decimal { + if (other == 0) + return this + + return plus(valueOf(other)) + } + + override fun minus(other: Int): Decimal { + if (other == 0) + return this + + return minus(valueOf(other)) + } + + override fun times(other: Int): Decimal { + if (other == 1) { + return this + } else if (other == 0) { + return Zero + } else if (other == -1) { + return -this + } + + return times(valueOf(other)) + } + + override fun div(other: Int): Decimal { + if (other == 0) { + throw ArithmeticException("$this / 0") + } else if (other == 1) { + return this + } else if (other == -1) { + return -this + } + + return div(valueOf(other)) + } + + override fun plus(other: Long): Decimal { + if (other == 0L) + return this + + return plus(valueOf(other)) + } + + override fun minus(other: Long): Decimal { + if (other == 0L) + return this + + return minus(valueOf(other)) + } + + override fun times(other: Long): Decimal { + if (other == 1L) { + return this + } else if (other == 0L) { + return Zero + } else if (other == -1L) { + return -this + } + + return times(valueOf(other)) + } + + override fun div(other: Long): Decimal { + if (other == 0L) { + throw ArithmeticException("$this / 0") + } else if (other == 1L || isZero) { + return this + } else if (other == -1L) { + return -this + } + + return div(valueOf(other)) + } + + override fun plus(other: BigInteger): Decimal { + if (other == BigInteger.ZERO) + return this + + return plus(valueOf(other)) + } + + override fun minus(other: BigInteger): Decimal { + if (other == BigInteger.ZERO) + return this + + return minus(valueOf(other)) + } + + override fun times(other: BigInteger): Decimal { + if (other == BigInteger.ONE) { + return this + } else if (other.signum() == 0) { + return Zero + } else if (other == BI_MINUS_ONE) { + return -this + } + + return times(valueOf(other)) + } + + override fun div(other: BigInteger): Decimal { + if (other == BigInteger.ZERO) { + throw ArithmeticException("$this / 0") + } else if (other == BigInteger.ONE) { + return this + } else if (other == BI_MINUS_ONE) { + return -this + } + + return div(valueOf(other)) + } + // /Primitive operators + + override fun unaryMinus(): Decimal { + return Regular(-mag, null) + } + + override fun toByteArray(): ByteArray { + val result = mag.toByteArray() + val copy = ByteArray(result.size + 1) + copy[0] = TYPE_NORMAL + result.copyInto(copy, 1) + return copy + } + + override fun floor(): Decimal { + return Regular(whole) + } + + override fun toString(decimals: Int): String { + if (decimals == 0) { + return whole.toString() + } + + if (mag.signum() == 0) { + return if (decimals < 0) { + "0.0" + } else { + "0." + "0".repeat(decimals) + } + } + + var original = mag.toString() + + if (mag.signum() < 0) { + // если у нас отрицательное число - убираем знак минуса + original = original.substring(1) + } + + if (original.length <= PRECISION) { + // если у нас чисто дробное число - дописываем нули в начало + original = "0".repeat(PRECISION - original.length + 1) + original + } + + // теперь у нас беззнаковая строка с нужной длиной + + if (decimals < 0) { + // нам неважно количество знаков после запятой + var result = original.substring(0, original.length - PRECISION) + "." + original.substring(original.length - PRECISION) + + if (mag.signum() < 0) { + result = "-$result" + } + + var pos = result.length - 1 + + while (result[pos] == '0') { + if (result[pos - 1] == '0' || result[pos - 1] != '.') + pos-- + else + break + } + + return result.substring(0, pos + 1) + } else { + // нужно некоторое количество знаков после запятой + val result = original.substring(0, original.length - PRECISION) + "." + var decimalPart = original.substring(original.length - PRECISION) + + if (decimalPart.length < decimals) { + decimalPart += "0".repeat(decimals - decimalPart.length) + } else if (decimalPart.length > decimals) { + decimalPart = decimalPart.substring(0, decimals) + } + + return if (mag.signum() < 0) { + "-$result$decimalPart" + } else { + "$result$decimalPart" + } + } + } + + override fun toBigDecmial(): BigDecimal { + return BigDecimal(mag, PRECISION) + } + + override fun equals(other: Any?): Boolean { + return this === other || other is Regular && mag == other.mag + } + + override fun hashCode(): Int { + return mag.hashCode() + } + } + + private object PositiveInfinity : Decimal() { + private fun readResolve(): Any = PositiveInfinity + + override val isInfinite: Boolean + get() = true + override val isFinite: Boolean + get() = false + + override fun compareTo(other: Decimal): Int { + return if (other === this) 0 else 1 + } + + override fun toByte(): Byte { + return Byte.MAX_VALUE + } + + override fun toDouble(): Double { + return Double.POSITIVE_INFINITY + } + + override fun toFloat(): Float { + return Float.POSITIVE_INFINITY + } + + override fun toInt(): Int { + return Int.MAX_VALUE + } + + override fun toLong(): Long { + return Long.MAX_VALUE + } + + override fun toShort(): Short { + return Short.MAX_VALUE + } + + override val whole: BigInteger + get() = throw UnsupportedOperationException("Attempt to get whole part of positive infinity") + override val fractional: BigInteger + get() = throw UnsupportedOperationException("Attempt to get fractional part of positive infinity") + + override fun plus(other: Decimal): Decimal { + // not very mathematically correct, since + // case "infinity + infinity" with either sign on either side + // is undefined + return this + } + + override fun minus(other: Decimal): Decimal { + // not very mathematically correct, since + // case "infinity - infinity" with either sign on either side + // is undefined + return this + } + + override fun times(other: Decimal): Decimal { + return if (other.signum() == 0) + Zero + else if (other.signum() < 0) + NegativeInfinity + else + this + } + + override fun div(other: Decimal): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other === this) + throw ArithmeticException("Dividing positive infinity by itself is undefined") + else if (other === NegativeInfinity) + throw ArithmeticException("Dividing positive infinity by negative infinity is undefined") + else if (other.signum() < 0) + NegativeInfinity + else + this + } + + override fun rem(other: Decimal): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to remainder divide positive infinity by zero") + else if (other.signum() < 0) + NegativeInfinity + else + this + } + + override fun plus(other: Float): Decimal { + return this + } + + override fun minus(other: Float): Decimal { + return this + } + + override fun times(other: Float): Decimal { + return if (other == 0f) + Zero + else if (other < 0f) + NegativeInfinity + else + PositiveInfinity + } + + override fun div(other: Float): Decimal { + return if (other == 0f) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other < 0f) + NegativeInfinity + else + PositiveInfinity + } + + override fun plus(other: Double): Decimal { + return this + } + + override fun minus(other: Double): Decimal { + return this + } + + override fun times(other: Double): Decimal { + return if (other == 0.0) + Zero + else if (other < 0.0) + NegativeInfinity + else + PositiveInfinity + } + + override fun div(other: Double): Decimal { + return if (other == 0.0) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other < 0.0) + NegativeInfinity + else + PositiveInfinity + } + + override fun plus(other: Int): Decimal { + return this + } + + override fun minus(other: Int): Decimal { + return this + } + + override fun times(other: Int): Decimal { + return if (other == 0) + Zero + else if (other < 0) + NegativeInfinity + else + PositiveInfinity + } + + override fun div(other: Int): Decimal { + return if (other == 0) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other < 0) + NegativeInfinity + else + PositiveInfinity + } + + override fun plus(other: Long): Decimal { + return this + } + + override fun minus(other: Long): Decimal { + return this + } + + override fun times(other: Long): Decimal { + return if (other == 0L) + Zero + else if (other < 0L) + NegativeInfinity + else + PositiveInfinity + } + + override fun div(other: Long): Decimal { + return if (other == 0L) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other < 0L) + NegativeInfinity + else + PositiveInfinity + } + + override fun plus(other: BigInteger): Decimal { + return this + } + + override fun minus(other: BigInteger): Decimal { + return this + } + + override fun times(other: BigInteger): Decimal { + return if (other.signum() == 0) + Zero + else if (other.signum() < 0) + NegativeInfinity + else + PositiveInfinity + } + + override fun div(other: BigInteger): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to divide positive infinity by zero") + else if (other.signum() < 0) + NegativeInfinity + else + PositiveInfinity + } + + override fun unaryMinus(): Decimal { + return NegativeInfinity + } + + override fun signum(): Int { + return 1 + } + + override fun toByteArray(): ByteArray { + return byteArrayOf(TYPE_POSITIVE_INFINITY) + } + + override fun floor(): Decimal { + return this + } + + override fun toString(decimals: Int): String { + return "+Infinity" + } + + override fun toBigDecmial(): BigDecimal { + throw UnsupportedOperationException("Unable to construct BigDecimal of positive infinity") + } + } + + private object NegativeInfinity : Decimal() { + private fun readResolve(): Any = NegativeInfinity + + override val isInfinite: Boolean + get() = true + override val isFinite: Boolean + get() = false + + override fun compareTo(other: Decimal): Int { + return if (other === this) 0 else -1 + } + + override fun toByte(): Byte { + return Byte.MIN_VALUE + } + + override fun toDouble(): Double { + return Double.NEGATIVE_INFINITY + } + + override fun toFloat(): Float { + return Float.NEGATIVE_INFINITY + } + + override fun toInt(): Int { + return Int.MIN_VALUE + } + + override fun toLong(): Long { + return Long.MIN_VALUE + } + + override fun toShort(): Short { + return Short.MIN_VALUE + } + + override val whole: BigInteger + get() = throw UnsupportedOperationException("Attempt to get whole part of negative infinity") + override val fractional: BigInteger + get() = throw UnsupportedOperationException("Attempt to get fractional part of negative infinity") + + override fun plus(other: Decimal): Decimal { + return this + } + + override fun minus(other: Decimal): Decimal { + return this + } + + override fun times(other: Decimal): Decimal { + return if (other.signum() == 0) + Zero + else if (other.signum() < 0) + PositiveInfinity + else + this + } + + override fun div(other: Decimal): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to divide negative infinity by zero") + else if (other === this) + throw ArithmeticException("Dividing negative infinity by itself is undefined") + else if (other === PositiveInfinity) + throw ArithmeticException("Dividing negative infinity by positive infinity is undefined") + else if (other.signum() < 0) + PositiveInfinity + else + this + } + + override fun rem(other: Decimal): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to remainder divide negative infinity by zero") + else if (other.signum() < 0) + PositiveInfinity + else + this + } + + override fun plus(other: Float): Decimal { + return this + } + + override fun minus(other: Float): Decimal { + return this + } + + override fun times(other: Float): Decimal { + return if (other == 0f) + Zero + else if (other < 0f) + PositiveInfinity + else + this + } + + override fun div(other: Float): Decimal { + return if (other == 0f) + throw ArithmeticException("Attempt to divide negative negative by zero") + else if (other < 0f) + PositiveInfinity + else + this + } + + override fun plus(other: Double): Decimal { + return this + } + + override fun minus(other: Double): Decimal { + return this + } + + override fun times(other: Double): Decimal { + return if (other == 0.0) + Zero + else if (other < 0.0) + PositiveInfinity + else + this + } + + override fun div(other: Double): Decimal { + return if (other == 0.0) + throw ArithmeticException("Attempt to divide negative infinity by zero") + else if (other < 0.0) + PositiveInfinity + else + this + } + + override fun plus(other: Int): Decimal { + return this + } + + override fun minus(other: Int): Decimal { + return this + } + + override fun times(other: Int): Decimal { + return if (other == 0) + Zero + else if (other < 0) + PositiveInfinity + else + this + } + + override fun div(other: Int): Decimal { + return if (other == 0) + throw ArithmeticException("Attempt to divide negative infinity by zero") + else if (other < 0) + PositiveInfinity + else + this + } + + override fun plus(other: Long): Decimal { + return this + } + + override fun minus(other: Long): Decimal { + return this + } + + override fun times(other: Long): Decimal { + return if (other == 0L) + Zero + else if (other < 0L) + PositiveInfinity + else + this + } + + override fun div(other: Long): Decimal { + return if (other == 0L) + throw ArithmeticException("Attempt to divide negative infinity by zero") + else if (other < 0L) + PositiveInfinity + else + this + } + + override fun plus(other: BigInteger): Decimal { + return this + } + + override fun minus(other: BigInteger): Decimal { + return this + } + + override fun times(other: BigInteger): Decimal { + return if (other.signum() == 0) + Zero + else if (other.signum() < 0) + PositiveInfinity + else + this + } + + override fun div(other: BigInteger): Decimal { + return if (other.signum() == 0) + throw ArithmeticException("Attempt to divide negative infinity by zero") + else if (other.signum() < 0) + PositiveInfinity + else + this + } + + override fun unaryMinus(): Decimal { + return PositiveInfinity + } + + override fun signum(): Int { + return -1 + } + + override fun toByteArray(): ByteArray { + return byteArrayOf(TYPE_NEGATIVE_INFINITY) + } + + override fun floor(): Decimal { + return this + } + + override fun toString(decimals: Int): String { + return "-Infinity" + } + + override fun toBigDecmial(): BigDecimal { + throw UnsupportedOperationException("Unable to construct BigDecimal of negative infinity") + } + } + + private object Zero : Decimal() { + private fun readResolve(): Any = Zero + + override val isInfinite: Boolean + get() = false + override val isFinite: Boolean + get() = true + + override fun compareTo(other: Decimal): Int { + return -other.signum() + } + + override fun toByte(): Byte { + return 0 + } + + override fun toDouble(): Double { + return 0.0 + } + + override fun toFloat(): Float { + return 0f + } + + override fun toInt(): Int { + return 0 + } + + override fun toLong(): Long { + return 0L + } + + override fun toShort(): Short { + return 0 + } + + override val whole: BigInteger + get() = BigInteger.ZERO + override val fractional: BigInteger + get() = BigInteger.ZERO + + override fun plus(other: Decimal): Decimal { + return other + } + + override fun minus(other: Decimal): Decimal { + return -other + } + + override fun times(other: Decimal): Decimal { + return this + } + + override fun div(other: Decimal): Decimal { + if (other === this) + throw ArithmeticException("0 / 0") + else + return this + } + + override fun rem(other: Decimal): Decimal { + if (other === this) + throw ArithmeticException("0 % 0") + else + return this + } + + override fun plus(other: Float): Decimal { + return valueOf(other) + } + + override fun minus(other: Float): Decimal { + return valueOf(-other) + } + + override fun times(other: Float): Decimal { + return this + } + + override fun div(other: Float): Decimal { + if (other == 0f) + throw ArithmeticException("0 / 0") + + return this + } + + override fun plus(other: Double): Decimal { + return valueOf(other) + } + + override fun minus(other: Double): Decimal { + return valueOf(-other) + } + + override fun times(other: Double): Decimal { + return this + } + + override fun div(other: Double): Decimal { + if (other == 0.0) + throw ArithmeticException("0 / 0") + + return this + } + + override fun plus(other: Int): Decimal { + return valueOf(other) + } + + override fun minus(other: Int): Decimal { + return valueOf(-other) + } + + override fun times(other: Int): Decimal { + return this + } + + override fun div(other: Int): Decimal { + if (other == 0) + throw ArithmeticException("0 / 0") + + return this + } + + override fun plus(other: Long): Decimal { + return valueOf(other) + } + + override fun minus(other: Long): Decimal { + return valueOf(-other) + } + + override fun times(other: Long): Decimal { + return this + } + + override fun div(other: Long): Decimal { + if (other == 0L) + throw ArithmeticException("0 / 0") + + return this + } + + override fun plus(other: BigInteger): Decimal { + return valueOf(other) + } + + override fun minus(other: BigInteger): Decimal { + return valueOf(-other) + } + + override fun times(other: BigInteger): Decimal { + return this + } + + override fun div(other: BigInteger): Decimal { + if (other == BigInteger.ZERO) + throw ArithmeticException("0 / 0") + + return this + } + + override fun unaryMinus(): Decimal { + return this + } + + override fun signum(): Int { + return 0 + } + + override fun toByteArray(): ByteArray { + return byteArrayOf(TYPE_ZERO) + } + + override fun floor(): Decimal { + return this + } + + override fun toString(decimals: Int): String { + if (decimals == 0) + return "0" + else if (decimals < 0) + return "0.0" + else + return "0." + "0".repeat(decimals) + } + + override fun toBigDecmial(): BigDecimal { + return BigDecimal.ZERO + } + } + + @Suppress("unused") + companion object { + /** + * Amount of digits after fixed point, in 10 radix + */ + const val PRECISION = 11 + + const val TYPE_ZERO: Byte = 0 + const val TYPE_NORMAL: Byte = 1 + const val TYPE_POSITIVE_INFINITY: Byte = 2 + const val TYPE_NEGATIVE_INFINITY: Byte = 3 + + @JvmField + val PRECISION_POW_BI: BigInteger = BigInteger("1" + "0".repeat(PRECISION)) + @JvmField + val PRECISION_DOUBLE = PRECISION_POW_BI.toDouble() + @JvmField + val PRECISION_FLOAT = PRECISION_POW_BI.toFloat() + @JvmField + val PRECISION_POW_BI_NEGATIVE: BigInteger = -PRECISION_POW_BI + @JvmField + val PRECISION_POW_BI_HIGH: BigInteger = PRECISION_POW_BI / BigInteger.TWO + + private const val cacheSize = 16384 + private const val cacheSizeL = cacheSize.toLong() + + private val cache = Array(cacheSize) { Regular(BigInteger.valueOf(it.toLong() - cacheSize / 2)) } + + init { + check(cache[cacheSize / 2].signum() == 0) + cache[cacheSize / 2] = Zero + } + + /** + * Returns pooled value if present, otherwise constructs new object + */ + @JvmStatic + fun valueOf(value: Int): Decimal { + if (value in (cacheSize / -2) until cacheSize / 2) { + return cache[value + cacheSize / 2] + } + + return Regular(BigInteger.valueOf(value.toLong())) + } + + /** + * Returns pooled value if present, otherwise constructs new object + */ + @JvmStatic + fun valueOf(value: Long): Decimal { + if (value in (cacheSizeL / -2L) until cacheSizeL / 2L) { + return cache[value.toInt() + cacheSize / 2] + } + + return Regular(BigInteger.valueOf(value)) + } + + /** + * Returns pooled value if present, otherwise constructs new object + */ + @JvmStatic + fun valueOf(value: Short) = valueOf(value.toInt()) + + /** + * Returns pooled value if present, otherwise constructs new object + */ + @JvmStatic + fun valueOf(value: Byte) = valueOf(value.toInt()) + + @JvmStatic fun valueOf(value: BigInteger): Decimal { + return if (value.signum() == 0) { + Zero + } else { + Regular(value) + } + } + + @JvmStatic fun valueOf(value: BigDecimal): Decimal { + return if (value.signum() == 0) { + Zero + } else { + Regular(value) + } + } + + @JvmStatic fun valueOf(value: String): Decimal { + if (value == "0") { + return Zero + } else { + val lower = value.lowercase() + + // why not + if (lower.startsWith("+inf") || lower.startsWith("inf") || lower == "∞" || lower == "+∞") { + return PositiveInfinity + } else if (lower.startsWith("-inf") || lower == "-∞") { + return NegativeInfinity + } else { + val result = Regular(value) + if (result.signum() == 0) return Zero + return result + } + } + } + + @JvmStatic fun valueOf(value: Float): Decimal { + return if (value == 0f) { + Zero + } else if (value == Float.POSITIVE_INFINITY) { + PositiveInfinity + } else if (value == Float.NEGATIVE_INFINITY) { + NegativeInfinity + } else { + Regular(value) + } + } + + @JvmStatic fun valueOf(value: Double): Decimal { + return if (value == 0.0) { + Zero + } else if (value == Double.POSITIVE_INFINITY) { + PositiveInfinity + } else if (value == Double.NEGATIVE_INFINITY) { + NegativeInfinity + } else { + Regular(value) + } + } + + @JvmStatic + fun fromByteArray(input: ByteArray): Decimal { + return if (input.isEmpty()) { + Zero + } else { + return when (input[0]) { + TYPE_NORMAL -> raw(BigInteger(input.copyOfRange(1, input.size))) + TYPE_NEGATIVE_INFINITY -> NegativeInfinity + TYPE_POSITIVE_INFINITY -> PositiveInfinity + // TYPE_ZERO -> Zero + else -> Zero + } + } + } + + @JvmStatic + fun deserializeNBT(input: Tag?): Decimal { + if (input is ByteArrayTag) { + return fromByteArray(input.asByteArray) + } else if (input is StringTag) { + return valueOf(input.asString) + } + + return Zero + } + + @JvmStatic + fun read(buff: FriendlyByteBuf): Decimal { + return fromByteArray(buff.readByteArray()) + } + + /** + * Takes in [value] as-is and constructs [Decimal] out of it ([value] is treated as already scaled by [PRECISION]) + */ + @JvmStatic + fun raw(value: BigInteger): Decimal { + return if (value.signum() == 0) { + Zero + } else { + Regular(value, null) + } + } + + @JvmField val POSITIVE_INFINITY: Decimal = PositiveInfinity + @JvmField val NEGATIVE_INFINITY: Decimal = NegativeInfinity + @JvmField val ZERO: Decimal = Zero + @JvmField val ONE = valueOf(1) + @JvmField val TWO = valueOf(2) + @JvmField val TEN = valueOf(10) + @JvmField val MINUS_ONE = valueOf(-1) + @JvmField val MINUS_TWO = valueOf(-2) + @JvmField val MINUS_TEN = valueOf(-10) + @JvmField val ONE_TENTH = valueOf("0.1") + @JvmField val ONE_QUARTER = valueOf("0.25") + @JvmField val ONE_EIGHTH = valueOf("0.125") + @JvmField val ONE_HALF = valueOf("0.5") + @JvmField val MINUS_ONE_TENTH = valueOf("-0.1") + @JvmField val MINUS_ONE_EIGHTH = valueOf("-0.125") + @JvmField val MINUS_ONE_QUARTER = valueOf("-0.25") + @JvmField val MINUS_ONE_HALF = valueOf("-0.5") + + @JvmField val INT_MAX_VALUE: Decimal = Regular(BI_INT_MAX) + @JvmField val INT_MIN_VALUE: Decimal = Regular(BI_INT_MIN) + @JvmField val LONG_MAX_VALUE: Decimal = Regular(BI_LONG_MAX) + @JvmField val LONG_MIN_VALUE: Decimal = Regular(BI_LONG_MIN) + + @JvmField val FLOAT_MAX_VALUE: Decimal = Regular(BI_FLOAT_MAX) + @JvmField val FLOAT_MIN_VALUE: Decimal = Regular(BI_FLOAT_MIN) + @JvmField val DOUBLE_MAX_VALUE: Decimal = Regular(BI_DOUBLE_MAX) + @JvmField val DOUBLE_MIN_VALUE: Decimal = Regular(BI_DOUBLE_MIN) + } +} + +fun FriendlyByteBuf.readDecimal() = Decimal.read(this) +fun FriendlyByteBuf.writeDecimal(value: Decimal) = value.write(this) + +fun InputStream.readDecimal(): Decimal { + val size = readVarIntLE() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return Decimal.fromByteArray(bytes) +} + +fun OutputStream.writeDecimal(value: Decimal) { + val bytes = value.toByteArray() + writeVarIntLE(bytes.size) + write(bytes) +} + +fun CompoundTag.getDecimal(key: String) = Decimal.deserializeNBT(this[key]) +fun CompoundTag.putDecimal(key: String, value: Decimal) = put(key, value.serializeNBT()) + +operator fun CompoundTag.set(key: String, value: Decimal) = putDecimal(key, value) + +fun Float.toDecimal() = Decimal(this) +fun Double.toDecimal() = Decimal(this) +fun Int.toDecimal() = Decimal(this) +fun Byte.toDecimal() = Decimal(this) +fun Short.toDecimal() = Decimal(this) +fun Long.toDecimal() = Decimal(this) +fun Decimal.toDecimal() = this + +class DecimalConfigValue( + parent: ForgeConfigSpec.ConfigValue, + val minimum: Decimal? = null, + val maximum: Decimal? = null, +) : ObservedConfigValue(parent) { + override fun fromString(value: String): Decimal? { + try { + val parsed = Decimal(value) + + if (minimum != null && minimum > parsed) { + return minimum + } else if (maximum != null && maximum < parsed) { + return maximum + } + + return parsed + } catch (err: java.lang.NumberFormatException) { + return null + } + } + + override fun toString(value: Decimal): Pair { + if (minimum != null && minimum > value) { + return minimum.toString() to minimum + } else if (maximum != null && maximum < value) { + return maximum.toString() to maximum + } + + return value.toString() to value + } +} + +private fun ForgeConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) { + if (minimum != null && maximum != null) { + comment("Range: $minimum ~ $maximum") + } else if (minimum != null) { + comment("Range: >= $minimum") + } else if (maximum != null) { + comment("Range: <= $maximum") + } +} + +fun ForgeConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { + commentRange(minimum, maximum) + comment("Default: $defaultValue") + return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) +} + +fun ForgeConfigSpec.Builder.defineDecimal(path: List, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { + commentRange(minimum, maximum) + comment("Default: $defaultValue") + return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) +} + +fun RandomSource.nextDecimal(min: Decimal, max: Decimal, round: Boolean = false): Decimal { + val value = nextDouble() + + return if (round) { + Decimal((min + (max - min) * value).whole) + } else { + min + (max - min) * value + } +} + +fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal { + return nextDecimal(-value / 2, value / 2, round) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt similarity index 59% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt index cbdf67f10..2f3602cd2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt @@ -1,16 +1,23 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.math -import com.mojang.math.Matrix3f -import com.mojang.math.Matrix4f -import com.mojang.math.Quaternion -import com.mojang.math.Vector3f -import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap +import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntSpliterators import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.core.Vec3i import net.minecraft.world.phys.Vec3 +import org.joml.AxisAngle4f +import org.joml.Matrix3f +import org.joml.Matrix4f +import org.joml.Quaternionf +import org.joml.Vector3f +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.flatMap +import ru.dbotthepony.mc.otm.core.collect.map import java.lang.ref.SoftReference +import java.util.stream.Stream +import java.util.stream.StreamSupport import kotlin.math.* typealias Vector = Vec3 @@ -22,6 +29,12 @@ val VECTOR_DOWN = Vector(0.0, -1.0, 0.0) val VECTOR_RIGHT = Vector(0.0, 0.0, 1.0) val VECTOR_LEFT = Vector(0.0, 0.0, -1.0) +private const val DEGREES_TO_RADIANS = 0.017453292f + +fun toRadians(angle: Float): Float { + return angle * DEGREES_TO_RADIANS +} + fun Vector.asAngle(): Angle { val norm = normalize() return Angle(asin(norm.y), atan2(norm.x, norm.z)) @@ -33,39 +46,30 @@ fun Vector.asMutableAngle(): MutableAngle { } fun Vector3f.asAngle(): Angle { - val len = length() + val len = length().toDouble() val norm = Vector(x() / len, y() / len, z() / len) return Angle(asin(norm.y), atan2(norm.x, norm.z)) } fun Vector3f.asMutableAngle(): MutableAngle { - val len = length() + val len = length().toDouble() val norm = Vector(x() / len, y() / len, z() / len) return MutableAngle(asin(norm.y), atan2(norm.x, norm.z)) } +fun Vector3f.rotation(radians: Float): Quaternionf { + return Quaternionf(AxisAngle4f(radians, x, y, z)) +} + +fun Vector3f.rotationDegrees(degrees: Float): Quaternionf { + return rotation(toRadians(degrees)) +} + operator fun Vector3f.unaryMinus(): Vector3f { - setX(-x()) - setY(-y()) - setZ(-z()) - return this + return Vector3f(-x, -y, -z) } -fun Vector3f.length() = sqrt(x().toDouble() * x() + y().toDouble() * y() + z().toDouble() * z()) - -operator fun Vector3f.times(v: Float): Vector3f { - setX(x() * v) - setY(y() * v) - setZ(z() * v) - return this -} - -operator fun Vector3f.div(v: Float): Vector3f { - setX(x() / v) - setY(y() / v) - setZ(z() / v) - return this -} +operator fun Vector3f.times(v: Float): Vector3f = Vector3f(v).mul(v) operator fun Vector3f.component1() = x() operator fun Vector3f.component2() = y() @@ -129,9 +133,9 @@ fun Vector.rotateAroundAxis(axis: Vector, rotation: Double): Vector { fun Vector.rotate(angle: IAngle): Vector { val rotation = angle.rotationXYZ() - val newx = x * rotation[0, 0] + y * rotation[0, 1] + z * rotation[0, 2] - val newy = x * rotation[1, 0] + y * rotation[1, 1] + z * rotation[1, 2] - val newz = x * rotation[2, 0] + y * rotation[2, 1] + z * rotation[2, 2] + val newx = x * rotation[0, 0,] + y * rotation[1, 0] + z * rotation[2, 0] + val newy = x * rotation[0, 1,] + y * rotation[1, 1] + z * rotation[2, 1] + val newz = x * rotation[0, 2,] + y * rotation[1, 2] + z * rotation[2, 2] return Vector(newx, newy, newz) } @@ -140,8 +144,14 @@ fun Vector.asVector3f(): Vector3f { return Vector3f(x.toFloat(), y.toFloat(), z.toFloat()) } -fun Quaternion(vec: Vector, rotation: Float, isDegrees: Boolean = false): Quaternion { - return Quaternion(vec.asVector3f(), rotation, isDegrees) +fun Quaternion(vec: Vector, rotation: Float, isDegrees: Boolean = false): Quaternionf { + // TODO: 1.19.3 + return Quaternionf(AxisAngle4f(if (isDegrees) toRadians(rotation) else rotation, vec.x.toFloat(), vec.y.toFloat(), vec.z.toFloat())) +} + +fun AxisAngle4f(vec: Vector, rotation: Float, isDegrees: Boolean = false): AxisAngle4f { + // TODO: 1.19.3 + return AxisAngle4f(if (isDegrees) toRadians(rotation) else rotation, vec.x.toFloat(), vec.y.toFloat(), vec.z.toFloat()) } fun Vector(x: Double): Vector { @@ -152,17 +162,24 @@ fun Vector(x: Double, y: Double): Vector { return Vector(x, y, 0.0) } -fun Vector.rotate(q: Quaternion): Vector { - val quaternion = Quaternion(q) - quaternion.mul(Quaternion(x.toFloat(), y.toFloat(), z.toFloat(), 0.0f)) - val quaternion1 = Quaternion(q) - quaternion1.conj() +fun Vector.rotate(q: Quaternionf): Vector { + // TODO: 1.19.3 + val quaternion = Quaternionf(q) + quaternion.mul(rotateAroundThis()) + val quaternion1 = Quaternionf(q) + quaternion1.conjugate() quaternion.mul(quaternion1) - return Vector(quaternion.i().toDouble(), quaternion.j().toDouble(), quaternion.k().toDouble()) + return Vector(quaternion.x.toDouble(), quaternion.y.toDouble(), quaternion.z.toDouble()) } -fun Vector.rotateAroundThis(rotation: Float, isDegrees: Boolean = false): Quaternion { - return Quaternion(this, rotation, isDegrees) +fun Vector.rotateAroundThis(rotation: Float, isDegrees: Boolean = false): Quaternionf { + // TODO: 1.19.3 + return Quaternionf(AxisAngle4f(if (isDegrees) toRadians(rotation) else rotation, x.toFloat(), y.toFloat(), z.toFloat())) +} + +fun Vector.rotateAroundThis(): Quaternionf { + // TODO: 1.19.3 + return Quaternionf(AxisAngle4f(0f, x.toFloat(), y.toFloat(), z.toFloat())) } fun Vector.asMatrix(): Matrix3f { @@ -197,7 +214,7 @@ interface IAngle { fun rotationX(): Matrix3f { if (roll == 0.0) { - return Matrix3f().also { it.identityFast() } + return Matrix3f().also { it.identity() } } return Matrix3f().also { @@ -205,16 +222,18 @@ interface IAngle { val c = cos(roll).toFloat() it[0, 0] = 1f + it[1, 1] = c - it[1, 2] = -s - it[2, 1] = s + it[2, 1] = -s + + it[1, 2] = s it[2, 2] = c } } fun rotationZ(): Matrix3f { if (pitch == 0.0) { - return Matrix3f().also { it.identityFast() } + return Matrix3f().also { it.identity() } } return Matrix3f().also { @@ -222,35 +241,37 @@ interface IAngle { val c = cos(pitch).toFloat() it[0, 0] = c - it[0, 1] = -s - it[1, 0] = s + it[1, 0] = -s + + it[0, 1] = s it[1, 1] = c + it[2, 2] = 1f } } fun rotationY(): Matrix3f { if (yaw == 0.0) { - return Matrix3f().also { it.identityFast() } + return Matrix3f().also { it.identity() } } return Matrix3f().also { val s = sin(yaw).toFloat() val c = cos(yaw).toFloat() - it[0, 0] = c - it[0, 2] = s + it[0, 0,] = c + it[2, 0,] = s - it[2, 0] = -s - it[2, 2] = c + it[0, 2,] = -s + it[2, 2,] = c - it[1, 1] = 1f + it[1, 1,] = 1f } } fun rotationXYZ(): Matrix3f { if (pitch == 0.0 && yaw == 0.0 && roll == 0.0) - return Matrix3f().also { it.identityFast() } + return Matrix3f().also { it.identity() } return rotationY().also { it.mul(rotationZ()) @@ -260,13 +281,14 @@ interface IAngle { fun rotationXYZW(): Matrix4f { if (pitch == 0.0 && yaw == 0.0 && roll == 0.0) - return Matrix4f().also { it.identityFast() } + return Matrix4f().also { it.identity() } return rotationXYZ().toMatrix4f().also { it[3, 3] = 1f } } } -data class Angle(override val pitch: Double = 0.0, override val yaw: Double = 0.0, override val roll: Double = 0.0) : IAngle { +data class Angle(override val pitch: Double = 0.0, override val yaw: Double = 0.0, override val roll: Double = 0.0) : + IAngle { companion object { const val PI_HALF = Math.PI / 2.0 @@ -284,7 +306,8 @@ data class Angle(override val pitch: Double = 0.0, override val yaw: Double = 0. } } -data class MutableAngle(override var pitch: Double = 0.0, override var yaw: Double = 0.0, override var roll: Double = 0.0) : IAngle { +data class MutableAngle(override var pitch: Double = 0.0, override var yaw: Double = 0.0, override var roll: Double = 0.0) : + IAngle { companion object { fun deg(pitch: Double, yaw: Double, roll: Double): MutableAngle { return MutableAngle(Math.toRadians(pitch), Math.toRadians(yaw), Math.toRadians(roll)) @@ -416,66 +439,26 @@ operator fun Direction.unaryMinus(): Direction = this.opposite operator fun Vec3i.unaryMinus(): Vec3i = Vec3i(-x, -y, -z) operator fun BlockPos.unaryMinus(): BlockPos = BlockPos(-x, -y, -z) -private fun ellipsoidShapeCacheKey(x: Int, y: Int, z: Int): Long { - require(x > 0) { "Negative X $x" } - require(y > 0) { "Negative Y $y" } - require(z > 0) { "Negative Z $z" } - - return (x.toLong() and 0xFFFFFL) or - ((y.toLong() ushr 20) and 0xFFFFFL) or - ((z.toLong() ushr 40) and 0xFFFFFL) -} - -private val blockShapeCache = Long2ObjectOpenHashMap>>() - -fun getSphericalBlockPositions(size: Int): List { +fun getSphericalBlockPositions(size: Int): Iterator { return getEllipsoidBlockPositions(size, size, size) } -fun getEllipsoidBlockPositions(x: Int, y: Int, z: Int): List { - val getResult = blockShapeCache[ellipsoidShapeCacheKey(x, y, z)]?.get() - - if (getResult != null) { - return getResult - } - - val result = ArrayList((x * y * z * Math.PI * (4.0 / 3.0)).toInt() + 16) - +fun getEllipsoidBlockPositions(x: Int, y: Int, z: Int): Iterator { val xPow = x.toDouble().pow(2.0) val yPow = y.toDouble().pow(2.0) val zPow = z.toDouble().pow(2.0) - for (ellipsoidX in -x..x) { - for (ellipsoidY in -y..y) { - for (ellipsoidZ in -z..z) { - if ( - ellipsoidX.toDouble().pow(2.0) / xPow + - ellipsoidY.toDouble().pow(2.0) / yPow + - ellipsoidZ.toDouble().pow(2.0) / zPow <= 1.0 - ) { - result.add(BlockPos(ellipsoidX, ellipsoidY, ellipsoidZ)) - } - } + return (-x .. x).iterator().flatMap { ix -> + (-y .. y).iterator().flatMap { iy -> + (-z .. z).iterator().map { BlockPos(ix, iy, it) } } + }.filter { pos -> + val (ellipsoidX, ellipsoidY, ellipsoidZ) = pos + + ellipsoidX.toDouble().pow(2.0) / xPow + + ellipsoidY.toDouble().pow(2.0) / yPow + + ellipsoidZ.toDouble().pow(2.0) / zPow <= 1.0 } - - result.sortWith { a, b -> - val xa = a.x - val ya = a.y - val za = a.z - val distA = sqrt(xa * xa.toDouble() + ya * ya.toDouble() + za * za.toDouble()) - - val xb = b.x - val yb = b.y - val zb = b.z - val distB = sqrt(xb * xb.toDouble() + yb * yb.toDouble() + zb * zb.toDouble()) - - return@sortWith distA.compareTo(distB) - } - - val immutableList = com.google.common.collect.ImmutableList.copyOf(result) - blockShapeCache[ellipsoidShapeCacheKey(x, y, z)] = SoftReference(immutableList) - return immutableList } fun normalizeAngle(angle: Double): Double { @@ -527,3 +510,202 @@ fun angleDifferenceDeg(angle1: Float, angle2: Float): Float { return 360f - diff } + +// 1.19.3 stuff +fun PoseStack.rotateY(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(rotation, 0f, 1f, 0f))) + return this +} + +fun PoseStack.rotateX(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(rotation, 1f, 0f, 0f))) + return this +} + +fun PoseStack.rotateZ(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(rotation, 0f, 0f, 1f))) + return this +} + +fun PoseStack.rotate(rotation: Direction): PoseStack { + return rotateY(rotation.yRotationNorth()).rotateX(rotation.xRotationNorth()) +} + +fun PoseStack.rotateSouth(rotation: Direction): PoseStack { + return rotateY(rotation.yRotationSouth()).rotateX(rotation.xRotationSouth()) +} +// /1.19.3 stuff + +const val PIf = 3.1415927f + +/** + * aligns 0,0 with top left point of block corner when looking directly at it, + * and swaps Y to +Y when going down and -Y when going up + * + * allows to draw 2D interface in 3D space + */ +fun PoseStack.rotateWithBlockFacing(rotation: Direction, clarifyingAxis: Direction? = null): PoseStack { + when (rotation) { + Direction.NORTH -> { + rotateX(PIf) + rotateY(PIf) + translate(-1f, -1f, 0f) + } + + Direction.EAST -> { + rotateX(PIf) + rotateY(PIf / -2f) + translate(-1f, -1f, -1f) + } + + Direction.WEST -> { + rotateX(PIf) + rotateY(PIf / 2f) + translate(0f, -1f, 0f) + } + + Direction.UP -> { + rotateX(PIf / 2f) + translate(0f, 0f, -1f) + + if (clarifyingAxis != null) { + when (clarifyingAxis) { + Direction.NORTH -> {} + Direction.SOUTH -> { + rotateZ(PIf) + translate(-1f, -1f, 0f) + } + Direction.WEST -> { + rotateZ(PIf / -2f) + translate(-1f, 0f, 0f) + } + Direction.EAST -> { + rotateZ(PIf / 2f) + translate(0f, -1f, 0f) + } + else -> {} + } + } + } + + Direction.DOWN -> { + rotateY(PIf) + rotateX(PIf / -2f) + translate(-1f, 0f, 0f) + + if (clarifyingAxis != null) { + when (clarifyingAxis) { + Direction.NORTH -> {} + Direction.SOUTH -> { + rotateZ(PIf) + translate(-1f, -1f, 0f) + } + Direction.WEST -> { + rotateZ(PIf / 2f) + translate(0f, -1f, 0f) + } + Direction.EAST -> { + rotateZ(PIf / -2f) + translate(-1f, 0f, 0f) + } + else -> {} + } + } + } + + Direction.SOUTH -> { + rotateX(PIf) + translate(0f, -1f, -1f) + } + } + + return this +} + +fun PoseStack.rotateWithBlockFacing(rotation: BlockRotation): PoseStack { + return rotateWithBlockFacing(rotation = rotation.front, clarifyingAxis = rotation.top) +} + +fun PoseStack.rotateYDegrees(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(toRadians(rotation), 0f, 1f, 0f))) + return this +} + +fun PoseStack.rotateXDegrees(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(toRadians(rotation), 1f, 0f, 0f))) + return this +} + +fun PoseStack.rotateZDegrees(rotation: Float): PoseStack { + mulPose(Quaternionf(AxisAngle4f(toRadians(rotation), 0f, 0f, 1f))) + return this +} + +fun Direction.yRotationBlockstateNorth(): Int { + return when (this) { + Direction.DOWN, Direction.UP, Direction.NORTH -> 0 + Direction.SOUTH -> 180 + Direction.WEST -> -90 + Direction.EAST -> 90 + } +} + +fun Direction.yRotationBlockstateSouth(): Int { + return when (this) { + Direction.DOWN, Direction.UP, Direction.SOUTH -> 0 + Direction.NORTH -> 180 + Direction.WEST -> 90 + Direction.EAST -> -90 + } +} + +fun Direction.xRotationBlockstateNorth(): Int { + return when (this) { + Direction.DOWN -> 90 + Direction.UP -> -90 + else -> 0 + } +} + +fun Direction.xRotationBlockstateSouth(): Int { + return when (this) { + Direction.DOWN -> -90 + Direction.UP -> 90 + else -> 0 + } +} + +fun Direction.yRotationNorth(): Float { + return when (this) { + Direction.DOWN, Direction.UP, Direction.NORTH -> 0f + Direction.SOUTH -> PIf + Direction.WEST -> -PIf / 2f + Direction.EAST -> PIf / 2f + } +} + +fun Direction.yRotationSouth(): Float { + return when (this) { + Direction.DOWN, Direction.UP, Direction.SOUTH -> 0f + Direction.NORTH -> PIf + Direction.WEST -> PIf / 2f + Direction.EAST -> -PIf / 2f + } +} + +fun Direction.xRotationNorth(): Float { + return when (this) { + Direction.DOWN -> PIf / 2f + Direction.UP -> -PIf / 2f + else -> 0f + } +} + +fun Direction.xRotationSouth(): Float { + return when (this) { + Direction.DOWN -> -PIf / 2f + Direction.UP -> PIf / 2f + else -> 0f + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Math.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Math.kt similarity index 57% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/Math.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Math.kt index a5955aa08..21ddabcfc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Math.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Math.kt @@ -1,15 +1,16 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.math import java.math.BigDecimal import java.math.BigInteger +import kotlin.math.absoluteValue -inline val BigInteger.isZero get() = this == BigInteger.ZERO -inline val BigInteger.isPositive get() = this > BigInteger.ZERO -inline val BigInteger.isNegative get() = this < BigInteger.ZERO +inline val BigInteger.isZero get() = this.signum() == 0 +inline val BigInteger.isPositive get() = this.signum() > 0 +inline val BigInteger.isNegative get() = this.signum() < 0 -inline val BigDecimal.isZero get() = this == BigDecimal.ZERO -inline val BigDecimal.isPositive get() = this > BigDecimal.ZERO -inline val BigDecimal.isNegative get() = this < BigDecimal.ZERO +inline val BigDecimal.isZero get() = this.signum() == 0 +inline val BigDecimal.isPositive get() = this.signum() > 0 +inline val BigDecimal.isNegative get() = this.signum() < 0 val BI_INT_MAX: BigInteger = BigInteger.valueOf(Int.MAX_VALUE.toLong()) val BI_INT_MIN: BigInteger = BigInteger.valueOf(Int.MIN_VALUE.toLong()) @@ -21,6 +22,35 @@ val BI_FLOAT_MIN: BigInteger = BigDecimal(Float.MIN_VALUE.toString()).toBigInteg val BI_DOUBLE_MAX: BigInteger = BigDecimal(Double.MAX_VALUE.toString()).toBigInteger() val BI_DOUBLE_MIN: BigInteger = BigDecimal(Double.MIN_VALUE.toString()).toBigInteger() +/** + * Constant value which represent edge of meaningful bits. + * + * Equals to 0.000000000001 + */ +const val EPSILON = 0.000000000001 + +fun weakEqualDoubles(a: Double, b: Double): Boolean { + if (a == b) + return true + + return (a - b).absoluteValue <= EPSILON +} + +fun weakCompareDoubles(a: Double, b: Double): Int { + if (weakEqualDoubles(a, b)) + return 0 + + if (a > b) + return 1 + + return -1 +} + +fun weakLessThan(a: Double, b: Double) = weakCompareDoubles(a, b) < 0 +fun weakGreaterThan(a: Double, b: Double) = weakCompareDoubles(a, b) > 0 +fun weakLessOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) <= 0 +fun weakGreaterOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) >= 0 + fun BigInteger.toIntSafe(): Int { if (this > BI_INT_MAX) { return Int.MAX_VALUE diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/MatrixExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/MatrixExt.kt new file mode 100644 index 000000000..11acd5701 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/MatrixExt.kt @@ -0,0 +1,115 @@ + +@file:Suppress("unused") + +package ru.dbotthepony.mc.otm.core.math + +import org.joml.Matrix3f +import org.joml.Matrix4f +import org.joml.Vector3f +import org.joml.Vector4f +import ru.dbotthepony.mc.otm.core.math.IAngle +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.asVector3f +import ru.dbotthepony.mc.otm.core.math.rotateAroundThis +import ru.dbotthepony.mc.otm.core.math.rotation +import ru.dbotthepony.mc.otm.core.math.rotationDegrees +import ru.dbotthepony.mc.otm.core.math.unaryMinus + +fun Matrix4f.rotate(angle: IAngle): Matrix4f { + mul(angle.rotationXYZW()) + return this +} + +fun Matrix4f.translate(vector: Vector) { + translate(-vector.asVector3f()) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + val p = point.asVector3f() + translate(-p) + rotation(axis.rotateAroundThis(rotation, isDegrees)) // TODO: 1.19.3 + translate(p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { + val p = point.asVector3f() + translate(-p) + rotation(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) // TODO: 1.19.3 + translate(p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector, rotation: IAngle) { + val p = point.asVector3f() + translate(-p) + mul(rotation.rotationXYZW()) + translate(p) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) { + translate(-point) + rotation(axis.rotateAroundThis(rotation, isDegrees)) // TODO: 1.19.3 + translate(point) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, rotation: IAngle) { + translate(-point) + mul(rotation.rotationXYZW()) + translate(point) +} + +fun Matrix4f.rotateAroundPoint(point: Vector3f, axis: Vector3f, rotation: Float, isDegrees: Boolean = false) { + translate(-point) + rotation(if (isDegrees) axis.rotationDegrees(rotation) else axis.rotation(rotation)) // TODO: 1.19.3 + translate(point) +} + +fun Matrix3f.toMatrix4f(): Matrix4f { + val result = Matrix4f() + + result.zero() + + result.m00(this.m00) + result.m10(this.m10) + result.m20(this.m20) + result.m01(this.m01) + result.m11(this.m11) + result.m21(this.m21) + result.m02(this.m02) + result.m12(this.m12) + result.m22(this.m22) + + return result +} + +var Matrix4f.translation: Vector3f + get() { + return Vector3f(this[0, 3], this[1, 3], this[2, 3]) + } + + set(value) { + this[0, 0] = 1f + this[1, 1] = 1f + this[2, 2] = 1f + this[3, 3] = 1f + + this[0, 3] = value.x() + this[1, 3] = value.y() + this[2, 3] = value.z() + } + +var Matrix4f.translation4: Vector4f + get() { + return Vector4f(this[1, 3], this[1, 3], this[2, 3], this[3, 3]) + } + + set(value) { + this[0, 0] = 1f + this[1, 1] = 1f + this[2, 2] = 1f + this[3, 3] = 1f + + this[0, 3] = value.x() + this[1, 3] = value.y() + this[2, 3] = value.z() + this[3, 3] = value.w() + } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/NumberExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/NumberExt.kt new file mode 100644 index 000000000..8c4c3dd64 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/NumberExt.kt @@ -0,0 +1,147 @@ +package ru.dbotthepony.mc.otm.core.math + +import net.minecraft.nbt.ByteArrayTag +import net.minecraft.nbt.Tag +import java.math.BigInteger + +/** + * Performs type check+cast and sums two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynPlus(other: Number): Number { + return when (this) { + is Float -> this + other.toFloat() + is Double -> this + other.toDouble() + is Int -> this + other.toInt() + is Long -> this + other.toLong() + is Short -> this + other.toShort() + is Byte -> this + other.toByte() + + is Decimal -> { + when (other) { + is Float -> this + other.toFloat() + is Double -> this + other.toDouble() + is Int, is Byte, is Short -> this + other.toInt() + is Long -> this + other.toLong() + is Decimal -> this + other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and subtracts two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynMinus(other: Number): Number { + return when (this) { + is Float -> this - other.toFloat() + is Double -> this - other.toDouble() + is Int -> this - other.toInt() + is Long -> this - other.toLong() + is Short -> this - other.toShort() + is Byte -> this - other.toByte() + + is Decimal -> { + when (other) { + is Float -> this - other.toFloat() + is Double -> this - other.toDouble() + is Int, is Byte, is Short -> this - other.toInt() + is Long -> this - other.toLong() + is Decimal -> this - other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and multiplies two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynTimes(other: Number): Number { + return when (this) { + is Float -> this * other.toFloat() + is Double -> this * other.toDouble() + is Int -> this * other.toInt() + is Long -> this * other.toLong() + is Short -> this * other.toShort() + is Byte -> this * other.toByte() + + is Decimal -> { + when (other) { + is Float -> this * other.toFloat() + is Double -> this * other.toDouble() + is Int, is Byte, is Short -> this * other.toInt() + is Long -> this * other.toLong() + is Decimal -> this * other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +/** + * Performs type check+cast and divides two numbers. + * + * Returns the same type as on input (adding Double to Float will not return Double, etc.) + */ +fun Number.dynDiv(other: Number): Number { + return when (this) { + is Float -> this / other.toFloat() + is Double -> this / other.toDouble() + is Int -> this / other.toInt() + is Long -> this / other.toLong() + is Short -> this / other.toShort() + is Byte -> this / other.toByte() + + is Decimal -> { + when (other) { + is Float -> this / other.toFloat() + is Double -> this / other.toDouble() + is Int, is Byte, is Short -> this / other.toInt() + is Long -> this / other.toLong() + is Decimal -> this / other + else -> throw IllegalStateException("I don't know what to do with ${other::class.qualifiedName} (right hand operand)") + } + } + + else -> throw IllegalStateException("I don't know what to do with ${this::class.qualifiedName} (left hand operand)") + } +} + +val Number.isFractional get() = this is Float || this is Double || this is Decimal +val Number.isWhole get() = !isFractional +fun Number.toDecimal(): Decimal { + return when (this) { + is Decimal -> this + is Float -> Decimal(this) + is Double -> Decimal(this) + is Int -> Decimal(this) + is Byte -> Decimal(this) + is Short -> Decimal(this) + is Long -> Decimal(this) + else -> throw ClassCastException("Can not turn $this into ImpreciseFraction") + } +} + +fun BigInteger(tag: Tag?): BigInteger { + if (tag !is ByteArrayTag) + return BigInteger.ZERO + + return BigInteger(tag.asByteArray) +} + +fun BigInteger.serializeNBT(): ByteArrayTag { + return ByteArrayTag(toByteArray()) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagDelegates.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagDelegates.kt similarity index 97% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagDelegates.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagDelegates.kt index c3fc4a3df..7b6161557 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagDelegates.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagDelegates.kt @@ -1,7 +1,6 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.nbt import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.LongArrayTag import java.util.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt similarity index 78% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagExt.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt index 2a93691b8..5adc25187 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/CompoundTagExt.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt @@ -1,5 +1,8 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.nbt +import com.google.gson.JsonElement +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import net.minecraft.nbt.ByteArrayTag import net.minecraft.nbt.ByteTag import net.minecraft.nbt.CompoundTag @@ -10,11 +13,15 @@ import net.minecraft.nbt.IntTag import net.minecraft.nbt.ListTag import net.minecraft.nbt.LongArrayTag import net.minecraft.nbt.LongTag +import net.minecraft.nbt.NbtAccounter import net.minecraft.nbt.NbtUtils +import net.minecraft.nbt.NumericTag import net.minecraft.nbt.ShortTag import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.util.readBinaryJson +import ru.dbotthepony.mc.otm.core.util.writeBinaryJson import java.util.UUID operator fun CompoundTag.set(index: String, value: Tag) = put(index, value) @@ -52,7 +59,7 @@ inline fun CompoundTag.map(key: String, consumer: (T) -> R return null } -inline fun CompoundTag.mapIf(key: String, consumer: (T) -> R): R? { +inline fun CompoundTag.mapPresent(key: String, consumer: (T) -> R): R? { val tag = get(key) if (tag is T) { @@ -62,46 +69,18 @@ inline fun CompoundTag.mapIf(key: String, consumer: (T) -> return null } -inline fun > CompoundTag.getEnum(key: String): R { - val tag = get(key) +fun CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T): T { + val tag = this[index] as? StringTag ?: return orElse - if (tag is StringTag) { - return R::class.java.enumConstants.first { it.name == tag.asString } + return try { + mapper.invoke(tag.asString) + } catch (err: NoSuchElementException) { + orElse } - - return R::class.java.enumConstants[0] } fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY -inline fun CompoundTag.ifHas(s: String, consumer: (Tag) -> Unit) { - val tag = get(s) - - if (tag != null) { - consumer(tag) - } -} - -inline fun CompoundTag.ifHas(s: String, type: Byte, consumer: (Tag) -> Unit) { - val tag = get(s) - - if (tag != null && tag.id == type) { - consumer(tag) - } -} - -inline fun CompoundTag.ifHas(s: String, type: Class, consumer: (T) -> Unit) { - val tag = get(s) - - if (tag != null && tag::class.java === type) { - consumer(tag as T) - } -} - -fun CompoundTag.getList(key: String): ListTag { - return this[key] as? ListTag ?: ListTag() -} - @Suppress("unchecked_cast") // type is checked inside getList fun CompoundTag.getByteList(key: String): MutableList = getList(key, Tag.TAG_BYTE.toInt()) as MutableList @Suppress("unchecked_cast") // type is checked inside getList @@ -131,3 +110,22 @@ fun CompoundTag.getUUIDSafe(key: String): UUID? { val value = this[key] as? IntArrayTag ?: return null return NbtUtils.loadUUID(value) } + +fun CompoundTag.putJson(key: String, json: JsonElement) { + val bytes = FastByteArrayOutputStream() + bytes.writeBinaryJson(json) + putByteArray(key, bytes.array.copyOfRange(0, bytes.length)) +} + +fun CompoundTag.getJson(key: String, sizeLimit: NbtAccounter = NbtAccounter(1 shl 18)): JsonElement? { + val bytes = getByteArray(key) + + if (bytes.isEmpty()) + return null + + return FastByteArrayInputStream(bytes).readBinaryJson() +} + +fun CompoundTag.getBoolean(index: String, orElse: Boolean): Boolean { + return (this[index] as? NumericTag)?.asInt?.let { it > 0 } ?: orElse +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt new file mode 100644 index 000000000..d8c7c0593 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.core.util + +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.locks.ReentrantLock +import java.util.function.IntSupplier + +/** + * If synchronization is not required, [CounterInvalidatedLazy] should be used instead. + */ +class AtomicallyInvalidatedLazy(private val invalidator: IntSupplier, private val initializer: () -> V) : InvalidableLazy { + constructor(invalidator: AtomicInteger, initializer: () -> V) : this(invalidator::get, initializer) + + @Volatile + private var thisCounter = -1 + @Volatile + private var stored: Any? = Companion + private val lock = ReentrantLock() + + override val value: V get() { + if (thisCounter != invalidator.asInt) { + invalidate() + } + + var stored = stored + + if (stored !== Companion) + return stored as V + + lock.lock() + + try { + stored = initializer.invoke() + this.stored = stored + return stored + } finally { + lock.unlock() + } + } + + override fun invalidate() { + lock.lock() + this.stored = Companion + thisCounter = invalidator.asInt + lock.unlock() + } + + override fun isInitialized(): Boolean { + return stored !== Companion && thisCounter == invalidator.asInt + } + + private companion object +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt new file mode 100644 index 000000000..bd2ae665c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BinaryJson.kt @@ -0,0 +1,212 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.common.collect.ImmutableList +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSyntaxException +import net.minecraft.nbt.NbtAccounter +import ru.dbotthepony.mc.otm.core.collect.stream +import java.io.InputStream +import java.io.OutputStream +import java.util.function.Predicate + +private enum class BinaryElementType(private val predicate: Predicate) : Predicate by predicate { + NULL({ it is JsonNull }) { + override fun write(stream: OutputStream, element: JsonElement) { + } + + override fun read(stream: InputStream): JsonElement { + return JsonNull.INSTANCE + } + }, + + DOUBLE({ it is JsonPrimitive && it.isNumber && (it.asNumber is Double || it.asNumber is Float) }) { + override fun write(stream: OutputStream, element: JsonElement) { + stream.writeDouble(element.asDouble) + } + + override fun read(stream: InputStream): JsonElement { + return JsonPrimitive(stream.readDouble()) + } + }, + + BOOLEAN({ it is JsonPrimitive && it.isBoolean }) { + override fun write(stream: OutputStream, element: JsonElement) { + stream.write(if (element.asBoolean) 1 else 0) + } + + override fun read(stream: InputStream): JsonElement { + return JsonPrimitive(stream.read() > 0) + } + }, + + INT({ it is JsonPrimitive && it.isNumber }) { + override fun write(stream: OutputStream, element: JsonElement) { + val it = element.asNumber + + if (it is Int || it is Long || it is Short || it is Byte) + stream.writeVarLongLE(element.asLong) + else + throw IllegalArgumentException("Unknown number type: ${it::class.qualifiedName}") + } + + override fun read(stream: InputStream): JsonElement { + return JsonPrimitive(stream.readVarLongLE()) + } + }, + + STRING({ it is JsonPrimitive && it.isString }) { + override fun write(stream: OutputStream, element: JsonElement) { + stream.writeBinaryString(element.asString) + } + + override fun read(stream: InputStream): JsonElement { + return JsonPrimitive(stream.readBinaryString()) + } + }, + + ARRAY({ it is JsonArray }) { + override fun write(stream: OutputStream, element: JsonElement) { + writeArray(stream, element, OutputStream::writeBinaryJson) + } + + override fun read(stream: InputStream): JsonElement { + return readArray(stream, InputStream::readBinaryJson) + } + }, + + OBJECT({ it is JsonObject }) { + override fun write(stream: OutputStream, element: JsonElement) { + element as JsonObject + stream.writeVarIntLE(element.size()) + + for ((k, v) in element.entrySet()) { + stream.writeBinaryString(k) + stream.writeBinaryJson(v) + } + } + + override fun read(stream: InputStream): JsonElement { + val count = stream.readVarIntLE() + + if (count == 0) return JsonObject() + if (count < 0) throw JsonSyntaxException("Tried to read json object with $count elements in it") + + val build = JsonObject() + + for (i in 0 until count) { + val key = try { + stream.readBinaryString() + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i", err) + } + + try { + build.add(key, stream.readBinaryJson()) + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i with name $key", err) + } + } + + return build + } + }, + + DOUBLE_ARRAY({ it is JsonArray && it.stream().allMatch { DOUBLE.test(it) } }) { + override fun write(stream: OutputStream, element: JsonElement) { + writeArray(stream, element, DOUBLE::write) + } + + override fun read(stream: InputStream): JsonElement { + return readArray(stream, DOUBLE::read) + } + }, + + INT_ARRAY({ it is JsonArray && it.stream().allMatch { INT.test(it) } }) { + override fun write(stream: OutputStream, element: JsonElement) { + writeArray(stream, element, INT::write) + } + + override fun read(stream: InputStream): JsonElement { + return readArray(stream, INT::read) + } + }, + + BOOLEAN_ARRAY({ it is JsonArray && it.stream().allMatch { BOOLEAN.test(it) } }) { + override fun write(stream: OutputStream, element: JsonElement) { + writeArray(stream, element, BOOLEAN::write) + } + + override fun read(stream: InputStream): JsonElement { + return readArray(stream, BOOLEAN::read) + } + }, + + NULL_ARRAY({ it is JsonArray && it.stream().allMatch { NULL.test(it) } }) { + override fun write(stream: OutputStream, element: JsonElement) { + writeArray(stream, element, NULL::write) + } + + override fun read(stream: InputStream): JsonElement { + return readArray(stream, NULL::read) + } + }; + + protected fun writeArray(stream: OutputStream, element: JsonElement, writer: (OutputStream, JsonElement) -> Unit) { + element as JsonArray + stream.writeVarIntLE(element.size()) + + for (v in element) { + writer.invoke(stream, v) + } + } + + protected fun readArray(stream: InputStream, reader: (InputStream) -> JsonElement): JsonArray { + val count = stream.readVarIntLE() + + if (count == 0) return JsonArray() + if (count < 0) throw JsonSyntaxException("Tried to read json array with $count elements in it") + + return JsonArray(count).also { + for (i in 0 until count) it.add(reader.invoke(stream)) + } + } + + abstract fun write(stream: OutputStream, element: JsonElement) + abstract fun read(stream: InputStream): JsonElement + + companion object { + val cached: ImmutableList = ImmutableList.of( + NULL, DOUBLE, BOOLEAN, INT, STRING, OBJECT, + DOUBLE_ARRAY, INT_ARRAY, BOOLEAN_ARRAY, ARRAY) + + } +} + +/** + * Writes json in efficient binary form to stream + */ +fun OutputStream.writeBinaryJson(element: JsonElement) { + for (writer in BinaryElementType.cached) { + if (writer.test(element)) { + write(writer.ordinal + 1) + writer.write(this, element) + return + } + } + + throw IllegalArgumentException("Unknown element type: ${element::class.qualifiedName}") +} + +/** + * Reads json in binary form from stream + */ +fun InputStream.readBinaryJson(): JsonElement { + val id = read() - 1 + val reader = BinaryElementType.entries.getOrNull(id) ?: throw JsonParseException("Unknown element type ${id + 1}") + return reader.read(this) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt new file mode 100644 index 000000000..3949f9121 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ByteBufExtensions.kt @@ -0,0 +1,48 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.gson.JsonElement +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.JsonOps +import io.netty.buffer.ByteBufInputStream +import io.netty.buffer.ByteBufOutputStream +import io.netty.handler.codec.DecoderException +import io.netty.handler.codec.EncoderException +import net.minecraft.nbt.NbtAccounter +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.network.chat.Component + +fun FriendlyByteBuf.readBinaryJson(): JsonElement { + return ByteBufInputStream(this).readBinaryJson() +} + +fun FriendlyByteBuf.writeBinaryJson(value: JsonElement) { + ByteBufOutputStream(this).writeBinaryJson(value) +} + +fun FriendlyByteBuf.writeBinaryJsonWithCodec(codec: Codec, value: S) { + writeBinaryJson(codec.encode(value, JsonOps.INSTANCE, JsonOps.INSTANCE.empty()) + .get().map({ it }, { throw EncoderException("Failed to encode input data: ${it.message()}") })) +} + +fun FriendlyByteBuf.readBinaryJsonWithCodec(codec: Codec): S { + return codec.decode(JsonOps.INSTANCE, readBinaryJson()) + .get().map({ it.first }, { throw DecoderException("Failed to decode data from network: ${it.message()}") }) +} + +fun FriendlyByteBuf.readBinaryJsonWithCodecIndirect(codec: Codec): DataResult { + return codec.decode(JsonOps.INSTANCE, readBinaryJson()).map { it.first } +} + +fun FriendlyByteBuf.readBinaryComponent(): Component { + return Component.Serializer.fromJson(readBinaryJson()) ?: throw NullPointerException("Received null component") +} + +fun FriendlyByteBuf.writeBinaryComponent(component: Component) { + writeBinaryJson(Component.Serializer.toJsonTree(component)) +} + +// обратный порядок аргументов у лямбда выражения +fun FriendlyByteBuf.writeCollection(collection: Collection, writer: (T, FriendlyByteBuf) -> Unit) { + writeCollection(collection) { a, b -> writer.invoke(b, a) } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/CounterInvalidatedLazy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/CounterInvalidatedLazy.kt new file mode 100644 index 000000000..aa398a7fd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/CounterInvalidatedLazy.kt @@ -0,0 +1,97 @@ +package ru.dbotthepony.mc.otm.core.util + +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.IntConsumer +import java.util.function.IntSupplier + +/** + * This lazy is not synchronized, so in event of two or more threads accessing value behavior of this implementation is undefined + * + * For thread synchronized use case, [AtomicallyInvalidatedLazy] should be used + */ +fun countingLazy(invalidator: IntSupplier, initializer: () -> V): InvalidableLazy { + if (invalidator is IntCounter) { + return SmartCounterInvalidatedLazy(invalidator, initializer) + } + + return CounterInvalidatedLazy(invalidator, initializer) +} + +/** + * This lazy is not synchronized, so in event of two or more threads accessing value behavior of this implementation is undefined + * + * For thread synchronized use case, [AtomicallyInvalidatedLazy] should be used + */ +fun countingLazy(invalidator: AtomicInteger, initializer: () -> V): InvalidableLazy { + return CounterInvalidatedLazy(invalidator, initializer) +} + +/** + * This lazy is not synchronized, so in event of two or more threads accessing value behavior of this implementation is undefined + * + * For synchronized use case, [AtomicallyInvalidatedLazy] should be used + */ +class CounterInvalidatedLazy(private val invalidator: IntSupplier, private val initializer: () -> V) : InvalidableLazy { + constructor(invalidator: AtomicInteger, initializer: () -> V) : this(invalidator::get, initializer) + + private var thisCounter = -1 + private var stored: Any? = Companion + + override val value: V get() { + var stored = stored + + if (stored !== Companion && thisCounter == invalidator.asInt) + return stored as V + + stored = initializer.invoke() + this.stored = stored + this.thisCounter = invalidator.asInt + return stored + } + + override fun isInitialized(): Boolean { + return stored !== Companion && thisCounter == invalidator.asInt + } + + override fun invalidate() { + this.stored = Companion + } + + private companion object +} + +/** + * This lazy is not synchronized, so in event of two or more threads accessing value behavior of this implementation is undefined + * + * For synchronized use case, [AtomicallyInvalidatedLazy] should be used + */ +class SmartCounterInvalidatedLazy(invalidator: IntCounter, private val initializer: () -> V) : InvalidableLazy { + init { + invalidator.addListener(IntConsumer { + stored = Companion + }) + } + + private var stored: Any? = Companion + + override val value: V get() { + var stored = stored + + if (stored !== Companion) + return stored as V + + stored = initializer.invoke() + this.stored = stored + return stored + } + + override fun invalidate() { + this.stored = Companion + } + + override fun isInitialized(): Boolean { + return stored !== Companion + } + + private companion object +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt new file mode 100644 index 000000000..5d979d0f4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ExperienceUtils.kt @@ -0,0 +1,97 @@ +package ru.dbotthepony.mc.otm.core.util + +import it.unimi.dsi.fastutil.longs.LongArrayList +import net.minecraft.world.entity.player.Player + +private val levelsTable = LongArrayList() +private val totalLevelsTable = LongArrayList().also { it.add(0L) } + +private fun calculate(level: Int): Long { + return if (level >= 30) { + 112L + (level - 30) * 9L + } else { + if (level >= 15) 37L + (level - 15) * 5L else 7L + level * 2L + } +} + +/** + * Experience points required for leveling **up from** specified [level]. + * + * Works exactly like [Player.getXpNeededForNextLevel], + * meaning that to achieve `level + 1` player need to acquire [getXpRequiredForLevelUp] experience points + */ +fun getXpRequiredForLevelUp(level: Int): Long { + require(level >= 0) { "Negative level: $level" } + + if (levelsTable.size <= level) { + synchronized(levelsTable) { + while (levelsTable.size <= level) { + levelsTable.add(calculate(levelsTable.size)) + } + } + } + + return levelsTable.getLong(level) +} + +/** + * Experience points required for achieving **exact** [level] specified. + * + * Example: + * * 0 level -> 0 points (because player starts at level 0) + * * 1 level -> [getXpRequiredForLevelUp]`(0)` + * * 2 level -> [getXpRequiredForLevelUp]`(0)` + [getXpRequiredForLevelUp]`(1)` + * * and so on + */ +fun getTotalXpRequiredForLevel(level: Int): Long { + require(level >= 0) { "Negative level: $level" } + + if (totalLevelsTable.size <= level) { + synchronized(totalLevelsTable) { + while (totalLevelsTable.size <= level) { + totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1)) + } + } + } + + return totalLevelsTable.getLong(level) +} + +/** + * Determines current level from [experience] points provided + */ +fun getLevelFromXp(experience: Long): Int { + require(experience >= 0L) { "Negative experience: $experience" } + + if (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) { + synchronized(totalLevelsTable) { + while (totalLevelsTable.getLong(totalLevelsTable.size - 1) < experience) { + totalLevelsTable.add(totalLevelsTable.getLong(totalLevelsTable.size - 1) + getXpRequiredForLevelUp(totalLevelsTable.size - 1)) + } + } + } + + var bottom = 0 + var top = totalLevelsTable.size - 1 + + while (top - bottom > 4) { + val middle = bottom + (top - bottom) / 2 + val middleValue = totalLevelsTable.getLong(middle) + + if (middleValue == experience) { + return middle + } else if (middleValue > experience) { + top = middle - 1 + } else { + bottom = middle + 1 + } + } + + for (i in bottom + 1 .. top) { + if (totalLevelsTable.getLong(i) > experience) { + return i - 1 + } + } + + return top +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt new file mode 100644 index 000000000..56339f947 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt @@ -0,0 +1,236 @@ +package ru.dbotthepony.mc.otm.core.util + +import it.unimi.dsi.fastutil.chars.CharArrayList +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.isNegative +import ru.dbotthepony.mc.otm.core.math.isZero +import java.math.BigInteger +import java.util.function.BooleanSupplier +import kotlin.math.absoluteValue +import kotlin.math.roundToInt + +private fun concat(numbers: String, suffix: Any): Component { + if (suffix == "") + return TextComponent(numbers) + else if (suffix is Component) + return TextComponent(numbers + " " + suffix.string) + else + return TextComponent("$numbers $suffix") +} + +fun BigInteger.formatReadableNumber(): String { + if (isZero) { + return "0" + } + + val strValue = toString() + + val absLength = strValue.length - (if (isNegative) 1 else 0) + val remainder = absLength % 3 + var groups = absLength / 3 + + if (remainder == 0) { + groups-- + } + + val buffer = CharArray((if (remainder == 0) 3 else remainder) + groups * 4 + if (isNegative) 1 else 0) + var c = 0 + var index = buffer.size - 1 + + for (i in strValue.length - 1 downTo (if (isNegative) 1 else 0)) { + c++ + + if (c == 4) { + buffer[index] = ' ' + index-- + c = 1 + } + + buffer[index] = strValue[i] + index-- + } + + if (isNegative) { + buffer[index] = '-' + } + + return String(buffer) +} + +fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never): Component { + require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } + val prefix = SiPrefix.determine(this) + if (prefix.isEmpty) return concat(toString(decimalPlaces), suffix) + val isNegative = isNegative + val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.bigInteger) + val divided = arr[0].toString() + val remainder = arr[1].toString() + + if (decimalPlaces == 0) { + if (isNegative) { + return TranslatableComponent(prefix.formatLocaleKey, "-$divided${prefix.symbol}", suffix) + } else { + return TranslatableComponent(prefix.formatLocaleKey, divided + prefix.symbol, suffix) + } + } + + @Suppress("NAME_SHADOWING") + val decimalPlaces = decimalPlaces.coerceAtMost(prefix.power) + val add = (if (isNegative) 1 else 0) + val buffer = CharArray(divided.length + 1 + decimalPlaces + add) + + if (isNegative) { + buffer[0] = '-' + } + + for (i in divided.indices) { + buffer[add + i] = divided[i] + } + + buffer[add + divided.length] = '.' + + for (i in 0 until decimalPlaces) { + buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i) + } + + if (suffix == "") + return TranslatableComponent(prefix.conciseFormatLocaleKey, String(buffer)) + else + return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix) +} + +private val never = BooleanSupplier { false } + +private fun reformat(numbers: String): String { + if (numbers.length <= 4) + return numbers + + val result = CharArrayList((numbers.length * 1.6).roundToInt()) + + var dot = numbers.lastIndexOf('.') + + if (dot != -1) { + for (i in numbers.length - 1 downTo dot) { + result.add(numbers[i]) + } + + dot-- + } else { + dot = numbers.length - 1 + } + + var c = 0 + + for (i in dot downTo 0) { + if (++c == 4) { + c = 1 + result.add(' ') + } + + result.add(numbers[i]) + } + + return String(CharArray(result.size) { + result.getChar(result.size - it - 1) + }) +} + +fun Long.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component { + require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } + + if (formatAsReadable.asBoolean) { + if (bias == 0) { + return concat(reformat(toString()), suffix) + } else { + val prefix = SiPrefix.NONE.neighbour(bias) + return TranslatableComponent(prefix.formatLocaleKey, reformat(toString()), suffix) + } + } + + val prefix = SiPrefix.determine(this) + return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, "%.${decimalPlaces}f".format(this.toDouble() / prefix.long!!.toDouble()), suffix) +} + +fun Int.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component { + return toLong().formatSiComponent(suffix, decimalPlaces, formatAsReadable, bias) +} + +fun Double.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component { + require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } + if (formatAsReadable.asBoolean && this.absoluteValue >= 1.0) return concat(reformat("%.${decimalPlaces}f".format(this)), suffix) + val prefix = SiPrefix.determine(this) + return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, "%.${decimalPlaces}f".format(this / prefix.double), suffix) +} + +fun Decimal.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never, bias: Int = 0): Component { + require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" } + if (this == Decimal.POSITIVE_INFINITY) return concat("∞", suffix) + if (this == Decimal.NEGATIVE_INFINITY) return concat("-∞", suffix) + if (formatAsReadable.asBoolean && this.absoluteValue >= Decimal.ONE) return concat(reformat(toString(decimalPlaces)), suffix) + val prefix = SiPrefix.determine(this) + return TranslatableComponent(prefix.neighbour(bias).formatLocaleKey, (this / prefix.decimal).toString(decimalPlaces), suffix) +} + +fun Int.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable) +fun Int.formatFluid(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.fluid.name"), decimalPlaces, formatAsReadable = formatAsReadable, bias = -1) +fun Decimal.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable) +fun Decimal.formatMatter(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable) +fun Decimal.formatMatterFull(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable)) + +fun BigInteger.formatPower(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.power.name"), decimalPlaces, formatAsReadable = formatAsReadable) +fun BigInteger.formatMatter(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable) +fun BigInteger.formatMatterFull(decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.matter.format", formatSiComponent(TranslatableComponent("otm.gui.matter.name"), decimalPlaces, formatAsReadable = formatAsReadable)) + +fun formatPowerLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatPower(decimalPlaces, formatAsReadable = formatAsReadable), b.formatPower(decimalPlaces, formatAsReadable = formatAsReadable)) +fun formatMatterLevel(a: Decimal, b: Decimal, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable), b.formatMatter(decimalPlaces, formatAsReadable = formatAsReadable)) +fun formatFluidLevel(a: Int, b: Int, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable)) +fun formatFluidLevel(a: Int, b: Int, name: Component, decimalPlaces: Int = 3, formatAsReadable: BooleanSupplier = never) = TranslatableComponent("otm.gui.fluid.level", a.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), b.formatFluid(decimalPlaces, formatAsReadable = formatAsReadable), name) + +private fun padded(num: Int): String { + if (num in 0 .. 9) { + return "0$num" + } + + return num.toString() +} + +fun formatTickDuration(ticks: Int, longFormat: Boolean = false): String { + @Suppress("name_shadowing") + var leftTicks = ticks + + // val mTicks = leftTicks % 20 + leftTicks /= 20 + + val seconds = padded(leftTicks % 60) + leftTicks /= 60 + + if (longFormat) { + if (ticks <= 0) { + return "00:00:00" + } else if (ticks <= 20) { + return ".00000${padded(ticks)}" + } + + val minutes = padded(leftTicks % 60) + leftTicks /= 60 + val hours = padded(leftTicks) + return "$hours:$minutes:$seconds" + } else { + if (ticks <= 0) { + return "00:00" + } else if (ticks <= 20) { + return ".00${padded(ticks)}" + } + + val minutes = leftTicks + + if (minutes > 99) { + return "**:**" + } + + return "${padded(minutes)}:$seconds" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt new file mode 100644 index 000000000..38c7b6e0a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt @@ -0,0 +1,332 @@ +package ru.dbotthepony.mc.otm.core.util + +import io.netty.handler.codec.EncoderException +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtAccounter +import net.minecraft.nbt.NbtIo +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.material.Fluid +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.ForgeRegistry +import java.io.* +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.math.absoluteValue + +// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement +// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway? + +// netty ByteBuf -> netty ByteBufInputStream -> Minecraft FriendlyInputStream + +fun OutputStream.writeNbt(value: CompoundTag) { + try { + NbtIo.write(value, if (this is DataOutputStream) this else DataOutputStream(this)) + } catch (ioexception: IOException) { + throw EncoderException(ioexception) + } +} + +fun InputStream.readNbt(): CompoundTag { + return try { + NbtIo.read(if (this is DataInputStream) this else DataInputStream(this)) + } catch (ioexception: IOException) { + throw EncoderException(ioexception) + } +} + +fun OutputStream.writeItem(itemStack: ItemStack, limitedTag: Boolean = true) { + if (itemStack.isEmpty) { + write(0) + } else { + write(1) + val id = (ForgeRegistries.ITEMS as ForgeRegistry).getID(itemStack.item) + + writeInt(id) + writeInt(itemStack.count) + + var compoundtag: CompoundTag? = null + + if (itemStack.item.isDamageable(itemStack) || itemStack.item.shouldOverrideMultiplayerNbt()) { + compoundtag = if (limitedTag) itemStack.shareTag else itemStack.tag + } + + write(if (compoundtag != null) 1 else 0) + + if (compoundtag != null) { + writeNbt(compoundtag) + } + } +} + +fun InputStream.readItem(): ItemStack { + if (read() == 0) { + return ItemStack.EMPTY + } + + val item = (ForgeRegistries.ITEMS as ForgeRegistry).getValue(readInt()) + val itemStack = ItemStack(item, readInt()) + + if (read() != 0) { + itemStack.readShareTag(readNbt()) + } + + return itemStack +} + +fun OutputStream.writeFluidStack(value: FluidStack) { + if (value.isEmpty) { + write(0) + } else { + write(1) + + val id = (ForgeRegistries.FLUIDS as ForgeRegistry).getID(value.fluid) + writeVarIntLE(id) + writeInt(value.amount) + + if (value.hasTag()) { + write(1) + writeNbt(value.tag!!) + } else { + write(0) + } + } +} + +fun InputStream.readFluidStack(): FluidStack { + if (read() == 0) { + return FluidStack.EMPTY + } else { + val id = readVarIntLE() + val fluid = (ForgeRegistries.FLUIDS as ForgeRegistry).getValue(id) ?: return FluidStack.EMPTY + val amount = readInt() + + if (read() > 0) { + return FluidStack(fluid, amount, readNbt()) + } else { + return FluidStack(fluid, amount) + } + } +} + +fun OutputStream.writeBigDecimal(value: BigDecimal) { + writeInt(value.scale()) + val bytes = value.unscaledValue().toByteArray() + writeVarIntLE(bytes.size) + write(bytes) +} + +fun InputStream.readBigDecimal(): BigDecimal { + val scale = readInt() + val size = readVarIntLE() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return BigDecimal(BigInteger(bytes), scale) +} + +fun InputStream.readBinaryComponent(): Component? { + return Component.Serializer.fromJson(readBinaryJson()) +} + +fun OutputStream.writeBinaryComponent(component: Component) { + writeBinaryJson(Component.Serializer.toJsonTree(component)) +} + +fun S.writeCollection(collection: Collection, writer: S.(V) -> Unit) { + writeVarIntLE(collection.size) + + for (value in collection) { + writer(this, value) + } +} + +fun > S.readCollection(reader: S.() -> V, factory: (Int) -> C): C { + val size = readVarIntLE() + val collection = factory.invoke(size) + + for (i in 0 until size) { + collection.add(reader(this)) + } + + return collection +} + +fun S.readCollection(reader: S.() -> V) = readCollection(reader, ::ArrayList) + +fun OutputStream.writeInt(value: Int) { + if (this is DataOutput) { + writeInt(value) + return + } + + write(value ushr 24) + write(value ushr 16) + write(value ushr 8) + write(value) +} + +fun InputStream.readInt(): Int { + if (this is DataInput) { + return readInt() + } + + return (read() shl 24) or (read() shl 16) or (read() shl 8) or read() +} + +fun OutputStream.writeLong(value: Long) { + if (this is DataOutput) { + writeLong(value) + return + } + + write((value ushr 48).toInt()) + write((value ushr 40).toInt()) + write((value ushr 32).toInt()) + write((value ushr 24).toInt()) + write((value ushr 16).toInt()) + write((value ushr 8).toInt()) + write(value.toInt()) +} + +fun InputStream.readLong(): Long { + if (this is DataInput) { + return readLong() + } + + return (read().toLong() shl 48) or + (read().toLong() shl 40) or + (read().toLong() shl 32) or + (read().toLong() shl 24) or + (read().toLong() shl 16) or + (read().toLong() shl 8) or + read().toLong() +} + +fun OutputStream.writeFloat(value: Float) = writeInt(value.toBits()) +fun InputStream.readFloat() = Float.fromBits(readInt()) +fun OutputStream.writeDouble(value: Double) = writeLong(value.toBits()) +fun InputStream.readDouble() = Double.fromBits(readLong()) + +fun InputStream.readVarIntLE(): Int { + val readFirst = read() + + if (readFirst < 0) { + throw NoSuchElementException("Reached end of stream") + } + + if (readFirst and 64 == 0) { + return if (readFirst and 128 != 0) -(readFirst and 63) else readFirst and 63 + } + + var result = 0 + var nextBit = readFirst and 64 + var read = readFirst and 63 + var i = 0 + + while (nextBit != 0) { + result = result or (read shl i) + read = read() + + if (read < 0) { + throw NoSuchElementException("Reached end of stream") + } + + nextBit = read and 128 + read = read and 127 + + if (i == 0) + i = 6 + else + i += 7 + } + + result = result or (read shl i) + return if (readFirst and 128 != 0) -result else result +} + +fun OutputStream.writeVarIntLE(value: Int) { + write((if (value < 0) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63)) + var written = value.absoluteValue ushr 6 + + while (written != 0) { + write((written and 127) or (if (written >= 128) 128 else 0)) + written = written ushr 7 + } +} + +fun InputStream.readVarLongLE(sizeLimit: NbtAccounter? = null): Long { + sizeLimit?.accountBytes(1L) + + val readFirst = read() + + if (readFirst < 0) { + throw NoSuchElementException("Reached end of stream") + } + + if (readFirst and 64 == 0) { + return if (readFirst and 128 != 0) -(readFirst and 63).toLong() else (readFirst and 63).toLong() + } + + var result = 0L + var nextBit = readFirst and 64 + var read = readFirst and 63 + var i = 0 + + while (nextBit != 0) { + result = result or (read shl i).toLong() + sizeLimit?.accountBytes(1L) + read = read() + + if (read < 0) { + throw NoSuchElementException("Reached end of stream") + } + + nextBit = read and 128 + read = read and 127 + + if (i == 0) + i = 6 + else + i += 7 + } + + result = result or (read shl i).toLong() + return if (readFirst and 128 != 0) -result else result +} + +fun OutputStream.writeVarLongLE(value: Long) { + write((if (value < 0L) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63).toInt()) + var written = value.absoluteValue ushr 6 + + while (written != 0L) { + write((written and 127).toInt() or (if (written >= 128) 128 else 0)) + written = written ushr 7 + } +} + +fun InputStream.readBinaryString(): String { + val size = readVarIntLE() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return bytes.decodeToString() +} + +fun OutputStream.writeBinaryString(input: String) { + val bytes = input.encodeToByteArray() + writeVarIntLE(bytes.size) + write(bytes) +} + +fun InputStream.readResourceLocation(): ResourceLocation { + return ResourceLocation(readBinaryString(), readBinaryString()) +} + +fun OutputStream.writeResourceLocation(value: ResourceLocation) { + writeBinaryString(value.namespace) + writeBinaryString(value.path) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt new file mode 100644 index 000000000..e73cd4c45 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.core.util + +import java.lang.ref.ReferenceQueue +import java.lang.ref.WeakReference + +/** + * [WeakReference], but with [hashCode] overridden with hash of referent object + */ +@Suppress("EqualsOrHashCode") +class HashedWeakReference : WeakReference { + constructor(value: T) : super(value) { + hash = value.hashCode() + } + + constructor(value: T, queue: ReferenceQueue) : super(value, queue) { + hash = value.hashCode() + } + + private val hash: Int + + override fun hashCode(): Int { + return hash + } + + override fun toString(): String { + return "HashedWeakReference[hash=$hash]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IConditionalTickable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IConditionalTickable.kt new file mode 100644 index 000000000..f100b383f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IConditionalTickable.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.core.util + +import java.util.function.BooleanSupplier + +/** + * Represents something that can tick without any additional callback, + * with sign whenever can it tick more + */ +fun interface IConditionalTickable { + /** + * Function to be called by tick executors + * + * Once this returns false, ticker is considered finished, and should be removed from ticking list + */ + fun tick(): Boolean + + companion object { + fun wrap(ticker: () -> Boolean): IConditionalTickable = IConditionalTickable { ticker.invoke() } + fun wrap(ticker: BooleanSupplier): IConditionalTickable = IConditionalTickable { ticker.asBoolean } + + fun wrap(condition: () -> Boolean, ticker: () -> Unit): IConditionalTickable { + return IConditionalTickable { + ticker.invoke() + condition.invoke() + } + } + + fun wrap(condition: BooleanSupplier, ticker: () -> Unit): IConditionalTickable { + return IConditionalTickable { + ticker.invoke() + condition.asBoolean + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ITickable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ITickable.kt new file mode 100644 index 000000000..97e828be0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ITickable.kt @@ -0,0 +1,11 @@ +package ru.dbotthepony.mc.otm.core.util + +/** + * Represents something that can tick without any additional context + */ +fun interface ITickable { + /** + * Function to be called by tick executors + */ + fun tick() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IntCounter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IntCounter.kt new file mode 100644 index 000000000..0c4c56e23 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/IntCounter.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.mc.otm.core.util + +import ru.dbotthepony.mc.otm.core.IIntSubcripable +import ru.dbotthepony.mc.otm.core.ISubscriptable +import java.util.function.BooleanSupplier +import java.util.function.IntConsumer +import java.util.function.IntSupplier + +class IntCounter : IntSupplier, IIntSubcripable { + /** + * Doesn't subscribe to counter (counter does not reference this invalidator) + */ + inner class WeakInvalidator : BooleanSupplier { + private var thisValue = value + + override fun getAsBoolean(): Boolean { + if (thisValue != value) { + thisValue = value + return true + } + + return false + } + } + + /** + * Subscribes to counter (counter references this invalidator, can be removed using [remove]) + */ + inner class StrongInvalidator : BooleanSupplier, ISubscriptable.L { + private var isValid = true + private val l = listeners.addListener(IntConsumer { isValid = false }) + + override fun getAsBoolean(): Boolean { + val isValid = isValid + this.isValid = true + return isValid + } + + override fun remove() { + l.remove() + } + } + + private val listeners = IIntSubcripable.Impl() + private var value = 0 + + fun increment() { + value++ + listeners.accept(value) + } + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return listeners.addListener(listener) + } + + override fun getAsInt(): Int { + return value + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt new file mode 100644 index 000000000..f6c16c449 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/InvalidableLazy.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.core.util + +interface InvalidableLazy : Lazy { + fun invalidate() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt new file mode 100644 index 000000000..0b4a15aff --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt @@ -0,0 +1,196 @@ +package ru.dbotthepony.mc.otm.core.util + +import it.unimi.dsi.fastutil.objects.Reference2IntFunction +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap +import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent +import net.minecraft.world.item.CreativeModeTabs +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.CreativeModeTabRegistry +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.nullsFirst +import ru.dbotthepony.mc.otm.core.nullsLast +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.suppliers +import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.client.render.Widgets18 + +private fun Comparator.stacks(): Comparator { + return Comparator { o1, o2 -> this@stacks.compare(o1.item, o2.item) } +} + +private fun Comparator.storage(): Comparator { + return Comparator { o1, o2 -> this@storage.compare(o1.item, o2.item) } +} + +object CreativeMenuItemComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + rebuild() + return item2index.getInt(o1).compareTo(item2index.getInt(o2)) + } + + private val item2index = Reference2IntOpenHashMap() + + init { + item2index.defaultReturnValue(Int.MAX_VALUE) + } + + private fun doRebuild() { + var i = 0 + + for (tab in CreativeModeTabRegistry.getSortedCreativeModeTabs()) { + for (item in tab.displayItems) { + item2index.computeIfAbsent(item.item, Reference2IntFunction { i++ }) + } + } + } + + private fun rebuild() { + if (item2index.isEmpty()) { + doRebuild() + + if (item2index.isEmpty()) { + val player = minecraft.player ?: return + // creative tabs were not populated yet + CreativeModeTabs.tryRebuildTabContents(player.connection.enabledFeatures(), false /* operator tabs */) + + doRebuild() + } + } + } + + internal fun invalidate() { + item2index.clear() + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object MatterValueComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + return MatterManager.get(o1).matter.compareTo(MatterManager.get(o2).matter) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object MatterComplexityComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + return MatterManager.get(o1).complexity.compareTo(MatterManager.get(o2).complexity) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemLocalizedNameComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + return o1.description.string.compareTo(o2.description.string) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStackNameComparator : Comparator { + override fun compare(o1: ItemStack, o2: ItemStack): Int { + return o1.hoverName.string.compareTo(o2.hoverName.string) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStorageStackNameComparator : Comparator { + override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int { + return o1.hoverName.string.compareTo(o2.hoverName.string) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemModComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + val a = o1.registryName?.namespace ?: return 0 + val b = o2.registryName?.namespace ?: return 0 + return a.compareTo(b) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemIDComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + val a = o1.registryName ?: return 0 + val b = o2.registryName ?: return 0 + return a.compareTo(b) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStackCountComparator : Comparator { + override fun compare(o1: ItemStack, o2: ItemStack): Int { + return o1.count.compareTo(o2.count) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStorageStackCountComparator : Comparator { + override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int { + return o1.count.compareTo(o2.count) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +enum class ItemSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator { + DEFAULT(CreativeMenuItemComparator, TranslatableComponent("otm.gui.sorting.default"), lazy { Widgets18.SORT_DEFAULT }), + NAME(ItemLocalizedNameComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.name"), lazy { Widgets18.SORT_ALPHABET }), + ID(ItemIDComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.id"), lazy { Widgets18.SORT_ID }), + MOD(ItemModComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.modid"), lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(MatterValueComparator.thenComparing(MatterComplexityComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_value"), lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(MatterComplexityComparator.thenComparing(MatterValueComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_complexity"), lazy { Widgets18.SORT_MATTER_COMPLEXITY }), + ; + + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() +} + +enum class ItemStackSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator { + DEFAULT(ItemSorter.DEFAULT.stacks(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }), + COUNT(ItemStackCountComparator.thenComparing(ItemSorter.DEFAULT.stacks()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }), + NAME(ItemStackNameComparator.thenComparing(ItemSorter.DEFAULT.stacks()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }), + ID(ItemSorter.ID.stacks(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }), + MOD(ItemSorter.MOD.stacks(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(ItemSorter.MATTER_VALUE.stacks(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.stacks(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY }); + + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() +} + +enum class ItemStorageStackSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator { + DEFAULT(ItemSorter.DEFAULT.storage(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }), + COUNT(ItemStorageStackCountComparator.thenComparing(ItemSorter.DEFAULT.storage()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }), + NAME(ItemStorageStackNameComparator.thenComparing(ItemSorter.DEFAULT.storage()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }), + ID(ItemSorter.ID.storage(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }), + MOD(ItemSorter.MOD.storage(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(ItemSorter.MATTER_VALUE.storage(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.storage(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY }); + + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt new file mode 100644 index 000000000..289c1785f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LOHolder.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.mc.otm.core.util + +import net.minecraftforge.common.util.LazyOptional + +class LOHolder(val value: T) { + private var lazyOptional: LazyOptional = LazyOptional.of { value } + + fun invalidate() { + lazyOptional.invalidate() + } + + fun revive() { + lazyOptional.invalidate() + lazyOptional = LazyOptional.of { value } + } + + fun get(): LazyOptional { + return lazyOptional.cast() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt new file mode 100644 index 000000000..bd8a2b099 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -0,0 +1,375 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.common.collect.ImmutableList +import com.mojang.serialization.Codec +import net.minecraft.nbt.ByteTag +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.DoubleTag +import net.minecraft.nbt.FloatTag +import net.minecraft.nbt.IntTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.LongTag +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.NumericTag +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.Tag +import net.minecraft.resources.ResourceLocation +import net.minecraftforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.data.getOrNull +import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty0 + +/** + * Utility class to manage serialization and deserialization of properties + * of mutable class + */ +class Savetables : INBTSerializable { + private val entries = ArrayList>() + + sealed interface Entry : INBTSerializable { + val name: String + val type: Class + fun validate() + } + + inline fun , reified T : Tag> stateful(getter: Supplier, name: String): Stateful { + return stateful(getter, name, T::class.java) + } + + inline fun , reified T : Tag> stateful(getter: V, name: String): Stateful { + return stateful(getter, name, T::class.java) + } + + inline fun , reified T : Tag> stateful(getter: KProperty0, name: String = getter.name): Stateful { + return stateful(getter, name, T::class.java) + } + + inline fun , reified T : Tag> stateful(values: List, name: String): ImmutableList> { + return immutableList(values.size) { + stateful(values[it], "${name}_$it", T::class.java) + } + } + + fun , T : Tag> stateful(getter: Supplier, name: String, type: Class): Stateful { + return Stateful(getter, name, type) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + + fun , T : Tag> stateful(getter: V, name: String, type: Class): Stateful { + return Stateful({ getter }, name, type) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + + fun , T : Tag> stateful(getter: KProperty0, name: String = getter.name, type: Class): Stateful { + return Stateful(getter, name, type) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + + fun decimal(prop: GetterSetter, name: String, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it.serializeNBT() } + .withDefault { default } + } + + fun decimalNullable(prop: GetterSetter, name: String, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it?.serializeNBT() } + .withDefault { default } + } + + fun decimal(prop: KMutableProperty0, name: String = prop.name, default: Decimal = Decimal.ZERO): Stateless { + return decimal(prop.asGetterSetter(), name, default) + } + + fun decimalNullable(prop: KMutableProperty0, name: String = prop.name, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it?.serializeNBT() } + .withDefault { default } + } + + fun float(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { FloatTag.valueOf(it) } + .withDeserializer { it.asFloat } + } + + fun float(prop: KMutableProperty0, name: String = prop.name): Stateless { + return float(prop.asGetterSetter(), name) + } + + fun double(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { DoubleTag.valueOf(it) } + .withDeserializer { it.asDouble } + } + + fun double(prop: KMutableProperty0, name: String = prop.name): Stateless { + return double(prop.asGetterSetter(), name) + } + + fun int(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { IntTag.valueOf(it) } + .withDeserializer { it.asInt } + } + + fun int(prop: KMutableProperty0, name: String = prop.name): Stateless { + return int(prop.asGetterSetter(), name) + } + + fun long(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { LongTag.valueOf(it) } + .withDeserializer { it.asLong } + } + + fun long(prop: KMutableProperty0, name: String = prop.name): Stateless { + return long(prop.asGetterSetter(), name) + } + + fun bool(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { ByteTag.valueOf(it) } + .withDeserializer { it.asByte > 0 } + } + + fun bool(prop: KMutableProperty0, name: String = prop.name): Stateless { + return bool(prop.asGetterSetter(), name) + } + + fun string(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, StringTag::class.java) + .withSerializer { StringTag.valueOf(it) } + .withDeserializer { it.asString } + } + + fun string(prop: KMutableProperty0, name: String = prop.name): Stateless { + return string(prop.asGetterSetter(), name) + } + + fun > enum(prop: GetterSetter, name: String, map: (String) -> E): Stateless { + return Stateless(prop, name, StringTag::class.java) + .withSerializer { StringTag.valueOf(it.name) } + .withDeserializer { map.invoke(it.asString) } + } + + fun > enum(prop: KMutableProperty0, name: String = prop.name, map: (String) -> E): Stateless { + return enum(prop.asGetterSetter(), name, map) + } + + fun codecNullable(prop: GetterSetter, codec: Codec, name: String): Stateless { + return Stateless(prop, name, Tag::class.java) + .withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } } + .withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow(false) { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first } + } + + fun codecNullable(prop: KMutableProperty0, codec: Codec, name: String = prop.name): Stateless { + return codecNullable(prop.asGetterSetter(), codec, name) + } + + fun codecNullable(prop: GetterSetter, codec: Codec, name: String, default: T?): Stateless { + return Stateless(prop, name, Tag::class.java) + .withSerializer { prop.get()?.let { codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } } + .withDeserializer { codec.decode(NbtOps.INSTANCE, it).get().map({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) } + .withDefault { default } + } + + fun codecNullable(prop: KMutableProperty0, codec: Codec, name: String = prop.name, default: T?): Stateless { + return codecNullable(prop.asGetterSetter(), codec, name, default) + } + + fun codec(prop: GetterSetter, codec: Codec, name: String): Stateless { + return Stateless(prop, name, Tag::class.java) + .withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } + .withDeserializer { codec.decode(NbtOps.INSTANCE, it).getOrThrow(false) { throw IllegalStateException("Failed to read NBT data for $name: $it") }.first } + } + + fun codec(prop: GetterSetter, codec: Codec, name: String, default: T): Stateless { + return Stateless(prop, name, Tag::class.java) + .withSerializer { codec.encode(prop.get(), NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).getOrThrow(false) { throw IllegalStateException("Failed to write NBT data for $name: $it") } } + .withDeserializer { codec.decode(NbtOps.INSTANCE, it).get().map({ it.first }, { LOGGER.error("Failed to read NBT data for $name", RuntimeException(it.message())); default }) } + .withDefault { default } + } + + fun codec(prop: KMutableProperty0, codec: Codec, name: String = prop.name): Stateless { + return codec(prop.asGetterSetter(), codec, name) + } + + fun codec(prop: KMutableProperty0, codec: Codec, name: String = prop.name, default: T): Stateless { + return codec(prop.asGetterSetter(), codec, name, default) + } + + fun vector(prop: GetterSetter, name: String, default: Vector = Vector.ZERO): Stateless { + return codec(prop, Vector.CODEC, name, default) + } + + fun vector(prop: KMutableProperty0, name: String = prop.name, default: Vector = Vector.ZERO): Stateless { + return vector(prop.asGetterSetter(), name, default) + } + + fun location(prop: GetterSetter, name: String): Stateless { + return codec(prop, ResourceLocation.CODEC, name) + } + + fun location(prop: KMutableProperty0, name: String = prop.name): Stateless { + return location(prop.asGetterSetter(), name) + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also(::serializeNBT) + } + + private var validated = false + + fun validate() { + if (validated) return + + for (entry in entries) + entry.validate() + + validated = true + } + + override fun deserializeNBT(nbt: CompoundTag?) { + validate() + + if (nbt == null) { + for (entry in entries) { + entry.deserializeNBT(null) + } + } else { + for (entry in entries) { + val value = nbt[entry.name] + + if (value != null && entry.type.isAssignableFrom(value.javaClass)) { + (entry as INBTSerializable).deserializeNBT(value) + } else { + entry.deserializeNBT(null) + } + } + } + } + + fun serializeNBT(nbt: CompoundTag) { + validate() + + for (entry in entries) { + val value = entry.serializeNBT() + + if (value != null) + nbt[entry.name] = value + } + } + + inner class Stateful(private val prop: Supplier, override val name: String, override val type: Class) : Entry { + constructor(field: KProperty0, name: String = field.name, type: Class) : this(field::get, name, type) + + init { + check(!entries.any { it.name == name }) { "Already has entry with name $name!" } + entries.add(this) + validated = false + } + + private var serializer: ((V) -> T?)? = null + private var deserializer: ((V, T) -> Unit)? = null + private var resetter: ((V) -> Unit)? = null + + fun withSerializer(serializer: (V) -> T?): Stateful { + this.serializer = serializer + return this + } + + fun withDeserializer(deserializer: (V, T) -> Unit): Stateful { + this.deserializer = deserializer + return this + } + + fun withResetter(resetter: (V) -> Unit): Stateful { + this.resetter = resetter + return this + } + + override fun serializeNBT(): T? { + return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get()) + } + + override fun deserializeNBT(nbt: T?) { + if (nbt == null) { + resetter?.invoke(prop.get()) + } else { + checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(prop.get(), nbt) + } + } + + override fun validate() { + checkNotNull(serializer) { "No serializer specified for $name" } + checkNotNull(deserializer) { "No deserializer specified for $name" } + } + } + + inner class Stateless(private val prop: GetterSetter, override val name: String, override val type: Class) : Entry { + constructor(field: KMutableProperty0, name: String = field.name, type: Class) : this(field.asGetterSetter(), name, type) + + init { + check(!entries.any { it.name == name }) { "Already has entry with name $name!" } + entries.add(this) + validated = false + } + + private var serializer: ((V) -> T?)? = null + private var deserializer: ((T) -> V)? = null + private var default: (() -> V)? = null + + override fun validate() { + checkNotNull(serializer) { "No serializer specified for $name" } + checkNotNull(deserializer) { "No deserializer specified for $name" } + } + + fun withSerializer(serializer: (V) -> T?): Stateless { + this.serializer = serializer + return this + } + + fun withDeserializer(deserializer: (T) -> V): Stateless { + this.deserializer = deserializer + return this + } + + fun withDefault(default: () -> V): Stateless { + this.default = default + return this + } + + override fun serializeNBT(): T? { + return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get()) + } + + override fun deserializeNBT(nbt: T?) { + if (nbt == null) { + if (default != null) { + prop.accept(default!!.invoke()) + } + } else { + prop.accept(checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(nbt)) + } + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt new file mode 100644 index 000000000..4255840f6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt @@ -0,0 +1,155 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.isZero +import java.math.BigInteger +import kotlin.math.absoluteValue + +enum class SiPrefix(val power: Int, val symbol: String) { + YOCTO(-8, "y"), + ZEPTO(-7, "z"), + ATTO (-6, "a"), + FEMTO(-5, "f"), + PICO (-4, "p"), + NANO (-3, "n"), + MICRO(-2, "μ"), + MILLI(-1, "m"), + + NONE(0, "") { + override val isEmpty: Boolean + get() = true + }, + + KILO (1, "k"), + MEGA (2, "M"), + GIGA (3, "G"), + TERA (4, "T"), + PETA (5, "P"), + EXA (6, "E"), + ZETTA(7, "Z"), + YOTTA(8, "Y"); + + open val isEmpty: Boolean get() = false + + val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern() + val conciseFormatLocaleKey = "otm.suffix_concise.${name.lowercase()}".intern() + val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern() + + val string: String + + init { + if (power == 0) { + string = "1" + } else if (power < 0) { + string = "0." + "0".repeat(power.absoluteValue * 3 - 1) + "1" + } else { + string = "1" + "0".repeat(power.absoluteValue * 3) + } + } + + fun paddedIndex(input: String, index: Int): Char { + val finalIndex = input.length - power.absoluteValue * 3 + index + + if (finalIndex >= 0) { + return input[finalIndex] + } + + return '0' + } + + val decimal = Decimal(string) + val bigInteger = if (power >= 0) BigInteger(string) else null + + val long = if (power >= 0) string.toLongOrNull() else null + val double = string.toDouble() + + fun neighbour(bias: Int): SiPrefix { + if (bias == 0) { + return this + } else { + val new = ordinal + bias + + if (new < 0) { + return YOCTO + } else if (new >= VALUES.size) { + return YOTTA + } else { + return VALUES[new] + } + } + } + + companion object { + @JvmField + val VALUES: ImmutableList = ImmutableList.copyOf(values()) + + @JvmField + val MULTIPLIES: ImmutableList = ImmutableList.builder() + .add(NONE) + .add(KILO) + .add(MEGA) + .add(GIGA) + .add(TERA) + .add(PETA) + .add(EXA) + .add(ZETTA) + .add(YOTTA) + .build() + + @JvmField + val DECIMALS: ImmutableList = ImmutableList.builder() + .add(NONE) + .add(MILLI) + .add(MICRO) + .add(NANO) + .add(PICO) + .add(FEMTO) + .add(ATTO) + .add(ZEPTO) + .add(YOCTO) + .build() + + fun determine(value: Int, bias: Int = 0): SiPrefix { + return determine(value.toLong(), bias) + } + + fun determine(value: Long, bias: Int = 0): SiPrefix { + val num = value.absoluteValue + if (num <= 1L) return NONE + return MULTIPLIES.last { it.long != null && it.long <= num }.neighbour(bias) + } + + fun determine(value: Decimal, bias: Int = 0): SiPrefix { + if (value.isZero || value.isInfinite) { + return NONE + } + + val num = value.absoluteValue + + if (num >= Decimal.ONE) { + return (MULTIPLIES.lastOrNull { it.decimal <= num } ?: NONE).neighbour(bias) + } else { + return (DECIMALS.lastOrNull { it.decimal >= num } ?: NONE).neighbour(bias) + } + } + + fun determine(value: Double, bias: Int = 0): SiPrefix { + if (value == 0.0) return NONE + val num = value.absoluteValue + + if (num >= 1.0) { + return (MULTIPLIES.lastOrNull { it.double <= num } ?: NONE).neighbour(bias) + } else { + return (DECIMALS.lastOrNull { it.double >= num } ?: NONE).neighbour(bias) + } + } + + fun determine(value: BigInteger, bias: Int = 0): SiPrefix { + if (value.isZero) return NONE + val num = value.abs() + return (MULTIPLIES.lastOrNull { it.bigInteger!! <= num } ?: NONE).neighbour(bias) + } + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt new file mode 100644 index 000000000..81968cf67 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/StreamCodecs.kt @@ -0,0 +1,228 @@ +package ru.dbotthepony.mc.otm.core.util + +import com.google.common.collect.ImmutableList +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import net.minecraft.nbt.NbtAccounter +import net.minecraft.world.item.ItemStack +import net.minecraftforge.fluids.FluidStack +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.readDecimal +import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.readItemType +import ru.dbotthepony.mc.otm.core.writeItemType +import java.io.DataInput +import java.io.DataInputStream +import java.io.DataOutput +import java.io.DataOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.util.* +import java.util.function.Predicate +import kotlin.NoSuchElementException +import kotlin.math.absoluteValue +import kotlin.reflect.KClass + +/** + * Represents value which can be encoded onto or decoded from stream. + * + * Also provides [copy] and [compare] methods + */ +interface IStreamCodec { + fun read(stream: DataInputStream): V + fun write(stream: DataOutputStream, value: V) + + /** + * if value is immutable, return it right away + */ + fun copy(value: V): V + + /** + * Optional equality check override. Utilized to determine whenever e.g. network value is different from new value + * + * By default uses [Any.equals] + */ + fun compare(a: V, b: V): Boolean { + return a == b + } +} + +class StreamCodec( + private val reader: (stream: DataInputStream) -> V, + private val writer: (stream: DataOutputStream, value: V) -> Unit, + private val copier: ((value: V) -> V) = { it }, + private val comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } +) : IStreamCodec { + constructor( + reader: (stream: DataInputStream) -> V, + payloadSize: Long, + writer: (stream: DataOutputStream, value: V) -> Unit, + copier: ((value: V) -> V) = { it }, + comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b } + ) : this({ stream -> reader.invoke(stream) }, writer, copier, comparator) + + val nullable = object : IStreamCodec { + override fun read(stream: DataInputStream): V? { + return if (stream.read() == 0) null else reader.invoke(stream) + } + + override fun write(stream: DataOutputStream, value: V?) { + if (value === null) stream.write(0) else { + stream.write(1) + writer.invoke(stream, value) + } + } + + override fun copy(value: V?): V? { + return if (value === null) null else copier.invoke(value) + } + + override fun compare(a: V?, b: V?): Boolean { + if (a === null && b === null) return true + if (a === null || b === null) return false + return comparator.invoke(a, b) + } + } + + override fun read(stream: DataInputStream): V { + return reader.invoke(stream) + } + + override fun write(stream: DataOutputStream, value: V) { + writer.invoke(stream, value) + } + + override fun copy(value: V): V { + return copier.invoke(value) + } + + override fun compare(a: V, b: V): Boolean { + return comparator.invoke(a, b) + } +} + +class CollectionStreamCodec>(val elementCodec: IStreamCodec, val collectionFactory: (Int) -> C) : IStreamCodec { + override fun read(stream: DataInputStream): C { + val size = stream.readVarIntLE() + + if (size <= 0) { + return collectionFactory.invoke(0) + } + + val collection = collectionFactory.invoke(size) + + for (i in 0 until size) { + collection.add(elementCodec.read(stream)) + } + + return collection + } + + override fun write(stream: DataOutputStream, value: C) { + stream.writeVarIntLE(value.size) + value.forEach { elementCodec.write(stream, it) } + } + + override fun copy(value: C): C { + val new = collectionFactory.invoke(value.size) + value.forEach { new.add(elementCodec.copy(it)) } + return new + } +} + +val NullValueCodec = StreamCodec({ _ -> null }, { _, _ -> }) +val BooleanValueCodec = StreamCodec(DataInputStream::readBoolean, 1L, DataOutputStream::writeBoolean) { a, b -> a == b } +val ByteValueCodec = StreamCodec(DataInputStream::readByte, 1L, { s, v -> s.writeByte(v.toInt()) }) { a, b -> a == b } +val ShortValueCodec = StreamCodec(DataInputStream::readShort, 2L, { s, v -> s.writeShort(v.toInt()) }) { a, b -> a == b } +val IntValueCodec = StreamCodec(DataInputStream::readInt, 4L, DataOutputStream::writeInt) { a, b -> a == b } +val LongValueCodec = StreamCodec(DataInputStream::readLong, 8L, DataOutputStream::writeLong) { a, b -> a == b } +val FloatValueCodec = StreamCodec(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat) { a, b -> a == b } +val DoubleValueCodec = StreamCodec(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble) { a, b -> a == b } +val ItemStackValueCodec = StreamCodec(DataInputStream::readItem, DataOutputStream::writeItem, ItemStack::copy) { a, b -> a.equals(b, true) } +val FluidStackValueCodec = StreamCodec(DataInputStream::readFluidStack, DataOutputStream::writeFluidStack, FluidStack::copy) { a, b -> a == b && a.amount == b.amount } +val ItemValueCodec = StreamCodec(DataInputStream::readItemType, DataOutputStream::writeItemType) { a, b -> a === b } +val DecimalValueCodec = StreamCodec(DataInputStream::readDecimal, DataOutputStream::writeDecimal) +val BigDecimalValueCodec = StreamCodec(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal) +val UUIDValueCodec = StreamCodec({ s -> UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) }) +val VarIntValueCodec = StreamCodec(DataInputStream::readVarIntLE, DataOutputStream::writeVarIntLE) { a, b -> a == b } +val VarLongValueCodec = StreamCodec(DataInputStream::readVarLongLE, DataOutputStream::writeVarLongLE) { a, b -> a == b } +val BinaryStringCodec = StreamCodec(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString) +val ResourceLocationValueCodec = StreamCodec(DataInputStream::readResourceLocation, DataOutputStream::writeResourceLocation) + +val RGBCodec: StreamCodec = StreamCodec( + { s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat()) }, + { s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue) }) + +val RGBACodec: StreamCodec = StreamCodec( + { s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat(), s.readFloat()) }, + { s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue); s.writeFloat(v.alpha) }) + +class EnumValueCodec>(clazz: Class) : IStreamCodec, Codec { + val clazz = searchClass(clazz) + val values: ImmutableList = ImmutableList.copyOf(this.clazz.enumConstants!!) + val valuesMap = immutableMap { + for (v in values) put(v.name, v) + } + + override fun read(stream: DataInputStream): V { + val id = stream.readVarIntLE() + return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id") + } + + override fun write(stream: DataOutputStream, value: V) { + stream.writeVarIntLE(value.ordinal) + } + + override fun copy(value: V): V { + return value + } + + override fun compare(a: V, b: V): Boolean { + return a === b + } + + override fun encode(input: V, ops: DynamicOps, prefix: T): DataResult { + if (ops.compressMaps()) { + return DataResult.success(ops.createInt(input.ordinal)) + } + + return DataResult.success(ops.createString(input.name)) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + if (ops.compressMaps()) { + return ops.getNumberValue(input) + .flatMap { values.getOrNull(it.toInt())?.let { DataResult.success(Pair(it, ops.empty())) } ?: DataResult.error("No such enum with ordinal index $it") } + } + + return ops.getStringValue(input) + .flatMap { valuesMap[it]?.let { DataResult.success(Pair(it, ops.empty())) } ?: DataResult.error("No such enum value $it") } + } + + companion object { + /** + * FIXME: enums with abstract methods which get compiled to subclasses, whose DO NOT expose "parent's" enum constants array + * + * is there an already existing solution? + */ + fun > searchClass(clazz: Class): Class { + var search: Class<*> = clazz + + while (search.enumConstants == null && search.superclass != null) { + search = search.superclass + } + + if (search.enumConstants == null) { + throw ClassCastException("$clazz does not represent an enum or enum subclass") + } + + return search as Class + } + } +} + +fun > Class.codec() = EnumValueCodec(this) +fun > KClass.codec() = EnumValueCodec(this.java) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt new file mode 100644 index 000000000..d3d006ba2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt @@ -0,0 +1,265 @@ +package ru.dbotthepony.mc.otm.core.util + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.addSorted +import java.util.concurrent.atomic.AtomicInteger + +class TickList : ITickable { + private val conditional = ArrayList() + private val conditionalQueued = ArrayList() + + private val once = ArrayList() + private val onceQueued = ArrayList() + + private val always = ArrayList() + private val alwaysQueued = ArrayList() + private val toRemoveFromAlways = ArrayList() + + private val timers = ObjectAVLTreeSet() + private val nextTimerID = AtomicInteger() + private val namedTimers = Object2ObjectOpenHashMap(0) + + private var shouldTick = false + + var inTicker = false + private set + var ticks = 0 + private set + + private var nextSometime = 0 + + inner class Timer(val timerTicks: Int, val runnable: Runnable) : Comparable { + val ringAt = ticks + timerTicks + val timerID = nextTimerID.incrementAndGet() + + var finished = false + private set + + init { + shouldTick = true + timers.add(this) + } + + override fun compareTo(other: Timer): Int { + var c = ringAt.compareTo(other.ringAt) + if (c != 0) return c + c = timerID.compareTo(other.timerID) + return c + } + + fun execute() { + if (finished) return + runnable.run() + finished = true + } + } + + /** + * Calling this method while timer already exists removes old timer + */ + fun namedTimer(name: Any?, ticks: Int, callback: Runnable): Timer { + val remove = namedTimers.remove(name) + + if (remove != null) + timers.remove(remove) + + val timer = Timer(ticks, callback) + namedTimers[name] = timer + return timer + } + + inner class Ticker(parent: ITickable) : ITickable by parent { + init { + add(this, always, alwaysQueued) + } + + var isEnabled = true + set(value) { + if (field != value) { + field = value + + if (value) { + add(this, always, alwaysQueued) + } else { + alwaysQueued.remove(this) + + if (inTicker) { + toRemoveFromAlways.add(this) + } else { + always.remove(this) + } + } + } + } + + fun disable() { + isEnabled = false + } + + fun enable() { + isEnabled = true + } + } + + private fun add(value: T, regular: MutableList, queue: MutableList) { + shouldTick = true + + if (inTicker) { + queue.add(value) + } else { + regular.add(value) + } + } + + fun add(ticker: IConditionalTickable) { + add(ticker, conditional, conditionalQueued) + } + + fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) { + if (!condition) { + LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) + return + } + + return add(ticker) + } + + fun once(ticker: ITickable) { + add(ticker, once, onceQueued) + } + + fun once(ticker: ITickable, condition: Boolean, reason: String) { + if (!condition) { + LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) + return + } + + return once(ticker) + } + + fun always(ticker: ITickable) { + add(ticker, always, alwaysQueued) + } + + fun timer(timerTicks: Int, action: Runnable, condition: Boolean, reason: String): Timer? { + if (!condition) { + LOGGER.error("Refusing to add timer $action in $timerTicks ticks because we $reason", IllegalStateException(reason)) + return null + } + + return Timer(timerTicks, action) + } + + fun timer(timerTicks: Int, action: Runnable): Timer { + return Timer(timerTicks, action) + } + + /** + * Schedules execution of Runnable somewhere in the future, + * at discretion of this tick list + */ + fun sometime(action: Runnable): Timer { + if (nextSometime < ticks || nextSometime - ticks >= 40) { + nextSometime = ticks + } + + return Timer((++nextSometime) - ticks, action) + } + + override fun tick() { + if (inTicker) { + throw ConcurrentModificationException("Already ticking") + } + + ticks++ + if (!shouldTick) return + + inTicker = true + shouldTick = timers.isNotEmpty() + + try { + if (conditional.isNotEmpty()) { + shouldTick = true + conditional.removeIf { !it.tick() } + } + + if (once.isNotEmpty()) { + shouldTick = true + + for (ticker in once) ticker.tick() + once.clear() + } + + if (toRemoveFromAlways.isNotEmpty()) { + shouldTick = true + + for (v in toRemoveFromAlways) always.remove(v) + toRemoveFromAlways.clear() + } + + if (always.isNotEmpty()) { + shouldTick = true + + for (ticker in always) { + ticker.tick() + } + } + + if (alwaysQueued.isNotEmpty()) { + shouldTick = true + + always.ensureCapacity(always.size + alwaysQueued.size) + for (v in alwaysQueued) always.add(v) // avoid toArray() + alwaysQueued.clear() + } + + if (conditionalQueued.isNotEmpty()) { + shouldTick = true + + for (ticker in conditionalQueued) conditional.add(ticker) + conditionalQueued.clear() + } + + if (onceQueued.isNotEmpty()) { + shouldTick = true + + for (ticker in onceQueued) once.add(ticker) + onceQueued.clear() + } + + while (timers.isNotEmpty()) { + val head = timers.first() + + if (head.ringAt <= ticks) { + head.execute() + timers.remove(head) + } else { + break + } + } + } finally { + inTicker = false + } + } + + fun clear() { + if (inTicker) throw ConcurrentModificationException() + + conditional.clear() + conditionalQueued.clear() + + once.clear() + onceQueued.clear() + + always.clear() + alwaysQueued.clear() + + timers.clear() + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/WriteOnce.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/WriteOnce.kt similarity index 61% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/WriteOnce.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/core/util/WriteOnce.kt index 2cd7c6c41..bda267313 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/WriteOnce.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/WriteOnce.kt @@ -1,13 +1,13 @@ -package ru.dbotthepony.mc.otm.core +package ru.dbotthepony.mc.otm.core.util import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class WriteOnce : ReadWriteProperty { +class WriteOnce(private val customMessage: String? = null) : ReadWriteProperty { private var value: V? = null override fun getValue(thisRef: Any?, property: KProperty<*>): V { - return checkNotNull(value) { "Property ${property.name} is not initialized" } + return checkNotNull(value) { customMessage ?: "Property ${property.name} is not initialized" } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt new file mode 100644 index 000000000..c577e9eb8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt @@ -0,0 +1,167 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import io.netty.buffer.Unpooled +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeSerializer +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodecIndirect +import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec +import kotlin.collections.ArrayDeque +import kotlin.concurrent.getOrSet + +class Codec2RecipeSerializer>( + val empty: S?, + codec: (Codec2RecipeSerializer.Context) -> Codec, +) : Codec, RecipeSerializer { + constructor(supplier: (Codec2RecipeSerializer.Context) -> Codec) : this(null, supplier) + + private class CurrentContext { + val idStack = ArrayDeque() + var isNetwork = 0 + } + + inner class Context { + val id: ResourceLocation + get() = checkNotNull(context.idStack.lastOrNull()) { "Not currently deserializing recipe" } + + val ingredients: Codec get() = ActualIngredientCodec + + val isNetwork: Boolean + get() = context.isNetwork > 0 + } + + private val codec = codec.invoke(Context()) + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + return codec.encode(input, ops, prefix) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return codec.decode(ops, input) + } + + fun > xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer { + return Codec2RecipeSerializer(empty?.let(to)) { _ -> + codec.xmap(to, from) + } + } + + override fun fromJson(id: ResourceLocation, data: JsonObject): S { + try { + context.idStack.addLast(id) + + return decode(JsonOps.INSTANCE, data).get().map( + { it.first }, + { empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}") } + ) + } finally { + context.idStack.removeLast() + } + } + + override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): S? { + try { + context.idStack.addLast(id) + context.isNetwork++ + + return data.readBinaryJsonWithCodecIndirect(this) + .resultOrPartial { LOGGER.error("Failed to read recipe $id from network: $it") }.orElse(null) + } finally { + context.isNetwork-- + context.idStack.removeLast() + } + } + + override fun toNetwork(data: FriendlyByteBuf, recipe: S) { + try { + context.idStack.addLast(recipe.id) + context.isNetwork++ + + data.writeBinaryJsonWithCodec(this, recipe) + } finally { + context.isNetwork-- + context.idStack.removeLast() + } + } + + fun toFinished(recipe: S): FinishedRecipe { + return object : FinishedRecipe { + override fun serializeRecipeData(p_125967_: JsonObject) { + encode(recipe, JsonOps.INSTANCE, p_125967_).get().map( + { + it as JsonObject + + for ((k, v) in it.entrySet()) { + p_125967_[k] = v + } + }, + { + throw JsonParseException("Failed to serialize recipe: ${it.message()}") + } + ) + } + + override fun getId(): ResourceLocation { + return recipe.id + } + + override fun getType(): RecipeSerializer<*> { + return this@Codec2RecipeSerializer + } + + override fun serializeAdvancement(): JsonObject? { + return null + } + + override fun getAdvancementId(): ResourceLocation? { + return null + } + } + } + + private object ActualIngredientCodec : Codec { + override fun encode(input: Ingredient, ops: DynamicOps, prefix: T): DataResult { + return if (context.isNetwork > 0) { + networkIngredientCodec.encode(input, ops, prefix) + } else { + IngredientCodec.encode(input, ops, prefix) + } + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return if (context.isNetwork > 0) { + networkIngredientCodec.decode(ops, input) + } else { + IngredientCodec.decode(ops, input) + } + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + private val networkIngredientCodec = Codec.list(ItemStack.CODEC).xmap({ Ingredient.of(it.stream()) }, { it.items.toMutableList() }) + + /** + * [ThreadLocal] because optimization mods can (and probably should) parallelize recipe deserialization, + * since RecipeSerializers are expected to be stateless. [Codec2RecipeSerializer], however, is stateful (threading PoV). + * To make it stateless, [ThreadLocal] is used. + */ + private val contextHolder = ThreadLocal() + private val context: CurrentContext + get() = contextHolder.getOrSet { CurrentContext() } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2Serializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2Serializer.kt new file mode 100644 index 000000000..5d43cd04e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2Serializer.kt @@ -0,0 +1,38 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSyntaxException +import com.mojang.serialization.Codec +import net.minecraft.world.level.storage.loot.Serializer +import ru.dbotthepony.mc.otm.core.fromJsonStrict +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.toJsonStrict + +class Codec2Serializer(val codec: Codec, val embed: Boolean = false) : Serializer { + override fun serialize(data: JsonObject, value: T, context: JsonSerializationContext) { + if (embed) { + val result = codec.toJsonStrict(value, data) + + if (result !is JsonObject) { + throw RuntimeException("Expected JsonObject from codec, got ${result::class.qualifiedName}") + } + + val keys = ArrayList(data.keySet()) + for (k in keys) data.remove(k) + for ((k, v) in result.entrySet()) data[k] = v + } else { + data["value"] = codec.toJsonStrict(value) + } + } + + override fun deserialize(data: JsonObject, context: JsonDeserializationContext): T { + if (embed) { + return codec.fromJsonStrict(data) + } else { + return codec.fromJsonStrict(data["value"] ?: JsonNull.INSTANCE) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt new file mode 100644 index 000000000..1636d73a3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt @@ -0,0 +1,112 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.common.collect.ImmutableList +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import ru.dbotthepony.mc.otm.core.stream +import java.util.function.Predicate +import java.util.stream.Stream + +class CodecList(codecs: Stream>) : Codec { + constructor(codecs: Collection>) : this(codecs.stream()) + constructor(vararg codecs: Codec) : this(codecs.stream() as Stream>) + + private val codecs = codecs.collect(ImmutableList.toImmutableList()) + + init { + require(this.codecs.isNotEmpty()) { "No codecs provided" } + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + val results = ArrayList>(codecs.size) + + for (codec in codecs) { + val result = codec.encode(input, ops, prefix) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error ( + "None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + ) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val results = ArrayList>>(codecs.size) + + for (codec in codecs) { + val result = codec.decode(ops, input) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error ( + "None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + ) + } +} + +class PredicatedCodecList(codecs: Stream, Predicate>>) : Codec { + constructor(codecs: Collection, Predicate>>) : this(codecs.stream()) + constructor(vararg codecs: kotlin.Pair, Predicate>) : this(codecs.stream() as Stream, Predicate>>) + + private val codecs = codecs.collect(ImmutableList.toImmutableList()) + + init { + require(this.codecs.isNotEmpty()) { "No codecs provided" } + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + val results = ArrayList>(codecs.size) + var i = -1 + + for ((codec, predicate) in codecs) { + i++ + + if (predicate.test(input)) { + val result = codec.encode(input, ops, prefix) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } else { + val i2 = i + results.add(DataResult.error("Codec #$i2 predicate tested false")) + } + } + + return DataResult.error ( + "None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + ) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val results = ArrayList>>(codecs.size) + + for ((codec) in codecs.asReversed()) { + val result = codec.decode(ops, input) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error ( + "None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codecs.kt new file mode 100644 index 000000000..b85df203a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codecs.kt @@ -0,0 +1,73 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.Dynamic +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.critereon.DamagePredicate +import net.minecraft.advancements.critereon.DamageSourcePredicate +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.MinMaxBounds +import java.util.Optional +import kotlin.reflect.KProperty1 + +val DoublesCodec: Codec = RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.optionalFieldOf("min").forGetter { Optional.ofNullable(it.min) }, + Codec.DOUBLE.optionalFieldOf("max").forGetter { Optional.ofNullable(it.max) }, + ).apply(it) { min, max -> + if (min.isEmpty && max.isEmpty) + MinMaxBounds.Doubles.ANY + else if (min.isEmpty) + MinMaxBounds.Doubles.atMost(max.get()) + else if (max.isEmpty) + MinMaxBounds.Doubles.atLeast(min.get()) + else + MinMaxBounds.Doubles.between(min.get(), max.get()) + } +} + +private val dealtReceived: Codec> = RecordCodecBuilder.create { + it.group( + DoublesCodec.fieldOf("dealt").forGetter { it.first }, + DoublesCodec.fieldOf("taken").forGetter { it.second }, + ).apply(it, ::Pair) +} + +val DamagePredicateCodec: Codec = RecordCodecBuilder.create { + it.group( + dealtReceived.fieldOf("damage").forGetter { it.dealtDamage to it.takenDamage }, + Codec.BOOL.optionalFieldOf("blocked").forGetter { Optional.ofNullable(it.blocked) }, + Codec.PASSTHROUGH.xmap({ EntityPredicate.fromJson(it.cast(JsonOps.INSTANCE)) }, { Dynamic(JsonOps.INSTANCE, it.serializeToJson()) }).fieldOf("source_entity").forGetter { it.sourceEntity }, + Codec.PASSTHROUGH.xmap({ DamageSourcePredicate.fromJson(it.cast(JsonOps.INSTANCE)) }, { Dynamic(JsonOps.INSTANCE, it.serializeToJson()) }).fieldOf("type").forGetter { it.type }, + ).apply(it) { damage, blocked, source, type -> DamagePredicate(damage.first, damage.second, source, blocked.orElse(null), type) } +} + +fun simpleCodec(factory: (T1) -> V, field1: KProperty1, codec1: Codec): Codec { + return RecordCodecBuilder.create { + it.group( + codec1.fieldOf(field1.name).forGetter(field1::get) + ).apply(it, factory) + } +} + +fun simpleCodec(factory: (T1, T2) -> V, field1: KProperty1, codec1: Codec, field2: KProperty1, codec2: Codec): Codec { + return RecordCodecBuilder.create { + it.group( + codec1.fieldOf(field1.name).forGetter(field1::get), + codec2.fieldOf(field2.name).forGetter(field2::get), + ).apply(it, factory) + } +} + +fun simpleCodec(factory: (T1, T2, T3) -> V, field1: KProperty1, codec1: Codec, field2: KProperty1, codec2: Codec, field3: KProperty1, codec3: Codec): Codec { + return RecordCodecBuilder.create { + it.group( + codec1.fieldOf(field1.name).forGetter(field1::get), + codec2.fieldOf(field2.name).forGetter(field2::get), + codec3.fieldOf(field3.name).forGetter(field3::get), + ).apply(it, factory) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComparableCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComparableCodec.kt new file mode 100644 index 000000000..83c457723 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComparableCodec.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps + +class ComparableCodec>(val parent: Codec, val min: S? = null, val max: S? = null, val minExclusive: Boolean = false, val maxExclusive: Boolean = false) : Codec { + private fun check(input: S): DataResult? { + if (min != null) { + if (minExclusive) { + if (input <= min) { + return DataResult.error("Value $input is smaller or equal to minimal $min") + } + } else { + if (input < min) { + return DataResult.error("Value $input is smaller than minimal $min") + } + } + } + + if (max != null) { + if (maxExclusive) { + if (input >= max) { + return DataResult.error("Value $input is bigger or equal to maximal $max") + } + } else { + if (input > max) { + return DataResult.error("Value $input is bigger than maximal $max") + } + } + } + + return null + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + check(input)?.let { return it } + return parent.encode(input, ops, prefix) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return parent.decode(ops, input).flatMap { + check>(it.first) ?: DataResult.success(it) + } + } +} + +fun > Codec.minRange(min: S, exclusive: Boolean = false) = ComparableCodec(this, min = min, minExclusive = exclusive) +fun > Codec.maxRange(max: S, exclusive: Boolean = false) = ComparableCodec(this, max = max, maxExclusive = exclusive) +fun > Codec.inRange(min: S, minExclusive: Boolean = false, max: S, maxExclusive: Boolean = false) = ComparableCodec(this, min, max, minExclusive, maxExclusive) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt new file mode 100644 index 000000000..82ff139e2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ComponentCodec.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import net.minecraft.network.chat.Component + +object ComponentCodec : Codec { + override fun encode(input: Component, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, Component.Serializer.toJsonTree(input))) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val value = ops.convertTo(JsonOps.INSTANCE, input) + + try { + return DataResult.success(Pair(Component.Serializer.fromJson(value), ops.empty())) + } catch (err: JsonSyntaxException) { + return DataResult.error("Error decoding component: ${err.message}") + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt new file mode 100644 index 000000000..445fb51eb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import it.unimi.dsi.fastutil.bytes.ByteArrayList +import net.minecraft.nbt.NbtOps +import ru.dbotthepony.mc.otm.core.math.Decimal +import java.nio.ByteBuffer +import java.util.stream.Collector +import java.util.stream.Stream + +object DecimalCodec : Codec { + override fun encode(input: Decimal, ops: DynamicOps, prefix: T): DataResult { + if (ops === NbtOps.INSTANCE) { + return DataResult.success((ops as DynamicOps).createByteList(ByteBuffer.wrap(input.toByteArray()))) + } + + return DataResult.success(ops.createString(input.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getStringValue(input).flatMap { + try { + DataResult.success(Pair(Decimal(it), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error("Not a valid number for converting into Decimal: $it") + } + }.get().map( + { + DataResult.success(it) + }, + { e0 -> + ops.getIntStream(input).flatMap { + try { + DataResult.success(Pair(Decimal.fromByteArray( + it + .collect(::ByteArrayList, { v, a -> v.add(a.toByte()) }, ByteArrayList::addAll) + .toByteArray() + ), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error("Failed to convert array of bytes into Decimal: $it") + } + }.get().map( + { + DataResult.success(it) + }, + { e1 -> + ops.getNumberValue(input).flatMap { + DataResult.success(Pair(Decimal(it.toString()), ops.empty())) + }.get().map( + { + DataResult.success(it) + }, + { e2 -> + DataResult.error("None of attempts at decoding Decimal were successful: ${e0.message()}; ${e1.message()}; ${e2.message()}") + } + ) + } + ) + } + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt new file mode 100644 index 000000000..1143aac1f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalProvider.kt @@ -0,0 +1,118 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.RandomSource +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.nextDecimal +import ru.dbotthepony.mc.otm.registry.RegistryDelegate + +fun interface SampledDecimal { + fun sample(source: RandomSource): Decimal +} + +abstract class DecimalProvider : SampledDecimal { + interface Type { + val codec: Codec + } + + abstract val minValue: Decimal + abstract val maxValue: Decimal + abstract val type: Type<*> + + companion object { + private val registryHolder = RegistryDelegate>("decimal_provider_type") { + setDefaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero")) + disableSaving() + } + + val CODEC: Codec by lazy { + Codec + .either(DecimalCodec, registry.codec.dispatch({ it.type }, { it.codec })) + .xmap( + { c -> c.map(::ConstantDecimal, { it }) }, + { if (it.type === ConstantDecimal.Companion) Either.left(it.minValue) else Either.right(it) } + ) + } + + val registry by registryHolder + val registryKey get() = registryHolder.key + + private val registror = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID) + + init { + registror.register("zero") { ConstantDecimal.Zero } + registror.register("constant") { ConstantDecimal.Companion } + registror.register("uniform") { UniformDecimal.Companion } + } + + internal fun register(bus: IEventBus) { + bus.addListener(registryHolder::build) + registror.register(bus) + } + } +} + +class ConstantDecimal(val value: Decimal) : DecimalProvider() { + object Zero : DecimalProvider(), Type { + override fun sample(source: RandomSource): Decimal { + return Decimal.ZERO + } + + override val codec: Codec = Codec.unit(this) + + override val minValue: Decimal + get() = Decimal.ZERO + override val maxValue: Decimal + get() = Decimal.ZERO + override val type: Type<*> + get() = this + } + + override fun sample(source: RandomSource): Decimal { + return value + } + + override val minValue: Decimal + get() = value + override val maxValue: Decimal + get() = value + override val type: Type<*> + get() = Companion + + companion object : Type { + override val codec: Codec = RecordCodecBuilder.create { + it.group(DecimalCodec.fieldOf("value").forGetter(ConstantDecimal::value)).apply(it, ::ConstantDecimal) + } + } +} + +class UniformDecimal(override val minValue: Decimal, override val maxValue: Decimal, val round: Boolean = true) : DecimalProvider() { + constructor(range: ClosedRange, round: Boolean = true) : this(range.start, range.endInclusive, round) + + init { + require(minValue <= maxValue) { "Invalid range provided: $minValue .. $maxValue" } + } + + override fun sample(source: RandomSource): Decimal { + return source.nextDecimal(minValue, maxValue, round = round) + } + + override val type: Type<*> + get() = Companion + + companion object : Type { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("minValue").forGetter(UniformDecimal::minValue), + DecimalCodec.fieldOf("maxValue").forGetter(UniformDecimal::maxValue), + Codec.BOOL.optionalFieldOf("round", true).forGetter(UniformDecimal::round) + ).apply(it, ::UniformDecimal) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt deleted file mode 100644 index e760a5a69..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.data - -import com.google.common.collect.ImmutableMap -import com.mojang.datafixers.util.Pair -import com.mojang.serialization.Codec -import com.mojang.serialization.DataResult -import com.mojang.serialization.DynamicOps -import kotlin.reflect.KClass - -class EnumCodec>(val type: Class) : Codec { - private val values: Map by lazy { - val builder = ImmutableMap.Builder() - for (value in type.enumConstants) builder.put(value.name, value) - builder.build() - } - - override fun encode(input: E, ops: DynamicOps, prefix: T): DataResult { - require(prefix == ops.empty()) { "Non-empty prefix: $prefix" } - return DataResult.success(ops.createString(input.name)) - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - return ops.getStringValue(input).flatMap { - DataResult.success(values[it] ?: return@flatMap DataResult.error("No such enum $it (valid ones are: ${values.keys.joinToString(", ")})")) - }.map { Pair.of(it, ops.empty()) } - } -} - -fun > Class.codec() = EnumCodec(this) -fun > KClass.codec() = EnumCodec(this.java) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt index bd3f096c1..da9e8fde5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt @@ -1,8 +1,60 @@ package ru.dbotthepony.mc.otm.data +import com.google.gson.JsonArray +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.mojang.datafixers.kinds.App +import com.mojang.serialization.Codec import com.mojang.serialization.DataResult +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import net.minecraft.world.entity.player.Player import net.minecraft.world.level.storage.loot.LootContext import net.minecraft.world.level.storage.loot.parameters.LootContextParam +import net.minecraft.world.level.storage.loot.parameters.LootContextParams +import java.util.Optional +import kotlin.reflect.KProperty1 + +fun JsonArray.getRidOfNulls(): JsonArray { + val toRemove = IntAVLTreeSet() + + for (i in 0 until size()) { + when (val value = this[i]) { + JsonNull.INSTANCE -> toRemove.add(i) + is JsonArray -> value.getRidOfNulls() + is JsonObject -> value.getRidOfNulls() + } + } + + if (toRemove.isNotEmpty()) { + remove(toRemove.lastInt()) + val iterator = toRemove.iterator(toRemove.lastInt()) + + while (iterator.hasPrevious()) { + remove(iterator.previousInt()) + } + } + + return this +} + +fun JsonObject.getRidOfNulls(): JsonObject { + val toRemove = ArrayList() + + for ((key, value) in entrySet()) { + when (value) { + JsonNull.INSTANCE -> toRemove.add(key) + is JsonArray -> value.getRidOfNulls() + is JsonObject -> value.getRidOfNulls() + } + } + + for (key in toRemove) { + remove(key) + } + + return this +} operator fun LootContext.get(param: LootContextParam): T? { return getParamOrNull(param) @@ -11,3 +63,14 @@ operator fun LootContext.get(param: LootContextParam): T? { fun DataResult.getOrNull(): T? { return get().left().orElse(null) } + +fun LootContext.findPlayer(): Player? { + return getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY).let { + if (it != null) + it as? Player + else + getParamOrNull(LootContextParams.KILLER_ENTITY).let { + if (it != null) it as? Player else getParamOrNull(LootContextParams.THIS_ENTITY) as? Player + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientCodec.kt new file mode 100644 index 000000000..aa6b50077 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientCodec.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import net.minecraft.world.item.crafting.Ingredient + +object IngredientCodec : Codec { + override fun encode(input: Ingredient, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + try { + return DataResult.success(Pair(Ingredient.fromJson(ops.convertTo(JsonOps.INSTANCE, input)), ops.empty())) + } catch (err: JsonSyntaxException) { + return DataResult.error("Error decoding Ingredient: ${err.message}") + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt new file mode 100644 index 000000000..d13d41320 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/IngredientMatrixCodec.kt @@ -0,0 +1,113 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.core.collect.allEqual +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.collect.toStream +import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.recipe.IIngredientMatrix +import ru.dbotthepony.mc.otm.recipe.IngredientMatrix +import java.util.function.Supplier + +class IngredientMatrixCodec(ingredientCodec: Codec) : Codec { + private val ingredientList = Codec.list(ingredientCodec) + private val doubleIngredientList = Codec.list(ingredientList) + + override fun encode(input: IIngredientMatrix, ops: DynamicOps, prefix: T): DataResult { + return doubleIngredientList.encode( + (0 until input.height).iterator().map { row -> + (0 until input.width).iterator().map { column -> + input[column, row] + }.toList() + }.toList(), + ops, + prefix + ) + } + + private data class Handwritten(val pattern: List, val key: Map) + + private val handwrittenCodec = RecordCodecBuilder.create { + it.group( + Codec.list(Codec.STRING) + .flatXmap( + { DataResult.success(it) }, + { if (it.iterator().map { it.length }.allEqual()) DataResult.success(it) else DataResult.error("One or more of patten strings differ in length") } + ) + .fieldOf("pattern").forGetter(Handwritten::pattern), + Codec.unboundedMap( + Codec.STRING + .flatXmap( + { if (it.length == 1) DataResult.success(it[0]) else DataResult.error("Ingredient key must be exactly 1 symbol in length, '$it' is invalid") }, + { DataResult.success(it.toString()) } + ), ingredientCodec).fieldOf("key").forGetter(Handwritten::key) + ).apply(it, ::Handwritten) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getList(input).get().map( + { + val lines = ArrayList>>() + + it.accept { + lines.add(ingredientList.decode(ops, it).map { it.first }) + } + + val errors = ArrayList>() + val ingredients = ArrayList>() + + lines.withIndex().forEach { + val (line, result) = it + result.get().map({ ingredients.add(it) }, { errors.add { "Line $line: ${it.message()}" } }) + } + + if (errors.isNotEmpty()) { + DataResult.error("Failed to decode ingredient matrix: ${errors.joinToString { it.get() }}") + } else if (ingredients.isEmpty()) { + DataResult.error("Ingredient list is empty") + } else if (!ingredients.iterator().map { it.size }.allEqual()) { + DataResult.error("Ingredient list is not a matrix (one or multiple of rows are mismatched size)") + } else { + val result = IngredientMatrix(ingredients.first().size, ingredients.size) + + for ((row, columns) in ingredients.withIndex()) { + for ((column, ingredient) in columns.withIndex()) { + result[column, row] = ingredient + } + } + + DataResult.success(Pair(result, ops.empty())) + } + }, + { err1 -> + handwrittenCodec.decode(ops, input).get().map( + { + DataResult.success(it) + }, + { + DataResult.error("Failed to decode ingredients as list: ${err1.message()} and as pattern/dictionary: ${it.message()}") + } + ).flatMap { + val handwritten = it.first + val result = IngredientMatrix(handwritten.pattern.first().length, handwritten.pattern.size) + + for ((row, pattern) in handwritten.pattern.withIndex()) { + for ((column, symbol) in pattern.withIndex()) { + val ingredient = if (symbol == ' ') handwritten.key[symbol] ?: Ingredient.EMPTY else handwritten.key[symbol] ?: return@flatMap DataResult.error("Unknown ingredient with index '$symbol'") + result[column, row] = ingredient + } + } + + DataResult.success(Pair(result, ops.empty())) + } + } + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemPredicateCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemPredicateCodec.kt new file mode 100644 index 000000000..c3a4a1e9f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemPredicateCodec.kt @@ -0,0 +1,27 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import net.minecraft.advancements.critereon.ItemPredicate + +object ItemPredicateCodec : Codec { + override fun encode(input: ItemPredicate, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.serializeToJson().let { if (it is JsonObject) it.getRidOfNulls() else it })) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return try { + DataResult.success(Pair(ItemPredicate.fromJson(ops.convertTo(JsonOps.INSTANCE, input)), ops.empty())) + } catch (err: JsonSyntaxException) { + DataResult.error("Failed to deserialize ItemPredicate: ${err.message}") + } catch (err: JsonParseException) { + DataResult.error("Failed to deserialize ItemPredicate: ${err.message}") + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt deleted file mode 100644 index a2a42a34d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/ItemStackCodec.kt +++ /dev/null @@ -1,113 +0,0 @@ -package ru.dbotthepony.mc.otm.data - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import com.google.gson.JsonSyntaxException -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken -import com.google.gson.stream.JsonWriter -import com.mojang.datafixers.util.Pair -import com.mojang.serialization.Codec -import com.mojang.serialization.DataResult -import com.mojang.serialization.DynamicOps -import com.mojang.serialization.codecs.ListCodec -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set -import java.lang.reflect.Type - -object ItemStackCodec : Codec, TypeAdapter(), JsonSerializer, JsonDeserializer { - override fun encode(input: ItemStack, ops: DynamicOps, prefix: T): DataResult { - require(prefix == ops.empty()) { "Non-empty prefix: $prefix" } - - return ForgeRegistries.ITEMS.codec.encode(input.item, ops, ops.empty()).flatMap { - DataResult.success(ops.createMap(linkedMapOf( - ops.createString("id") to it, - ops.createString("count") to ops.createInt(input.count) - ))) - } - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - return ops.getMap(input).flatMap { - val item = it["id"]?.let { ForgeRegistries.ITEMS.codec.decode(ops, it) }?.result()?.orElse(null)?.first - val count = it["count"]?.let(ops::getNumberValue)?.result()?.orElse(null)?.toInt() ?: return@flatMap DataResult.error("Invalid item count") - - if (item == null || item == Items.AIR) { - return@flatMap DataResult.error("Unknown item type $item") - } - - DataResult.success(ItemStack(item, count)) - }.map { Pair.of(it, ops.empty()) } - } - - val LIST = ListCodec(this) - - override fun write(out: JsonWriter, value: ItemStack) { - out.beginObject() - - out.name("id") - out.value(value.item.registryName!!.toString()) - - out.name("count") - out.value(value.count) - - out.endObject() - } - - override fun read(reader: JsonReader): ItemStack { - reader.beginObject() - - var id: String? = null - var count: Int? = null - - while (reader.peek() != JsonToken.END_OBJECT) { - when (val it = reader.nextName()) { - "id" -> id = reader.nextString() - "count" -> count = reader.nextInt() - else -> throw JsonSyntaxException("Unknown json key $it") - } - } - - reader.endObject() - - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(id ?: return ItemStack.EMPTY)) ?: return ItemStack.EMPTY - - return ItemStack(item, count ?: 1) - } - - override fun serialize(src: ItemStack, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return serialize(src) - } - - fun serialize(src: ItemStack): JsonElement { - return JsonObject().also { - it["id"] = JsonPrimitive(src.item.registryName!!.toString()) - it["count"] = JsonPrimitive(src.count) - } - } - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ItemStack { - return deserialize(json) - } - - fun deserialize(json: JsonElement): ItemStack { - if (json !is JsonObject) { - throw JsonSyntaxException("ItemStack json element must be JsonObject, ${json::class.qualifiedName} given") - } - - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(json["id"]?.asString ?: return ItemStack.EMPTY)) ?: return ItemStack.EMPTY - val count = json["count"]?.asInt ?: 1 - - return ItemStack(item, count) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonElementCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonElementCodec.kt new file mode 100644 index 000000000..783222a0e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/JsonElementCodec.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonElement +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps + +object JsonElementCodec : Codec { + override fun encode(input: JsonElement, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input)) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return DataResult.success(Pair(ops.convertTo(JsonOps.INSTANCE, input), ops.empty())) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/RecipeWrapperCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/RecipeWrapperCodec.kt new file mode 100644 index 000000000..e9f950796 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/RecipeWrapperCodec.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.gson.JsonObject +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import io.netty.buffer.UnpooledByteBufAllocator +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeSerializer +import java.nio.ByteBuffer + +data class RecipePair>(val recipe: R, val data: JsonObject) + +fun > RecipeSerializer.codec(context: Codec2RecipeSerializer<*>.Context): Codec> { + return object : Codec> { + override fun encode(input: RecipePair, ops: DynamicOps, prefix: T): DataResult { + if (context.isNetwork) { + val buffer = FriendlyByteBuf(UnpooledByteBufAllocator.DEFAULT.heapBuffer()) + buffer.writeResourceLocation(input.recipe.id) + toNetwork(buffer, input.recipe) + buffer.writerIndex(0) + buffer.readerIndex(0) + val array = ByteBuffer.allocate(buffer.readableBytes()) + buffer.readBytes(array) + array.position(0) + return DataResult.success(ops.createByteList(array)) + } else { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.data)) + } + } + + override fun decode(ops: DynamicOps, input: T): DataResult, T>> { + if (context.isNetwork) { + return ops.getByteBuffer(input).map { + it.position(0) + val buffer = FriendlyByteBuf(UnpooledByteBufAllocator.DEFAULT.heapBuffer()) + buffer.writeBytes(it) + buffer.writerIndex(0) + buffer.readerIndex(0) + Pair(RecipePair(fromNetwork(buffer.readResourceLocation(), buffer)!!, JsonObject()), ops.empty()) + } + } else { + val getJson = ops.convertTo(JsonOps.INSTANCE, input) as JsonObject + return DataResult.success(Pair(RecipePair(fromJson(context.id, getJson), getJson), ops.empty())) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt deleted file mode 100644 index 24f85d16e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SerializedFunctionRegistry.kt +++ /dev/null @@ -1,222 +0,0 @@ -package ru.dbotthepony.mc.otm.data - -import com.google.gson.Gson -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import com.google.gson.JsonSyntaxException -import com.google.gson.TypeAdapter -import com.google.gson.internal.bind.TypeAdapters -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter -import io.netty.buffer.ByteBufInputStream -import io.netty.buffer.ByteBufOutputStream -import it.unimi.dsi.fastutil.io.FastByteArrayInputStream -import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.core.readType -import ru.dbotthepony.mc.otm.core.readVarIntLE -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.writeType -import ru.dbotthepony.mc.otm.core.writeVarIntLE -import java.io.DataInputStream -import java.io.DataOutputStream -import java.lang.reflect.Type -import java.util.Base64 -import java.util.Collections -import java.util.LinkedList -import java.util.function.Consumer - -class SerializedFunctionRegistry(val gson: Gson = Gson()) : JsonSerializer>, JsonDeserializer>, TypeAdapter>() { - fun interface AnonymousFunction { - fun invoke(receiver: R, arguments: List): T - } - - data class BoundFunction( - val function: Function, - val arguments: List - ) : java.util.function.Function { - fun toJson(): JsonElement { - return JsonObject().also { - it["function"] = function.toJson() - val stream = FastByteArrayOutputStream() - val dataStream = DataOutputStream(stream) - - stream.writeVarIntLE(arguments.size) - - for (argument in arguments) { - dataStream.writeType(argument) - } - - it["arguments"] = JsonPrimitive(Base64.getEncoder().encode(stream.array.copyOfRange(0, stream.length)).toString(Charsets.UTF_8)) - } - } - - fun toNetwork(buff: FriendlyByteBuf) { - function.toNetwork(buff) - - val stream = DataOutputStream(ByteBufOutputStream(buff)) - stream.writeVarIntLE(arguments.size) - for (argument in arguments) { - stream.writeType(argument) - } - } - - override fun apply(t: R): T { - return function.body.invoke(t, arguments) - } - } - - data class Function( - val id: ResourceLocation, - val body: AnonymousFunction, - val registry: SerializedFunctionRegistry - ) { - fun bind(vararg arguments: Any?): BoundFunction { - validate(arguments.iterator().withIndex()) - return BoundFunction(this, Collections.unmodifiableList(LinkedList().also { it.addAll(arguments) })) - } - - fun bind(arguments: List): BoundFunction { - validate(arguments.iterator().withIndex()) - return BoundFunction(this, Collections.unmodifiableList(LinkedList().also { it.addAll(arguments) })) - } - - fun toJson(): JsonElement { - return JsonPrimitive(id.toString()) - } - - fun toNetwork(buff: FriendlyByteBuf) { - buff.writeUtf(id.toString()) - } - - companion object { - private fun validate(iterator: Iterator>) { - try { - val stream = DataOutputStream(FastByteArrayOutputStream()) - - for ((i, argument) in iterator) { - try { - stream.writeType(argument) - } catch(err: Exception) { - throw IllegalArgumentException("Argument at $i can not be serialized", err) - } - } - } catch(err: Exception) { - throw IllegalArgumentException("Argument list validation failed", err) - } - } - } - } - - private val map = HashMap>() - - fun register(id: ResourceLocation, function: AnonymousFunction): Function { - synchronized(map) { - return map.computeIfAbsent(id) { Function(id, function, this) } - } - } - - fun register(id: ResourceLocation, function: R.() -> T): Function { - return register(id, AnonymousFunction { r, args -> - check(args.isEmpty()) { "Invalid amount of arguments. No arguments are required" } - function.invoke(r) - }) - } - - inline fun register(id: ResourceLocation, noinline function: R.(A) -> T): Function { - return register(id, AnonymousFunction { r, args -> - check(args.size == 1) { "Invalid amount of arguments. 1 is required" } - function.invoke( - r, - args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given") - ) - }) - } - - inline fun register(id: ResourceLocation, noinline function: R.(A, B) -> T): Function { - return register(id, AnonymousFunction { r, args -> - check(args.size == 2) { "Invalid amount of arguments. 2 is required" } - - function.invoke( - r, - args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"), - args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"), - ) - }) - } - - inline fun register(id: ResourceLocation, noinline function: R.(A, B, C) -> T): Function { - return register(id, AnonymousFunction { r, args -> - check(args.size == 3) { "Invalid amount of arguments. 3 is required" } - - function.invoke( - r, - args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"), - args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"), - args[2] as? C ?: throw ClassCastException("Argument at 2 is supposed to be ${B::class.qualifiedName}, ${args[2]?.let { it::class.qualifiedName }} given"), - ) - }) - } - - fun get(id: ResourceLocation): Function? { - synchronized(map) { - return map[id] - } - } - - fun fromNetwork(buff: FriendlyByteBuf): BoundFunction? { - val id = ResourceLocation(buff.readUtf()) - val stream = DataInputStream(ByteBufInputStream(buff)) - - val arguments = LinkedList() - - for (i in 0 until stream.readVarIntLE()) { - arguments.add(stream.readType()) - } - - return map[id]?.bind(arguments) - } - - fun fromJson(value: JsonElement): BoundFunction? { - if (value !is JsonObject) { - return null - } - - val id = value["function"]?.asString ?: return null - val argumentString = value["arguments"]?.asString ?: return null - val stream = DataInputStream(FastByteArrayInputStream(Base64.getDecoder().decode(argumentString))) - val arguments = LinkedList() - - for (i in 0 until stream.readVarIntLE()) { - arguments.add(stream.readType()) - } - - return map[ResourceLocation(id)]?.bind(arguments) - } - - override fun serialize(src: BoundFunction, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return src.toJson() - } - - override fun deserialize( - json: JsonElement, - typeOfT: Type, - context: JsonDeserializationContext - ): BoundFunction { - return fromJson(json) ?: throw JsonSyntaxException("Function is invalid") - } - - override fun write(out: JsonWriter, value: BoundFunction) { - TypeAdapters.JSON_ELEMENT.write(out, value.toJson()) - } - - override fun read(`in`: JsonReader): BoundFunction { - return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid") - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt new file mode 100644 index 000000000..eb2ade209 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/SingletonCodec.kt @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps + +class SingletonCodec(val value: V) : Codec { + override fun encode(input: V, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.empty()) + } + + override fun decode(ops: DynamicOps, input: T? /* Так то, оно должно быть null */): DataResult> { + return DataResult.success(Pair(value, ops.empty())) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt new file mode 100644 index 000000000..9334b20d4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import net.minecraft.nbt.NbtOps +import java.util.UUID +import java.util.stream.LongStream + +// because UUIDUtil codec is in unexpected place +// and can't work with both strings, array of longs and array of ints +object UUIDCodec : Codec { + override fun encode(input: UUID, ops: DynamicOps, prefix: T): DataResult { + if (ops === NbtOps.INSTANCE) { + return DataResult.success((ops as DynamicOps).createLongList(LongStream.of(input.mostSignificantBits, input.leastSignificantBits))) + } + + return DataResult.success(ops.createString(input.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getLongStream(input).flatMap { + val l = it.limit(4).toArray() + + if (l.size == 4) { + // 4 int + DataResult.success(Pair(UUID((l[0] shl 32) or l[1], (l[2] shl 32) or l[3]), ops.empty())) + } else if (l.size == 2) { + DataResult.success(Pair(UUID(l[0], l[1]), ops.empty())) + } else { + DataResult.error("Can't construct UUID from ${l.size} elements") + } + }.get().map( + { + DataResult.success(it) + }, + { e0 -> + ops.getStringValue(input).map { + Pair(UUID.fromString(it), ops.empty()) + }.get().map( + { + DataResult.success(it) + }, + { + DataResult.error("Unable to deserialize UUID: ${e0.message()}; ${it.message()}") + } + ) + } + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt index bb92e5431..4f8316e44 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceCondition.kt @@ -1,15 +1,10 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSyntaxException +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType -import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.registry.MLootItemConditions /** @@ -32,13 +27,13 @@ data class ChanceCondition(val chance: Double) : LootItemCondition, LootItemCond return this } - companion object : Serializer { - override fun serialize(p_79325_: JsonObject, p_79326_: ChanceCondition, p_79327_: JsonSerializationContext) { - p_79325_["chance"] = JsonPrimitive(p_79326_.chance) - } - - override fun deserialize(p_79323_: JsonObject, p_79324_: JsonDeserializationContext): ChanceCondition { - return ChanceCondition(p_79323_["chance"]?.asDouble ?: throw JsonSyntaxException("Invalid chance json element")) + companion object { + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + Codec.doubleRange(0.0, 1.0).fieldOf("chance").forGetter(ChanceCondition::chance) + ).apply(it, ::ChanceCondition) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt index bcfee2b3d..92e501c6d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ChanceWithPlaytimeCondition.kt @@ -1,16 +1,12 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType import ru.dbotthepony.mc.otm.capability.matteryPlayer -import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.data.get import ru.dbotthepony.mc.otm.registry.MLootItemConditions @@ -51,28 +47,16 @@ data class ChanceWithPlaytimeCondition( return this } - companion object : Serializer { - override fun serialize( - p_79325_: JsonObject, - p_79326_: ChanceWithPlaytimeCondition, - p_79327_: JsonSerializationContext - ) { - p_79325_["minPlaytime"] = JsonPrimitive(p_79326_.minPlaytime) - p_79325_["maxPlaytime"] = JsonPrimitive(p_79326_.maxPlaytime) - p_79325_["minProbability"] = JsonPrimitive(p_79326_.minProbability) - p_79325_["maxProbability"] = JsonPrimitive(p_79326_.maxProbability) - } - - override fun deserialize( - p_79323_: JsonObject, - p_79324_: JsonDeserializationContext - ): ChanceWithPlaytimeCondition { - return ChanceWithPlaytimeCondition( - minPlaytime = p_79323_["minPlaytime"].asInt, - maxPlaytime = p_79323_["maxPlaytime"].asInt, - minProbability = p_79323_["minProbability"].asDouble, - maxProbability = p_79323_["maxProbability"].asDouble, - ) + companion object { + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + Codec.INT.optionalFieldOf("minPlaytime", 0).forGetter(ChanceWithPlaytimeCondition::minPlaytime), + Codec.INT.fieldOf("maxPlaytime").forGetter(ChanceWithPlaytimeCondition::maxPlaytime), + Codec.DOUBLE.fieldOf("minProbability").forGetter(ChanceWithPlaytimeCondition::minProbability), + Codec.DOUBLE.fieldOf("maxProbability").forGetter(ChanceWithPlaytimeCondition::maxProbability), + ).apply(it, ::ChanceWithPlaytimeCondition) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/HasExoPackCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/HasExoPackCondition.kt index 297e8638e..3fd16037f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/HasExoPackCondition.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/HasExoPackCondition.kt @@ -1,10 +1,6 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonSerializationContext import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.level.storage.loot.predicates.InvertedLootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemCondition @@ -13,10 +9,10 @@ import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.data.get import ru.dbotthepony.mc.otm.registry.MLootItemConditions -object HasExoPackCondition : LootItemCondition, Serializer, LootItemCondition.Builder { +object HasExoPackCondition : LootItemCondition, LootItemCondition.Builder { override fun test(t: LootContext): Boolean { t[LootContextParams.LAST_DAMAGE_PLAYER]?.matteryPlayer?.let { - return it.hasExoPack + return it.hasExopack } return false @@ -26,13 +22,6 @@ object HasExoPackCondition : LootItemCondition, Serializer, return MLootItemConditions.HAS_EXOPACK } - override fun serialize(p_79325_: JsonObject, p_79326_: HasExoPackCondition, p_79327_: JsonSerializationContext) { - } - - override fun deserialize(p_79323_: JsonObject, p_79324_: JsonDeserializationContext): HasExoPackCondition { - return this - } - override fun build(): LootItemCondition { return this } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt index 39dc2ef2c..f25f12e7d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt @@ -1,22 +1,14 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSyntaxException -import net.minecraft.resources.ResourceLocation +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType -import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.capability.itemsStream -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.capability.items +import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.data.get import ru.dbotthepony.mc.otm.registry.MLootItemConditions @@ -27,7 +19,7 @@ data class ItemInInventoryCondition( val matchCosmetics: Boolean = true, ) : LootItemCondition, LootItemCondition.Builder { override fun test(t: LootContext): Boolean { - val matches = t[LootContextParams.LAST_DAMAGE_PLAYER]?.itemsStream(matchCosmetics)?.filter { + val matches = t[LootContextParams.LAST_DAMAGE_PLAYER]?.items(matchCosmetics)?.filter { if (it.isEmpty) { return@filter false } @@ -64,36 +56,16 @@ data class ItemInInventoryCondition( return this } - companion object : Serializer { - override fun serialize( - p_79325_: JsonObject, - p_79326_: ItemInInventoryCondition, - p_79327_: JsonSerializationContext - ) { - p_79325_["item"] = JsonPrimitive(p_79326_.item.item.registryName!!.toString()) - p_79325_["itemCount"] = JsonPrimitive(p_79326_.item.count) - p_79325_["matchDamage"] = JsonPrimitive(p_79326_.matchDamage) - p_79325_["matchNBT"] = JsonPrimitive(p_79326_.matchNBT) - p_79325_["matchCosmetics"] = JsonPrimitive(p_79326_.matchCosmetics) - } - - override fun deserialize(p_79323_: JsonObject, p_79324_: JsonDeserializationContext): ItemInInventoryCondition { - val item = p_79323_["item"]?.asString ?: throw JsonSyntaxException("Missing item") - val itemCount = p_79323_["itemCount"]?.asInt ?: throw JsonSyntaxException("Missing itemCount") - val matchDamage = p_79323_["matchDamage"]?.asBoolean ?: false - val matchNBT = p_79323_["matchNBT"]?.asBoolean ?: false - val matchCosmetics = p_79323_["matchCosmetics"]?.asBoolean ?: true - - val getItem = ForgeRegistries.ITEMS.getValue(ResourceLocation(item)) - - if (getItem == null || getItem == Items.AIR) { - throw JsonSyntaxException("Invalid item $item") + companion object { + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + ItemStack.CODEC.fieldOf("item").forGetter(ItemInInventoryCondition::item), + Codec.BOOL.optionalFieldOf("matchDamage", false).forGetter(ItemInInventoryCondition::matchDamage), + Codec.BOOL.optionalFieldOf("matchNBT", false).forGetter(ItemInInventoryCondition::matchNBT), + Codec.BOOL.optionalFieldOf("matchCosmetics", false).forGetter(ItemInInventoryCondition::matchCosmetics), + ).apply(it, ::ItemInInventoryCondition) } - - return ItemInInventoryCondition( - ItemStack(getItem, itemCount), - matchDamage, matchNBT - ) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayer.kt index d4354c2a7..ad0cc9dfc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayer.kt @@ -1,34 +1,22 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonSerializationContext import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.level.storage.loot.predicates.InvertedLootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType -import net.minecraftforge.common.util.FakePlayer import ru.dbotthepony.mc.otm.data.get import ru.dbotthepony.mc.otm.registry.MLootItemConditions -object KilledByRealPlayer : LootItemCondition, Serializer, LootItemCondition.Builder { +object KilledByRealPlayer : LootItemCondition, LootItemCondition.Builder { override fun test(t: LootContext): Boolean { - return t.hasParam(LootContextParams.LAST_DAMAGE_PLAYER) && t[LootContextParams.LAST_DAMAGE_PLAYER] !is FakePlayer + return t.hasParam(LootContextParams.LAST_DAMAGE_PLAYER) //&& t[LootContextParams.LAST_DAMAGE_PLAYER] !is FakePlayer } override fun getType(): LootItemConditionType { return MLootItemConditions.KILLED_BY_REAL_PLAYER } - override fun serialize(p_79325_: JsonObject, p_79326_: KilledByRealPlayer, p_79327_: JsonSerializationContext) { - } - - override fun deserialize(p_79323_: JsonObject, p_79324_: JsonDeserializationContext): KilledByRealPlayer { - return this - } - override fun build(): LootItemCondition { return this } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt index df3d9dd14..b181ada7a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/KilledByRealPlayerOrIndirectly.kt @@ -1,23 +1,18 @@ package ru.dbotthepony.mc.otm.data.condition -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonSerializationContext import net.minecraft.world.entity.OwnableEntity import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer import net.minecraft.world.level.storage.loot.parameters.LootContextParams import net.minecraft.world.level.storage.loot.predicates.InvertedLootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType -import net.minecraftforge.common.util.FakePlayer import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.data.get import ru.dbotthepony.mc.otm.registry.MLootItemConditions -object KilledByRealPlayerOrIndirectly : LootItemCondition, Serializer, LootItemCondition.Builder { +object KilledByRealPlayerOrIndirectly : LootItemCondition, LootItemCondition.Builder { override fun test(t: LootContext): Boolean { - if (t.hasParam(LootContextParams.LAST_DAMAGE_PLAYER) && t[LootContextParams.LAST_DAMAGE_PLAYER] !is FakePlayer) { + if (t.hasParam(LootContextParams.LAST_DAMAGE_PLAYER) /*&& t[LootContextParams.LAST_DAMAGE_PLAYER] !is FakePlayer*/) { return true } @@ -27,12 +22,12 @@ object KilledByRealPlayerOrIndirectly : LootItemCondition, Serializer = Stream.empty()) : LootItemFunction, LootItemFunction.Builder { + constructor(filter: Collection) : this(filter.stream()) + constructor(vararg filter: String) : this(filter.stream()) + + val filter: ImmutableList = /*immutableList { + filter.forEach(this) + }*/ filter.collect(ImmutableList.toImmutableList()) + + override fun apply(t: ItemStack, u: LootContext): ItemStack { + val blockEntity = u.getParamOrNull(LootContextParams.BLOCK_ENTITY) ?: return t + val result = t.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] as? CompoundTag + + val data: CompoundTag + + if (blockEntity is MatteryBlockEntity) { + data = CompoundTag() + blockEntity.saveShared(data) + } else { + data = blockEntity.saveWithoutMetadata() + } + + for (k in filter) { + data.remove(k) + } + + if (result == null) { + t.tagNotNull[BlockItem.BLOCK_ENTITY_TAG] = data + } else { + for (k in data.allKeys) { + result[k] = data[k]!! + } + } + + if ("Name" in data && "display" !in t.tagNotNull) { + t.tagNotNull["display"] = CompoundTag().also { + it["Name"] = Component.Serializer.toJson(Component.Serializer.fromJson(data.getJson("Name")!!)!!) + } + + data.remove("Name") + } + + return t + } + + override fun getType(): LootItemFunctionType { + return MItemFunctionTypes.COPY_TILE_NBT + } + + override fun build(): LootItemFunction { + return this + } + + companion object { + val CODEC by lazy { + Codec2Serializer( + RecordCodecBuilder.create { + it.group( + Codec.STRING.listOf() + .optionalFieldOf("filter") + .xmap({ it.orElseGet { listOf() } }, { if (it.isEmpty()) Optional.empty() else Optional.of(it) }) + .forGetter(CopyTileNbtFunction::filter), + ).apply(it, ::CopyTileNbtFunction) + } + ) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/IRandomizableItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/IRandomizableItem.kt deleted file mode 100644 index b649659ac..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/IRandomizableItem.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ru.dbotthepony.mc.otm.data.loot - -import net.minecraft.util.RandomSource -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Rarity -import net.minecraft.world.level.ItemLike - -interface IRandomizableItem { - fun randomize(itemStack: ItemStack, random: RandomSource, ply: Player?, rarity: Rarity = Rarity.COMMON) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt index 487b6d274..0f0224793 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/LootPoolAppender.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.mc.otm.data.loot import com.google.common.collect.ImmutableList -import com.google.gson.* +import com.google.gson.JsonSyntaxException import com.mojang.serialization.Codec import com.mojang.serialization.DataResult import com.mojang.serialization.Dynamic @@ -17,8 +17,7 @@ import net.minecraft.world.level.storage.loot.predicates.LootItemCondition import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.loot.IGlobalLootModifier import net.minecraftforge.common.loot.LootModifier -import java.util.Arrays -import java.util.Deque +import java.util.* import java.util.stream.Stream class LootPoolAppender(conditions: Array, pools: Stream) : LootModifier(conditions) { @@ -39,7 +38,6 @@ class LootPoolAppender(conditions: Array, pools: Stream> diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/RandomizerFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/RandomizerFunction.kt deleted file mode 100644 index b764464c2..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/loot/RandomizerFunction.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ru.dbotthepony.mc.otm.data.loot - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSyntaxException -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Rarity -import net.minecraft.world.level.storage.loot.LootContext -import net.minecraft.world.level.storage.loot.Serializer -import net.minecraft.world.level.storage.loot.functions.LootItemFunction -import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType -import net.minecraft.world.level.storage.loot.parameters.LootContextParams -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes - -enum class RandomizerFunction(val rarity: Rarity) : LootItemFunction, LootItemFunction.Builder { - COMMON(Rarity.COMMON), - UNCOMMON(Rarity.UNCOMMON), - RARE(Rarity.RARE), - EPIC(Rarity.EPIC); - - override fun apply(itemStack: ItemStack, context: LootContext): ItemStack { - val randomizer = itemStack.item as? IRandomizableItem - - if (randomizer == null) { - LOGGER.error("${itemStack.item} does not implement ${IRandomizableItem::class.qualifiedName}! Can not randomize $itemStack!") - return itemStack - } - - val random = context.random - - val player = - context.getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY).let { - if (it != null) - it as? Player - else - context.getParamOrNull(LootContextParams.KILLER_ENTITY).let { - if (it != null) - it as? Player - else - context.getParamOrNull(LootContextParams.THIS_ENTITY) as? Player - } - } - - randomizer.randomize(itemStack, random, player, rarity) - return itemStack - } - - override fun getType(): LootItemFunctionType { - return MItemFunctionTypes.RANDOMIZER - } - - override fun build(): LootItemFunction { - return this - } - - companion object : Serializer { - fun valueOf(rarity: Rarity): RandomizerFunction { - return when(rarity) { - Rarity.COMMON -> COMMON - Rarity.UNCOMMON -> UNCOMMON - Rarity.RARE -> RARE - Rarity.EPIC -> EPIC - } - } - - override fun serialize(p_79325_: JsonObject, p_79326_: RandomizerFunction, p_79327_: JsonSerializationContext) { - p_79325_["rarity"] = JsonPrimitive(p_79326_.rarity.name) - } - - override fun deserialize(p_79323_: JsonObject, p_79324_: JsonDeserializationContext): RandomizerFunction { - return valueOf(Rarity.valueOf(p_79323_["rarity"]?.asString ?: throw JsonSyntaxException("Invalid rarity json element"))) - } - - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt index 3f6ded63c..9308aee87 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/MinecartCargoCrate.kt @@ -6,7 +6,9 @@ import net.minecraft.network.syncher.EntityDataSerializers import net.minecraft.network.syncher.SynchedEntityData import net.minecraft.sounds.SoundSource import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.monster.piglin.PiglinAi import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.vehicle.AbstractMinecartContainer import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.DyeColor @@ -15,10 +17,10 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.gameevent.GameEvent -import ru.dbotthepony.mc.otm.block.CargoCrateBlock -import ru.dbotthepony.mc.otm.block.entity.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity import ru.dbotthepony.mc.otm.core.position -import ru.dbotthepony.mc.otm.menu.MinecartCargoCrateMenu +import ru.dbotthepony.mc.otm.menu.decorative.MinecartCargoCrateMenu import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MSoundEvents @@ -81,19 +83,20 @@ class MinecartCargoCrate( var interactingPlayers by entityData.delegate(INTERACTING_PLAYERS) - fun onPlayerOpen() { + fun onPlayerOpen(player: Player) { if (isRemoved) return if (interactingPlayers++ == 0) { if (!isRemoved) { level.playSound(null, this, MSoundEvents.CARGO_CRATE_OPEN, SoundSource.BLOCKS, 1f, 0.8f + level.random.nextFloat() * 0.2f) - level.gameEvent(GameEvent.CONTAINER_OPEN, position, GameEvent.Context.of(this)) + this.gameEvent(GameEvent.CONTAINER_OPEN, player) + PiglinAi.angerNearbyPiglins(player, true) } } } - fun onPlayerClose() { + fun onPlayerClose(player: Player) { if (interactingPlayers <= 0) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt index 43096f523..719b67ca5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/PlasmaProjectile.kt @@ -12,7 +12,7 @@ import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.EntityHitResult import net.minecraft.world.phys.HitResult import net.minecraftforge.event.ForgeEventFactory -import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.registry.MDamageTypes import ru.dbotthepony.mc.otm.registry.MEntityTypes import ru.dbotthepony.mc.otm.registry.PlasmaDamageSource diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt deleted file mode 100644 index 915eb16d9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt +++ /dev/null @@ -1,194 +0,0 @@ -package ru.dbotthepony.mc.otm.graph - -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.core.SectionPos -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.level.block.entity.BlockEntity -import ru.dbotthepony.mc.otm.core.IConditionalTickable -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.addTicker -import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashMap - -abstract class Abstract6Graph : IConditionalTickable { - protected val nodes = ArrayList>() - fun size() = nodes.size - private val tickable = ArrayList>() - val nodeList: Collection> = Collections.unmodifiableCollection(nodes) - - /** - * Allows storing arbitrary data by external code - */ - @JvmField - val userData = HashMap() - - abstract fun onNodeRemoved(node: Graph6Node) - abstract fun onNodeAdded(node: Graph6Node) - - override val canTick: Boolean - get() = nodes.size > 0 - - override fun tick() { - for (i in tickable.size - 1 downTo 0) { - val node = tickable[i] - - if (node.canTick) { - node.tick() - } else { - tickable.removeAt(i) - } - } - } - - fun removeNode(node: Graph6Node) { - if (!nodes.remove(node)) - throw IllegalStateException("Not containing node $node") - - node.graph = null - onNodeRemoved(node) - - if (node.canTick) { - tickable.remove(node) - } - } - - fun addNode(node: Graph6Node) { - if (nodes.contains(node)) - throw IllegalStateException("Already containing node $node") - - nodes.add(node) - node.graph = this - onNodeAdded(node) - - if (node.canTick) { - tickable.add(node) - } - } - - fun merge(other: Abstract6Graph): Abstract6Graph { - if (other === this) - return this - - if (size() >= other.size()) { - for (node in other.nodes) { - nodes.add(node) - node.graph = this - onNodeAdded(node) - } - - return this - } else { - return other.merge(this) - } - } - - companion object { - fun discoverFull( - level: ServerLevel, - blockPos: BlockPos, - node: Graph6Node, - nodeGetter: (BlockEntity) -> Graph6Node?, - factory: () -> Abstract6Graph - ) { - level.addTicker(object : IConditionalTickable { - override fun tick() { - discovered = discover(level, blockPos, node, nodeGetter, factory) - } - - private var discovered = false - - override val canTick: Boolean - get() = !discovered && node.valid - }) - } - - @JvmStatic - fun discover( - level: ServerLevel, - blockPos: BlockPos, - node: Graph6Node, - nodeGetter: (BlockEntity) -> Graph6Node?, - factory: () -> Abstract6Graph - ): Boolean { - var fullDiscovery = true - - node.nullifyConnections() - - var _graph = node.graph - - // для начала найдем граф к которому будем принадлежать - if (_graph == null) { - for (dir in Direction.values()) { - val offset = blockPos + dir - val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(offset.x), SectionPos.blockToSectionCoord(offset.z)) - - if (chunk == null) { - fullDiscovery = false - continue - } - - val entity = chunk.getBlockEntity(offset) - - if (entity != null) { - val getNode = nodeGetter(entity) - - if (getNode?.graph != null) { - _graph = getNode.graph - break - } - } - } - - // мы нашли граф рядом - if (_graph != null) { - node.graph = _graph - _graph.addNode(node) - } else { - // графов рядом нет, создаем свой - _graph = factory() - node.graph = _graph - _graph.addNode(node) - } - } - - // теперь снова смотрим на соседей, если у них нет графа - присоединяем к своему - // если у них есть граф - слияем его со своим или свой с его - for (dir in Direction.values()) { - val offset = blockPos + dir - val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(offset.x), SectionPos.blockToSectionCoord(offset.z)) - - if (chunk == null) { - fullDiscovery = false - continue - } - - val entity = chunk.getBlockEntity(offset) - - if (entity != null) { - val getNode = nodeGetter(entity) - - if (getNode != null) { - // у вершины нет своего графа - // добавляем в свой граф - if (getNode.graph == null) { - getNode.graph = node.graph!! - node.graph!!.addNode(getNode) - } else if (getNode.graph != node.graph) { - // у вершины уже есть свой граф, и он не наш - // произведём слияние графов - val merged = getNode.graph!!.merge(node.graph!!) - getNode.graph = merged - node.graph = merged - } - - node.setToNeightbour(getNode, dir) - } - } - } - - return fullDiscovery - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt deleted file mode 100644 index 73c2cfe99..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt +++ /dev/null @@ -1,319 +0,0 @@ -package ru.dbotthepony.mc.otm.graph - -import net.minecraft.core.Direction -import ru.dbotthepony.mc.otm.core.IConditionalTickable -import ru.dbotthepony.mc.otm.core.ITickable -import ru.dbotthepony.mc.otm.SERVER_IS_LIVE - -interface GraphNodeListener { - fun onNeighbourTop(node: Graph6Node<*>) = onNeighbour(node, Direction.UP) - fun onNeighbourBottom(node: Graph6Node<*>) = onNeighbour(node, Direction.DOWN) - fun onNeighbourLeft(node: Graph6Node<*>) = onNeighbour(node, Direction.WEST) - fun onNeighbourRight(node: Graph6Node<*>) = onNeighbour(node, Direction.EAST) - fun onNeighbourFront(node: Graph6Node<*>) = onNeighbour(node, Direction.SOUTH) - fun onNeighbourBack(node: Graph6Node<*>) = onNeighbour(node, Direction.NORTH) - - fun onNeighbour(node: Graph6Node<*>, direction: Direction) - - fun onUnNeighbourTop(node: Graph6Node<*>) = onUnNeighbour(node, Direction.UP) - fun onUnNeighbourBottom(node: Graph6Node<*>) = onUnNeighbour(node, Direction.DOWN) - fun onUnNeighbourLeft(node: Graph6Node<*>) = onUnNeighbour(node, Direction.WEST) - fun onUnNeighbourRight(node: Graph6Node<*>) = onUnNeighbour(node, Direction.EAST) - fun onUnNeighbourFront(node: Graph6Node<*>) = onUnNeighbour(node, Direction.SOUTH) - fun onUnNeighbourBack(node: Graph6Node<*>) = onUnNeighbour(node, Direction.NORTH) - - fun onUnNeighbour(node: Graph6Node<*>, direction: Direction) -} - -// Вершина графа, содержит то, к какому графу принадлежит, соседей и своё "значение" -class Graph6Node(@JvmField val value: T, graph: Abstract6Graph? = null) : IConditionalTickable { - var graph: Abstract6Graph? = null - - init { - this.graph = graph - } - - var top: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourTop(field!!) - } - - if (value != null) { - this.value.onNeighbourTop(value) - } - } - - field = value - } - - var bottom: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourBottom(field!!) - } - - if (value != null) { - this.value.onNeighbourBottom(value) - } - } - - field = value - } - - var left: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourLeft(field!!) - } - - if (value != null) { - this.value.onNeighbourLeft(value) - } - } - - field = value - } - - var right: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourRight(field!!) - } - - if (value != null) { - this.value.onNeighbourRight(value) - } - } - - field = value - } - - var front: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourFront(field!!) - } - - if (value != null) { - this.value.onNeighbourFront(value) - } - } - - field = value - } - - var back: Graph6Node? = null - set(value) { - if (value != null && (graph == null || value.graph !== graph)) throw IllegalStateException("Can not be neighbour with node from different graph") - - if (field != value && this.value is GraphNodeListener) { - if (field != null) { - this.value.onUnNeighbourBack(field!!) - } - - if (value != null) { - this.value.onNeighbourBack(value) - } - } - - field = value - } - - fun nullifyConnections() { - top?.bottom = null - bottom?.top = null - left?.right = null - right?.left = null - front?.back = null - back?.front = null - - top = null - bottom = null - left = null - right = null - front = null - back = null - } - - fun setToNeightbour(node: Graph6Node, direction: Direction) { - when (direction) { - Direction.DOWN -> { - node.top = this - bottom = node - } - - Direction.UP -> { - node.bottom = this - top = node - } - - Direction.NORTH -> { - node.front = this - back = node - } - - Direction.SOUTH -> { - node.back = this - front = node - } - - Direction.WEST -> { - node.right = this - left = node - } - - Direction.EAST -> { - node.left = this - right = node - } - } - } - - override val canTick: Boolean - get() { - return if (value is IConditionalTickable) { - value.canTick - } else { - value is ITickable - } - } - - override fun tick() { - if (value is ITickable) { - value.tick() - } else { - throw ClassCastException("$value is not tickable") - } - } - - var seen: Int = 0 - - private fun _flood(): List> { - val list = ArrayList>() - var seen = Int.MAX_VALUE - - GraphFlooder.floodIf(top, seen) { - seen = it.seen - list.add(it) - } - - GraphFlooder.floodIf(bottom, seen) { - seen = it.seen - list.add(it) - } - - GraphFlooder.floodIf(left, seen) { - seen = it.seen - list.add(it) - } - - GraphFlooder.floodIf(right, seen) { - seen = it.seen - list.add(it) - } - - GraphFlooder.floodIf(front, seen) { - seen = it.seen - list.add(it) - } - - GraphFlooder.floodIf(back, seen) { - seen = it.seen - list.add(it) - } - - return list - } - - fun flood(): List> { - top?.bottom = null - bottom?.top = null - left?.right = null - right?.left = null - front?.back = null - back?.front = null - - val list = _flood() - - top?.bottom = this - bottom?.top = this - left?.right = this - right?.left = this - front?.back = this - back?.front = this - - return list - } - - var valid = true - private set - - fun destroy(factory: () -> Abstract6Graph): List> { - if (!valid || !SERVER_IS_LIVE) return emptyList() - - top?.bottom = null - bottom?.top = null - left?.right = null - right?.left = null - front?.back = null - back?.front = null - - graph?.removeNode(this) - - var num = 0 - - if (top != null) num++ - if (bottom != null) num++ - if (left != null) num++ - if (right != null) num++ - if (front != null) num++ - if (back != null) num++ - - if (num < 2) { - return emptyList() - } - - val paths = _flood() - - if (paths.size < 2) { - return paths - } - - var biggest = paths[0] - - for (i in 1 until paths.size) { - if (biggest.size() < paths[i].size()) { - biggest = paths[i] - } - } - - for (flooder in paths) { - if (flooder == biggest) continue - - val graph = factory() - - for (node in flooder.nodes) { - node.graph?.removeNode(node) - graph.addNode(node) - } - } - - return paths - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphFlooder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphFlooder.kt deleted file mode 100644 index 5c0a4d1c3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphFlooder.kt +++ /dev/null @@ -1,64 +0,0 @@ -package ru.dbotthepony.mc.otm.graph - -import java.util.* -import kotlin.collections.ArrayList - -class GraphFlooder(val startNode: Graph6Node, @JvmField val seen: Int = nextSeen++) { - var flooded = false - private set - - private val _nodes = ArrayList>() - - @JvmField - val nodes = Collections.unmodifiableCollection(_nodes)!! - - fun size() = _nodes.size - - private fun flood(node: Graph6Node) { - if (node.seen >= seen) return - - _nodes.add(node) - node.seen = seen - - if (node.top != null) flood(node.top!!) - if (node.bottom != null) flood(node.bottom!!) - if (node.left != null) flood(node.left!!) - if (node.right != null) flood(node.right!!) - if (node.front != null) flood(node.front!!) - if (node.back != null) flood(node.back!!) - } - - fun flood() { - if (flooded) throw IllegalStateException("Already flooded") - - flooded = true - - _nodes.add(startNode) - startNode.seen = seen - - if (startNode.top != null) flood(startNode.top!!) - if (startNode.bottom != null) flood(startNode.bottom!!) - if (startNode.left != null) flood(startNode.left!!) - if (startNode.right != null) flood(startNode.right!!) - if (startNode.front != null) flood(startNode.front!!) - if (startNode.back != null) flood(startNode.back!!) - } - - companion object { - private var nextSeen = 0 - - fun floodIf(node: Graph6Node?, seen: Int, runnable: (GraphFlooder) -> Unit) { - if (node != null && node.seen < seen) { - if (seen == Int.MAX_VALUE) { - val flooder = GraphFlooder(node) - flooder.flood() - runnable(flooder) - } else { - val flooder = GraphFlooder(node, seen) - flooder.flood() - runnable(flooder) - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt new file mode 100644 index 000000000..e6b2b754e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt @@ -0,0 +1,302 @@ +package ru.dbotthepony.mc.otm.graph + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.core.SectionPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraftforge.common.capabilities.Capability +import ru.dbotthepony.mc.otm.addTicker +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.util.* +import kotlin.collections.ArrayList + +open class GraphNode, G : GraphNodeList>(val graphFactory: () -> G) { + interface Link { + val opposite: Link + operator fun unaryMinus() = opposite + } + + data class DirectionLink(val direction: Direction) : Link { + override val opposite: Link + get() = wrapped[direction.opposite.ordinal] + + override fun toString(): String { + return "Link[$direction]" + } + } + + private val neighbours = Object2ObjectOpenHashMap() + val neighboursView: Map = Collections.unmodifiableMap(neighbours) + + var graph: G = graphFactory.invoke() + internal set + + init { + check(graph.addNodeQueued(this as N)) + } + + private var seen: Int = 0 + + fun beginTicking() { + require(this is IConditionalTickable || this is ITickable) { "Node must implement either ITickable or IConditionalTickable to tick" } + graph.beginTicking(this as N) + } + + operator fun get(key: Link): N? = neighbours[key] + + operator fun set(key: Link, node: N?) { + set(key, node, false) + } + + fun set(key: Link, target: N?, allowReplacement: Boolean) { + check(isValid) { "Can not neighbour any node while this node is invalid" } + + val old = neighbours[key] + if (old === target) return + + if (old != null) + breakConnection(this as N, old, key) + + if (target != null) { + require(target.isValid) { "Can not neighbour invalid node" } + + val opposite = key.opposite + + if (allowReplacement) { + target.neighbours[opposite]?.let { + breakConnection(target, it, opposite) + } + + check(target.neighbours[opposite] == null) { "$target didn't break connection at direction $opposite" } + } else { + check(target.neighbours[opposite] == null) { "Trying to form connection from $this to $target at $key, but $target already has neighbour at $opposite (${target.neighbours[opposite]})!" } + } + + target.neighbours[opposite] = this as N + neighbours[key] = target + target.graph.merge(graph) + target.onNeighbour(opposite) + onNeighbour(key) + } else { + neighbours.remove(key) + } + } + + var top: N? + get() = neighbours[UP] + set(value) { + set(UP, value) + } + + var bottom: N? + get() = neighbours[DOWN] + set(value) { + set(DOWN, value) + } + + var south: N? + get() = neighbours[SOUTH] + set(value) { + set(SOUTH, value) + } + + var north: N? + get() = neighbours[NORTH] + set(value) { + set(NORTH, value) + } + + var west: N? + get() = neighbours[WEST] + set(value) { + set(WEST, value) + } + + var east: N? + get() = neighbours[EAST] + set(value) { + set(EAST, value) + } + + protected open fun onNeighbour(link: Link) {} + protected open fun onUnNeighbour(link: Link) {} + + protected open fun invalidate() {} + protected open fun revive() {} + + var isValid: Boolean = true + set(value) { + if (value == field) return + field = value + + if (!value) { + val neighbours = neighbours.entries.map { it.key to it.value } + + for ((link, node) in neighbours) { + removeNeighbours(this as N, node, link) + } + + graph.removeNode(this as N) + rebuildGraphs(neighbours.map { it.second }) + invalidate() + } else { + revive() + } + } + + fun discover( + level: ServerLevel, + blockPos: BlockPos, + nodeGetter: (BlockEntity) -> N? + ) { + if (!isValid) return + + level.addTicker { + isValid && !discoverStep(level, blockPos, nodeGetter) + } + } + + fun discover( + level: ServerLevel, + blockPos: BlockPos, + capability: Capability + ) { + if (!isValid) return + + level.addTicker { + isValid && !discoverStep(level, blockPos) { it.getCapability(capability).orNull() } + } + } + + fun discover(blockEntity: BlockEntity, nodeGetter: (BlockEntity) -> N?) { + discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, nodeGetter) + } + + fun discover(blockEntity: BlockEntity, capability: Capability) { + discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, capability) + } + + fun discoverStep( + level: ServerLevel, + blockPos: BlockPos, + nodeGetter: (BlockEntity) -> N?, + ): Boolean { + if (!isValid) return false + var fullDiscovery = true + + for (dir in wrapped) { + val offset = blockPos + dir.direction + val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(offset.x), SectionPos.blockToSectionCoord(offset.z)) + + if (chunk == null) { + fullDiscovery = false + set(dir, null) + continue + } + + val entity = chunk.getBlockEntity(offset) + + if (entity != null) { + set(dir, nodeGetter(entity)) + } else { + set(dir, null) + } + } + + return fullDiscovery + } + + companion object { + private var nextSeen = 0 + private val wrapped = Direction.entries.map { DirectionLink(it) } + + fun link(direction: Direction): Link { + return wrapped[direction.ordinal] + } + + val UP = link(Direction.UP) + val DOWN = link(Direction.DOWN) + val NORTH = link(Direction.NORTH) + val SOUTH = link(Direction.SOUTH) + val WEST = link(Direction.WEST) + val EAST = link(Direction.EAST) + + private fun , G : GraphNodeList> removeNeighbours(a: N, b: N, key: Link) { + val opposite = key.opposite + + // проверяем, действительно ли a соединён с b по key + require(a.neighbours[key] === b) { "$a does not neighbour with $b at $key (a -> b)" } + + // проверяем обратную связь + require(b.neighbours[opposite] === a) { "$b does not neighbour with $a at $opposite (b -> a)" } + + // находятся ли они в одном графе + require(a.graph === b.graph) { "$a and $b belong to different graphs (${a.graph} / ${b.graph})" } + + a.neighbours.remove(key) + b.neighbours.remove(opposite) + + if (a.isValid) a.onUnNeighbour(key) + if (b.isValid) b.onUnNeighbour(key.opposite) + } + + private fun , G : GraphNodeList> rebuildGraphs(nodes: Collection) { + if (nodes.isEmpty()) + return + + val seen = ++nextSeen + val floods = ArrayList>() + + for (node in nodes) { + if (node.seen < seen) { + floods.add(flood(node, seen)) + } + } + + floods.sortedBy { it.first().graph.size } + floods.first().first().graph.retain(ReferenceOpenHashSet(floods.first())) + + for (i in 1 until floods.size) { + val graph = floods[i].first().graphFactory.invoke() + + for (node in floods[i]) { + graph.addNode(node) + } + } + } + + private fun , G : GraphNodeList> breakConnection(a: N, b: N, key: Link) { + removeNeighbours(a, b, key) + rebuildGraphs(listOf(a, b)) + } + + private fun , G : GraphNodeList> flood(startingNode: N, seen: Int): ArrayList { + val unopen = ArrayList() + val result = ArrayList() + unopen.add(startingNode) + + while (unopen.isNotEmpty()) { + val last = unopen.removeLast() + + if (last.seen < seen) { + result.add(last) + last.seen = seen + + for (node in last.neighbours.values) { + if (node.seen < seen) { + unopen.add(node) + } + } + } + } + + return result + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt new file mode 100644 index 000000000..de223337d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt @@ -0,0 +1,217 @@ +package ru.dbotthepony.mc.otm.graph + +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.lang.ref.WeakReference +import java.util.* +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList + +open class GraphNodeList, G : GraphNodeList> : IConditionalTickable { + private val queuedAdd = ArrayDeque() + private val queuedRemove = ArrayDeque() + private val nodesInternal = ArrayList() + private val conditional = ArrayList() + private val always = ArrayList() + private var isTicking = false + private var shouldQueueChanges = false + + protected val nodes: List = Collections.unmodifiableList(nodesInternal) + + val size get() = nodesInternal.size + + var isValid = true + private set + + /** + * Allows storing arbitrary data by external code + */ + val userData = HashMap() + + protected open fun onNodeRemoved(node: N) {} + protected open fun onNodeAdded(node: N) {} + + protected open fun innerTick(): Boolean { + return false + } + + fun beginTicking(node: N) { + require(node in nodesInternal || node in queuedAdd) { "Node $node does not belong to $this" } + if (node in queuedRemove) return + + if (node is IConditionalTickable) { + conditional.add(node) + beginTicking() + } else if (node is ITickable) { + always.add(node) + beginTicking() + } else { + throw ClassCastException("$node does not implement ITickable nor IConditionalTickable") + } + } + + private fun addNow(node: N) { + node.graph = this as G + nodesInternal.add(node) + + if (node is IConditionalTickable) { + conditional.add(node) + beginTicking() + } else if (node is ITickable) { + always.add(node) + beginTicking() + } + + onNodeAdded(node) + } + + private fun removeNow(node: N, removeFromList: Boolean = true) { + if (removeFromList) + nodesInternal.remove(node) + + if (node is IConditionalTickable) + conditional.remove(node) + else if (node is ITickable) + always.remove(node) + + onNodeRemoved(node) + } + + private fun processAddQueue() { + while (queuedAdd.isNotEmpty()) { + addNow(queuedAdd.removeFirst()) + } + } + + private fun processRemoveQueue() { + while (queuedRemove.isNotEmpty()) { + removeNow(queuedRemove.removeFirst()) + } + } + + private fun processQueues() { + processRemoveQueue() + processAddQueue() + } + + final override fun tick(): Boolean { + if (!isValid) + return false + + processQueues() + conditional.removeIf { !it.tick() } + for (node in always) node.tick() + + return innerTick() || always.isNotEmpty() || conditional.isNotEmpty() + } + + fun addNodeQueued(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node in nodesInternal || node in queuedAdd) + return false + + queuedAdd.add(node) + beginTicking() + return true + } + + fun addNode(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node in nodesInternal) + return false + + if (shouldQueueChanges) { + if (node in queuedAdd) return false + queuedAdd.add(node) + beginTicking() + } else { + queuedAdd.remove(node) + addNow(node) + } + + return true + } + + fun removeNode(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node !in nodesInternal) + return false + + if (shouldQueueChanges) { + if (node in queuedRemove) return false + queuedRemove.add(node) + beginTicking() + } else { + queuedRemove.remove(node) + removeNow(node) + } + + return true + } + + fun retain(nodes: Set) { + check(isValid) { "$this is no longer valid" } + queuedAdd.retainAll(nodes) + nodesInternal.removeIf { + if (it !in nodes) { + removeNow(it, false) + true + } else { + false + } + } + } + + fun merge(other: G): G { + if (other === this) + return this + + check(isValid) { "$this is no longer valid" } + + if (size >= other.size) { + shouldQueueChanges = true + other.processQueues() + other.nodesInternal.forEach { if (it.isValid) addNow(it) } + processQueues() + shouldQueueChanges = false + + other.isValid = false + return this as G + } else { + return other.merge(this as G) + } + } + + private fun beginTicking() { + if (!isTicking) { + isTicking = true + queue.add(WeakReference(this)) + } + } + + companion object { + private val graphs = ArrayList>>() + private val queue = ArrayList>>() + + internal fun tick() { + if (queue.isNotEmpty()) { + graphs.addAll(queue) + queue.clear() + } + + val iterator = graphs.iterator() + + for (value in iterator) { + val graph = value.get() + + if (graph == null || !graph.tick()) { + graph?.isTicking = false + iterator.remove() + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphListener.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphListener.kt new file mode 100644 index 000000000..f4aebfb66 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphListener.kt @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.graph.matter + +import ru.dbotthepony.mc.otm.capability.matter.* + +interface IMatterGraphListener { + fun onPatternAdded(state: PatternState) {} + fun onPatternRemoved(state: PatternState) {} + fun onPatternUpdated(newState: PatternState, oldState: PatternState) {} + + /** + * This can be called any amount of times with the same task + */ + fun onMatterTaskCreated(task: ReplicationTask) {} + + /** + * This can be called any amount of times with the same task + */ + fun onMatterTaskUpdated(newState: ReplicationTask, oldState: ReplicationTask) {} + + /** + * This can be called any amount of times with the same task + */ + fun onMatterTaskFinished(state: ReplicationTask) {} + + /** + * This can be called any amount of times with the same task + */ + fun onMatterTaskRemoved(state: ReplicationTask) {} +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphNode.kt deleted file mode 100644 index c1c995cfc..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/IMatterGraphNode.kt +++ /dev/null @@ -1,24 +0,0 @@ -package ru.dbotthepony.mc.otm.graph.matter - -import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.graph.Graph6Node - -interface IMatterGraphListener { - fun onPatternAdded(state: IPatternState) {} - fun onPatternRemoved(state: IPatternState) {} - fun onPatternUpdated(newState: IPatternState, oldState: IPatternState) {} - - fun onMatterTaskCreated(task: IReplicationTask<*>) {} - fun > onMatterTaskUpdated(newState: T, oldState: T) {} - fun onMatterTaskFinished(state: IReplicationTask<*>) {} - fun onMatterTaskRemoved(state: IReplicationTask<*>) {} -} - -interface IMatterGraphNode : IMatterGraphListener { - fun getMatterHandler(): IMatterHandler? = null - fun getPatternHandler(): IPatternStorage? = null - fun getTaskHandler(): IReplicationTaskProvider? = null - - val matterNode: Graph6Node - val matterGraph: MatterNetworkGraph? get() = matterNode.graph as MatterNetworkGraph? -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt new file mode 100644 index 000000000..0518c56e2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt @@ -0,0 +1,334 @@ +package ru.dbotthepony.mc.otm.graph.matter + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import net.minecraft.world.item.Item +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.matter.* +import ru.dbotthepony.mc.otm.core.filterNotNull +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.graph.GraphNodeList +import java.util.* +import java.util.function.Predicate +import java.util.stream.Stream + +@Suppress("unused") +class MatterGraph : GraphNodeList(), IMatterGraphListener { + private val listeners = ObjectOpenHashSet() + + fun addListener(listener: IMatterGraphListener) = listeners.add(listener) + fun removeListener(listener: IMatterGraphListener) = listeners.remove(listener) + + override fun onNodeRemoved(node: MatterNode) { + val patterns = node.getPatternHandler() + + if (patterns != null) { + for (pattern in patterns.patterns) { + onPatternRemoved(pattern) + } + } + + val tasks = node.getTaskHandler() + + if (tasks != null) { + for (task in tasks.replicationTasks) { + onMatterTaskRemoved(task) + } + } + + for (pattern in this.patterns) { + node.onPatternRemoved(pattern) + } + + for (task in this.tasks) { + node.onMatterTaskRemoved(task) + } + } + + override fun onNodeAdded(node: MatterNode) { + for (pattern in this.patterns) { + node.onPatternAdded(pattern) + } + + for (task in this.tasks) { + node.onMatterTaskCreated(task) + } + + val patterns = node.getPatternHandler() + + if (patterns != null) { + for (pattern in patterns.patterns) { + onPatternAdded(pattern) + } + } + + val tasks = node.getTaskHandler() + + if (tasks != null) { + for (task in tasks.replicationTasks) { + onMatterTaskCreated(task) + } + } + } + + fun getMatterStorageLevel(): Decimal { + var level = Decimal.ZERO + + for (node in nodes) { + val matter = node.getMatterHandler() + + if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) { + level += matter.storedMatter + } + } + + return level + } + + fun getMatterStorageMaxLevel(): Decimal { + var level = Decimal.ZERO + + for (node in nodes) { + val matter = node.getMatterHandler() + + if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) { + level += matter.maxStoredMatter + } + } + + return level + } + + fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { + if (howMuch <= Decimal.ZERO) + return Decimal.ZERO + + @Suppress("name_shadowing") + var howMuch = howMuch + var extracted = Decimal.ZERO + + for (node in nodes) { + val matter = node.getMatterHandler() + + if (matter != null) { + val value = matter.extractMatterChecked(howMuch, simulate) + howMuch -= value + extracted += value + + if (howMuch <= Decimal.ZERO) + return extracted + } + } + + return extracted + } + + fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { + if (howMuch <= Decimal.ZERO) + return Decimal.ZERO + + @Suppress("name_shadowing") + var howMuch = howMuch + var received = Decimal.ZERO + + for (node in nodes) { + val matter = node.getMatterHandler() + + if (matter != null && matter.matterFlow == FlowDirection.BI_DIRECTIONAL) { + val value = matter.receiveMatterChecked(howMuch, simulate) + howMuch -= value + received += value + + if (howMuch <= Decimal.ZERO) + return received + } + } + + return received + } + + fun receiveMatterForce(howMuch: Decimal, simulate: Boolean): Decimal { + if (howMuch <= Decimal.ZERO) + return Decimal.ZERO + + @Suppress("name_shadowing") + var howMuch = howMuch + var received = Decimal.ZERO + + for (node in nodes) { + val matter = node.getMatterHandler() + + if (matter != null && matter.matterFlow != FlowDirection.OUTPUT) { + val value = matter.receiveMatterChecked(howMuch, simulate) + howMuch -= value + received += value + + if (howMuch <= Decimal.ZERO) + return received + } + } + + return received + } + + private fun doInsertPattern(state: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { + for (node in nodes) { + val storage = node.getPatternHandler() + + if (storage != null) { + val status = storage.insertPattern(state, onlyUpdate, simulate) + + if (!status.isFailed) { + return status + } + } + } + + return PatternInsertFailure + } + + fun insertPattern(state: PatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { + if (onlyUpdate) return doInsertPattern(state, true, simulate) + val status = doInsertPattern(state, true, simulate) + if (!status.isFailed) return status + return doInsertPattern(state, false, simulate) + } + + val tasks: Stream get() { + return nodes.stream().map { it.getTaskHandler()?.replicationTasks }.filterNotNull().flatMap { it } + } + + val allTasks: Stream get() { + return nodes.stream().map { it.getTaskHandler()?.allReplicationTasks }.filterNotNull().flatMap { it } + } + + val patterns: Stream get() { + return nodes.stream().map { it.getPatternHandler()?.patterns }.filterNotNull().flatMap { it } + } + + val patternCount: Long get() { + var value = 0L + + for (node in nodes) { + val storage = node.getPatternHandler() + + if (storage != null) { + value += storage.storedPatterns + } + } + + return value + } + + val patternCapacity: Long get() { + var value = 0L + + for (node in nodes) { + val storage = node.getPatternHandler() + + if (storage != null) { + value += storage.patternCapacity + } + } + + return value + } + + fun getPattern(id: UUID): PatternState? { + for (node in nodes) { + val storage = node.getPatternHandler() + + if (storage != null) { + val get = storage.getPattern(id) + + if (get != null) { + return get + } + } + } + + return null + } + + fun getPattern(state: PatternState) = getPattern(state.id) + fun hasPattern(state: PatternState) = getPattern(state.id) != null + fun hasPattern(id: UUID) = getPattern(id) != null + + fun findPattern(predicate: Predicate): PatternState? { + for (node in nodes) { + val storage = node.getPatternHandler() + + if (storage != null) { + val find = storage.patterns.filter(predicate).findAny() + + if (find.isPresent) { + return find.get() + } + } + } + + return null + } + + fun findPattern(predicate: Item) = findPattern { it.item == predicate } + + fun findPatterns(predicate: Predicate): List { + return patterns.filter(predicate).toList() + } + + fun findPatterns(predicate: Item) = findPatterns { it.item == predicate } + + fun allocateTask(simulate: Boolean): ReplicationTaskAllocation? { + for (node in nodes) { + val tasks = node.getTaskHandler() + + if (tasks != null) { + val allocated = tasks.allocateTask(simulate) + + if (allocated != null) { + return allocated + } + } + } + + return null + } + + fun notifyTaskCompletion(taskId: UUID): Boolean { + return nodes.any { it.getTaskHandler()?.notifyTaskCompletion(taskId) == true } + } + + override fun onPatternAdded(state: PatternState) { + for (node in nodes) node.onPatternAdded(state) + for (node in listeners) node.onPatternAdded(state) + } + + override fun onPatternRemoved(state: PatternState) { + for (node in nodes) node.onPatternRemoved(state) + for (node in listeners) node.onPatternRemoved(state) + } + + override fun onPatternUpdated(newState: PatternState, oldState: PatternState) { + for (node in nodes) node.onPatternUpdated(newState, oldState) + for (node in listeners) node.onPatternUpdated(newState, oldState) + } + + override fun onMatterTaskCreated(task: ReplicationTask) { + for (node in nodes) node.onMatterTaskCreated(task) + for (node in listeners) node.onMatterTaskCreated(task) + } + + override fun onMatterTaskUpdated(newState: ReplicationTask, oldState: ReplicationTask) { + for (node in nodes) node.onMatterTaskUpdated(newState, oldState) + for (node in listeners) node.onMatterTaskUpdated(newState, oldState) + } + + override fun onMatterTaskFinished(state: ReplicationTask) { + for (node in nodes) node.onMatterTaskFinished(state) + for (node in listeners) node.onMatterTaskFinished(state) + } + + override fun onMatterTaskRemoved(state: ReplicationTask) { + for (node in nodes) node.onMatterTaskRemoved(state) + for (node in listeners) node.onMatterTaskRemoved(state) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNetworkGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNetworkGraph.kt deleted file mode 100644 index d9251d542..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNetworkGraph.kt +++ /dev/null @@ -1,383 +0,0 @@ -package ru.dbotthepony.mc.otm.graph.matter - -import com.google.common.collect.Streams -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.item.Item -import net.minecraft.world.level.block.entity.BlockEntity -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.graph.Abstract6Graph -import ru.dbotthepony.mc.otm.graph.Graph6Node -import java.util.* -import java.util.function.Predicate -import java.util.stream.Stream -import kotlin.collections.HashSet - -@Suppress("unused") -class MatterNetworkGraph : Abstract6Graph(), IMatterGraphListener { - private val listeners = HashSet() - - fun addListener(listener: IMatterGraphListener) = listeners.add(listener) - fun removeListener(listener: IMatterGraphListener) = listeners.remove(listener) - - override fun onNodeRemoved(node: Graph6Node) { - val patterns = node.value.getPatternHandler() - - if (patterns != null) { - for (pattern in patterns.patterns) { - onPatternRemoved(pattern) - } - } - - val tasks = node.value.getTaskHandler() - - if (tasks != null) { - for (task in tasks.replicationTasks) { - onMatterTaskRemoved(task) - } - } - - for (pattern in this.patterns) { - node.value.onPatternRemoved(pattern) - } - - for (task in this.tasks) { - node.value.onMatterTaskRemoved(task) - } - } - - override fun onNodeAdded(node: Graph6Node) { - for (pattern in this.patterns) { - node.value.onPatternAdded(pattern) - } - - for (task in this.tasks) { - node.value.onMatterTaskCreated(task) - } - - val patterns = node.value.getPatternHandler() - - if (patterns != null) { - for (pattern in patterns.patterns) { - onPatternAdded(pattern) - } - } - - val tasks = node.value.getTaskHandler() - - if (tasks != null) { - for (task in tasks.replicationTasks) { - onMatterTaskCreated(task) - } - } - } - - fun getMatterStorageLevel(): Decimal { - var level = Decimal.ZERO - - for (node in nodes) { - val matter = node.value.getMatterHandler() - - if (matter != null && matter.direction == MatterDirection.BIDIRECTIONAL) { - level += matter.storedMatter - } - } - - return level - } - - fun getMatterStorageMaxLevel(): Decimal { - var level = Decimal.ZERO - - for (node in nodes) { - val matter = node.value.getMatterHandler() - - if (matter != null && matter.direction == MatterDirection.BIDIRECTIONAL) { - level += matter.maxStoredMatter - } - } - - return level - } - - fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { - if (howMuch <= Decimal.ZERO) - return Decimal.ZERO - - @Suppress("name_shadowing") - var howMuch = howMuch - var extracted = Decimal.ZERO - - for (node in nodes) { - val matter = node.value.getMatterHandler() - - if (matter != null) { - val value = matter.extractMatterOuter(howMuch, simulate) - howMuch -= value - extracted += value - - if (howMuch <= Decimal.ZERO) - return extracted - } - } - - return extracted - } - - fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { - if (howMuch <= Decimal.ZERO) - return Decimal.ZERO - - @Suppress("name_shadowing") - var howMuch = howMuch - var received = Decimal.ZERO - - for (node in nodes) { - val matter = node.value.getMatterHandler() - - if (matter != null && matter.direction == MatterDirection.BIDIRECTIONAL) { - val value = matter.receiveMatterOuter(howMuch, simulate) - howMuch -= value - received += value - - if (howMuch <= Decimal.ZERO) - return received - } - } - - return received - } - - fun receiveMatterForce(howMuch: Decimal, simulate: Boolean): Decimal { - if (howMuch <= Decimal.ZERO) - return Decimal.ZERO - - @Suppress("name_shadowing") - var howMuch = howMuch - var received = Decimal.ZERO - - for (node in nodes) { - val matter = node.value.getMatterHandler() - - if (matter != null && matter.direction != MatterDirection.EXTRACT) { - val value = matter.receiveMatterOuter(howMuch, simulate) - howMuch -= value - received += value - - if (howMuch <= Decimal.ZERO) - return received - } - } - - return received - } - - private fun doInsertPattern(state: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { - for (node in nodes) { - val storage = node.value.getPatternHandler() - - if (storage != null) { - val status = storage.insertPattern(state, onlyUpdate, simulate) - - if (!status.isFailed) { - return status - } - } - } - - return PatternInsertFailure - } - - fun insertPattern(state: IPatternState, onlyUpdate: Boolean, simulate: Boolean): PatternInsertStatus { - if (onlyUpdate) return doInsertPattern(state, true, simulate) - val status = doInsertPattern(state, true, simulate) - if (!status.isFailed) return status - return doInsertPattern(state, false, simulate) - } - - val tasks: Stream> get() { - return Streams.concat(*nodes.mapNotNull { it.value.getTaskHandler()?.replicationTasks }.toTypedArray()) - } - - val allTasks: Stream> get() { - return Streams.concat(*nodes.mapNotNull { it.value.getTaskHandler()?.allReplicationTasks }.toTypedArray()) - } - - val patterns: Stream get() { - return Streams.concat(*nodes.mapNotNull { it.value.getPatternHandler()?.patterns }.toTypedArray()) - } - - val patternCount: Long get() { - var value = 0L - - for (node in nodes) { - val storage = node.value.getPatternHandler() - - if (storage != null) { - value += storage.storedPatterns - } - } - - return value - } - - val patternCapacity: Long get() { - var value = 0L - - for (node in nodes) { - val storage = node.value.getPatternHandler() - - if (storage != null) { - value += storage.patternCapacity - } - } - - return value - } - - fun getPattern(id: UUID): IPatternState? { - for (node in nodes) { - val storage = node.value.getPatternHandler() - - if (storage != null) { - val get = storage.getPattern(id) - - if (get != null) { - return get - } - } - } - - return null - } - - fun getPattern(state: PatternState) = getPattern(state.id) - fun hasPattern(state: PatternState) = getPattern(state.id) != null - fun hasPattern(id: UUID) = getPattern(id) != null - - fun findPattern(predicate: Predicate): IPatternState? { - for (node in nodes) { - val storage = node.value.getPatternHandler() - - if (storage != null) { - val find = storage.patterns.filter(predicate).findAny() - - if (find.isPresent) { - return find.get() - } - } - } - - return null - } - - fun findPattern(predicate: Item) = findPattern { it.item == predicate } - - fun findPatterns(predicate: Predicate): List { - return patterns.filter(predicate).toList() - } - - fun findPatterns(predicate: Item) = findPatterns { it.item == predicate } - - fun allocateTask(simulate: Boolean): ReplicationTaskAllocation? { - for (node in nodes) { - val tasks = node.value.getTaskHandler() - - if (tasks != null) { - val allocated = tasks.allocateTask(simulate) - - if (allocated != null) { - return allocated - } - } - } - - return null - } - - fun notifyTaskCompletion(taskId: UUID): Boolean { - return nodes.any { it.value.getTaskHandler()?.notifyTaskCompletion(taskId) == true } - } - - override fun onPatternAdded(state: IPatternState) { - for (node in nodes) node.value.onPatternAdded(state) - for (node in listeners) node.onPatternAdded(state) - } - - override fun onPatternRemoved(state: IPatternState) { - for (node in nodes) node.value.onPatternRemoved(state) - for (node in listeners) node.onPatternRemoved(state) - } - - override fun onPatternUpdated(newState: IPatternState, oldState: IPatternState) { - for (node in nodes) node.value.onPatternUpdated(newState, oldState) - for (node in listeners) node.onPatternUpdated(newState, oldState) - } - - override fun onMatterTaskCreated(task: IReplicationTask<*>) { - for (node in nodes) node.value.onMatterTaskCreated(task) - for (node in listeners) node.onMatterTaskCreated(task) - } - - override fun > onMatterTaskUpdated(newState: T, oldState: T) { - for (node in nodes) node.value.onMatterTaskUpdated(newState, oldState) - for (node in listeners) node.onMatterTaskUpdated(newState, oldState) - } - - override fun onMatterTaskFinished(state: IReplicationTask<*>) { - for (node in nodes) node.value.onMatterTaskFinished(state) - for (node in listeners) node.onMatterTaskFinished(state) - } - - override fun onMatterTaskRemoved(state: IReplicationTask<*>) { - for (node in nodes) node.value.onMatterTaskRemoved(state) - for (node in listeners) node.onMatterTaskRemoved(state) - } - - companion object { - @JvmStatic - fun discoverFull(tile: BlockEntity, node: Graph6Node) { - if (tile.level !is ServerLevel) - return - - return discoverFull( - tile.level!! as ServerLevel, - tile.blockPos, - node, - fun(_tile): Graph6Node? { - val resolve = _tile.getCapability(MatteryCapability.MATTER_NODE) - - return if (resolve.isPresent) { - resolve.resolve().get().matterNode - } else { - null - } - }, - ::MatterNetworkGraph - ) - } - - @JvmStatic - fun discover(tile: BlockEntity, node: Graph6Node): Boolean { - if (tile.level !is ServerLevel) - return false - - return discover( - tile.level!! as ServerLevel, - tile.blockPos, - node, - fun(_tile): Graph6Node? { - val resolve = _tile.getCapability(MatteryCapability.MATTER_NODE) - - return if (resolve.isPresent) { - resolve.resolve().get().matterNode - } else { - null - } - }, - ::MatterNetworkGraph - ) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt new file mode 100644 index 000000000..17b1ca440 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.mc.otm.graph.matter + +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage +import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider +import ru.dbotthepony.mc.otm.graph.GraphNode + +open class MatterNode : GraphNode(::MatterGraph), IMatterGraphListener { + open fun getMatterHandler(): IMatterStorage? = null + open fun getPatternHandler(): IPatternStorage? = null + open fun getTaskHandler(): IReplicationTaskProvider? = null + + fun discover(blockEntity: BlockEntity) { + discover(blockEntity, MatteryCapability.MATTER_NODE) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/SimpleMatterNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/SimpleMatterNode.kt new file mode 100644 index 000000000..6f171f85c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/SimpleMatterNode.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.mc.otm.graph.matter + +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage +import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider + +open class SimpleMatterNode( + private val matter: IMatterStorage? = null, + private val patterns: IPatternStorage? = null, + private val tasks: IReplicationTaskProvider? = null, +) : MatterNode() { + override fun getMatterHandler(): IMatterStorage? { + return matter + } + + override fun getPatternHandler(): IPatternStorage? { + return patterns + } + + override fun getTaskHandler(): IReplicationTaskProvider? { + return tasks + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/BasicStorageGraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/BasicStorageGraphNode.kt deleted file mode 100644 index d76c2d5e9..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/BasicStorageGraphNode.kt +++ /dev/null @@ -1,180 +0,0 @@ -package ru.dbotthepony.mc.otm.graph.storage - -import it.unimi.dsi.fastutil.objects.ObjectArraySet -import net.minecraft.world.level.Level -import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.storage.IStorage -import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer -import ru.dbotthepony.mc.otm.storage.IStorageStack -import ru.dbotthepony.mc.otm.storage.StorageStackType -import java.util.* - -open class BasicStorageGraphNode(private val energyDemander: IMatteryEnergyStorage? = null) : IStorageGraphNode { - private var resolver = LazyOptional.of { this } - private var valid = true - protected val components = ArrayList>() - - /** - * Setting this to true will render non functional default [attachComponents], - * make [addStorageComponent] and [removeStorageComponent] not add/remove components - * to attached graph, and [revive] not re-attach components. - * - * [invalidate] and [removeComponents] still detach all components - */ - protected var manualAttaching = false - - @Suppress("LeakingThis") - final override val storageNode = Graph6Node(this) - - private var demandingEnergy = false - - override fun attachComponents(to: StorageNetworkGraph) { - if (energyDemander != null) { - if (energyDemander.missingPower.isPositive) { - demandingEnergy = true - to.powerDemandingNodes.add(energyDemander) - } - } - - if (manualAttaching) { - return - } - - for (component in components) { - to.add(component) - } - } - - fun tickEnergyDemanding() { - energyDemander ?: throw IllegalStateException("No energy demander") - - if (!demandingEnergy && storageGraph != null && energyDemander.missingPower.isPositive) { - demandingEnergy = true - storageGraph!!.powerDemandingNodes.add(energyDemander) - } else if (demandingEnergy && storageGraph != null && !energyDemander.missingPower.isPositive) { - demandingEnergy = false - storageGraph!!.powerDemandingNodes.remove(energyDemander) - } - } - - override fun removeComponents(from: StorageNetworkGraph) { - for (component in components) { - from.remove(component) - } - - if (energyDemander != null) { - from.powerDemandingNodes.remove(energyDemander) - demandingEnergy = false - } - } - - @Suppress("unchecked_cast") - fun > computeIfAbsent(identity: StorageStackType, provider: (StorageStackType) -> U): U { - for (component in components) { - if (component.storageType === identity) { - return component as U - } - } - - val factory = provider(identity) - addStorageComponent(factory) - return factory - } - - fun addStorageComponent(component: IStorage<*>) { - for (component1 in components) { - if (component === component1 || component1.storageType === component.storageType) { - return - } - } - - components.add(component) - - if (valid && !manualAttaching) - storageGraph?.add(component) - } - - fun removeStorageComponent(component: IStorage<*>) { - var indexOf = -1 - - for ((i, component1) in components.withIndex()) { - if (component === component1 || component1.storageType === component.storageType) { - indexOf = i - break - } - } - - if (indexOf == -1) { - return - } - - val self = components[indexOf] - components.removeAt(indexOf) - - if (valid) - storageGraph?.remove(self) - } - - fun removeStorageComponent(component: StorageStackType<*>) { - var indexOf = -1 - - for ((i, component1) in components.withIndex()) { - if (component1.storageType === component) { - indexOf = i - break - } - } - - if (indexOf == -1) { - return - } - - val self = components[indexOf] - components.removeAt(indexOf) - - if (valid && !manualAttaching) - storageGraph?.remove(self) - } - - open fun invalidate() { - if (!valid) return - valid = false - resolver.invalidate() - - val storageGraph = storageGraph - - if (storageGraph != null) { - for (component in components) { - storageGraph.remove(component) - } - } - } - - open fun revive() { - if (valid) return - valid = true - resolver = LazyOptional.of { this } - - val storageGraph = storageGraph - - if (storageGraph != null && !manualAttaching) { - for (component in components) { - storageGraph.add(component) - } - } - } - - fun get(): LazyOptional { - return if (valid) resolver else LazyOptional.empty() - } - - fun destroy(level: Level?) { - if (level != null) { - storageNode.destroy { StorageNetworkGraph(level) } - } else { - storageGraph?.removeNode(storageNode) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/IStorageGraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/IStorageGraphNode.kt deleted file mode 100644 index 4362f1b55..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/IStorageGraphNode.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ru.dbotthepony.mc.otm.graph.storage - -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.storage.IStorage - -interface IStorageGraphNode { - /** - * Called by storage graph on node being attached to it - */ - fun attachComponents(to: StorageNetworkGraph) - - /** - * Called by storage graph on node being detached from it - * - * This is NOT called when graph is being destroyed (e.g. even by merging - * with another graph). - */ - fun removeComponents(from: StorageNetworkGraph) - - val storageNode: Graph6Node - val storageGraph: StorageNetworkGraph? get() = storageNode.graph as StorageNetworkGraph? -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt new file mode 100644 index 000000000..ab8221d28 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt @@ -0,0 +1,38 @@ +package ru.dbotthepony.mc.otm.graph.storage + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.graph.GraphNodeList +import ru.dbotthepony.mc.otm.storage.* +import java.util.LinkedList + +class StorageGraph : GraphNodeList() { + private val virtualComponents = Object2ObjectArrayMap, VirtualComponent<*>>() + val powerDemandingNodes = LinkedList() + + fun > getVirtualComponent(type: StorageStack.Type): VirtualComponent { + return virtualComponents.computeIfAbsent(type, Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent + } + + fun > add(storage: IStorage) { + getVirtualComponent(storage.storageType).add(storage) + } + + fun > remove(storage: IStorage) { + getVirtualComponent(storage.storageType).remove(storage) + } + + fun > contains(storage: IStorage): Boolean { + val virtual = virtualComponents[storage.storageType] ?: return false + return (virtual as VirtualComponent).contains(storage) + } + + override fun onNodeAdded(node: StorageNode) { + node.attachComponents(this) + } + + override fun onNodeRemoved(node: StorageNode) { + node.removeComponents(this) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNetworkGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNetworkGraph.kt deleted file mode 100644 index 5c4f94710..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNetworkGraph.kt +++ /dev/null @@ -1,95 +0,0 @@ -package ru.dbotthepony.mc.otm.graph.storage - -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap -import it.unimi.dsi.fastutil.objects.Object2ObjectFunction -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.entity.BlockEntity -import ru.dbotthepony.mc.otm.addTickerPre -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.graph.Abstract6Graph -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.core.orNull -import ru.dbotthepony.mc.otm.storage.* -import java.util.LinkedList - -class StorageNetworkGraph(private val level: Level) : Abstract6Graph() { - private val virtualComponents = Object2ObjectArrayMap, VirtualComponent<*>>() - - /** - * Returns a [VirtualComponent] representing [type] storage - */ - fun getVirtualComponent(type: StorageStackType): VirtualComponent { - return virtualComponents.computeIfAbsent(type, Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent - } - - /** - * Returns a [VirtualComponent] representing [type] storage - */ - fun getVirtualComponent(type: Class): VirtualComponent { - return virtualComponents.computeIfAbsent(StorageRegistry.get(type), Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent - } - - private var addedTicker = false - - fun add(storage: IStorage) { - getVirtualComponent(storage.storageType).add(storage) - } - - fun remove(storage: IStorage) { - getVirtualComponent(storage.storageType).remove(storage) - } - - fun contains(storage: IStorage): Boolean { - val virtual = virtualComponents[storage.storageType] ?: return false - return (virtual as VirtualComponent).contains(storage) - } - - override fun onNodeAdded(node: Graph6Node) { - node.value.attachComponents(this) - - if (!addedTicker) { - addedTicker = true - level.addTickerPre(this) - } - } - - override fun onNodeRemoved(node: Graph6Node) { - node.value.removeComponents(this) - } - - val powerDemandingNodes = LinkedList() - - companion object { - @JvmStatic - fun discoverFull(tile: BlockEntity, node: Graph6Node) { - if (tile.level !is ServerLevel) - return - - return discoverFull( - tile.level as ServerLevel, - tile.blockPos, - node, - fun(_tile): Graph6Node? { - return _tile.getCapability(MatteryCapability.STORAGE_NODE).orNull()?.storageNode - } - ) { StorageNetworkGraph(tile.level!!) } - } - - @JvmStatic - fun discover(tile: BlockEntity, node: Graph6Node): Boolean { - if (tile.level !is ServerLevel) - return false - - return discover( - tile.level as ServerLevel, - tile.blockPos, - node, - fun(_tile): Graph6Node? { - return _tile.getCapability(MatteryCapability.STORAGE_NODE).orNull()?.storageNode - }, - ) { StorageNetworkGraph(tile.level!!) } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt new file mode 100644 index 000000000..a176a192c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt @@ -0,0 +1,123 @@ +package ru.dbotthepony.mc.otm.graph.storage + +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import net.minecraft.world.level.block.entity.BlockEntity +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.graph.GraphNode +import ru.dbotthepony.mc.otm.storage.IStorage +import ru.dbotthepony.mc.otm.storage.IVirtualStorageComponent +import ru.dbotthepony.mc.otm.storage.StorageStack +import ru.dbotthepony.mc.otm.storage.VirtualComponent +import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent +import java.util.* +import java.util.function.Supplier + +open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode(::StorageGraph) { + protected val components = ObjectArraySet>() + + /** + * Setting this to true will render non functional default [attachComponents], + * make [addStorageComponent] and [removeStorageComponent] not add/remove components + * to attached graph, and [revive] not re-attach components. + * + * [invalidate] and [removeComponents] still detach all components + */ + protected var manualAttaching = false + private var demandingEnergy = false + + /** + * Called by storage graph on node being attached to it + */ + open fun attachComponents(to: StorageGraph) { + if (energyDemander != null) { + if (energyDemander.missingPower.isPositive) { + demandingEnergy = true + to.powerDemandingNodes.add(energyDemander) + } + } + + if (manualAttaching) { + return + } + + for (component in components) { + to.add(component) + } + } + + /** + * Called by storage graph on node being detached from it + * + * This is NOT called when graph is being destroyed (e.g. even by merging + * with another graph). + */ + open fun removeComponents(from: StorageGraph) { + for (component in components) { + from.remove(component) + } + + if (energyDemander != null) { + from.powerDemandingNodes.remove(energyDemander) + demandingEnergy = false + } + } + + fun tickEnergyDemanding() { + energyDemander ?: throw IllegalStateException("No energy demander") + + if (!demandingEnergy && energyDemander.missingPower.isPositive) { + demandingEnergy = true + graph.powerDemandingNodes.add(energyDemander) + } else if (demandingEnergy && !energyDemander.missingPower.isPositive) { + demandingEnergy = false + graph.powerDemandingNodes.remove(energyDemander) + } + } + + fun addStorageComponent(component: IStorage<*>) { + if (components.add(component)) + if (isValid && !manualAttaching && !isDetached) + graph.add(component) + } + + fun removeStorageComponent(component: IStorage<*>) { + if (components.remove(component) && isValid) + graph.remove(component) + } + + var isDetached = false + set(value) { + if (field != value) { + field = value + + if (value) { + for (component in components) { + graph.remove(component) + } + } else if (!manualAttaching) { + for (component in components) { + graph.add(component) + } + } + } + } + + override fun invalidate() { + for (component in components) { + graph.remove(component) + } + } + + override fun revive() { + if (!manualAttaching) { + for (component in components) { + graph.add(component) + } + } + } + + fun discover(blockEntity: BlockEntity) { + discover(blockEntity, MatteryCapability.STORAGE_NODE) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt index 02a5b8bb8..bcbeae5b9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/BatteryItem.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.item import net.minecraft.ChatFormatting -import net.minecraft.core.NonNullList import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer @@ -15,43 +14,22 @@ import net.minecraft.world.item.* import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.ForgeCapabilities import net.minecraftforge.common.capabilities.ICapabilityProvider -import ru.dbotthepony.mc.otm.BatteryBalanceValues -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.config.BatteryBalanceValues import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.EnergyCapacitorItem +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.config.ItemsConfig import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.registry.EMPDamageSource +import ru.dbotthepony.mc.otm.registry.MDamageTypes import ru.dbotthepony.mc.otm.runIfClient import kotlin.math.roundToInt open class BatteryItem : Item { - private inner class Power(stack: ItemStack) : EnergyCapacitorItem(stack, this@BatteryItem.capacity, this@BatteryItem.receive, this@BatteryItem.extract, initialBatteryLevel = this@BatteryItem.initialBatteryLevel) { - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (isCreative) return howMuch - return super.extractEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (isCreative) return howMuch - return super.receiveEnergyInner(howMuch, simulate) - } - - override val missingPower: Decimal get() { - return if (isCreative) Decimal.LONG_MAX_VALUE else super.missingPower - } - - fun maxPower() { - batteryLevel = maxBatteryLevel - } - - override var batteryLevel: Decimal - get() { return if (isCreative) Decimal.LONG_MAX_VALUE else super.batteryLevel } - set(value) { super.batteryLevel = value } - } - - private val isCreative: Boolean - val _capacity: () -> Decimal val _receive: () -> Decimal val _extract: () -> Decimal @@ -71,8 +49,7 @@ open class BatteryItem : Item { receive: Decimal, extract: Decimal = receive, initialBatteryLevel: Decimal = Decimal.ZERO - ) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = false + ) : super(Properties().stacksTo(1)) { this._capacity = { storage } this._receive = { receive } this._extract = { extract } @@ -84,46 +61,43 @@ open class BatteryItem : Item { receive: () -> Decimal, extract: () -> Decimal = receive, initialBatteryLevel: () -> Decimal = { Decimal.ZERO } - ) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = false + ) : super(Properties().stacksTo(1)) { this._capacity = storage this._receive = receive this._extract = extract this._initialBatteryLevel = initialBatteryLevel } - constructor(values: BatteryBalanceValues) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = false - this._capacity = values::capacity - this._receive = values::receive - this._extract = values::extract + constructor(values: BatteryBalanceValues) : super(Properties().stacksTo(1)) { + this._capacity = values::energyCapacity + this._receive = values::maxEnergyReceive + this._extract = values::maxEnergyExtract this._initialBatteryLevel = values::initialBatteryLevel } - constructor() : super(Properties().stacksTo(1).rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = true - _capacity = { Decimal.LONG_MAX_VALUE } - _receive = { Decimal.LONG_MAX_VALUE } - _extract = { Decimal.LONG_MAX_VALUE } - _initialBatteryLevel = { Decimal.LONG_MAX_VALUE } + constructor() : super(Properties().stacksTo(1).rarity(Rarity.EPIC)) { + _capacity = { Decimal.POSITIVE_INFINITY } + _receive = { Decimal.POSITIVE_INFINITY } + _extract = { Decimal.POSITIVE_INFINITY } + _initialBatteryLevel = { Decimal.POSITIVE_INFINITY } } override fun isBarVisible(p_150899_: ItemStack): Boolean { - if (isCreative) + if (_capacity.invoke().isInfinite) return false return p_150899_.matteryEnergy != null } override fun getBarWidth(p_150900_: ItemStack): Int { - if (isCreative) + if (_capacity.invoke().isInfinite) return 13 return p_150900_.matteryEnergy?.getBarWidth() ?: super.getBarWidth(p_150900_) } override fun getBarColor(p_150901_: ItemStack): Int { - if (isCreative) + if (_capacity.invoke().isInfinite) return 0 return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_) @@ -136,40 +110,15 @@ open class BatteryItem : Item { p_41424_: TooltipFlag ) { super.appendHoverText(stack, p_41422_, p_41423_, p_41424_) - - if (isCreative) { - p_41423_.add(INFINITE_STORAGE) - p_41423_.add(TranslatableComponent("otm.item.power.infinite.throughput").withStyle(ChatFormatting.GRAY)) - } else { - ItemEnergyStorageImpl.appendHoverText(stack, p_41423_) - } - } - - override fun fillItemCategory(p_41391_: CreativeModeTab, p_41392_: NonNullList) { - super.fillItemCategory(p_41391_, p_41392_) - - if (!isCreative && allowedIn(p_41391_)) { - p_41392_.add(ItemStack(this).also { - it.matteryEnergy?.let { - if (it is Power) { - it.maxPower() - } - } - }) - } + ItemEnergyStorageImpl.appendHoverText(stack, p_41423_) } override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { - return Power(stack) - } - - companion object { - private val INFINITE_STORAGE: Component = - TranslatableComponent("otm.item.power.infinite.storage").withStyle(ChatFormatting.GRAY) + return EnergyCapacitorItem(stack, this@BatteryItem.capacity, this@BatteryItem.receive, this@BatteryItem.extract, initialBatteryLevel = this@BatteryItem.initialBatteryLevel) } } -class CrudeBatteryItem : BatteryItem(ServerConfig.BATTERY_CRUDE) { +class CrudeBatteryItem : BatteryItem(ItemsConfig.Batteries.CRUDE) { override fun appendHoverText( stack: ItemStack, p_41422_: Level?, @@ -178,9 +127,7 @@ class CrudeBatteryItem : BatteryItem(ServerConfig.BATTERY_CRUDE) { ) { super.appendHoverText(stack, p_41422_, p_41423_, p_41424_) - val isAndroid = runIfClient(false) { - return@runIfClient minecraft.player?.matteryPlayer?.isAndroid ?: false - } + val isAndroid = runIfClient(false) { minecraft.player?.matteryPlayer?.isAndroid ?: false } if (isAndroid) { p_41423_.add(TranslatableComponent("otm.gui.crude_battery.replace_in_world").withStyle(ChatFormatting.GRAY)) @@ -224,7 +171,7 @@ class CrudeBatteryItem : BatteryItem(ServerConfig.BATTERY_CRUDE) { mattery.androidEnergy.item = copyStack val extraDamageMult = level.random.nextFloat() - player.hurt(MRegistry.DAMAGE_EMP, 1.5f + extraDamageMult * 3.5f) + player.hurt(EMPDamageSource(inflictor = itemStack), 1.5f + extraDamageMult * 3.5f) val debuffDuration = 100 + (100 * (1f - extraDamageMult)).roundToInt() player.addEffect(MobEffectInstance(MobEffects.BLINDNESS, debuffDuration), player) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt new file mode 100644 index 000000000..641910205 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ChestUpgraderItem.kt @@ -0,0 +1,174 @@ +package ru.dbotthepony.mc.otm.item + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.Container +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult +import net.minecraft.world.damagesource.DamageSource +import net.minecraft.world.entity.vehicle.AbstractMinecart +import net.minecraft.world.entity.vehicle.MinecartChest +import net.minecraft.world.item.* +import net.minecraft.world.item.context.BlockPlaceContext +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.LevelEvent +import net.minecraft.world.level.block.entity.BarrelBlockEntity +import net.minecraft.world.level.block.entity.ChestBlockEntity +import net.minecraft.world.level.gameevent.GameEvent +import net.minecraft.world.level.storage.loot.LootContext +import net.minecraft.world.level.storage.loot.parameters.LootContextParams +import net.minecraftforge.event.entity.player.PlayerInteractEvent +import ru.dbotthepony.mc.otm.OverdriveThatMatters.MOD_ID +import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate +import ru.dbotthepony.mc.otm.registry.MEntityTypes + +class ChestUpgraderItem : Item(Properties().stacksTo(1)) { + override fun onItemUseFirst(stack: ItemStack, context: UseOnContext): InteractionResult { + val player = context.player ?: return super.onItemUseFirst(stack, context) + + val otherHand = if (context.hand == InteractionHand.MAIN_HAND) InteractionHand.OFF_HAND else InteractionHand.MAIN_HAND + val crateStack = player.getItemInHand(otherHand) ?: return super.onItemUseFirst(stack, context) + + val block : Block + if (!crateStack.isEmpty && crateStack.item is BlockItem && (crateStack.item as BlockItem).block is CargoCrateBlock) { + block = (crateStack.item as BlockItem).block + } else { + return super.onItemUseFirst(stack, context) + } + + val pos = context.clickedPos + val hitBlockEntity = context.level.getBlockEntity(pos) + + if (hitBlockEntity is ChestBlockEntity || hitBlockEntity is BarrelBlockEntity) { + val container = hitBlockEntity as Container + + if (container.containerSize > 54) return super.onItemUseFirst(stack, context) + + val blockState = context.level.getBlockState(pos) + val newState = block.getStateForPlacement(BlockPlaceContext(context)) ?: return InteractionResult.FAIL + if (!newState.canSurvive(context.level, pos)) return InteractionResult.FAIL + + context.level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(player, blockState)) + context.level.levelEvent(player, LevelEvent.PARTICLES_DESTROY_BLOCK, pos, Block.getId(blockState)) + + if (context.level is ServerLevel) { + val contents = Int2ObjectArrayMap(container.containerSize) + + for (i in 0 until container.containerSize) + contents.put(i, container[i]) + + container.clearContent() + + val level = context.level as ServerLevel + + val lootparams = LootContext.Builder(level) + .withParameter(LootContextParams.ORIGIN, Vector.atCenterOf(context.clickedPos)) + .withParameter(LootContextParams.TOOL, stack) + .withOptionalParameter(LootContextParams.BLOCK_ENTITY, hitBlockEntity) + .withOptionalParameter(LootContextParams.BLOCK_STATE, blockState) + .withOptionalParameter(LootContextParams.THIS_ENTITY, context.player) + + blockState.spawnAfterBreak(level, pos, stack, true) + blockState.getDrops(lootparams).forEach { Block.popResource(level, pos, it) } + + if (!player.isCreative) + player.getItemInHand(otherHand).shrink(1) + + level.setBlockAndUpdate(pos, newState) + + val newBlockEntity = level.getBlockEntity(pos) + if (newBlockEntity is CargoCrateBlockEntity) { + for (i in 0 until contents.size) { + newBlockEntity.container[i] = contents.get(i) + } + } else { + for (i in 0 until contents.size) { + Block.popResource(level, pos, contents.get(i)) + } + } + } + + context.level.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(player, newState)) + + return InteractionResult.SUCCESS + } + + return super.onItemUseFirst(stack, context) + } + + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltip: MutableList, pFlag: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltip, pFlag) + + pTooltip.add(DESCRIPTION) + pTooltip.add(DESCRIPTION2) + } + + companion object { + private val DESCRIPTION = TranslatableComponent("item.${MOD_ID}.chest_upgrader.desc").withStyle(ChatFormatting.DARK_GRAY) + private val DESCRIPTION2= TranslatableComponent("item.${MOD_ID}.chest_upgrader.desc2").withStyle(ChatFormatting.DARK_GRAY) + + fun onEntityInteract(event: PlayerInteractEvent.EntityInteract) { + if (event.target !is MinecartChest) return + + val offhand = if (event.entity.getItemInHand(InteractionHand.MAIN_HAND).item is ChestUpgraderItem) { + InteractionHand.OFF_HAND + } else if (event.entity.getItemInHand(InteractionHand.OFF_HAND).item is ChestUpgraderItem) { + InteractionHand.MAIN_HAND + } else { + return + } + + val stack = event.entity.getItemInHand(offhand) + if (stack.isEmpty || stack.item !is MinecartCargoCrateItem) return + val color = (stack.item as MinecartCargoCrateItem).color + + val cart = event.target as MinecartChest + if (cart.containerSize > 54) return + + event.level.levelEvent(event.entity, 2001, event.pos, Block.getId(cart.blockStateOn)) + + if (event.level is ServerLevel) { + val level = event.level as ServerLevel + + val pos = cart.position() + val delta = cart.deltaMovement + + val type = MEntityTypes.CARGO_CRATE_MINECARTS[color] ?: return + val newCart = MinecartCargoCrate(type, color, level, pos.x, pos.y, pos.z) + newCart.xRot = cart.xRot + newCart.yRot = cart.yRot + newCart.deltaMovement = delta + newCart.customName = if (stack.hasCustomHoverName()) stack.hoverName else cart.customName + + val contents = Int2ObjectArrayMap(cart.containerSize) + for (i in 0 until cart.containerSize) { + contents.put(i, cart.getItem(i)) + } + cart.clearContent() + + (cart as AbstractMinecart).destroy(DamageSource.GENERIC) + level.addFreshEntity(newCart) + level.gameEvent(GameEvent.ENTITY_PLACE, event.pos, GameEvent.Context.of(event.entity, level.getBlockState(event.pos.below()))) + + if (!event.entity.isCreative) + stack.shrink(1) + + for (i in 0 until contents.size) { + newCart[i] = contents.get(i) + } + + event.cancellationResult = InteractionResult.SUCCESS + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt new file mode 100644 index 000000000..d1416ef0e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceCapsuleItem.kt @@ -0,0 +1,104 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResultHolder +import net.minecraft.world.entity.ExperienceOrb +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.* +import net.minecraft.world.item.alchemy.PotionUtils +import net.minecraft.world.item.alchemy.Potions +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.isShiftDown +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.core.util.getLevelFromXp +import ru.dbotthepony.mc.otm.runIfClient + +class EssenceCapsuleItem(private val digital: Boolean) : Item(Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) { + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList, pIsAdvanced: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced) + pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule").withStyle(ChatFormatting.DARK_GRAY)) + pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule2").withStyle(ChatFormatting.DARK_GRAY)) + + if (!digital) { + pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule3").withStyle(ChatFormatting.DARK_GRAY)) + } else if (runIfClient(false) { minecraft.player?.matteryPlayer?.isAndroid ?: false }) { + pTooltipComponents.add(TranslatableComponent("otm.gui.essence_capsule.digital").withStyle(ChatFormatting.DARK_GRAY)) + } + + if (runIfClient(false) { minecraft.window.isShiftDown }) { + pTooltipComponents.add(TranslatableComponent("otm.gui.experience", experienceStored(pStack)).withStyle(ChatFormatting.GRAY)) + } else { + pTooltipComponents.add(TranslatableComponent("otm.gui.experience_levels", getLevelFromXp(experienceStored(pStack))).withStyle(ChatFormatting.GRAY)) + } + } + + override fun getUseAnimation(itemStack: ItemStack): UseAnim { + return UseAnim.BOW + } + + override fun getUseDuration(itemStack: ItemStack): Int { + return if (digital) 20 else 30 + } + + override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder { + if (!digital || player.matteryPlayer?.isAndroid == true) { + player.startUsingItem(hand) + + return InteractionResultHolder.consume(player.getItemInHand(hand)) + } + + return super.use(level, player, hand) + } + + override fun finishUsingItem(itemStack: ItemStack, level: Level, player: LivingEntity): ItemStack { + if (player !is Player) return super.finishUsingItem(itemStack, level, player) + + val exp = experienceStored(itemStack) / itemStack.count + if (digital) { + val mattery = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player) + if (!mattery.isAndroid) return super.finishUsingItem(itemStack, level, player) + + if (player is ServerPlayer) { + player.giveExperiencePoints(exp.toInt()) + } + } else { + if (level is ServerLevel) { + level.levelEvent(2002, player.blockPosition(), PotionUtils.getColor(Potions.WATER)) + ExperienceOrb.award(level, player.position(), (exp * (.5 + level.random.nextFloat() * .25)).toInt()) + } + } + + if (!level.isClientSide && !player.abilities.instabuild) { + itemStack.shrink(1) + } + + return itemStack + } + + fun make(experience: Long): ItemStack { + return ItemStack(this, 1).also { + setExperience(it, experience) + } + } + + companion object { + @JvmStatic + fun setExperience(itemStack: ItemStack, experience: Long) { + itemStack.tagNotNull["experience"] = experience + } + + @JvmStatic + fun experienceStored(itemStack: ItemStack): Long { + return (itemStack.tag?.getLong("experience") ?: 0L) * itemStack.count + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt new file mode 100644 index 000000000..f6417fdc8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/EssenceServoItem.kt @@ -0,0 +1,80 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.ChatFormatting +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.InteractionResult +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.core.TranslatableComponent + +class EssenceServoItem : Item(Properties().stacksTo(64)) { + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList, pIsAdvanced: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced) + pTooltipComponents.add(TranslatableComponent("$descriptionId.desc2").withStyle(ChatFormatting.GRAY)) + pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY)) + } + + fun useServo(player: Player, pos: BlockPos): InteractionResult { + val block = player.level.getBlockEntity(pos) ?: return InteractionResult.FAIL + + // TODO: опыт как жидкость + if (block is EssenceStorageBlockEntity) { + if (player.level.isClientSide) return InteractionResult.SUCCESS + if (player !is ServerPlayer) return InteractionResult.FAIL + + if (player.isCrouching) { // выгружаем в блок + storeLevel(player, block) + } else { // выгружаем из блока + dispenseLevel(player, block) + } + + return InteractionResult.SUCCESS + } + + return InteractionResult.FAIL + } + + override fun useOn(pContext: UseOnContext): InteractionResult { + val player = pContext.player ?: return InteractionResult.FAIL + return useServo(player, pContext.clickedPos) + } + + companion object { + @JvmStatic + fun storeLevel(player: ServerPlayer, block: EssenceStorageBlockEntity) { + val fpoints = (player.experienceProgress * player.xpNeededForNextLevel).toInt() + + if (fpoints > 0) { + block.experienceStored += fpoints + player.setExperiencePoints(0) + } else if (player.experienceLevel > 0) { + player.setExperienceLevels(player.experienceLevel - 1) + block.experienceStored += player.xpNeededForNextLevel + player.setExperiencePoints(0) // для надёжности + } + } + + @JvmStatic + fun dispenseLevel(player: ServerPlayer, block: EssenceStorageBlockEntity) { + val fpoints = (player.experienceProgress * player.xpNeededForNextLevel).toInt() + val missingPoints = player.xpNeededForNextLevel - fpoints + val diff = block.experienceStored.coerceAtMost(missingPoints.toLong()).toInt() + + if (diff == missingPoints) { + player.setExperienceLevels(player.experienceLevel + 1) + player.setExperiencePoints(0) + } else { + player.setExperiencePoints(fpoints + diff) + } + + block.experienceStored -= diff + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt new file mode 100644 index 000000000..1494165ae --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidCapsuleItem.kt @@ -0,0 +1,305 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.server.level.ServerLevel +import net.minecraft.sounds.SoundEvent +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResult +import net.minecraft.world.InteractionResultHolder +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.ClipContext +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.LayeredCauldronBlock +import net.minecraft.world.level.block.LiquidBlockContainer +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.material.Fluid +import net.minecraft.world.level.material.Fluids +import net.minecraft.world.phys.HitResult +import net.minecraftforge.common.SoundActions +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.FluidUtil +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.capability.fluid.ItemMatteryFluidHandler +import ru.dbotthepony.mc.otm.capability.fluidLevel +import ru.dbotthepony.mc.otm.capability.fluid.iterator +import ru.dbotthepony.mc.otm.capability.moveFluid +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.collect.any +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.orNull +import java.util.function.IntSupplier + +class FluidCapsuleItem(val capacity: IntSupplier) : Item(Properties().stacksTo(64)) { + // TODO: Так как использование предмета заблокировано за player.abilities.canBuild + // капсулу нельзя использовать в режиме приключения + // почему же можно использовать вёдра на котлах? + // ИБО КОТЛЫ ЗАХАРДКОЖЕНЫ НА ВЗАИМОДЕЙСТВИЕ С ВЁДРАМИ + override fun onItemUseFirst(stack: ItemStack, pContext: UseOnContext): InteractionResult { + val context = Context(pContext.clickedPos, pContext.level.getBlockState(pContext.clickedPos), pContext.clickedFace) + + if (canInteract(pContext.itemInHand, pContext.player ?: return InteractionResult.FAIL, context)) + return interact(pContext.itemInHand, pContext.player!!, context) + + return super.onItemUseFirst(stack, pContext) + } + + override fun getName(pStack: ItemStack): Component { + pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.getFluidInTank(0).also { + if (!it.isEmpty) { + return TranslatableComponent("$descriptionId.named", it.displayName) + } + } + } + + return super.getName(pStack) + } + + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList, pIsAdvanced: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced) + + pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.fluidLevel(pTooltipComponents) + } + } + + override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { + return ItemMatteryFluidHandler(stack, capacity) + } + + override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder { + val item = player.getItemInHand(hand) + var targetItem = item.copyWithCount(1) + val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResultHolder.fail(item) + val fluid = cap.getFluidInTank(0) + if (fluid.isNotEmpty && fluid.amount != 1000) return InteractionResultHolder.pass(item) + + val hitResult = getPlayerPOVHitResult(level, player, if (fluid.isEmpty) ClipContext.Fluid.SOURCE_ONLY else ClipContext.Fluid.NONE) + if (hitResult.type != HitResult.Type.BLOCK) return InteractionResultHolder.pass(item) + + val hitPos = hitResult.blockPos + val hitDir = hitResult.direction + val nextPos = hitPos.relative(hitDir) + + if (level.mayInteract(player, hitPos) && player.mayUseItemAt(nextPos, hitDir, item)) { + targetItem = if (fluid.isEmpty) { + val actionResult = FluidUtil.tryPickUpFluid(item, player, level, hitPos, hitDir) + if (!actionResult.isSuccess) return InteractionResultHolder.pass(item) + + actionResult.result + } else { + val state = level.getBlockState(hitPos) + val placePos = if (state.block is LiquidBlockContainer && (state.block as LiquidBlockContainer).canPlaceLiquid(level, hitPos, state, fluid.fluid)) hitPos else nextPos + + val actionResult = FluidUtil.tryPlaceFluid(player, level, hand, placePos, targetItem, fluid) + if (!actionResult.isSuccess) return InteractionResultHolder.pass(item) + + actionResult.result + } + + if (!player.abilities.instabuild && !player.level.isClientSide) { + item.shrink(1) + + if (item.isEmpty) return InteractionResultHolder.sidedSuccess(targetItem, player.level.isClientSide) + if (!player.inventory.add(targetItem)) { + player.spawnAtLocation(targetItem) + } + } + + return InteractionResultHolder.sidedSuccess(item, player.level.isClientSide) + } + + return InteractionResultHolder.pass(item) + } + + interface Interaction { + fun canInteract(item: ItemStack, player: Player, context: Context): Boolean + fun interact(item: ItemStack, player: Player, context: Context): InteractionResult + } + + private object FillCauldron : Interaction { + private data class Mapping(val fluid: Fluid, val blockState: BlockState, val soundEvent: SoundEvent) + + private val mapping = immutableList { + accept(Mapping(Fluids.WATER, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY)) + accept(Mapping(Fluids.LAVA, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA)) + } + + override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean { + return player.level.getBlockState(context.blockPos).`is`(Blocks.CAULDRON) + } + + override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult { + if (item.isEmpty || !context.blockState.`is`(Blocks.CAULDRON)) return InteractionResult.FAIL + + val targetItem = if (item.count == 1) item else item.copyWithCount(1) + + val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL + + for ((fluid, newState, soundEvent) in mapping) { + val toDrain = FluidStack(fluid, 1000) + val drained = cap.drain(toDrain, IFluidHandler.FluidAction.SIMULATE) + + if (!drained.isEmpty && drained.amount == 1000) { + player.level.playSound(player, context.blockPos, soundEvent, SoundSource.BLOCKS) + val level = player.level as? ServerLevel ?: return InteractionResult.SUCCESS + level.setBlock(context.blockPos, newState, Block.UPDATE_ALL) + cap.drain(toDrain, IFluidHandler.FluidAction.EXECUTE) + + if (item.count != 1 && !player.level.isClientSide) { + item.count-- + + if (!player.inventory.add(targetItem)) { + player.spawnAtLocation(targetItem) + } + } + + return InteractionResult.CONSUME + } + } + + return InteractionResult.FAIL + } + } + + private object EmptyCauldron : Interaction { + private val mapping = immutableMap { + put(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), Fluids.WATER to SoundEvents.BUCKET_FILL) + put(Blocks.LAVA_CAULDRON.defaultBlockState(), Fluids.LAVA to SoundEvents.BUCKET_FILL_LAVA) + } + + override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean { + return player.level.getBlockState(context.blockPos) in mapping + } + + override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult { + if (item.isEmpty) return InteractionResult.FAIL + + val targetItem = if (item.count == 1) item else item.copyWithCount(1) + + val cap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL + val mapped = mapping[context.blockState] ?: return InteractionResult.FAIL + + val toFill = FluidStack(mapped.first, 1000) + + val fill = cap.fill(toFill, IFluidHandler.FluidAction.SIMULATE) + if (fill != 1000) return InteractionResult.FAIL + + player.level.playSound(player, context.blockPos, mapped.second, SoundSource.BLOCKS) + + val level = player.level as? ServerLevel ?: return InteractionResult.SUCCESS + level.setBlock(context.blockPos, Blocks.CAULDRON.defaultBlockState(), Block.UPDATE_ALL) + cap.fill(toFill, IFluidHandler.FluidAction.EXECUTE) + + if (item.count != 1 && !player.level.isClientSide) { + item.count-- + + if (!player.inventory.add(targetItem)) { + player.spawnAtLocation(targetItem) + } + } + + return InteractionResult.sidedSuccess(player.level.isClientSide) + } + } + + private object FillEmptyCapability : Interaction { + override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean { + val target = player.level.getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return false + val cap = item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return false + return target.iterator().any { !it.isEmpty } || cap.iterator().any { !it.isEmpty } + } + + override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult { + if (item.isEmpty) return InteractionResult.FAIL + + val targetItem = if (item.count == 1) item else item.copyWithCount(1) + + val itemCap = targetItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).orNull() ?: return InteractionResult.FAIL + val blockCap = player.level.getBlockEntity(context.blockPos)?.getCapability(ForgeCapabilities.FLUID_HANDLER, context.side)?.orNull() ?: return InteractionResult.FAIL + val fluid = itemCap[0] + + if (fluid.isEmpty) { + // заполняем из блока + val moveResult = moveFluid(source = blockCap, destination = itemCap, actuallyDrain = !player.level.isClientSide, actuallyFill = !player.level.isClientSide) + + if (!moveResult.isEmpty) { + val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_FILL) + + if (sound != null) { + player.level.playSound(player, context.blockPos, sound, SoundSource.BLOCKS) + } + + if (item.count != 1 && !player.level.isClientSide) { + item.count-- + + if (!player.inventory.add(targetItem)) { + player.spawnAtLocation(targetItem) + } + } + + return InteractionResult.sidedSuccess(player.level.isClientSide) + } else { + return InteractionResult.FAIL + } + } else { + val moveResult = moveFluid(source = itemCap, destination = blockCap, actuallyDrain = !player.level.isClientSide, actuallyFill = !player.level.isClientSide) + + if (!moveResult.isEmpty) { + val sound = moveResult.fluid.fluidType.getSound(moveResult, SoundActions.BUCKET_EMPTY) + + if (sound != null) { + player.level.playSound(player, context.blockPos, sound, SoundSource.BLOCKS) + } + + if (item.count != 1 && !player.level.isClientSide) { + item.count-- + + if (!player.inventory.add(targetItem)) { + player.spawnAtLocation(targetItem) + } + } + + return InteractionResult.sidedSuccess(player.level.isClientSide) + } else { + return InteractionResult.FAIL + } + } + } + } + + data class Context(val blockPos: BlockPos, val blockState: BlockState, val side: Direction) + + companion object : Interaction { + val interactions = immutableList { + accept(FillCauldron) + accept(EmptyCauldron) + // accept(FillEmptyCapability) // блоки из модов это сами контролируют + } + + override fun canInteract(item: ItemStack, player: Player, context: Context): Boolean { + return interactions.any { it.canInteract(item, player, context) } + } + + override fun interact(item: ItemStack, player: Player, context: Context): InteractionResult { + return interactions.firstOrNull { it.canInteract(item, player, context) }?.interact(item, player, context) ?: InteractionResult.PASS + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt new file mode 100644 index 000000000..911e44120 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/FluidTankItem.kt @@ -0,0 +1,71 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.InteractionResult +import net.minecraft.world.item.BlockItem +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.context.UseOnContext +import net.minecraft.world.level.Level +import net.minecraftforge.client.extensions.common.IClientItemExtensions +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.capabilities.ICapabilityProvider +import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock +import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity +import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler +import ru.dbotthepony.mc.otm.capability.fluidLevel +import ru.dbotthepony.mc.otm.client.render.blockentity.FluidTankRenderer +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.ifPresentK +import java.util.function.Consumer +import java.util.function.IntSupplier + +class FluidTankItem(block: FluidTankBlock, properties: Properties, val capacity: IntSupplier) : BlockItem(block, properties) { + override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { + return BlockMatteryFluidHandler.Item(stack, capacity, FluidTankBlockEntity.FLUID_KEY) + } + + override fun onItemUseFirst(stack: ItemStack, pContext: UseOnContext): InteractionResult { + if (pContext.player?.isCrouching == true) + return InteractionResult.PASS + + val context = FluidCapsuleItem.Context(pContext.clickedPos, pContext.level.getBlockState(pContext.clickedPos), pContext.clickedFace) + + if (FluidCapsuleItem.canInteract(pContext.itemInHand, pContext.player ?: return InteractionResult.FAIL, context)) + return FluidCapsuleItem.interact(pContext.itemInHand, pContext.player!!, context) + + return super.onItemUseFirst(stack, pContext) + } + + override fun getName(pStack: ItemStack): Component { + pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.getFluidInTank(0).also { + if (!it.isEmpty) { + return TranslatableComponent("$descriptionId.named", it.displayName) + } + } + } + + return super.getName(pStack) + } + + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltip: MutableList, pFlag: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltip, pFlag) + + pStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.fluidLevel(pTooltip) + } + } + + override fun initializeClient(consumer: Consumer) { + super.initializeClient(consumer) + + consumer.accept(object : IClientItemExtensions { + override fun getCustomRenderer(): BlockEntityWithoutLevelRenderer { + return FluidTankRenderer.FluidTankItemRenderer + } + }) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt index df6c8e937..4839d319f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/GravitationalDisruptorItem.kt @@ -11,7 +11,7 @@ import net.minecraft.world.level.Level import ru.dbotthepony.mc.otm.core.TranslatableComponent class GravitationalDisruptorItem : - Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(Rarity.EPIC)) { + Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) { override fun appendHoverText( p_41421_: ItemStack, p_41422_: Level?, @@ -51,4 +51,4 @@ class GravitationalDisruptorItem : ) ).withStyle(ChatFormatting.DARK_RED) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/IQuantumLinked.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/IQuantumLinked.kt new file mode 100644 index 000000000..1049bb407 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/IQuantumLinked.kt @@ -0,0 +1,7 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.world.item.ItemStack + +interface IQuantumLinked { + fun merge(from: ItemStack, into: ItemStack): ItemStack +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt deleted file mode 100644 index 73bf251da..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterDustItem.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ru.dbotthepony.mc.otm.item - -import net.minecraft.ChatFormatting -import net.minecraft.network.chat.Component -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.TooltipFlag -import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.matter.IMatterItem -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.matter.IMatterValue -import ru.dbotthepony.mc.otm.matter.MatterValue - -class MatterDustItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(64)), IMatterItem { - private fun matter(stack: ItemStack): Decimal { - return stack.tag?.get("matter")?.let { return@let Decimal.deserializeNBT(it) } ?: return Decimal.ZERO - } - - private fun matter(stack: ItemStack, matter: Decimal) { - stack.orCreateTag["matter"] = matter.serializeNBT() - } - - override fun getMatterValue(stack: ItemStack): IMatterValue? { - val value = stack.tag?.get("matter") ?: return null - return MatterValue(Decimal.deserializeNBT(value), 1.0) - } - - override fun canDecompose(stack: ItemStack) = false - - override fun appendHoverText( - p_41421_: ItemStack, - p_41422_: Level?, - p_41423_: MutableList, - p_41424_: TooltipFlag - ) { - super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) - val matter = this.getMatterValue(p_41421_) - - if (matter == null) { - p_41423_.add(DESC) - } - - p_41423_.add(DESC2) - - if (matter != null) { - p_41423_.add(DESC3) - } - } - - fun addMatterValue(stack: ItemStack, matter: Decimal, simulate: Boolean): Decimal { - if (stack.count != 1) - return Decimal.ZERO - - val matterThis = matter(stack) - - if (matterThis >= ServerConfig.MATTER_DUST_CAPACITY) - return Decimal.ZERO - - val newMatter = (matterThis + matter).coerceAtMost(ServerConfig.MATTER_DUST_CAPACITY) - val diff = newMatter - matterThis - - if (!simulate) - matter(stack, newMatter) - - return diff - } - - fun isFull(stack: ItemStack): Boolean { - return matter(stack) >= ServerConfig.MATTER_DUST_CAPACITY - } - - companion object { - private val DESC = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc").withStyle(ChatFormatting.DARK_GRAY) - private val DESC2 = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc2").withStyle(ChatFormatting.GRAY) - private val DESC3 = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc3").withStyle(ChatFormatting.DARK_GRAY) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MinecartCargoCrateItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MinecartCargoCrateItem.kt index f12f2f3e5..25b520997 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MinecartCargoCrateItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/MinecartCargoCrateItem.kt @@ -9,19 +9,16 @@ import net.minecraft.world.InteractionResult import net.minecraft.world.item.DyeColor import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.MinecartItem import net.minecraft.world.item.context.UseOnContext import net.minecraft.world.level.Level import net.minecraft.world.level.block.BaseRailBlock import net.minecraft.world.level.block.DispenserBlock import net.minecraft.world.level.block.state.properties.RailShape import net.minecraft.world.level.gameevent.GameEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate import ru.dbotthepony.mc.otm.registry.MEntityTypes -import kotlin.math.floor -class MinecartCargoCrateItem(val color: DyeColor?) : Item(Properties().stacksTo(16).tab(if (color == null) OverdriveThatMatters.INSTANCE.CREATIVE_TAB else OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE)) { +class MinecartCargoCrateItem(val color: DyeColor?) : Item(Properties().stacksTo(16)) { init { DispenserBlock.registerBehavior(this, Companion) } @@ -69,9 +66,9 @@ class MinecartCargoCrateItem(val color: DyeColor?) : Item(Properties().stacksTo( override fun dispense(blockSource: BlockSource, itemStack: ItemStack): ItemStack { val direction = blockSource.blockState.getValue(DispenserBlock.FACING) val level: Level = blockSource.level - val x = blockSource.x() + direction.stepX.toDouble() * 1.125 - val y = floor(blockSource.y()) + direction.stepY.toDouble() - val z = blockSource.z() + direction.stepZ.toDouble() * 1.125 + val x = blockSource.pos.x + direction.stepX.toDouble() * 1.125 + val y = blockSource.pos.y + direction.stepY.toDouble() + val z = blockSource.pos.z + direction.stepZ.toDouble() * 1.125 val blockpos = blockSource.pos.relative(direction) val blockstate = level.getBlockState(blockpos) val railshape = diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt index ac7026454..3fc956300 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PillItem.kt @@ -11,8 +11,6 @@ import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player import net.minecraft.world.item.* import net.minecraft.world.level.Level -import net.minecraftforge.common.util.FakePlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.capability.MatteryCapability @@ -20,7 +18,7 @@ enum class PillType { BECOME_ANDROID, BECOME_HUMANE, OBLIVION } -class HealPillItem : Item(Properties().stacksTo(64).rarity(Rarity.UNCOMMON).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { +class HealPillItem : Item(Properties().stacksTo(64).rarity(Rarity.UNCOMMON)) { override fun getUseDuration(p_41454_: ItemStack): Int { return 24 } @@ -65,7 +63,7 @@ class HealPillItem : Item(Properties().stacksTo(64).rarity(Rarity.UNCOMMON).tab( } class PillItem(val pillType: PillType) : - Item(Properties().stacksTo(64).rarity(Rarity.UNCOMMON).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { + Item(Properties().stacksTo(64).rarity(Rarity.UNCOMMON)) { override fun getUseDuration(p_41454_: ItemStack): Int { return 32 @@ -92,9 +90,6 @@ class PillItem(val pillType: PillType) : } override fun use(level: Level, ply: Player, hand: InteractionHand): InteractionResultHolder { - if (ply is FakePlayer) - return super.use(level, ply, hand) - val resolver = ply.getCapability(MatteryCapability.MATTERY_PLAYER).resolve() if (resolver.isEmpty) @@ -115,9 +110,6 @@ class PillItem(val pillType: PillType) : } override fun finishUsingItem(stack: ItemStack, level: Level, ent: LivingEntity): ItemStack { - if (ent is FakePlayer) - super.finishUsingItem(stack, level, ent) - if (ent is Player) { val resolver = ent.getCapability(MatteryCapability.MATTERY_PLAYER).resolve() @@ -168,4 +160,4 @@ class PillItem(val pillType: PillType) : } override fun getUseAnimation(p_41452_: ItemStack): UseAnim = UseAnim.EAT -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt index ac493a33c..7de8df6ae 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt @@ -1,7 +1,5 @@ package ru.dbotthepony.mc.otm.item -import ru.dbotthepony.mc.otm.capability.drive.DrivePool.isLegalAccess -import ru.dbotthepony.mc.otm.OverdriveThatMatters import net.minecraft.world.item.ItemStack import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.LazyOptional @@ -18,25 +16,26 @@ import net.minecraft.world.item.Item import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.event.ForgeEventFactory -import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.event.entity.player.EntityItemPickupEvent -import net.minecraftforge.eventbus.api.EventPriority import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.container.ItemFilter -import ru.dbotthepony.mc.otm.core.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.isServerThread import java.math.BigInteger import java.util.* class PortableCondensationDriveItem(capacity: Int) : - Item(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { + Item(Properties().stacksTo(1)) { val capacity: BigInteger = capacity.toBigInteger() private inner class DriveCapability(private val stack: ItemStack) : ICapabilityProvider { private var uuid: UUID? = null private val resolver = LazyOptional.of> { - if (!isLegalAccess()) { + if (!isServerThread()) { return@of ItemMatteryDrive.DUMMY } @@ -89,9 +88,11 @@ class PortableCondensationDriveItem(capacity: Int) : } fun getFilterSettings(drive: ItemStack): ItemFilter { - val filter = ItemFilter(MAX_FILTERS) + var ignore = true + val filter = ItemFilter(MAX_FILTERS) { if (!ignore) drive.tagNotNull[FILTER_PATH] = it.serializeNBT() } filter.isWhitelist = true - drive.tag?.ifHas(FILTER_PATH, CompoundTag::class.java, filter::deserializeNBT) + drive.tag?.map(FILTER_PATH, filter::deserializeNBT) + ignore = false return filter } @@ -154,4 +155,4 @@ class PortableCondensationDriveItem(capacity: Int) : } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt new file mode 100644 index 000000000..0932bc8d4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralBatteryItem.kt @@ -0,0 +1,141 @@ +package ru.dbotthepony.mc.otm.item + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.ChatFormatting +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import net.minecraft.world.level.storage.loot.LootContext +import net.minecraft.world.level.storage.loot.functions.LootItemFunction +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType +import net.minecraftforge.common.capabilities.ICapabilityProvider +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.energy.batteryLevel +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth +import ru.dbotthepony.mc.otm.capability.matteryEnergy +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.set +import ru.dbotthepony.mc.otm.core.nbt.mapPresent +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.data.Codec2Serializer +import ru.dbotthepony.mc.otm.data.DecimalProvider +import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes +import java.util.Optional + +class ProceduralBatteryItem : Item(Properties().stacksTo(1)) { + class Cap(stack: ItemStack) : ItemEnergyStorageImpl(stack) { + override var maxInput: Decimal + get() = itemStack.tag?.mapPresent("maxInput", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO + set(value) { itemStack.tagNotNull["maxInput"] = value } + + override var maxOutput: Decimal + get() = itemStack.tag?.mapPresent("maxOutput", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO + set(value) { itemStack.tagNotNull["maxOutput"] = value } + + override var maxBatteryLevel: Decimal + get() = itemStack.tag?.mapPresent("maxBatteryLevel", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO + set(value) { itemStack.tagNotNull["maxBatteryLevel"] = value } + + override val energyFlow: FlowDirection + get() = FlowDirection.BI_DIRECTIONAL + + override val initialBatteryLevel: Decimal + get() = maxBatteryLevel + } + + override fun isBarVisible(p_150899_: ItemStack): Boolean { + return p_150899_.matteryEnergy != null + } + + override fun getBarWidth(p_150900_: ItemStack): Int { + return p_150900_.matteryEnergy?.getBarWidth() ?: super.getBarWidth(p_150900_) + } + + override fun getBarColor(p_150901_: ItemStack): Int { + return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_) + } + + override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, p_41423_: MutableList, p_41424_: TooltipFlag) { + super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) + + val energy = p_41421_.matteryEnergy + + if (energy is Cap) { + if (energy.maxInput.isZero && energy.maxOutput.isZero && energy.maxBatteryLevel.isZero) { + p_41423_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY)) + } else { + batteryLevel(energy, p_41423_) + } + } + } + + data class Randomizer( + val maxBatteryLevel: DecimalProvider, + val batteryLevel: Optional, + val maxInput: DecimalProvider, + val maxOutput: Optional + ) : LootItemFunction, LootItemFunction.Builder { + constructor( + maxBatteryLevel: DecimalProvider, + batteryLevel: DecimalProvider? = null, + maxInput: DecimalProvider, + maxOutput: DecimalProvider? = null + ) : this(maxBatteryLevel, Optional.ofNullable(batteryLevel), maxInput, Optional.ofNullable(maxOutput)) + + override fun apply(t: ItemStack, u: LootContext): ItemStack { + val data = Cap(t) + + if (maxOutput.isPresent) { + data.maxInput = maxInput.sample(u.random) + data.maxOutput = maxOutput.get().sample(u.random) + } else { + data.maxInput = maxInput.sample(u.random) + data.maxOutput = data.maxInput + } + + data.maxBatteryLevel = maxBatteryLevel.sample(u.random) + + if (batteryLevel.isPresent) { + data.batteryLevel = batteryLevel.get().sample(u.random).coerceAtMost(data.maxBatteryLevel) + } else { + data.batteryLevel = data.maxBatteryLevel + } + + return t + } + + override fun getType(): LootItemFunctionType { + return MItemFunctionTypes.PROCEDURAL_BATTERY + } + + override fun build(): LootItemFunction { + return this + } + + companion object { + val CODEC by lazy { + Codec2Serializer( + RecordCodecBuilder.create { + it.group( + DecimalProvider.CODEC.fieldOf("maxBatteryLevel").forGetter(Randomizer::maxBatteryLevel), + DecimalProvider.CODEC.optionalFieldOf("batteryLevel").forGetter(Randomizer::batteryLevel), + DecimalProvider.CODEC.fieldOf("maxInput").forGetter(Randomizer::maxInput), + DecimalProvider.CODEC.optionalFieldOf("maxOutput").forGetter(Randomizer::maxOutput), + ).apply(it, ::Randomizer) + } + ) + } + } + } + + override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { + return Cap(stack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralExoPackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralExoPackSlotUpgradeItem.kt deleted file mode 100644 index b7c645f20..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ProceduralExoPackSlotUpgradeItem.kt +++ /dev/null @@ -1,83 +0,0 @@ -package ru.dbotthepony.mc.otm.item - -import net.minecraft.ChatFormatting -import net.minecraft.network.chat.Component -import net.minecraft.util.RandomSource -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Rarity -import net.minecraft.world.item.TooltipFlag -import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.tagNotNull -import ru.dbotthepony.mc.otm.data.loot.IRandomizableItem -import java.util.* - -@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // .tab(null) is a legal statement because tab field itself is nullable -class ProceduralExoPackSlotUpgradeItem : AbstractExoPackSlotUpgradeItem(defaultProperties().tab(null)), - IRandomizableItem { - override fun getRarity(itemStack: ItemStack): Rarity { - return when (slotCount(itemStack)) { - in 0 .. 9 -> Rarity.COMMON - in 10 .. 18 -> Rarity.UNCOMMON - in 19 .. 27 -> Rarity.RARE - in 28 .. 36 -> Rarity.EPIC - else -> Rarity.EPIC - } - } - - override fun hasDescription(itemStack: ItemStack): Boolean { - return false - } - - override fun slotCount(itemStack: ItemStack): Int { - return itemStack.tag?.getInt(SLOT_COUNT_KEY) ?: 0 - } - - override fun uuid(itemStack: ItemStack): UUID? { - return null - } - - override fun appendHoverText( - p_41421_: ItemStack, - p_41422_: Level?, - tooltip: MutableList, - p_41424_: TooltipFlag - ) { - super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) - - if (p_41421_.tag?.get(SLOT_COUNT_KEY) == null) { - tooltip.add(TranslatableComponent("$descriptionId.description").withStyle(ChatFormatting.GRAY)) - } - } - - override fun randomize(itemStack: ItemStack, random: RandomSource, ply: Player?, rarity: Rarity) { - var amount = random.nextIntBetweenInclusive(6, 9) - val luck = (ply?.luck ?: 0f) * 0.3f - val finalLuck = (0.5f - (luck / 512f).coerceAtLeast(-0.3f).coerceAtMost(0.3f)) - - if (random.nextFloat() >= finalLuck && rarity.ordinal >= Rarity.UNCOMMON.ordinal) { - amount += random.nextIntBetweenInclusive(4, 9) - - if (random.nextFloat() >= finalLuck && rarity.ordinal >= Rarity.RARE.ordinal) { - amount += random.nextIntBetweenInclusive(6, 9) - - if (random.nextFloat() >= finalLuck && rarity.ordinal >= Rarity.EPIC.ordinal) { - amount += random.nextIntBetweenInclusive(8, 12) - - if (random.nextFloat() >= finalLuck) { - amount += random.nextIntBetweenInclusive(14, 18) - } - } - } - } - - itemStack.tagNotNull[SLOT_COUNT_KEY] = amount - } - - companion object { - const val SLOT_COUNT_KEY = "slotCount" - const val UUID_KEY = "uuid" - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt index bc2fbf15d..ab27241bd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt @@ -1,18 +1,19 @@ package ru.dbotthepony.mc.otm.item -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import it.unimi.dsi.fastutil.ints.IntAVLTreeSet -import it.unimi.dsi.fastutil.ints.IntOpenHashSet +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import net.minecraft.ChatFormatting import net.minecraft.core.Direction -import net.minecraft.nbt.ByteArrayTag import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component -import net.minecraft.world.item.* +import net.minecraft.util.datafix.DataFixTypes +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Rarity +import net.minecraft.world.item.TooltipFlag import net.minecraft.world.level.Level +import net.minecraft.world.level.saveddata.SavedData import net.minecraftforge.client.event.ClientPlayerNetworkEvent import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities @@ -20,317 +21,274 @@ import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.event.TickEvent import net.minecraftforge.event.TickEvent.ServerTickEvent -import net.minecraftforge.network.NetworkEvent import net.minecraftforge.registries.ForgeRegistries -import net.minecraftforge.registries.ForgeRegistry -import ru.dbotthepony.mc.otm.* -import ru.dbotthepony.mc.otm.capability.* -import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper -import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.trackedItems +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth +import ru.dbotthepony.mc.otm.capability.matteryEnergy +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.getID +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.math.readDecimal +import ru.dbotthepony.mc.otm.core.math.set +import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.nbt.getUUIDSafe +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.orThrow +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.isClientThread +import ru.dbotthepony.mc.otm.isServerThread +import ru.dbotthepony.mc.otm.lazyPerServer import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.packetHandled -import ru.dbotthepony.mc.otm.saveddata.SavedCountingMap +import java.util.* +import java.util.function.Function import java.util.function.Supplier +import kotlin.collections.MutableList +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.forEach +import kotlin.collections.iterator +import kotlin.collections.set -class QuantumBatteryItem : Item { - class Data( - val parent: SavedCountingMap?, - val index: Int = -1, - energy: Decimal = Decimal.ZERO, - passed: Decimal = Decimal.ZERO, - received: Decimal = Decimal.ZERO, - ) { - constructor( - energy: Decimal = Decimal.ZERO, - passed: Decimal = Decimal.ZERO, - received: Decimal = Decimal.ZERO, - ) : this(null, -1, energy, passed, received) +class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanceValues?) : Item(Properties().stacksTo(1).rarity(if (balanceValues == null) Rarity.EPIC else Rarity.UNCOMMON)), IQuantumLinked { + val isCreative = balanceValues == null - var energy: Decimal = energy - set(value) { - if (field != value) { + interface IValues { + val uuid: UUID + var energy: Decimal + var passed: Decimal + var received: Decimal + val isServer: Boolean + + fun serialize(): CompoundTag { + return CompoundTag().also { + it["energy"] = energy + it["passed"] = passed + it["received"] = received + } + } + + fun deserialize(nbt: CompoundTag) { + energy = nbt.getDecimal("energy") + passed = nbt.getDecimal("passed") + received = nbt.getDecimal("received") + } + } + + class UnboundValues(override val uuid: UUID = UUID.randomUUID()) : IValues { + override var energy: Decimal = Decimal.ZERO + override var passed: Decimal = Decimal.ZERO + override var received: Decimal = Decimal.ZERO + override val isServer: Boolean + get() = false + } + + class Data() : SavedData() { + constructor(nbt: CompoundTag) : this() { + load(nbt) + } + + inner class Values(override val uuid: UUID) : IValues { + override var energy = Decimal.ZERO + set(value) { field = value - parent?.isDirty = true + isDirty = true } + + override var passed = Decimal.ZERO + set(value) { + field = value + isDirty = true + } + + override var received = Decimal.ZERO + set(value) { + field = value + isDirty = true + } + + override val isServer: Boolean + get() = true + } + + private val data = Object2ObjectOpenHashMap() + + fun values(uuid: UUID): Values { + return data.computeIfAbsent(uuid, Function { Values(uuid) }) + } + + fun values(): Values { + return values(UUID.randomUUID()) + } + + override fun save(nbt: CompoundTag): CompoundTag { + for ((key, values) in data) { + nbt[key.toString()] = values.serialize() } - var passed: Decimal = passed - set(value) { - if (field != value) { - field = value - parent?.isDirty = true - } - } + return nbt + } - var received: Decimal = received - set(value) { - if (field != value) { - field = value - parent?.isDirty = true - } + fun load(nbt: CompoundTag) { + data.clear() + + for (key in nbt.allKeys) { + val id = UUID.fromString(key) + data[id] = Values(id).also { it.deserialize(nbt.getCompound(key)) } } + } + } + + val clientData = Object2ObjectOpenHashMap() + + val serverData: Data by lazyPerServer { + it.overworld().dataStorage.computeIfAbsent(::Data, ::Data, "otm_$savedataID") } private inner class Power(private val stack: ItemStack) : IMatteryEnergyStorage, ICapabilityProvider { private val resolver = LazyOptional.of { this } - private val resolverMekanism = if (isMekanismLoaded) LazyOptional.of { Mattery2MekanismEnergyWrapper(this) } else null - var data = Data() + override val energyFlow: FlowDirection + get() = FlowDirection.BI_DIRECTIONAL + + var values: IValues = UnboundValues() + + fun updateValues() { + if (!values.isServer && isServerThread()) { + values = serverData.values(stack.tag?.getUUIDSafe("uuid") ?: UUID.randomUUID().also { stack.tagNotNull["uuid"] = it }) + } else if (isClientThread()) { + val id = stack.tag?.getUUIDSafe("uuid") ?: return + + if (values.uuid != id) + values = clientData.computeIfAbsent(id, Function { UnboundValues(it) }) + } + } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { if (cap == ForgeCapabilities.ENERGY || cap == MatteryCapability.ENERGY) { return resolver.cast() - } else if (cap == MatteryCapability.MEKANISM_ENERGY) { - return resolverMekanism?.cast() ?: LazyOptional.empty() } return LazyOptional.empty() } - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return extractEnergyInner(howMuch, simulate) - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (howMuch.isNegative) { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) return Decimal.ZERO - } - if (data.parent == null && isServerThread()) { - determineQuantumLink() + updateValues() - if (data.parent == null) { - return Decimal.ZERO - } - } + if (!values.isServer && !simulate) + return Decimal.ZERO - if (isCreative) { - val newEnergy = (data.energy - howMuch).moreThanZero() - val diff = data.energy - newEnergy + if (balanceValues == null) { + val newEnergy = (values.energy - howMuch).moreThanZero() + val diff = values.energy - newEnergy if (!simulate) { - data.energy = newEnergy - data.passed += diff + values.energy = newEnergy + values.passed += diff + } + + return diff + } else { + val newEnergy = (values.energy - howMuch.coerceAtMost(balanceValues.energyThroughput)).moreThanZero() + val diff = values.energy - newEnergy + + if (!simulate) { + values.energy = newEnergy + values.passed += diff } return diff } - - val newEnergy = (data.energy - howMuch.coerceAtMost(throughput!!)).moreThanZero() - val diff = data.energy - newEnergy - - if (!simulate) { - data.energy = newEnergy - data.passed += diff - } - - return diff } - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { - if (howMuch.isNegative) { + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { + if (!howMuch.isPositive) return Decimal.ZERO - } - if (data.parent == null && isServerThread()) { - determineQuantumLink() + updateValues() - if (data.parent == null) { - return Decimal.ZERO - } - } + if (!values.isServer && !simulate) + return Decimal.ZERO - if (isCreative) { + if (balanceValues == null) { if (!simulate) { - data.energy += howMuch - data.received += howMuch + values.energy += howMuch + values.received += howMuch } return howMuch - } - - if (data.energy >= capacity!!) { + } else if (values.energy >= balanceValues.energyCapacity) { return Decimal.ZERO + } else { + val newEnergy = (values.energy + howMuch.coerceAtMost(balanceValues.energyThroughput)).coerceAtMost(balanceValues.energyCapacity) + val diff = newEnergy - values.energy + + if (!simulate) { + values.energy = newEnergy + values.received += diff + } + + return diff } - - val newEnergy = (data.energy + howMuch.coerceAtMost(throughput!!)).coerceAtMost(capacity!!) - val diff = newEnergy - data.energy - - if (!simulate) { - data.energy = newEnergy - data.received += diff - } - - return diff } - override val batteryLevel: Decimal + override var batteryLevel: Decimal get() { - if (data.parent == null) { - determineQuantumLink() - } - - if (isClientThread()) { - return clientPowerMap[data.index]?.energy ?: data.energy - } - - return data.energy + updateValues() + return values.energy + } + set(value) { + updateValues() + values.energy = value } val passed: Decimal get() { - if (data.parent == null) { - determineQuantumLink() - } - - if (isClientThread()) { - return clientPowerMap[data.index]?.passed ?: data.passed - } - - return data.passed + updateValues() + return values.passed } val received: Decimal get() { - if (data.parent == null) { - determineQuantumLink() - } - - if (isClientThread()) { - return clientPowerMap[data.index]?.received ?: data.received - } - - return data.received + updateValues() + return values.received } override val maxBatteryLevel: Decimal - get() = capacity ?: (batteryLevel + Decimal.LONG_MAX_VALUE) + get() = balanceValues?.energyCapacity ?: (batteryLevel + Decimal.LONG_MAX_VALUE) override val missingPower: Decimal get() = if (isCreative) Decimal.LONG_MAX_VALUE else super.missingPower - - override fun canExtract(): Boolean { - return true - } - - override fun canReceive(): Boolean { - return true - } - - private fun determineQuantumLink() { - if (data.parent == null && isServerThread()) { - val existing = stack.tag?.getInt("link_id") - - if (existing == null) { - val old = data - data = saveData!!.factorize() - data.energy = old.energy - stack.tagNotNull["link_id"] = data.index - } else { - data = saveData?.computeIfAbsent(existing) ?: Data(null, existing, data.energy, data.passed, data.received) - } - } else if (!isServerThread()) { - // client ? - val existing = stack.tag?.getInt("link_id") ?: return - - if (existing != data.index) { - data = Data(data.parent, existing, data.energy, data.passed, data.received) - } - } - } - - fun determineQuantumLinkWeak() { - if (data.parent == null && isServerThread()) { - val existing = stack.tag?.getInt("link_id") - - if (existing != null) { - data = saveData?.computeIfAbsent(existing) ?: Data(null, existing, data.energy, data.passed, data.received) - } - } else if (!isServerThread()) { - val existing = stack.tag?.getInt("link_id") ?: return - - if (existing != data.index) { - data = Data(data.parent, existing, data.energy, data.passed, data.received) - } - } - } } - val isCreative: Boolean - - private val _capacity: () -> Decimal? - private val _throughput: () -> Decimal? - - val capacity get() = _capacity.invoke() - val throughput get() = _throughput.invoke() - - val saveDataID: String - - val saveData: SavedCountingMap? get() { - if (isServerThread()) { - return MINECRAFT_SERVER.overworld().dataStorage.computeIfAbsent({ - SavedCountingMap(Companion::storeValue, Companion::loadValue, ::Data).load(it) - }, { - SavedCountingMap(Companion::storeValue, Companion::loadValue, ::Data) - }, saveDataID) ?: throw NullPointerException("Unable to get save data for $this in ${MINECRAFT_SERVER.overworld()}") - } - - return null - } - - data class ClientData( - val energy: Decimal = Decimal.ZERO, - val passed: Decimal = Decimal.ZERO, - val received: Decimal = Decimal.ZERO, - ) - - val clientPowerMap: Int2ObjectOpenHashMap by lazy { - check(isClient) { "Invalid side" } - Int2ObjectOpenHashMap() - } - - constructor(saveDataID: String) : super(Properties().stacksTo(1).rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = true - _capacity = { null } - _throughput = { null } - this.saveDataID = "otm_$saveDataID".intern() - } - - constructor(saveDataID: String, capacity: Decimal, io: Decimal) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = false - _capacity = { capacity } - _throughput = { io } - this.saveDataID = "otm_$saveDataID".intern() - } - - constructor(saveDataID: String, values: ConciseBalanceValues) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { - isCreative = false - _capacity = values::capacity - _throughput = values::throughput - this.saveDataID = "otm_$saveDataID".intern() + override fun merge(from: ItemStack, into: ItemStack): ItemStack { + return from.copyWithCount(from.count + into.count) } override fun isBarVisible(p_150899_: ItemStack): Boolean { - if (isCreative) - return false - + if (isCreative) return false return p_150899_.matteryEnergy != null } override fun getBarWidth(p_150900_: ItemStack): Int { - if (isCreative) - return 13 - + if (isCreative) return 13 return p_150900_.matteryEnergy?.getBarWidth() ?: super.getBarWidth(p_150900_) } override fun getBarColor(p_150901_: ItemStack): Int { - if (isCreative) - return 0 - + if (isCreative) return 0 return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_) } @@ -338,29 +296,19 @@ class QuantumBatteryItem : Item { return Power(stack) } - override fun appendHoverText( - itemStack: ItemStack, - p_41422_: Level?, - components: MutableList, - p_41424_: TooltipFlag - ) { - super.appendHoverText(itemStack, p_41422_, components, p_41424_) + override fun appendHoverText(itemStack: ItemStack, level: Level?, components: MutableList, flags: TooltipFlag) { + super.appendHoverText(itemStack, level, components, flags) val power = itemStack.getCapability(MatteryCapability.ENERGY).orThrow() as Power + power.updateValues() components.add(TranslatableComponent("otm.item.quantum_description").withStyle(ChatFormatting.DARK_GRAY)) - if (isCreative) { + if (balanceValues == null) { components.add(TranslatableComponent("otm.item.quantum_battery.creative_power", power.batteryLevel.formatPower()).withStyle(ChatFormatting.GRAY)) } else { - components.add(TranslatableComponent("otm.item.power.storage", power.batteryLevel.formatPower(), capacity!!.formatPower()).withStyle(ChatFormatting.GRAY)) - - components.add( - TranslatableComponent( - "otm.item.power.throughput", - throughput!!.formatPower(), - throughput!!.formatPower() - ).withStyle(ChatFormatting.GRAY)) + components.add(TranslatableComponent("otm.item.power.storage", power.batteryLevel.formatPower(), balanceValues.energyCapacity.formatPower()).withStyle(ChatFormatting.GRAY)) + components.add(TranslatableComponent("otm.item.power.throughput_mono", balanceValues.energyThroughput.formatPower()).withStyle(ChatFormatting.GRAY)) } components.add(TranslatableComponent("otm.item.power.passed", power.passed.formatPower()).withStyle(ChatFormatting.GRAY)) @@ -371,18 +319,18 @@ class QuantumBatteryItem : Item { components.add(TranslatableComponent("otm.item.quantum_battery.creative2").withStyle(ChatFormatting.DARK_GRAY)) } - components.add(TranslatableComponent("otm.item.quantum_link_id", power.data.index).withStyle(ChatFormatting.DARK_GRAY)) + components.add(TranslatableComponent("otm.item.quantum_link_id", power.values.uuid).withStyle(ChatFormatting.DARK_GRAY)) } companion object { fun clientDisconnect(event: ClientPlayerNetworkEvent.LoggingOut) { - ForgeRegistries.ITEMS.values.parallelStream().forEach { if (it is QuantumBatteryItem) it.clientPowerMap.clear() } + ForgeRegistries.ITEMS.values.forEach { if (it is QuantumBatteryItem) it.clientData.clear() } } fun readPacket(buff: FriendlyByteBuf): ChargePacket { return ChargePacket( - (ForgeRegistries.ITEMS as ForgeRegistry).getValue(buff.readInt()) as QuantumBatteryItem, - buff.readInt(), + ForgeRegistries.ITEMS.getValue(buff.readInt()) as QuantumBatteryItem, + buff.readUUID(), buff.readDecimal(), buff.readDecimal(), buff.readDecimal(), @@ -392,62 +340,43 @@ class QuantumBatteryItem : Item { fun tick(event: ServerTickEvent) { if (event.phase == TickEvent.Phase.END) { for (ply in event.server.playerList.players) { - val networkedChannels = IntOpenHashSet(0) + val networkedChannels = ObjectOpenHashSet(0) - for (item in ply.allItemsStream().filter { !it.isEmpty && it.item is QuantumBatteryItem }) { + for (item in ply.trackedItems().filter { it.isNotEmpty && it.item is QuantumBatteryItem }) { val power = item.getCapability(MatteryCapability.ENERGY).orThrow() as Power - - power.determineQuantumLinkWeak() - - if (power.data.index < 0) { - continue - } - - if (networkedChannels.add(power.data.index)) { - GenericNetworkChannel.send(ply, ChargePacket(item.item as QuantumBatteryItem, power.data.index, power.batteryLevel, power.data.passed, power.data.received)) + power.updateValues() + if (power.values.isServer && networkedChannels.add(power.values.uuid)) { + GenericNetworkChannel.send(ply, ChargePacket(item.item as QuantumBatteryItem, power.values.uuid, power.batteryLevel, power.passed, power.received)) } } } } } - - private fun loadValue(parent: SavedCountingMap, tag: Tag, index: Int): Data { - if (tag is ByteArrayTag) { - return Data(parent, index, Decimal.deserializeNBT(tag)) - } else if (tag is CompoundTag) { - return Data(parent, index, Decimal.deserializeNBT(tag["energy"]), Decimal.deserializeNBT(tag["passed"]), Decimal.deserializeNBT(tag["received"])) - } else { - return Data(parent, index) - } - } - - private fun storeValue(parent: SavedCountingMap, value: Data, index: Int): CompoundTag { - return CompoundTag().also { - it["energy"] = value.energy.serializeNBT() - it["passed"] = value.passed.serializeNBT() - it["received"] = value.received.serializeNBT() - } - } } class ChargePacket( val type: QuantumBatteryItem, - val channel: Int, - val energy: Decimal, - val passed: Decimal, - val received: Decimal, - ) : MatteryPacket { + override val uuid: UUID, + override var energy: Decimal, + override var passed: Decimal, + override var received: Decimal, + ) : MatteryPacket, IValues { + override val isServer: Boolean + get() = false + override fun write(buff: FriendlyByteBuf) { - buff.writeInt((ForgeRegistries.ITEMS as ForgeRegistry).getID(type)) - buff.writeInt(channel) + buff.writeInt(ForgeRegistries.ITEMS.getID(type)) + buff.writeUUID(uuid) buff.writeDecimal(energy) buff.writeDecimal(passed) buff.writeDecimal(received) } - override fun play(context: Supplier) { - context.packetHandled = true - type.clientPowerMap[channel] = ClientData(energy, passed, received) + override fun play(context: MNetworkContext) { + val data = type.clientData.computeIfAbsent(uuid, Function { UnboundValues(it) }) + data.energy = energy + data.passed = passed + data.received = received } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt new file mode 100644 index 000000000..ff1acb9e2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt @@ -0,0 +1,53 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.core.Direction +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import net.minecraftforge.common.capabilities.Capability +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.common.util.LazyOptional +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.addUpgradeTooltipLines +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.Decimal + +class SimpleUpgrade( + properties: Properties, + override val upgradeTypes: Set, + override val speedBonus: Double = 0.0, + override val processingItems: Int = 0, + override val energyStorageFlat: Decimal = Decimal.ZERO, + override val energyStorage: Decimal = Decimal.ZERO, + override val energyConsumed: Decimal = Decimal.ZERO, + override val energyThroughputFlat: Decimal = Decimal.ZERO, + override val energyThroughput: Decimal = Decimal.ZERO, + override val matterStorage: Decimal = Decimal.ZERO, + override val matterStorageFlat: Decimal = Decimal.ZERO, + override val failureMultiplier: Double = 1.0, +) : Item(properties), IMatteryUpgrade, ICapabilityProvider { + private val resolver = LazyOptional.of { this } + + override fun getCapability(cap: Capability, side: Direction?): LazyOptional { + if (cap === MatteryCapability.UPGRADE) { + return resolver.cast() + } + + return LazyOptional.empty() + } + + override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, p_41423_: MutableList, p_41424_: TooltipFlag) { + super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) + addUpgradeTooltipLines(p_41423_) + } + + override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { + return this + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt index 37c0bb5f8..5750b2004 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SingleUseBatteryItem.kt @@ -9,19 +9,23 @@ import net.minecraft.world.item.Rarity import net.minecraft.world.item.TooltipFlag import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.ICapabilityProvider -import ru.dbotthepony.mc.otm.ConciseBalanceValues -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.EnergyProducerItem +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth +import ru.dbotthepony.mc.otm.config.ItemsConfig import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.Decimal open class SingleUseBatteryItem( private val _capacity: () -> Decimal, private val _throughput: () -> Decimal? = { null }, - properties: Properties = Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB) + properties: Properties = Properties().stacksTo(1) ) : Item(properties) { - constructor(values: ConciseBalanceValues, properties: Properties = Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) : this(values::capacity, values::throughput, properties) - constructor(storage: Decimal, throughput: Decimal? = null, properties: Properties = Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) : this({ storage }, { throughput }, properties) + constructor(values: EnergyBalanceValues, properties: Properties = Properties().stacksTo(1)) : this(values::energyCapacity, values::energyThroughput, properties) + constructor(storage: Decimal, throughput: Decimal? = null, properties: Properties = Properties().stacksTo(1)) : this({ storage }, { throughput }, properties) val capacity get() = _capacity.invoke() val throughput get() = _throughput.invoke() @@ -33,7 +37,7 @@ open class SingleUseBatteryItem( p_41424_: TooltipFlag ) { super.appendHoverText(itemStack, p_41422_, list, p_41424_) - list.add(SINGLE_USE) + list.add(TranslatableComponent("otm.battery.single_use").withStyle(ChatFormatting.GRAY)) ItemEnergyStorageImpl.appendHoverText(itemStack, list) } @@ -52,13 +56,9 @@ open class SingleUseBatteryItem( override fun getBarColor(p_150901_: ItemStack): Int { return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_) } - - companion object { - private val SINGLE_USE = TranslatableComponent("otm.battery.single_use").withStyle(ChatFormatting.GRAY) - } } -class ZPMItem : SingleUseBatteryItem(ServerConfig.ZPM_BATTERY, Properties().stacksTo(1).rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { +class ZPMItem : SingleUseBatteryItem(ItemsConfig.Batteries.ZPM, Properties().stacksTo(1).rarity(Rarity.EPIC)) { override fun appendHoverText( itemStack: ItemStack, p_41422_: Level?, @@ -66,13 +66,6 @@ class ZPMItem : SingleUseBatteryItem(ServerConfig.ZPM_BATTERY, Properties().stac p_41424_: TooltipFlag ) { super.appendHoverText(itemStack, p_41422_, list, p_41424_) - list.add(DESCRIPTION) - } - - companion object { - val MAX_STORAGE = Decimal(200_000_000_000_000L) - val THROUGHPUT = Decimal(200_000_000L) - - private val DESCRIPTION = TranslatableComponent("item.${OverdriveThatMatters.MOD_ID}.zpm_battery.description").withStyle(ChatFormatting.DARK_GRAY) + list.add(TranslatableComponent("$descriptionId.description").withStyle(ChatFormatting.DARK_GRAY)) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableGravitationStabilizerItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt similarity index 92% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableGravitationStabilizerItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt index 16c39dc80..2bf541b69 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableGravitationStabilizerItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/PortableGravitationStabilizerItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.armor import net.minecraft.ChatFormatting import net.minecraft.client.model.HumanoidModel @@ -41,7 +41,7 @@ private object GravitationStabilizerArmorRenderProperties : IClientItemExtension } } -class ItemPortableGravitationStabilizer : ArmorItem(GravitationStabilizerArmorMaterial, EquipmentSlot.CHEST, Properties().stacksTo(1).rarity(Rarity.RARE).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { +class PortableGravitationStabilizerItem : ArmorItem(GravitationStabilizerArmorMaterial, EquipmentSlot.CHEST, Properties().stacksTo(1).rarity(Rarity.RARE)) { override fun initializeClient(consumer: Consumer) { super.initializeClient(consumer) consumer.accept(GravitationStabilizerArmorRenderProperties) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt new file mode 100644 index 000000000..892d74340 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/SimpleTritaniumArmorItem.kt @@ -0,0 +1,57 @@ +package ru.dbotthepony.mc.otm.item.armor + +import net.minecraft.sounds.SoundEvent +import net.minecraft.sounds.SoundEvents +import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.item.ArmorItem +import net.minecraft.world.item.ArmorMaterial +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.registry.MItemTags + +private object SimpleTritaniumArmorMaterial : ArmorMaterial { + override fun getDurabilityForSlot(p_40410_: EquipmentSlot): Int { + return when (p_40410_) { + EquipmentSlot.HEAD -> 380 + EquipmentSlot.CHEST -> 590 + EquipmentSlot.LEGS -> 500 + EquipmentSlot.FEET -> 420 + else -> throw IllegalArgumentException("yo dude what the fuck $p_40410_") + } + } + + override fun getDefenseForSlot(p_40411_: EquipmentSlot): Int { + return when (p_40411_) { + EquipmentSlot.HEAD -> 2 + EquipmentSlot.CHEST -> 6 + EquipmentSlot.LEGS -> 7 + EquipmentSlot.FEET -> 2 + else -> throw IllegalArgumentException("yo dude what the fuck $p_40411_") + } + } + + override fun getEnchantmentValue() = 9 + override fun getEquipSound(): SoundEvent = SoundEvents.ARMOR_EQUIP_IRON + override fun getRepairIngredient(): Ingredient = Ingredient.of(MItemTags.TRITANIUM_INGOTS) + + const val ID = "${OverdriveThatMatters.MOD_ID}:simple_tritanium_armor" + + override fun getName(): String = ID + override fun getToughness() = 0.3f + override fun getKnockbackResistance() = 0f +} + +class SimpleTritaniumArmorItem(slot: EquipmentSlot) : ArmorItem(SimpleTritaniumArmorMaterial, slot, Properties().stacksTo(1)) { + override fun getArmorTexture(stack: ItemStack, entity: Entity?, slot: EquipmentSlot, type: String?): String? { + if (type != "overlay" || slot == EquipmentSlot.FEET) + return when (slot) { + EquipmentSlot.LEGS -> "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_simple_layer_2.png" + EquipmentSlot.FEET, EquipmentSlot.CHEST, EquipmentSlot.HEAD -> "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_simple_layer_1.png" + else -> throw IllegalArgumentException("Invalid slot $slot") + } + + return null + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/TritaniumArmorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt similarity index 61% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/TritaniumArmorItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt index c26c0e48b..0abbd1be4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/TritaniumArmorItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/armor/TritaniumArmorItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.armor import net.minecraft.client.model.HumanoidModel import net.minecraft.sounds.SoundEvent @@ -7,16 +7,13 @@ import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.entity.Entity import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.entity.LivingEntity -import net.minecraft.world.item.ArmorItem -import net.minecraft.world.item.ArmorMaterial -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Rarity +import net.minecraft.world.item.* import net.minecraft.world.item.crafting.Ingredient import net.minecraftforge.client.extensions.common.IClientItemExtensions import net.minecraftforge.event.entity.living.LivingAttackEvent -import net.minecraftforge.event.entity.living.LivingHurtEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.client.model.TritaniumArmorModel +import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.registry.MItemTags import ru.dbotthepony.mc.otm.registry.MItems import java.util.function.Consumer @@ -24,33 +21,33 @@ import java.util.function.Consumer private object TritaniumArmorMaterial : ArmorMaterial { override fun getDurabilityForSlot(p_40410_: EquipmentSlot): Int { return when (p_40410_) { - EquipmentSlot.FEET -> 470 - EquipmentSlot.LEGS -> 540 - EquipmentSlot.CHEST -> 820 EquipmentSlot.HEAD -> 520 - else -> throw IllegalArgumentException("yo dude what the fuck") + EquipmentSlot.CHEST -> 920 + EquipmentSlot.LEGS -> 650 + EquipmentSlot.FEET -> 540 + else -> throw IllegalArgumentException(p_40410_.toString()) } } override fun getDefenseForSlot(p_40411_: EquipmentSlot): Int { return when (p_40411_) { + EquipmentSlot.HEAD -> 4 + EquipmentSlot.CHEST -> 9 + EquipmentSlot.LEGS -> 7 EquipmentSlot.FEET -> 3 - EquipmentSlot.LEGS -> 6 - EquipmentSlot.CHEST -> 8 - EquipmentSlot.HEAD -> 3 - else -> throw IllegalArgumentException("yo dude what the fuck") + else -> throw IllegalArgumentException(p_40411_.toString()) } } override fun getEnchantmentValue() = 9 override fun getEquipSound(): SoundEvent = SoundEvents.ARMOR_EQUIP_IRON - override fun getRepairIngredient(): Ingredient = Ingredient.of(MItemTags.TRITANIUM_INGOTS) + override fun getRepairIngredient(): Ingredient = Ingredient.of(MItemTags.REINFORCED_TRITANIUM_PLATES) const val ID = "${OverdriveThatMatters.MOD_ID}:tritanium_armor" override fun getName(): String = ID override fun getToughness() = 1f - override fun getKnockbackResistance() = 0.05f + override fun getKnockbackResistance() = 0.08f } private object TritaniumArmorRenderProperties : IClientItemExtensions { @@ -68,21 +65,31 @@ private object TritaniumArmorRenderProperties : IClientItemExtensions { } } -class ItemTritaniumArmor(slot: EquipmentSlot) : ArmorItem(TritaniumArmorMaterial, slot, Properties().stacksTo(1).rarity(Rarity.RARE).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { +class TritaniumArmorItem(slot: EquipmentSlot) : DyeableArmorItem(TritaniumArmorMaterial, slot, Properties().stacksTo(1).rarity(Rarity.RARE)) { override fun initializeClient(consumer: Consumer) { super.initializeClient(consumer) consumer.accept(TritaniumArmorRenderProperties) } - override fun getArmorTexture(stack: ItemStack?, entity: Entity?, slot: EquipmentSlot?, type: String?): String { - return TEXTURE_LOCATION + override fun getArmorTexture(stack: ItemStack, entity: Entity, slot: EquipmentSlot, type: String?): String = + if (type.equals("overlay")) TEXTURE_LOCATION_OVERLAY else TEXTURE_LOCATION_BASE + + override fun getColor(stack: ItemStack): Int { + val tag = stack.getTagElement("display")?: return TRITANIUM_COLOR + if (tag.contains("color", 99)) { + return tag.getInt("color") + } + + return TRITANIUM_COLOR } companion object { - const val TEXTURE_LOCATION = "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_armor.png" + val TRITANIUM_COLOR = RGBAColor(157, 187, 204).toARGB() + const val TEXTURE_LOCATION_BASE = "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_armor_base.png" + const val TEXTURE_LOCATION_OVERLAY = "${OverdriveThatMatters.MOD_ID}:textures/models/armor/tritanium_armor_overlay.png" fun onHurt(event: LivingAttackEvent) { - if (event.source === DamageSource.SWEET_BERRY_BUSH || event.source.msgId == "sweetBerryBush") { + if (event.source == DamageSource.SWEET_BERRY_BUSH || event.source.msgId == "sweetBerryBush") { if ( event.entity.getItemBySlot(EquipmentSlot.FEET).let { !it.isEmpty && it.item == MItems.TRITANIUM_BOOTS } && event.entity.getItemBySlot(EquipmentSlot.LEGS).let { !it.isEmpty && it.item == MItems.TRITANIUM_PANTS } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/AbstractExoPackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt similarity index 73% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/AbstractExoPackSlotUpgradeItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt index dc568a513..ba3213c5c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/AbstractExoPackSlotUpgradeItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/AbstractExopackSlotUpgradeItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.exopack import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component @@ -14,15 +14,16 @@ import net.minecraft.world.item.Rarity import net.minecraft.world.item.TooltipFlag import net.minecraft.world.item.UseAnim import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig +import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.runIfClient +import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger import java.util.UUID -abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultProperties()) : Item(properties) { +abstract class AbstractExopackSlotUpgradeItem(properties: Properties = defaultProperties()) : Item(properties) { abstract fun hasDescription(itemStack: ItemStack): Boolean abstract fun slotCount(itemStack: ItemStack): Int abstract fun uuid(itemStack: ItemStack): UUID? @@ -39,11 +40,11 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) val alreadyHasExosuit = runIfClient(true) { - minecraft.player?.matteryPlayer?.hasExoPack == true + minecraft.player?.matteryPlayer?.hasExopack == true } if (!alreadyHasExosuit) { - tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.no_exosuit").withStyle(ChatFormatting.GRAY)) + tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.no_exopack").withStyle(ChatFormatting.GRAY)) if (runIfClient(false) { minecraft.player?.isCreative != true }) { return @@ -53,7 +54,7 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr val uuid = uuid(p_41421_) val alreadyHas = (uuid != null && !ServerConfig.INFINITE_EXOSUIT_UPGRADES) && runIfClient(false) { - minecraft.player?.matteryPlayer?.exoPackSlotModifier?.contains(uuid) == true + minecraft.player?.matteryPlayer?.exopackSlotModifier?.contains(uuid) == true } if (alreadyHas) { @@ -72,7 +73,7 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr val uuid = uuid(player.getItemInHand(hand)) - if (matteryPlayer.hasExoPack && ((uuid == null || ServerConfig.INFINITE_EXOSUIT_UPGRADES) || uuid !in matteryPlayer.exoPackSlotModifier)) { + if (matteryPlayer.hasExopack && ((uuid == null || ServerConfig.INFINITE_EXOSUIT_UPGRADES) || uuid !in matteryPlayer.exopackSlotModifier)) { player.startUsingItem(hand) return InteractionResultHolder.consume(player.getItemInHand(hand)) } @@ -86,7 +87,7 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr val uuid = uuid(itemStack) val slotCount = slotCount(itemStack) - if (slotCount <= 0 || !matteryPlayer.hasExoPack || (!ServerConfig.INFINITE_EXOSUIT_UPGRADES && uuid != null && uuid in matteryPlayer.exoPackSlotModifier)) { + if (slotCount <= 0 || !matteryPlayer.hasExopack || (!ServerConfig.INFINITE_EXOSUIT_UPGRADES && uuid != null && uuid in matteryPlayer.exopackSlotModifier)) { return super.finishUsingItem(itemStack, level, player) } @@ -95,16 +96,21 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr if (player is ServerPlayer) { if (uuid != null) { - if (ServerConfig.INFINITE_EXOSUIT_UPGRADES && uuid in matteryPlayer.exoPackSlotModifier) { - matteryPlayer.exoPackSlotModifier[UUID.randomUUID()] = slotCount + if (ServerConfig.INFINITE_EXOSUIT_UPGRADES && uuid in matteryPlayer.exopackSlotModifier) { + matteryPlayer.exopackSlotModifier[UUID.randomUUID()] = slotCount } else { - matteryPlayer.exoPackSlotModifier[uuid] = slotCount + matteryPlayer.exopackSlotModifier[uuid] = slotCount } } else { - matteryPlayer.exoPackSlotModifier[UUID.randomUUID()] = slotCount + matteryPlayer.exopackSlotModifier[UUID.randomUUID()] = slotCount } + ExopackSlotsExpandedTrigger.trigger(player, slotCount, matteryPlayer.exopackContainer.containerSize) player.displayClientMessage(TranslatableComponent("otm.exopack_upgrades.slots_upgraded", slotCount).withStyle(ChatFormatting.DARK_GREEN), false) + + if (this === MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON) { + MItems.ExopackUpgrades.ENDER_UPGRADE.finishUsingItem(ItemStack(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON), level, player) + } } return itemStack @@ -113,6 +119,6 @@ abstract class AbstractExoPackSlotUpgradeItem(properties: Properties = defaultPr override fun getUseAnimation(p_41452_: ItemStack): UseAnim = UseAnim.BOW companion object { - fun defaultProperties(rarity: Rarity = Rarity.UNCOMMON) = Properties().stacksTo(8).rarity(Rarity.UNCOMMON).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).fireResistant() + fun defaultProperties(rarity: Rarity = Rarity.UNCOMMON): Properties = Properties().stacksTo(8).rarity(Rarity.UNCOMMON).fireResistant() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackProbeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt similarity index 81% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackProbeItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt index ba8136365..899549add 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackProbeItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackProbeItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.exopack import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component @@ -9,15 +9,15 @@ import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player import net.minecraft.world.item.* import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.registry.MRegistry +import ru.dbotthepony.mc.otm.registry.ExopackDamageSource +import ru.dbotthepony.mc.otm.registry.MDamageTypes import ru.dbotthepony.mc.otm.runIfClient -class ExoPackProbeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(Rarity.EPIC)) { +class ExopackProbeItem : Item(Properties().stacksTo(1).rarity(Rarity.EPIC)) { override fun getUseDuration(p_41454_: ItemStack): Int { return 30 } @@ -26,7 +26,7 @@ class ExoPackProbeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CRE super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) val alreadyHas = runIfClient(false) { - return@runIfClient minecraft.player?.matteryPlayer?.hasExoPack ?: false + return@runIfClient minecraft.player?.matteryPlayer?.hasExopack ?: false } if (alreadyHas) { @@ -39,7 +39,7 @@ class ExoPackProbeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CRE } override fun use(p_41432_: Level, player: Player, hand: InteractionHand): InteractionResultHolder { - if (player.matteryPlayer?.hasExoPack == false) { + if (player.matteryPlayer?.hasExopack == false) { player.startUsingItem(hand) return InteractionResultHolder.consume(player.getItemInHand(hand)) } @@ -51,20 +51,23 @@ class ExoPackProbeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CRE if (player !is Player) return super.finishUsingItem(itemStack, level, player) val mattery = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player) - if (mattery.hasExoPack) { + if (mattery.hasExopack) { return super.finishUsingItem(itemStack, level, player) } + val copy = itemStack.copy() + if (!player.abilities.instabuild) itemStack.shrink(1) if (player is ServerPlayer) { - mattery.hasExoPack = true + mattery.hasExopack = true player.displayClientMessage(TranslatableComponent("otm.exopack.granted1").withStyle(ChatFormatting.GRAY), false) - player.hurt(MRegistry.DAMAGE_EXOPACK_PROBE, 10f) + player.displayClientMessage(TranslatableComponent("otm.exopack.granted2").withStyle(ChatFormatting.GRAY), false) + player.hurt(ExopackDamageSource(inflictor = copy), 10f) - for (i in 2 .. 8) { + for (i in 3 .. 7) { onceServer((i - 1) * 100) { if (!player.hasDisconnected()) { if (i == 6) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackSlotUpgradeItem.kt similarity index 55% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackSlotUpgradeItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackSlotUpgradeItem.kt index 76f14b5a9..ef1c5981c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackSlotUpgradeItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackSlotUpgradeItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.exopack import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Rarity @@ -6,10 +6,9 @@ import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.toUUID import java.util.UUID -class ExoPackSlotUpgradeItem : AbstractExoPackSlotUpgradeItem { +class ExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem { val hasDescription: Boolean val slotCount: Int - val isCreative: Boolean private val uuidProvider: () -> UUID? @@ -25,31 +24,15 @@ class ExoPackSlotUpgradeItem : AbstractExoPackSlotUpgradeItem { return slotCount } - constructor(slotCount: Int, properties: Properties = defaultProperties(), hasDescription: Boolean = false) : super(properties) { - isCreative = false - this.slotCount = slotCount - this.uuidProvider = lazy { registryName!!.toUUID() }::value - this.hasDescription = hasDescription - } - - constructor(uuid: UUID?, slotCount: Int, properties: Properties = defaultProperties(), hasDescription: Boolean = false) : super(properties) { - isCreative = uuid == null - this.slotCount = slotCount - this.uuidProvider = { UUID(0L, 0L) } - this.hasDescription = hasDescription - } - constructor(slotCount: Int, rarity: Rarity, hasDescription: Boolean = false) : super(defaultProperties(rarity)) { - isCreative = false this.slotCount = slotCount this.uuidProvider = lazy { registryName!!.toUUID() }::value this.hasDescription = hasDescription } constructor(uuid: UUID?, slotCount: Int, rarity: Rarity, hasDescription: Boolean = false) : super(defaultProperties(rarity)) { - isCreative = uuid == null this.slotCount = slotCount - this.uuidProvider = { UUID(0L, 0L) } + this.uuidProvider = { uuid } this.hasDescription = hasDescription } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackCraftingUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt similarity index 67% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackCraftingUpgradeItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt index 55fd51e3f..668539320 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/ExoPackCraftingUpgradeItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ExopackUpgradeItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.exopack import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component @@ -7,15 +7,23 @@ import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionResultHolder import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.* +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Rarity +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.UseAnim import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.runIfClient -class ExoPackCraftingUpgradeItem : Item(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(Rarity.RARE)) { +class ExopackUpgradeItem( + val type: MatteryPlayerCapability.UpgradeType, + val upgradeName: String, + val upgradedName: String, +) : Item(Properties().stacksTo(1).rarity(Rarity.RARE)) { override fun getUseDuration(p_41454_: ItemStack): Int { return 30 } @@ -24,11 +32,11 @@ class ExoPackCraftingUpgradeItem : Item(Properties().tab(OverdriveThatMatters.IN super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) val alreadyHasExosuit = runIfClient(true) { - minecraft.player?.matteryPlayer?.hasExoPack == true + minecraft.player?.matteryPlayer?.hasExopack == true } if (!alreadyHasExosuit) { - tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.no_exosuit").withStyle(ChatFormatting.GRAY)) + tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.no_exopack").withStyle(ChatFormatting.GRAY)) if (runIfClient(false) { minecraft.player?.isCreative != true }) { return @@ -36,18 +44,18 @@ class ExoPackCraftingUpgradeItem : Item(Properties().tab(OverdriveThatMatters.IN } val alreadyHas = runIfClient(true) { - minecraft.player?.matteryPlayer?.isExoPackCraftingUpgraded == true + type.prop.get(minecraft.player?.matteryPlayer ?: return@runIfClient false) } if (alreadyHas) { tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.already_activated").withStyle(ChatFormatting.DARK_RED)) } else { - tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.crafting_upgrade").withStyle(ChatFormatting.DARK_GREEN)) + tooltip.add(TranslatableComponent("otm.gui.exopack_upgrades.$upgradeName").withStyle(ChatFormatting.DARK_GREEN)) } } override fun use(p_41432_: Level, player: Player, hand: InteractionHand): InteractionResultHolder { - if (player.matteryPlayer?.hasExoPack == true && !player.matteryPlayer!!.isExoPackCraftingUpgraded) { + if (player.matteryPlayer?.hasExopack == true && !type.prop.get(player.matteryPlayer!!)) { player.startUsingItem(hand) return InteractionResultHolder.consume(player.getItemInHand(hand)) } @@ -59,7 +67,7 @@ class ExoPackCraftingUpgradeItem : Item(Properties().tab(OverdriveThatMatters.IN if (player !is Player) return super.finishUsingItem(itemStack, level, player) val mattery = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player) - if (!mattery.hasExoPack || mattery.isExoPackCraftingUpgraded) { + if (!mattery.hasExopack || type.prop.get(mattery)) { return super.finishUsingItem(itemStack, level, player) } @@ -67,9 +75,8 @@ class ExoPackCraftingUpgradeItem : Item(Properties().tab(OverdriveThatMatters.IN itemStack.shrink(1) if (player is ServerPlayer) { - mattery.isExoPackCraftingUpgraded = true - - player.displayClientMessage(TranslatableComponent("otm.exopack_upgrades.crafting_upgraded").withStyle(ChatFormatting.DARK_GREEN), false) + type.prop.set(mattery, true) + player.displayClientMessage(TranslatableComponent("otm.exopack_upgrades.$upgradedName").withStyle(ChatFormatting.DARK_GREEN), false) } return itemStack diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt new file mode 100644 index 000000000..579148473 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/exopack/ProceduralExopackSlotUpgradeItem.kt @@ -0,0 +1,80 @@ +package ru.dbotthepony.mc.otm.item.exopack + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.util.valueproviders.ConstantInt +import net.minecraft.util.valueproviders.IntProvider +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import net.minecraft.world.level.storage.loot.LootContext +import net.minecraft.world.level.storage.loot.functions.LootItemFunction +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.data.Codec2Serializer +import ru.dbotthepony.mc.otm.registry.MItemFunctionTypes +import java.util.* + +class ProceduralExopackSlotUpgradeItem : AbstractExopackSlotUpgradeItem(defaultProperties()) { + class Randomizer(val slots: IntProvider, val luckBias: IntProvider = ConstantInt.ZERO) : LootItemFunction, LootItemFunction.Builder { + override fun apply(t: ItemStack, u: LootContext): ItemStack { + t.tagNotNull[SLOT_COUNT_KEY] = slots.sample(u.random) + (luckBias.sample(u.random) * u.luck / 1024f).coerceAtLeast(0f).toInt() + return t + } + + override fun getType(): LootItemFunctionType { + return MItemFunctionTypes.PROCEDURAL_EXOPACK_UPGRADE + } + + override fun build(): LootItemFunction { + return this + } + + companion object { + val CODEC by lazy { + Codec2Serializer( + RecordCodecBuilder.create { + it.group( + IntProvider.CODEC.fieldOf("slots").forGetter(Randomizer::slots), + IntProvider.CODEC.optionalFieldOf("luck_bias", ConstantInt.ZERO).forGetter(Randomizer::luckBias), + ).apply(it, ::Randomizer) + } + ) + } + } + } + + override fun hasDescription(itemStack: ItemStack): Boolean { + return false + } + + override fun slotCount(itemStack: ItemStack): Int { + return itemStack.tag?.getInt(SLOT_COUNT_KEY) ?: 0 + } + + override fun uuid(itemStack: ItemStack): UUID? { + return null + } + + override fun appendHoverText( + p_41421_: ItemStack, + p_41422_: Level?, + tooltip: MutableList, + p_41424_: TooltipFlag + ) { + super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_) + + if (p_41421_.tag?.get(SLOT_COUNT_KEY) == null) { + tooltip.add(TranslatableComponent("$descriptionId.description").withStyle(ChatFormatting.GRAY)) + } + } + + companion object { + const val SLOT_COUNT_KEY = "slotCount" + const val UUID_KEY = "uuid" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt similarity index 89% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt index 2f2c04c0d..b06eb053f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/CreativePatternItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/CreativePatternItem.kt @@ -1,4 +1,4 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.matter import net.minecraft.ChatFormatting import net.minecraft.core.Direction @@ -13,24 +13,22 @@ import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.matter.IPatternState +import ru.dbotthepony.mc.otm.capability.matter.PatternState import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage import ru.dbotthepony.mc.otm.capability.matter.PatternInsertFailure import ru.dbotthepony.mc.otm.capability.matter.PatternInsertStatus -import ru.dbotthepony.mc.otm.capability.matter.PatternState import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.getID import ru.dbotthepony.mc.otm.matter.MatterManager import java.util.* import java.util.stream.Stream -class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1)) { +class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).stacksTo(1)) { private object Patterns : IPatternStorage, ICapabilityProvider { private val resolver = LazyOptional.of { this } - override val patterns: Stream + override val patterns: Stream get() = MatterManager.map.keys.stream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it)), it, 1.0) } override val patternCapacity: Int @@ -44,7 +42,7 @@ class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).tab(OverdriveT } override fun insertPattern( - pattern: IPatternState, + pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean ): PatternInsertStatus { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt similarity index 67% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt index 612c6a831..c2ef417ad 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/MatterCapacitorItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterCapacitorItem.kt @@ -1,12 +1,10 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.matter import net.minecraft.ChatFormatting import net.minecraft.MethodsReturnNonnullByDefault import net.minecraft.core.Direction -import net.minecraft.core.NonNullList import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component -import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Rarity @@ -15,21 +13,23 @@ import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ICapabilityProvider import net.minecraftforge.common.util.LazyOptional -import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.formatMatter +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatMatter import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.tagNotNull import javax.annotation.ParametersAreNonnullByDefault @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault class MatterCapacitorItem : Item { - private inner class Matter(private val stack: ItemStack) : ICapabilityProvider, IMatterHandler { - private val resolver = LazyOptional.of { this } + private inner class Matter(private val stack: ItemStack) : ICapabilityProvider, IMatterStorage { + private val resolver = LazyOptional.of { this } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { return if (cap === MatteryCapability.MATTER) resolver.cast() else LazyOptional.empty() @@ -37,10 +37,8 @@ class MatterCapacitorItem : Item { override var storedMatter: Decimal get() { - val tag = stack.orCreateTag - return if (tag.contains("matter")) { - Decimal.deserializeNBT(tag["matter"]) - } else Decimal.ZERO + if (isCreative) return Decimal.LONG_MAX_VALUE + return stack.tag?.map("matter", Decimal.Companion::deserializeNBT) ?: Decimal.ZERO } set(value) { stack.tagNotNull.put("matter", value.serializeNBT()) @@ -54,11 +52,7 @@ class MatterCapacitorItem : Item { return if (isCreative) Decimal.LONG_MAX_VALUE else super.missingMatter } - override fun receiveMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveMatterInner(howMuch, simulate) - } - - override fun receiveMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { if (isCreative) return howMuch val new = storedMatter.plus(howMuch.coerceAtMost(maxInput)).coerceAtMost(capacity) val diff = new.minus(storedMatter) @@ -70,11 +64,7 @@ class MatterCapacitorItem : Item { return diff } - override fun extractMatterOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return extractMatterInner(howMuch, simulate) - } - - override fun extractMatterInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun extractMatter(howMuch: Decimal, simulate: Boolean): Decimal { if (isCreative) return howMuch val new = storedMatter.minus(howMuch.coerceAtMost(maxOutput)).moreThanZero() val diff = storedMatter.minus(new) @@ -86,7 +76,7 @@ class MatterCapacitorItem : Item { return diff } - override val direction = MatterDirection.BIDIRECTIONAL + override val matterFlow = FlowDirection.BI_DIRECTIONAL } private val _capacity: () -> Decimal @@ -94,35 +84,21 @@ class MatterCapacitorItem : Item { private val isCreative: Boolean - constructor(storage: Decimal) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { + constructor(storage: Decimal) : super(Properties().stacksTo(1)) { isCreative = false _capacity = { storage } } - constructor(storage: () -> Decimal) : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) { + constructor(storage: () -> Decimal) : super(Properties().stacksTo(1)) { isCreative = false _capacity = storage } - constructor() : super(Properties().stacksTo(1).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).rarity(Rarity.EPIC)) { + constructor() : super(Properties().stacksTo(1).rarity(Rarity.EPIC)) { isCreative = true _capacity = { Decimal.LONG_MAX_VALUE } } - override fun fillItemCategory(p_41391_: CreativeModeTab, p_41392_: NonNullList) { - super.fillItemCategory(p_41391_, p_41392_) - - if (!isCreative && allowedIn(p_41391_)) { - p_41392_.add(ItemStack(this).also { - it.getCapability(MatteryCapability.MATTER).ifPresentK { - if (it is Matter) { - it.storedMatter = it.maxStoredMatter - } - } - }) - } - } - override fun isBarVisible(p_150899_: ItemStack): Boolean { if (isCreative) return false @@ -158,8 +134,8 @@ class MatterCapacitorItem : Item { p_41423_.add( TranslatableComponent( "otm.item.matter.normal", - it.storedMatter.formatMatter(), - capacity.formatMatter() + it.storedMatter.formatMatter(formatAsReadable = ShiftPressedCond), + capacity.formatMatter(formatAsReadable = ShiftPressedCond) ).withStyle(ChatFormatting.GRAY) ) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt new file mode 100644 index 000000000..5f02f26c9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt @@ -0,0 +1,204 @@ +package ru.dbotthepony.mc.otm.item.matter + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.SlotAccess +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.ClickAction +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.config.ItemsConfig +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.matter.IMatterItem +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.matter.IMatterValue +import ru.dbotthepony.mc.otm.matter.MatterValue +import ru.dbotthepony.mc.otm.registry.MItems + +class MatterDustItem : Item(Properties().stacksTo(64)), IMatterItem { + private fun matter(stack: ItemStack): Decimal { + return stack.tag?.get("matter")?.let { return@let Decimal.deserializeNBT(it) } ?: return Decimal.ZERO + } + + private fun matter(stack: ItemStack, matter: Decimal) { + stack.orCreateTag["matter"] = matter.serializeNBT() + } + + override fun getMatterValue(stack: ItemStack): IMatterValue? { + val value = stack.tag?.get("matter") ?: return null + return MatterValue(Decimal.deserializeNBT(value), 1.0) + } + + fun moveIntoContainer(matterValue: Decimal, container: MatteryContainer, mainSlot: Int, stackingSlot: Int): Decimal { + @Suppress("name_shadowing") + var matterValue = matterValue + + while (matterValue > Decimal.ZERO) { + val stack = container[mainSlot] + + // первый слот пустой + if (stack.isEmpty) { + container[mainSlot] = ItemStack(this, 1).also { + matterValue -= addMatterValue(it, matterValue, false) + } + // первый слот не пустой, но мы можем влить туда материю + } else if (!isFull(stack) && stack.count == 1) { + matterValue -= addMatterValue(stack, matterValue, false) + container.setChanged(mainSlot) + // первый слот не пустой и мы не можем влить туда материю + } else { + val stack2 = container[stackingSlot] + + // второй слот пустой + if (stack2.isEmpty) { + container[stackingSlot] = ItemStack(this, 1).also { + matterValue -= addMatterValue(it, matterValue, false) + } + // второй слот не пустой, но мы можем влить туда материю + } else if (!isFull(stack2)) { + if (stack2.count != 1) { + return matterValue + } + + matterValue -= addMatterValue(stack2, matterValue, false) + container.setChanged(stackingSlot) + } + + // можем ли мы стакнуть материю из второго слота в первый? + if (!stack2.isEmpty && isFull(stack2)) { + if (ItemStack.isSameItemSameTags(stack, stack2) && container.getMaxStackSize(mainSlot, stack) >= stack.count + 1) { + stack.count++ + stack2.count-- + + container.setChanged(mainSlot) + container.setChanged(stackingSlot) + } else { + return matterValue + } + } + } + } + + return Decimal.ZERO + } + + override fun canDecompose(stack: ItemStack) = false + + override fun appendHoverText( + p_41421_: ItemStack, + p_41422_: Level?, + p_41423_: MutableList, + p_41424_: TooltipFlag + ) { + super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) + val matter = this.getMatterValue(p_41421_) + + if (matter == null) { + p_41423_.add(DESC) + } + + p_41423_.add(DESC2) + + if (matter != null) { + p_41423_.add(DESC3) + } + } + + fun addMatterValue(stack: ItemStack, matter: Decimal, simulate: Boolean): Decimal { + if (stack.count != 1) + return Decimal.ZERO + + val matterThis = matter(stack) + + if (matterThis >= ItemsConfig.MATTER_DUST_CAPACITY && matter.isPositive) + return Decimal.ZERO + + val newMatter = (matterThis + matter).coerceAtMost(ItemsConfig.MATTER_DUST_CAPACITY) + val diff = newMatter - matterThis + + if (!simulate) + matter(stack, newMatter) + + return diff + } + + fun isFull(stack: ItemStack): Boolean { + return matter(stack) >= ItemsConfig.MATTER_DUST_CAPACITY + } + + override fun overrideStackedOnOther( + pStack: ItemStack, + pSlot: Slot, + pAction: ClickAction, + pPlayer: Player + ): Boolean { + if (pStack.isEmpty || pStack.count > 1) + return super.overrideStackedOnOther(pStack, pSlot, pAction, pPlayer) + + if (pAction == ClickAction.SECONDARY && pSlot.allowModification(pPlayer)) { + if (!pSlot.hasItem()) { + val matterHalf = getMatterValue(pStack)?.matter?.div(2) ?: return true + + val halfStack = pStack.copyWithCount(1) + matter(halfStack, -addMatterValue(pStack, -matterHalf, pPlayer.level.isClientSide())) + pSlot.set(halfStack) + + if (getMatterValue(pStack)?.matter!! <= Decimal.ZERO) { + pStack.shrink(1) + } + + return true + } + } + + return super.overrideStackedOnOther(pStack, pSlot, pAction, pPlayer) + } + + override fun overrideOtherStackedOnMe( + pStack: ItemStack, + pOther: ItemStack, + pSlot: Slot, + pAction: ClickAction, + pPlayer: Player, + pAccess: SlotAccess + ): Boolean { + if (pStack.isEmpty || pStack.count > 1) + return super.overrideStackedOnOther(pStack, pSlot, pAction, pPlayer) + + if (pAction == ClickAction.SECONDARY && pSlot.allowModification(pPlayer)) { + if (pOther.isEmpty) { + val matterHalf = getMatterValue(pStack)?.matter?.div(2) ?: return true + + val halfStack = pStack.copyWithCount(1) + matter(halfStack, -addMatterValue(pStack, -matterHalf, pPlayer.level.isClientSide())) + pAccess.set(halfStack) + + return true + } else if (pOther.item is MatterDustItem && pOther.count == 1) { + val matterOther = getMatterValue(pOther)?.matter ?: return true + val diff = addMatterValue(pStack, matterOther, pPlayer.level.isClientSide()) + + if (matterOther - diff <= Decimal.ZERO) { + pOther.shrink(1) + } else { + matter(pStack, matterOther - diff) + } + + return true + } + } + + return super.overrideOtherStackedOnMe(pStack, pOther, pSlot, pAction, pPlayer, pAccess) + } + + companion object { + private val DESC = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc").withStyle(ChatFormatting.DARK_GRAY) + private val DESC2 = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc2").withStyle(ChatFormatting.GRAY) + private val DESC3 = TranslatableComponent("item.overdrive_that_matters.matter_dust.desc3").withStyle(ChatFormatting.DARK_GRAY) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt similarity index 79% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt index d9f83835a..6358a8258 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/PatternStorageItem.kt @@ -1,6 +1,5 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.matter -import ru.dbotthepony.mc.otm.OverdriveThatMatters import net.minecraft.world.item.ItemStack import net.minecraft.nbt.CompoundTag import net.minecraftforge.common.capabilities.ICapabilityProvider @@ -11,14 +10,14 @@ import net.minecraftforge.common.util.LazyOptional import net.minecraft.nbt.ListTag import net.minecraft.core.Direction import net.minecraft.network.chat.Component -import net.minecraft.util.Mth import net.minecraft.world.item.Item import net.minecraft.world.item.Rarity import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.Capability import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.core.ifHas +import ru.dbotthepony.mc.otm.core.filterNotNull +import ru.dbotthepony.mc.otm.core.nbt.map import java.util.* import java.util.stream.Stream @@ -27,17 +26,17 @@ class PatternStorageItem : Item { val capacity get() = _capacity.invoke() var isCreative: Boolean - constructor(capacity: Int) : super(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1)) { + constructor(capacity: Int) : super(Properties().stacksTo(1)) { _capacity = { capacity } isCreative = false } - constructor(capacity: () -> Int) : super(Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1)) { + constructor(capacity: () -> Int) : super(Properties().stacksTo(1)) { _capacity = capacity isCreative = false } - constructor() : super(Properties().rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1)) { + constructor() : super(Properties().rarity(Rarity.EPIC).stacksTo(1)) { isCreative = true _capacity = { Int.MAX_VALUE } } @@ -101,27 +100,23 @@ class PatternStorageItem : Item { } override val storedPatterns: Int get() { - stack.tag?.ifHas("otm_patterns", ListTag::class.java) { - return it.size - } - - return 0 + return stack.tag?.map("otm_patterns") { it: ListTag -> + it.size + } ?: 0 } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { return if (cap == MatteryCapability.PATTERN) resolver.cast() else LazyOptional.empty() } - override val patterns: Stream get() { - stack.tag?.ifHas("otm_patterns", ListTag::class.java) { - return it.stream().map { PatternState.deserializeNBT(it) }.filter { it != null } as Stream - } - - return Stream.empty() + override val patterns: Stream get() { + return stack.tag?.map("otm_patterns") { it: ListTag -> + it.stream().map { PatternState.deserializeNBT(it) }.filterNotNull() + } ?: Stream.empty() } override fun insertPattern( - pattern: IPatternState, + pattern: PatternState, onlyUpdate: Boolean, simulate: Boolean ): PatternInsertStatus { @@ -134,7 +129,7 @@ class PatternStorageItem : Item { if (simulate) { if (patternCapacity > 0) - return PatternInsertInserted(pattern.asImmutable()) + return PatternInsertInserted(pattern) else return PatternInsertFailure } @@ -154,7 +149,7 @@ class PatternStorageItem : Item { list[i] = pattern.serializeNBT() } - return PatternInsertUpdated(pattern.asImmutable(), state) + return PatternInsertUpdated(pattern, state) } } else { invalidCounter++ @@ -166,7 +161,7 @@ class PatternStorageItem : Item { if (invalidCounter > 0) { if (simulate) - return PatternInsertInserted(pattern.asImmutable()) + return PatternInsertInserted(pattern) for (i in list.indices) { val state = PatternState.deserializeNBT(list.getCompound(i)) @@ -184,4 +179,4 @@ class PatternStorageItem : Item { return PatternInsertInserted(pattern) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt new file mode 100644 index 000000000..da381bb92 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/ExplosiveHammerItem.kt @@ -0,0 +1,338 @@ +package ru.dbotthepony.mc.otm.item.tool + +import com.google.common.collect.ImmutableMultimap +import com.google.common.collect.Multimap +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import net.minecraft.ChatFormatting +import net.minecraft.core.BlockPos +import net.minecraft.network.chat.Component +import net.minecraft.network.protocol.game.ClientboundExplodePacket +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.sounds.SoundEvents +import net.minecraft.sounds.SoundSource +import net.minecraft.world.InteractionHand +import net.minecraft.world.InteractionResultHolder +import net.minecraft.world.damagesource.DamageSource +import net.minecraft.world.entity.EquipmentSlot +import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.ai.attributes.Attribute +import net.minecraft.world.entity.ai.attributes.AttributeModifier +import net.minecraft.world.entity.ai.attributes.Attributes +import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.UseAnim +import net.minecraft.world.level.Explosion +import net.minecraft.world.level.Level +import net.minecraft.world.phys.AABB +import net.minecraft.world.phys.Vec3 +import net.minecraftforge.common.ForgeHooks +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.common.Tags +import net.minecraftforge.event.entity.player.PlayerInteractEvent +import net.minecraftforge.event.level.BlockEvent +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.config.ToolsConfig +import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.* +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.registry.ExplosiveHammerDamageSource +import ru.dbotthepony.mc.otm.registry.HammerNailDamageSource +import ru.dbotthepony.mc.otm.registry.MDamageTypes +import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger +import java.util.function.Predicate + +class ExplosiveHammerItem(durability: Int = 512) : Item(Properties().stacksTo(1).fireResistant().durability(durability)) { + override fun canBeHurtBy(pDamageSource: DamageSource): Boolean { + return super.canBeHurtBy(pDamageSource) && !pDamageSource.isExplosion + } + + fun isPrimed(itemStack: ItemStack): Boolean { + return itemStack.tag?.getBoolean("primed") ?: false + } + + fun prime(itemStack: ItemStack) { + itemStack.tagNotNull["primed"] = true + } + + fun unprime(itemStack: ItemStack) { + if (isPrimed(itemStack)) + itemStack.tagNotNull["primed"] = false + } + + fun canPrime(player: Player): Boolean { + return player.inventory.clearOrCountMatchingItems(GUNPOWDER_PREDICATE, 0, player.inventoryMenu.craftSlots) > 0 && + player.inventory.clearOrCountMatchingItems(IRON_NUGGET_PREDICATE, 0, player.inventoryMenu.craftSlots) > 0 + } + + private val aabb = AABB(-0.1, -0.1, -0.1, 0.1, 0.1, 0.1) + + val attributes = immutableMultimap { + put(Attributes.ATTACK_DAMAGE, AttributeModifier(BASE_ATTACK_DAMAGE_UUID, "Weapon modifier", 3.0, AttributeModifier.Operation.ADDITION)) + put(Attributes.ATTACK_SPEED, AttributeModifier(BASE_ATTACK_SPEED_UUID, "Weapon modifier", -3.4, AttributeModifier.Operation.ADDITION)) + } + + override fun hasCraftingRemainingItem(itemStack: ItemStack): Boolean = true + + override fun getCraftingRemainingItem(itemStack: ItemStack): ItemStack { + val player = ForgeHooks.getCraftingPlayer() ?: return itemStack.copy() + if (player.level.isClientSide) return itemStack.copy() + + if (!isPrimed(itemStack)) { + itemStack.hurtAndBreak(1, player) {} + } else { + val level = player.level as ServerLevel + + itemStack.hurtAndBreak(level.random.nextInt(1, 20), player) {} + unprime(itemStack) + + val (ex, ey, ez) = Vector.atCenterOf(player.blockPosition()) + + val exp = Explosion(player.level, player, ex, ey, ez, 1f, false, if (ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) Explosion.BlockInteraction.DESTROY_WITH_DECAY else Explosion.BlockInteraction.KEEP) + exp.explode() + exp.finalizeExplosion(true) + + if (!ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) + exp.clearToBlow() + + MatteryBlockEntity.watchingPlayers(player.position(), level) + .filter { it.position.distanceTo(player.position()) <= 64.0 } + .forEach { + it.connection.send(ClientboundExplodePacket(ex, ey, ez, 1f, exp.toBlow, exp.hitPlayers[it])) + } + + if (!player.isCreative) { + val copy = itemStack.copy() + + if (!itemStack.isEmpty && player.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_CHANCE) { + val (x, y, z) = player.position + val entity = ItemEntity(level, x, y, z, copy) + + val (xv, yv, zv) = player.getViewVector(0f) + + entity.deltaMovement = Vec3( + player.random.nextDouble() * xv * -0.5, + player.random.nextDouble() * yv * -0.5, + (player.random.nextDouble() + 0.65) * zv * -2.0, + ) + + level.addFreshEntity(entity) + + if (player.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_DAMAGE_CHANCE) { + player.invulnerableTime = 0 + val dmg = ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE + player.random.nextDouble() * (ToolsConfig.ExplosiveHammer.FLY_OFF_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE) + player.hurt(ExplosiveHammerDamageSource(inflictor = copy), dmg.toFloat()) + } + + itemStack.shrink(itemStack.count) + } else if (player.random.nextDouble() <= ToolsConfig.ExplosiveHammer.SELF_HARM_CHANCE) { + player.invulnerableTime = 0 + val dmg = ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE + player.random.nextDouble() * (ToolsConfig.ExplosiveHammer.SELF_HARM_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE) + player.hurt(ExplosiveHammerDamageSource(inflictor = copy), dmg.toFloat()) + } + } + } + + return itemStack.copy() + } + + override fun getAttributeModifiers( + slot: EquipmentSlot, + itemStack: ItemStack + ): Multimap { + if (slot != EquipmentSlot.MAINHAND) { + return ImmutableMultimap.of() + } + + return attributes + } + + fun attackAt(itemStack: ItemStack, attacker: LivingEntity, pos: Vec3, aim: Vec3, hand: InteractionHand) { + if (!isPrimed(itemStack) || attacker.level.isClientSide || attacker is Player && attacker.getAttackStrengthScale(0.4f) < 0.98f) + return + + val (ex, ey, ez) = pos + + // взрыв в месте удара молотком + val exp = Explosion(attacker.level, attacker, ex, ey, ez, 1f, false, if (ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) Explosion.BlockInteraction.DESTROY_WITH_DECAY else Explosion.BlockInteraction.KEEP) + exp.explode() + exp.finalizeExplosion(true) + + if (!ToolsConfig.ExplosiveHammer.EXPLOSION_DAMAGE_BLOCKS) + exp.clearToBlow() + + val level = attacker.level as ServerLevel + + val hitEntities = ObjectArraySet() + hitEntities.add(attacker) + var canTravel = ToolsConfig.ExplosiveHammer.MAX_TRAVEL + var rayPos = pos + var lastBlockPos: BlockPos? = null + + // "полёт" гвоздя + // так как у меня алгоритм "своеобразный", я изобрету велосипед ибо я такой нехороший и непослушный + // Трассировка луча используя наивный метод + while (canTravel > 0.0) { + val blockPos = BlockPos(rayPos) + + if (blockPos != lastBlockPos) { + if (!level.hasChunkAt(blockPos)) break + + val block = level.getBlockState(blockPos) + + if (!block.isAir) { + val resist = block.getExplosionResistance(level, blockPos) * ToolsConfig.ExplosiveHammer.TRAVEL_EXPLOSION_RESIST_MULT + + if (resist >= 0.0) { + if (resist <= canTravel) { + canTravel -= resist + val cond = ToolsConfig.ExplosiveHammer.DAMAGE_BLOCKS && + (attacker !is Player || level.mayInteract(attacker, blockPos) && !MinecraftForge.EVENT_BUS.post(BlockEvent.BreakEvent(level, blockPos, block, attacker))) + + if (cond) { + level.gracefulBlockBreak(blockPos, block) + } + } else { + break + } + } + } + + lastBlockPos = blockPos + } + + val rayBox = aabb.move(rayPos) + val entities = level.getEntities(null, rayBox) { it is LivingEntity && it.isAlive && !it.isSpectator && hitEntities.add(it) } as List + val damageSource = HammerNailDamageSource(attacker, itemStack) + + for (it in entities) { + val damage = canTravel * ToolsConfig.ExplosiveHammer.TRAVEL_DAMAGE_MULT + canTravel -= it.maxHealth * ToolsConfig.ExplosiveHammer.TRAVEL_MAX_HEALTH_MULT + + it.hurt(damageSource, damage.toFloat()) + + if (attacker is ServerPlayer) { + NailedEntityTrigger.trigger(attacker, it, damage.toFloat(), damageSource) + } + } + + val delta = canTravel.coerceAtMost(0.25) + canTravel -= delta + rayPos += aim * delta + } + + MatteryBlockEntity.watchingPlayers(pos, level) + .filter { it.position.distanceTo(pos) <= 64.0 } + .forEach { + it.connection.send(ClientboundExplodePacket(ex, ey, ez, 1f, exp.toBlow, exp.hitPlayers[it])) + } + + if (attacker !is Player || !attacker.isCreative) { + unprime(itemStack) + + val copy = itemStack.copy() + + itemStack.hurtAndBreak(level.random.nextInt(1, 20), attacker) { + it.broadcastBreakEvent(hand) + } + + if (!itemStack.isEmpty && attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_CHANCE) { + attacker.setItemInHand(hand, ItemStack.EMPTY) + + val (x, y, z) = attacker.position + val entity = ItemEntity(level, x, y, z, itemStack) + + val (xv, yv, zv) = attacker.getViewVector(0f) + + entity.deltaMovement = Vec3( + attacker.random.nextDouble() * xv * -0.5, + attacker.random.nextDouble() * yv * -0.5, + (attacker.random.nextDouble() + 0.65) * zv * -2.0, + ) + + level.addFreshEntity(entity) + + if (attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.FLY_OFF_DAMAGE_CHANCE) { + attacker.invulnerableTime = 0 + val dmg = ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE + attacker.random.nextDouble() * (ToolsConfig.ExplosiveHammer.FLY_OFF_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.FLY_OFF_MIN_DAMAGE) + attacker.hurt(ExplosiveHammerDamageSource(inflictor = copy), dmg.toFloat()) + } + } else if (attacker.random.nextDouble() <= ToolsConfig.ExplosiveHammer.SELF_HARM_CHANCE) { + attacker.invulnerableTime = 0 + val dmg = ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE + attacker.random.nextDouble() * (ToolsConfig.ExplosiveHammer.SELF_HARM_MAX_DAMAGE - ToolsConfig.ExplosiveHammer.SELF_HARM_MIN_DAMAGE) + attacker.hurt(ExplosiveHammerDamageSource(inflictor = copy), dmg.toFloat()) + } + } + } + + override fun hurtEnemy(pStack: ItemStack, pTarget: LivingEntity, pAttacker: LivingEntity): Boolean { + val aim = pAttacker.getViewVector(0f) + val pos = pAttacker.eyePosition + aim * pTarget.position.distanceTo(pAttacker.position) + attackAt(pStack, pAttacker, pos, aim, InteractionHand.MAIN_HAND) + return true + } + + override fun appendHoverText(pStack: ItemStack, pLevel: Level?, pTooltipComponents: MutableList, pIsAdvanced: TooltipFlag) { + super.appendHoverText(pStack, pLevel, pTooltipComponents, pIsAdvanced) + + pTooltipComponents.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY)) + + if (isPrimed(pStack)) { + pTooltipComponents.add(TranslatableComponent("$descriptionId.primed").withStyle(ChatFormatting.GRAY)) + } else { + pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed0").withStyle(ChatFormatting.GRAY)) + pTooltipComponents.add(TranslatableComponent("$descriptionId.not_primed1").withStyle(ChatFormatting.GRAY)) + } + } + + override fun getUseAnimation(stack: ItemStack): UseAnim { + return if (isPrimed(stack)) super.getUseAnimation(stack) else UseAnim.CROSSBOW + } + + override fun getUseDuration(stack: ItemStack): Int { + return if (isPrimed(stack)) super.getUseDuration(stack) else 20 + } + + override fun use(level: Level, player: Player, hand: InteractionHand): InteractionResultHolder { + val stack = player.getItemInHand(hand) + + if (stack.isEmpty || isPrimed(stack) || !canPrime(player)) + return super.use(level, player, hand) + + player.startUsingItem(hand) + return InteractionResultHolder.consume(stack) + } + + override fun finishUsingItem(stack: ItemStack, level: Level, entity: LivingEntity): ItemStack { + + if (entity is Player && canPrime(entity)) { + if (level is ServerLevel) { + entity.inventory.clearOrCountMatchingItems(GUNPOWDER_PREDICATE, 1, entity.inventoryMenu.craftSlots) + entity.inventory.clearOrCountMatchingItems(IRON_NUGGET_PREDICATE, 1, entity.inventoryMenu.craftSlots) + + prime(stack) + } + + level.playSound(entity, entity.blockPosition(), SoundEvents.CROSSBOW_LOADING_END, SoundSource.PLAYERS, 1.0F, 1.0F) + } + + return super.finishUsingItem(stack, level, entity) + } + + companion object { + val GUNPOWDER_PREDICATE = Predicate { stack: ItemStack -> stack.`is`(Tags.Items.GUNPOWDER) } + val IRON_NUGGET_PREDICATE = Predicate { stack: ItemStack -> stack.`is`(Tags.Items.NUGGETS_IRON) } + + fun onLeftClickBlock(event: PlayerInteractEvent.LeftClickBlock) { + val item = event.itemStack.item + + if (item is ExplosiveHammerItem) { + item.attackAt(event.itemStack, event.entity, Vector.atCenterOf(event.pos), event.face?.opposite?.normal?.toDoubleVector() ?: event.entity.getViewVector(0f), event.hand) + event.isCanceled = true + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt new file mode 100644 index 000000000..fcb65eaf3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/tool/MatteryAxeItem.kt @@ -0,0 +1,17 @@ +package ru.dbotthepony.mc.otm.item.tool + +import net.minecraft.tags.BlockTags +import net.minecraft.world.item.AxeItem +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Tier +import net.minecraft.world.level.block.state.BlockState +import ru.dbotthepony.mc.otm.config.ToolsConfig + +class MatteryAxeItem(pTier: Tier, pAttackDamageModifier: Float, pAttackSpeedModifier: Float, pProperties: Properties) : AxeItem(pTier, pAttackDamageModifier, pAttackSpeedModifier, pProperties) { + override fun getDestroySpeed(pStack: ItemStack, pState: BlockState): Float { + if (pState.`is`(BlockTags.LEAVES) && ToolsConfig.AXES_BREAK_LEAVES_INSTANTLY) + return 64f + + return super.getDestroySpeed(pStack, pState) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt index 40c6a9f5d..964402223 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/AbstractWeaponItem.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.item.weapon import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.math.Vector3f import net.minecraft.client.Minecraft import net.minecraft.client.model.HumanoidModel import net.minecraft.client.renderer.block.model.ItemTransforms @@ -20,18 +19,32 @@ import net.minecraftforge.client.event.RenderPlayerEvent import net.minecraftforge.client.event.ViewportEvent import net.minecraftforge.event.TickEvent import net.minecraftforge.fml.LogicalSide -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.capability.matteryEnergy import ru.dbotthepony.mc.otm.client.font -import ru.dbotthepony.mc.otm.client.render.drawRect -import ru.dbotthepony.mc.otm.client.render.setDrawColor -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.client.render.draw +import ru.dbotthepony.mc.otm.client.render.renderRect +import ru.dbotthepony.mc.otm.core.math.Angle +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.bezierCurve +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.linearInterpolation +import ru.dbotthepony.mc.otm.core.math.rotateX +import ru.dbotthepony.mc.otm.core.math.rotateY +import ru.dbotthepony.mc.otm.core.math.rotateZ +import ru.dbotthepony.mc.otm.core.nbt.EMPTY_UUID +import ru.dbotthepony.mc.otm.core.nbt.booleans +import ru.dbotthepony.mc.otm.core.nbt.ints +import ru.dbotthepony.mc.otm.core.nbt.uuids +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.core.util.formatPower +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.WeaponNetworkChannel import java.util.* -import java.util.function.Supplier +import kotlin.collections.set import kotlin.math.PI import kotlin.math.abs import kotlin.reflect.KClass @@ -47,16 +60,12 @@ enum class WeaponScopePacket(val scope: Boolean) : MatteryPacket { buff.writeBoolean(scope) } - override fun play(context: Supplier) { - context.get().packetHandled = true - + override fun play(context: MNetworkContext) { // TODO: Manual synchronization - context.get().enqueueWork { - val stack = context.get().sender!!.mainHandItem - val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork + val stack = context.sender!!.mainHandItem + val item = stack.item as? AbstractWeaponItem<*> ?: return - item.dataTable(stack).wantsToScope = scope - } + item.dataTable(stack).wantsToScope = scope } companion object { @@ -71,26 +80,22 @@ enum class WeaponFireInputPacket(val primary: Boolean) : MatteryPacket { buff.writeBoolean(primary) } - override fun play(context: Supplier) { - context.get().packetHandled = true - + override fun play(context: MNetworkContext) { // TODO: Manual synchronization - context.get().enqueueWork { - val stack = context.get().sender!!.mainHandItem - val item = stack.item as? AbstractWeaponItem<*> ?: return@enqueueWork + val stack = context.sender!!.mainHandItem + val item = stack.item as? AbstractWeaponItem<*> ?: return - // Listen server: client and server thread compete for lock - // so it is very likely item is being in predicted context - val predictedData = item.dataTable - item.dataTable = null + // Listen server: client and server thread compete for lock + // so it is very likely item is being in predicted context + val predictedData = item.dataTable + item.dataTable = null - if (primary) - item.tryPrimaryFire(stack, context.get().sender!!) - else - item.trySecondaryFire(stack, context.get().sender!!) + if (primary) + item.tryPrimaryFire(stack, context.sender) + else + item.trySecondaryFire(stack, context.sender) - (item as AbstractWeaponItem).dataTable = predictedData - } + (item as AbstractWeaponItem).dataTable = predictedData } companion object { @@ -131,8 +136,7 @@ open class WeaponDataTable(val tag: CompoundTag) { } } -abstract class AbstractWeaponItem(val tables: KClass, rarity: Rarity = Rarity.UNCOMMON) : Item( - Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB).stacksTo(1).rarity(rarity)) { +abstract class AbstractWeaponItem(val tables: KClass, properties: Properties = Properties().stacksTo(1).rarity(Rarity.RARE)) : Item(properties) { fun makeDataTable(tag: CompoundTag) = tables.primaryConstructor!!.call(tag) /** @@ -286,7 +290,7 @@ abstract class AbstractWeaponItem(val tables: KClass, ra if (it.compatibleDataTable(predictedData)) (it as AbstractWeaponItem).dataTable = predictedData - val interp = linearInterpolation( + val interp = ru.dbotthepony.mc.otm.core.math.linearInterpolation( it.ironSightsProgress( player.mainHandItem, event.partialTick @@ -295,7 +299,9 @@ abstract class AbstractWeaponItem(val tables: KClass, ra val time = System.nanoTime() val diff = abs(lastFovTime - time) - lastFov = linearInterpolation(diff.coerceIn(0L, 10_000_000L).toDouble() / 10_000_000.0, lastFov, interp).coerceAtLeast(0.001) + lastFov = ru.dbotthepony.mc.otm.core.math.linearInterpolation( + diff.coerceIn(0L, 10_000_000L).toDouble() / 10_000_000.0, lastFov, interp + ).coerceAtLeast(0.001) lastFovTime = time event.fov /= lastFov @@ -416,9 +422,9 @@ abstract class AbstractWeaponItem(val tables: KClass, ra yaw += (rotFire.yaw + (predictedData?.fireAnimDeviation?.yaw ?: 0.0)) * fireAnim * (1.0 - progress * 0.6) roll += (rotFire.roll + (predictedData?.fireAnimDeviation?.roll ?: 0.0)) * fireAnim * (1.0 - progress * 0.6) - pose.mulPose(Vector3f.ZP.rotation(roll.toFloat())) - pose.mulPose(Vector3f.YP.rotation(yaw.toFloat())) - pose.mulPose(Vector3f.XP.rotation(pitch.toFloat())) + pose.rotateZ(roll.toFloat()) + pose.rotateY(yaw.toFloat()) + pose.rotateX(pitch.toFloat()) itemInHandRenderer.renderItem( player, @@ -438,30 +444,27 @@ abstract class AbstractWeaponItem(val tables: KClass, ra RenderSystem.setShaderColor(1f, 1f, 1f, 1f) pose.translate(-0.85, 0.25, -0.25) - pose.mulPose(Vector3f.ZP.rotationDegrees(180f)) - pose.mulPose(Vector3f.YP.rotationDegrees(180f)) + pose.rotateZ(PI.toFloat()) + pose.rotateY(PI.toFloat()) pose.scale(0.01f, 0.01f, 0.01f) - setDrawColor(holoHudBackground) - drawRect(pose, -2f, -2f, 72f, 34f) + renderRect(pose.last().pose(), -2f, -2f, 72f, 34f, color = holoHudBackground) stack.matteryEnergy?.let { pose.pushPose() pose.translate(0.0, 0.0, -1.0) pose.scale(0.7f, 0.7f, 0.7f) val text = it.batteryLevel.formatPower() - font.draw(pose, text, 2f, 2f, RGBAColor.WHITE.toInt()) + font.draw(pose, text, 2f, 2f, color = RGBAColor.WHITE) pose.popPose() } pose.translate(60.0, 0.0, 0.0) - setDrawColor(heatBackground) - drawRect(pose, -1f, -1f, 9f, 32f) + renderRect(pose.last().pose(), -1f, -1f, 9f, 32f, color = heatBackground) val heat = item.heatProgress(stack, event.partialTick.toDouble()).toFloat() - setDrawColor(linearInterpolation(heat, initialHeatColor, finalHeatColor)) - drawRect(pose, 0f, 30f * (1f - heat), 7f, 30f * heat) + renderRect(pose.last().pose(), 0f, 30f * (1f - heat), 7f, 30f * heat, color = linearInterpolation(heat, initialHeatColor, finalHeatColor)) } pose.popPose() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EnergySwordItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt similarity index 67% rename from src/main/kotlin/ru/dbotthepony/mc/otm/item/EnergySwordItem.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt index 248b2cffd..177169b3a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/EnergySwordItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt @@ -1,10 +1,9 @@ -package ru.dbotthepony.mc.otm.item +package ru.dbotthepony.mc.otm.item.weapon import com.google.common.collect.ImmutableMultimap import com.google.common.collect.Multimap import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos -import net.minecraft.core.NonNullList import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.world.entity.EquipmentSlot @@ -13,7 +12,6 @@ import net.minecraft.world.entity.ai.attributes.Attribute import net.minecraft.world.entity.ai.attributes.AttributeModifier import net.minecraft.world.entity.ai.attributes.Attributes import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Rarity @@ -21,6 +19,7 @@ import net.minecraft.world.item.TooltipFlag import net.minecraft.world.item.Vanishable import net.minecraft.world.item.enchantment.Enchantment import net.minecraft.world.item.enchantment.EnchantmentCategory +import net.minecraft.world.item.enchantment.Enchantments import net.minecraft.world.level.Level import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.state.BlockState @@ -29,37 +28,27 @@ import net.minecraftforge.common.ForgeConfigSpec import net.minecraftforge.common.ToolAction import net.minecraftforge.common.ToolActions import net.minecraftforge.common.capabilities.ICapabilityProvider -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.capability.EnergyConsumerItem -import ru.dbotthepony.mc.otm.capability.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.energy.EnergyConsumerItem +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact -import ru.dbotthepony.mc.otm.capability.getBarColor -import ru.dbotthepony.mc.otm.capability.getBarWidth +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth import ru.dbotthepony.mc.otm.capability.matteryEnergy import ru.dbotthepony.mc.otm.capability.matteryPlayer -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.DecimalConfigValue +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.mc.otm.core.defineDecimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.math.nextVariance import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.registry.EMPDamageSource -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.registry.MDamageTypes +import ru.dbotthepony.mc.otm.registry.MatteryDamageSource -/** - * This is called from [net.minecraft.world.item.enchantment.EnchantmentHelper.getSweepingDamageRatio] - * by coremod patch - */ -fun getSweepingDamageRatioHook(ply: LivingEntity): Float? { - if (ply.mainHandItem.item is EnergySwordItem && ply.mainHandItem.matteryEnergy?.extractEnergyInnerExact(EnergySwordItem.ENERGY_PER_SWING, true)?.isPositive == true) { - return 1f - } - - return null -} - -class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)), Vanishable { +class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE)), Vanishable { val chargedAttributes: Multimap val dischargedAttributes: Multimap @@ -84,7 +73,7 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov } override fun canApplyAtEnchantingTable(stack: ItemStack, enchantment: Enchantment): Boolean { - return enchantment.category == EnchantmentCategory.WEAPON + return enchantment.category == EnchantmentCategory.WEAPON && enchantment != Enchantments.SWEEPING_EDGE } override fun getEnchantmentValue(stack: ItemStack): Int { @@ -117,7 +106,8 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov if (attacker is Player && attacker.isCreative) { victim.matteryPlayer?.let { if (it.isAndroid) { - victim.hurt(EMPDamageSource(attacker), 8f) + victim.invulnerableTime = 0 + victim.hurt(EMPDamageSource(attacker, itemStack), 8f) } } @@ -125,11 +115,12 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov } itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK { - if (!it.extractEnergyInnerExact(ENERGY_PER_SWING, false).isZero) { + if (it.extractEnergyExact(ENERGY_PER_SWING, false)) { + it.extractEnergy(attacker.level.random.nextVariance(ENERGY_PER_SWING_VARIANCE), false) victim.matteryPlayer?.let { - if (it.isAndroid) { - it.androidEnergy.extractEnergyInner(ENERGY_ZAP, false) - victim.hurt(EMPDamageSource(attacker), 8f) + if (it.isAndroid && it.androidEnergy.extractEnergyExact(ENERGY_ZAP, false)) { + it.androidEnergy.extractEnergy(attacker.level.random.nextVariance(ENERGY_ZAP_VARIANCE), false) + victim.hurt(EMPDamageSource(attacker, itemStack), 8f) } } } @@ -171,18 +162,20 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov p_41417_: Level, blockState: BlockState, p_41419_: BlockPos, - p_41420_: LivingEntity + user: LivingEntity ): Boolean { - if (blockState.getDestroySpeed(p_41417_, p_41419_) != 0f && (p_41420_ !is Player || !p_41420_.isCreative)) { + if (blockState.getDestroySpeed(p_41417_, p_41419_) != 0f && (user !is Player || !user.isCreative)) { val energy = itemStack.matteryEnergy when (blockState.material) { Material.PLANT, Material.REPLACEABLE_PLANT, Material.VEGETABLE, Material.LEAVES -> - energy?.extractEnergyInnerExact(PLANT_POWER_COST, false) + if (energy?.extractEnergyExact(PLANT_POWER_COST, false) == true) + energy.extractEnergyExact(user.level.random.nextVariance(PLANT_POWER_COST_VARIANCE), false) } if (blockState.`is`(Blocks.COBWEB)) { - energy?.extractEnergyInnerExact(COBWEB_POWER_COST, false) + if (energy?.extractEnergyExact(COBWEB_POWER_COST, false) == true) + energy.extractEnergyExact(user.level.random.nextVariance(COBWEB_POWER_COST_VARIANCE), false) } } @@ -193,16 +186,6 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov return EnergyConsumerItem(stack, MAX_ENERGY) } - override fun fillItemCategory(p_41391_: CreativeModeTab, p_41392_: NonNullList) { - super.fillItemCategory(p_41391_, p_41392_) - - if (allowedIn(p_41391_)) { - p_41392_.add(ItemStack(this).also { - it.matteryEnergy?.receiveEnergyInner(MAX_ENERGY, false) - }) - } - } - override fun getAttributeModifiers( slot: EquipmentSlot, itemStack: ItemStack @@ -221,7 +204,7 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov } override fun canPerformAction(stack: ItemStack, toolAction: ToolAction): Boolean { - if (stack.matteryEnergy?.extractEnergyInnerExact(ENERGY_PER_SWING, true)?.isPositive == true) { + if (stack.matteryEnergy?.extractEnergyExact(ENERGY_PER_SWING, true) == true) { return ToolActions.DEFAULT_SWORD_ACTIONS.contains(toolAction) } @@ -231,28 +214,49 @@ class EnergySwordItem : Item(Properties().stacksTo(1).rarity(Rarity.RARE).tab(Ov companion object { val MAX_ENERGY get() = _MAX_ENERGY.get() val ENERGY_ZAP get() = _ENERGY_ZAP.get() + val ENERGY_ZAP_VARIANCE get() = _ENERGY_ZAP_VARIANCE.get() val ENERGY_PER_SWING get() = _ENERGY_PER_SWING.get() + val ENERGY_PER_SWING_VARIANCE get() = _ENERGY_PER_SWING_VARIANCE.get() val COBWEB_POWER_COST get() = _COBWEB_POWER_COST.get() + val COBWEB_POWER_COST_VARIANCE get() = _COBWEB_POWER_COST_VARIANCE.get() val PLANT_POWER_COST get() = _PLANT_POWER_COST.get() + val PLANT_POWER_COST_VARIANCE get() = _PLANT_POWER_COST_VARIANCE.get() private var _MAX_ENERGY: DecimalConfigValue by WriteOnce() private var _ENERGY_ZAP: DecimalConfigValue by WriteOnce() + private var _ENERGY_ZAP_VARIANCE: DecimalConfigValue by WriteOnce() private var _ENERGY_PER_SWING: DecimalConfigValue by WriteOnce() + private var _ENERGY_PER_SWING_VARIANCE: DecimalConfigValue by WriteOnce() private var _COBWEB_POWER_COST: DecimalConfigValue by WriteOnce() + private var _COBWEB_POWER_COST_VARIANCE: DecimalConfigValue by WriteOnce() private var _PLANT_POWER_COST: DecimalConfigValue by WriteOnce() + private var _PLANT_POWER_COST_VARIANCE: DecimalConfigValue by WriteOnce() fun registerConfig(builder: ForgeConfigSpec.Builder) { - builder.comment("Energy sword values").push("energy_sword") + builder.comment("Energy sword values").push("EnergySword") _MAX_ENERGY = builder.defineDecimal("MAX_ENERGY", Decimal(500_000), Decimal.ZERO) - _ENERGY_ZAP = builder.defineDecimal("ENERGY_ZAP", Decimal(4_000), Decimal.ZERO) + _ENERGY_ZAP = builder.comment("Extra energy required when hitting androids").defineDecimal("ENERGY_ZAP", Decimal(4_000), Decimal.ZERO) + _ENERGY_ZAP_VARIANCE = builder.comment("Random deviation from ENERGY_ZAP").defineDecimal("ENERGY_ZAP_VARIANCE", Decimal(800), Decimal.ZERO) _ENERGY_PER_SWING = builder.defineDecimal("ENERGY_PER_SWING", Decimal(2_000), Decimal.ZERO) + _ENERGY_PER_SWING_VARIANCE = builder.comment("Random deviation from ENERGY_PER_SWING").defineDecimal("ENERGY_PER_SWING_VARIANCE", Decimal(500), Decimal.ZERO) _COBWEB_POWER_COST = builder.defineDecimal("COBWEB_POWER_COST", Decimal(2_500), Decimal.ZERO) + _COBWEB_POWER_COST_VARIANCE = builder.comment("Random deviation from COBWEB_POWER_COST").defineDecimal("COBWEB_POWER_COST_VARIANCE", Decimal(500), Decimal.ZERO) _PLANT_POWER_COST = builder.defineDecimal("PLANT_POWER_COST", Decimal(500), Decimal.ZERO) + _PLANT_POWER_COST_VARIANCE = builder.comment("Random deviation from PLANT_POWER_COST").defineDecimal("PLANT_POWER_COST_VARIANCE", Decimal(100), Decimal.ZERO) builder.pop() } + @JvmStatic + fun getSweepingDamageRatioHook(ply: LivingEntity): Float? { + if (ply.mainHandItem.item is EnergySwordItem && ply.mainHandItem.matteryEnergy?.extractEnergyExact(ENERGY_PER_SWING, true) == true) { + return 1f + } + + return null + } + private val DESCRIPTION = TranslatableComponent("item.overdrive_that_matters.energy_sword.desc").withStyle(ChatFormatting.DARK_GRAY) private val DESCRIPTION2 = TranslatableComponent("item.overdrive_that_matters.energy_sword.desc2").withStyle(ChatFormatting.DARK_GRAY) private val DESCRIPTION3 = TranslatableComponent("item.overdrive_that_matters.energy_sword.desc3").withStyle(ChatFormatting.DARK_GRAY) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt index 13c13645a..053b96d5e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaRifleItem.kt @@ -3,9 +3,10 @@ package ru.dbotthepony.mc.otm.item.weapon import net.minecraft.sounds.SoundSource import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.core.Vector +import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact +import ru.dbotthepony.mc.otm.core.math.Angle +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.entity.PlasmaProjectile import ru.dbotthepony.mc.otm.core.position import ru.dbotthepony.mc.otm.registry.MSoundEvents @@ -50,13 +51,13 @@ class PlasmaRifleItem : PlasmaWeaponItem(PlasmaWeaponData ) if (!player.abilities.instabuild) - energyData(itemStack).extractEnergyInner(ENERGY_PER_SHOT, false) + energyData(itemStack).extractEnergy(ENERGY_PER_SHOT, false) return true } override fun canPrimaryFire(itemStack: ItemStack, player: Player, dt: PlasmaWeaponDataTable): Boolean { - return super.canPrimaryFire(itemStack, player, dt) && (player.abilities.instabuild || energyData(itemStack).extractEnergyInnerExact(ENERGY_PER_SHOT, true).isPositive) + return super.canPrimaryFire(itemStack, player, dt) && (player.abilities.instabuild || energyData(itemStack).extractEnergyExact(ENERGY_PER_SHOT, true)) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt index d16fe3a3f..71eccdb0e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/PlasmaWeaponItem.kt @@ -17,15 +17,27 @@ import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.* import ru.dbotthepony.mc.otm.capability.* +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl +import ru.dbotthepony.mc.otm.capability.energy.getBarColor +import ru.dbotthepony.mc.otm.capability.energy.getBarWidth import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.bezierCurve +import ru.dbotthepony.mc.otm.core.nbt.doubles +import ru.dbotthepony.mc.otm.core.nbt.ints +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.registry.MSoundEvents import kotlin.reflect.KClass -class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: Decimal) : - IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable { +class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: Decimal) : IMatteryEnergyStorage, ICapabilityProvider, INBTSerializable { private val energyResolver = LazyOptional.of { this } private var innerBatteryLevel = Decimal.ZERO + override val energyFlow: FlowDirection + get() = FlowDirection.INPUT + var battery: ItemStack = ItemStack.EMPTY override fun getCapability(cap: Capability, side: Direction?): LazyOptional { @@ -48,11 +60,7 @@ class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: De battery = ItemStack.of(nbt["battery"] as CompoundTag) } - override fun extractEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return Decimal.ZERO - } - - override fun extractEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { if (!howMuch.isPositive) return Decimal.ZERO @@ -83,11 +91,7 @@ class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: De return diff + totalExtracted } - override fun receiveEnergyOuter(howMuch: Decimal, simulate: Boolean): Decimal { - return receiveEnergyInner(howMuch, simulate) - } - - override fun receiveEnergyInner(howMuch: Decimal, simulate: Boolean): Decimal { + override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { if (!howMuch.isPositive) return Decimal.ZERO @@ -130,16 +134,35 @@ class PlasmaWeaponEnergy(val itemStack: ItemStack, private val innerCapacity: De } } - override fun canExtract(): Boolean { - return false - } + override fun drainBattery(): Boolean { + innerBatteryLevel = Decimal.ZERO + + if (!battery.isEmpty) { + battery.getCapability(MatteryCapability.ENERGY).ifPresentK { + return it.drainBattery() + } + } - override fun canReceive(): Boolean { return true } - override val batteryLevel: Decimal + override fun fillBattery(): Boolean { + innerBatteryLevel = innerCapacity + + if (!battery.isEmpty) { + battery.getCapability(MatteryCapability.ENERGY).ifPresentK { + return it.fillBattery() + } + } + + return true + } + + override var batteryLevel: Decimal get() = (battery.energy?.energyStoredMattery ?: Decimal.ZERO) + innerBatteryLevel + set(value) { + innerBatteryLevel = value + } override val maxBatteryLevel: Decimal get() = (battery.energy?.maxEnergyStoredMattery ?: Decimal.ZERO) + innerCapacity @@ -159,15 +182,7 @@ abstract class PlasmaWeaponItem(tables: KClass, pr ) { super.appendHoverText(itemStack, p_41422_, p_41423_, p_41424_) - itemStack.getCapability(MatteryCapability.ENERGY).ifPresentK { - p_41423_.add( - TranslatableComponent( - "otm.item.power.normal.storage", - it.batteryLevel.formatPower(), - it.maxBatteryLevel.formatPower() - ).withStyle(ChatFormatting.GRAY) - ) - } + ItemEnergyStorageImpl.appendHoverText(itemStack, p_41423_) } fun energyData(itemStack: ItemStack) = itemStack.matteryEnergy as PlasmaWeaponEnergy diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/VelocityCalculation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/VelocityCalculation.kt index c94c26837..a3d95a956 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/VelocityCalculation.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/VelocityCalculation.kt @@ -3,8 +3,8 @@ package ru.dbotthepony.mc.otm.item.weapon import net.minecraft.util.Mth import net.minecraft.world.entity.Entity import net.minecraft.world.phys.Vec3 -import ru.dbotthepony.mc.otm.core.plus -import ru.dbotthepony.mc.otm.core.times +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.times import java.util.* import kotlin.math.PI import kotlin.math.cos diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt index 6b77baf72..d03613743 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/AbstractRegistryAction.kt @@ -1,127 +1,46 @@ package ru.dbotthepony.mc.otm.matter -import com.google.gson.JsonObject import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Either +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import net.minecraft.core.registries.Registries import net.minecraft.resources.ResourceLocation -import net.minecraft.tags.ItemTags import net.minecraft.tags.TagKey import net.minecraft.world.item.Item -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.set +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.registry.RegistryDelegate +import java.util.* -sealed class AbstractRegistryAction : Comparable { - sealed class Condition { - abstract fun test(): Boolean +abstract class AbstractRegistryAction( + val id: Either>, + val errorOnFailure: Boolean = false, + val priority: Optional = Optional.empty(), +) : Comparable { + interface Type { + val codec: Codec } - class CombinedCondition(val conditions: Collection) : Condition() { - override fun test(): Boolean { - return conditions.all { it.test() } + fun update(registry: MutableCollection, source: ResourceLocation) { + if (!checkConditions()) { + return } + + doUpdate(registry, source) } - val errorOnFailure: Boolean - val tag: TagKey? - val key: ResourceLocation? - val matter: Decimal? - val complexity: Double? - val priority: Int? + protected abstract fun doUpdate(registry: MutableCollection, source: ResourceLocation) - constructor(json: JsonObject) { - errorOnFailure = json["error_on_failure"]?.asBoolean ?: false - priority = json["priority"]?.asInt + abstract val type: Type<*> - val id = json["id"]?.asString ?: throw JsonParseException("Missing `id` value") - - if (id.startsWith("#")) { - if (id.startsWith("#:")) { - throw JsonSyntaxException("Invalid `id` value: $id") - } - - tag = ItemTags.create( - ResourceLocation.tryParse(id.substring(1)) ?: throw JsonSyntaxException("Invalid `id` value: $id") - ) - key = null - } else { - key = ResourceLocation.tryParse(id) ?: throw JsonSyntaxException("Invalid `id` value: $id") - tag = null - } - - try { - matter = json["matter"]?.asString?.let(::Decimal) - - if (matter != null && !matter.isPositive) { - throw JsonParseException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") - } - } catch(err: NumberFormatException) { - throw JsonParseException("Invalid `matter` field: ${json["matter"]}", err) - } - - try { - complexity = json["complexity"]?.asString?.toDouble() - - if (complexity != null && complexity <= 0.0) { - throw JsonParseException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") - } - } catch(err: NumberFormatException) { - throw JsonParseException("Invalid `complexity` field: ${json["complexity"]}", err) - } - } - - constructor( - tag: TagKey, - matter: Decimal?, - complexity: Double?, - priority: Int? = null, - errorOnFailure: Boolean = false - ) { - this.tag = tag - this.key = null - this.matter = matter - this.complexity = complexity - this.priority = priority - this.errorOnFailure = errorOnFailure - - if (matter != null && !matter.isPositive) { - throw IllegalArgumentException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") - } - - if (complexity != null && complexity <= 0.0) { - throw IllegalArgumentException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") - } - } - - constructor( - key: ResourceLocation, - matter: Decimal?, - complexity: Double?, - priority: Int? = null, - errorOnFailure: Boolean = false - ) { - this.tag = null - this.key = key - this.matter = matter - this.complexity = complexity - this.priority = priority - this.errorOnFailure = errorOnFailure - - if (matter != null && !matter.isPositive) { - throw IllegalArgumentException("Can't have non-positive matter value. To remove an entry from registry please use 'delete' action instead.") - } - - if (complexity != null && complexity <= 0.0) { - throw IllegalArgumentException("Can't have non-positive complexity. To remove an entry from registry please use 'delete' action instead.") - } - } - - fun checkConditions(): Boolean { + open fun checkConditions(): Boolean { return true } - internal abstract fun update(registry: MutableCollection, modifier: ResourceLocation) - protected inline fun fail(reason: () -> String) { if (errorOnFailure) { throw JsonParseException(reason.invoke()) @@ -132,43 +51,76 @@ sealed class AbstractRegistryAction : Comparable { if (other == null) return true - if (other.priority != null && priority != null) - return other.priority <= priority + if (other.priority.isPresent && priority.isPresent) + return other.priority.get() <= priority.get() - return other.priority == null + return other.priority.isEmpty } - override fun compareTo(other: AbstractRegistryAction): Int { - if (other.priority == null) { - if (priority == null) { + final override fun compareTo(other: AbstractRegistryAction): Int { + if (other.priority.isEmpty) { + if (priority.isEmpty) { return 0 } return 1 } - if (priority == null) { + if (priority.isEmpty) { return -1 } - return priority.compareTo(other.priority) + return priority.get().compareTo(other.priority.get()) } - open fun toJson(): JsonObject { - return JsonObject().also { - if (key != null) - it["id"] = JsonPrimitive(key.toString()) - else - it["id"] = JsonPrimitive("#${tag!!.location}") + object TargetCodec : Codec>> { + private val tagCodec = TagKey.codec(Registries.ITEM) - if (priority != null) - it["priority"] = priority + override fun encode(input: Either>, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(input.map( + { + ops.createString(it.toString()) + }, + { + ops.createString("#" + it.location.toString()) + } + )) + } - if (matter != null) - it["matter"] = matter.toString() + override fun decode(ops: DynamicOps, input: T): DataResult>, T>> { + return ops.getStringValue(input).flatMap { + if (it[0] == '#') + tagCodec.decode(ops, ops.createString(it.substring(1))).map { Pair(Either.right(it.first), it.second) } + else + ResourceLocation.CODEC.decode(ops, input).map { Pair(Either.left(it.first), it.second) } + } + } + } - if (complexity != null) - it["complexity"] = complexity + companion object { + private val registryDelegate = RegistryDelegate>("matter_registry_action") { disableSaving(); disableSync() } + + val registryKey get() = registryDelegate.key + val registry by registryDelegate + + private val registrar = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID) + + init { + registrar.register("insert") { InsertAction.Companion } + registrar.register("update") { UpdateAction.Companion } + registrar.register("compute") { ComputeAction.Companion } + registrar.register("delete") { DeleteAction.Companion } + registrar.register("blacklist") { BlacklistAction.Companion } + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + bus.addListener(registryDelegate::build) + } + + val CODEC: Codec by lazy { + registry.codec.dispatch({ it.type }, { it.codec }) } } } + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BlacklistAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BlacklistAction.kt deleted file mode 100644 index ae9a980ea..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BlacklistAction.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.mc.otm.matter - -import com.google.gson.JsonObject -import net.minecraft.resources.ResourceLocation -import net.minecraft.tags.TagKey -import net.minecraft.world.item.Item -import ru.dbotthepony.mc.otm.core.set - -class BlacklistAction : DeleteAction { - constructor( - tag: TagKey, - ) : super(tag = tag, errorOnFailure = false) - - constructor( - key: ResourceLocation, - ) : super(key = key, errorOnFailure = false) - - constructor(json: JsonObject) : super(json) - - override fun toJson(): JsonObject { - return super.toJson().also { - it["action"] = "blacklist" - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BoundMatterFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BoundMatterFunction.kt deleted file mode 100644 index f4a239e52..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/BoundMatterFunction.kt +++ /dev/null @@ -1,56 +0,0 @@ -package ru.dbotthepony.mc.otm.matter - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.set - -class BoundMatterFunction(val function: MatterFunction, val value: T) { - fun apply(self: T): T { - return function.updateValue(self, value) - } - - fun toJson(): JsonObject { - return JsonObject().also { - it["type"] = function.name - - if (value is Int || value is Double) - it["value"] = value - else - it["value"] = value.toString() - } - } - - companion object { - inline fun getValue(value: JsonElement): T { - return when (T::class) { - Decimal::class -> Decimal(value.asString) as T - Double::class -> value.asDouble as T - Int::class -> value.asInt as T - else -> throw RuntimeException("Unknown number type: ${T::class.qualifiedName}") - } - } - - fun apply(value: T, funcs: Collection>): T { - if (funcs.isEmpty()) { - return value - } - - var newValue = value - - for (func in funcs) { - newValue = func.apply(newValue) - } - - return newValue - } - - fun apply(funcs: Collection>, value: T): T { - return apply(value, funcs) - } - } -} - -fun Collection>.apply(value: T): T { - return BoundMatterFunction.apply(this, value) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt index 9ffb62311..5de261641 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt @@ -1,68 +1,250 @@ package ru.dbotthepony.mc.otm.matter import com.google.common.collect.ImmutableList -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException +import com.mojang.datafixers.util.Either +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.resources.ResourceLocation -import net.minecraft.tags.ItemTags import net.minecraft.tags.TagKey import net.minecraft.world.item.Item -import net.minecraft.world.level.ItemLike import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.data.stream -import java.util.stream.Stream +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.PredicatedCodecList +import java.util.Optional +import java.util.function.Predicate -class ComputeAction : AbstractRegistryAction { - sealed class Source { - val matterFunction: MatterFunction - val complexityFunction: MatterFunction - - constructor( - matterFunction: MatterFunction, - complexityFunction: MatterFunction, - ) { - this.matterFunction = matterFunction - this.complexityFunction = complexityFunction +class ComputeAction( + id: Either>, + values: Collection, + priority: Optional = Optional.empty(), +) : AbstractRegistryAction(id, priority = priority) { + companion object : Type { + private val constantCodecFull: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), + ).apply(it, ::Constant) + } to Predicate { true } } - constructor(json: JsonObject) { - val function = json["function"]?.asString?.uppercase() + private val constantCodecValues: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b, c -> Constant(a, b, c, c) } + } to Predicate { it.matterFunction == it.complexityFunction } + } - if (function == null) { - try { - this.matterFunction = MatterFunction.valueOf(json["matter_function"]?.asString?.uppercase() ?: throw JsonSyntaxException("Missing matter_function value")) - } catch(err: NoSuchElementException) { - throw JsonSyntaxException("Invalid matter_function: ${json["matter_function"]}", err) - } + private val constantCodecFunctions: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("value").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), + ).apply(it) { a, b, c -> Constant(a, a.toDouble(), b, c) } + } to Predicate { it.matter == Decimal(it.complexity.toString()) } + } - try { - this.complexityFunction = MatterFunction.valueOf(json["complexity_function"]?.asString?.uppercase() ?: throw JsonSyntaxException("Missing complexity_function value")) - } catch(err: NoSuchElementException) { - throw JsonSyntaxException("Invalid complexity_function: ${json["complexity_function"]}", err) - } - } else { - try { - this.matterFunction = MatterFunction.valueOf(function) - this.complexityFunction = this.matterFunction - } catch(err: NoSuchElementException) { - throw JsonSyntaxException("Invalid function: ${json["function"]}", err) + private val constantCodecConcise: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("value").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, a.toDouble(), b, b) } + } to Predicate { it.matter == Decimal(it.complexity.toString()) && it.matterFunction == it.complexityFunction } + } + + private val constantCodecComplexity: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::complexityFunction), + ).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) } + } to Predicate { it.matterFunction == IMatterFunction.NOOP } + } + + private val constantCodecComplexity2: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), + ).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) } + } to Predicate { it.matterFunction == IMatterFunction.NOOP } + } + + private val constantCodecMatter: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) } + } to Predicate { it.complexityFunction == IMatterFunction.NOOP } + } + + private val constantCodecMatter2: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) } + } to Predicate { it.complexityFunction == IMatterFunction.NOOP } + } + + private data class StringConstantCodec(val fn: IMatterFunction, val symbol: Char) : Codec, Predicate { + val pair = this to this + + override fun encode(input: Constant, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.createString(symbol + input.matter.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getStringValue(input).flatMap { + if (it[0] == symbol) { + try { + DataResult.success(Pair(Constant(Decimal(it.substring(1).trim()), it.substring(1).trim().toDouble(), fn, fn), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error("Not a number: ${it.substring(1).trim()} (input string: $it)") + } + } else { + DataResult.error("Input string does not match expected operand: expected $symbol, got ${it[0]} (for input $it)") + } } } + + override fun test(t: Constant): Boolean { + return t.matterFunction == fn && t.complexityFunction == fn && t.matter == Decimal(t.complexity.toString()) + } } + private object PlainStringConstantCodec : Codec, Predicate { + override fun encode(input: Constant, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.createString(input.matter.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val result = ops.getNumberValue(input).flatMap { + try { + DataResult.success(Pair(Constant(Decimal(it.toString()), it.toDouble(), IMatterFunction.PLUS, IMatterFunction.PLUS), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error("Not a number: $it") + } + } + + if (result.result().isPresent) + return result + + return ops.getStringValue(input).flatMap { + try { + DataResult.success(Pair(Constant(Decimal(it), it.toDouble(), IMatterFunction.PLUS, IMatterFunction.PLUS), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error("Not a number: $it") + } + } + } + + override fun test(t: Constant): Boolean { + return t.matterFunction == IMatterFunction.PLUS && t.complexityFunction == IMatterFunction.PLUS && t.matter == Decimal(t.complexity.toString()) + } + } + + private val constantCodec: Codec by lazy { + PredicatedCodecList( + PlainStringConstantCodec to PlainStringConstantCodec, + StringConstantCodec(IMatterFunction.DIV, '/').pair, + StringConstantCodec(IMatterFunction.MUL, '*').pair, + StringConstantCodec(IMatterFunction.AT_LEAST, '>').pair, + StringConstantCodec(IMatterFunction.AT_MOST, '<').pair, + StringConstantCodec(IMatterFunction.MINUS, '-').pair, + StringConstantCodec(IMatterFunction.MOD, '%').pair, + StringConstantCodec(IMatterFunction.REPLACE, '^').pair, + constantCodecMatter, + constantCodecMatter2, + constantCodecComplexity, + constantCodecComplexity2, + constantCodecConcise, + constantCodecFunctions, + constantCodecValues, + constantCodecFull, + ) + } + + private val valueCodecConcise: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(Value::id), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Value::matterFunction), + ).apply(it, ::Value) + } + } + + private val valueCodecVerbose: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(Value::id), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Value::matterFunction), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Value::complexityFunction), + ).apply(it, ::Value) + } + } + + private val valueCodec: Codec by lazy { + Codec.either(valueCodecConcise, valueCodecVerbose) + .xmap({ it.map({ it }, { it }) }, { if (it.matterFunction == it.complexityFunction) Either.left(it) else Either.right(it) }) + } + + private object ActualValueCodec : Codec { + override fun encode(input: Value, ops: DynamicOps, prefix: T): DataResult { + if (input.matterFunction == input.complexityFunction && input.matterFunction == IMatterFunction.PLUS) { + return TargetCodec.encode(input.id, ops, prefix) + } else { + return valueCodec.encode(input, ops, prefix) + } + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val result = TargetCodec.decode(ops, input) + + if (result.result().isPresent) { + return result.map { it.mapFirst { Value(it, IMatterFunction.PLUS) } } + } + + return valueCodec.decode(ops, input) + } + } + + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(ComputeAction::id), + Codec.list(Codec + .either(constantCodec, ActualValueCodec) + .xmap({ it.map({ it }, { it }) }, { if (it is Constant) Either.left(it) else Either.right(it as Value) })) + .fieldOf("values") + .forGetter(ComputeAction::values), + Codec.INT.optionalFieldOf("prority").forGetter(ComputeAction::priority) + ).apply(it, ::ComputeAction) + } + } + } + + override val type: Type<*> + get() = Companion + + sealed class Source( + val matterFunction: IMatterFunction, + val complexityFunction: IMatterFunction + ) { internal abstract fun provideValues(): MatterManager.Result - open fun toJson(): JsonObject { - return JsonObject().also { - it["matter_function"] = matterFunction.name - it["complexity_function"] = complexityFunction.name - } - } - internal fun update(self: IMatterValue): MatterManager.Result { val grab = provideValues() @@ -77,156 +259,41 @@ class ComputeAction : AbstractRegistryAction { } } - class Constant : Source { - val matter: Decimal - val complexity: Double - + class Constant( + val matter: Decimal, + val complexity: Double, + matterFunction: IMatterFunction, + complexityFunction: IMatterFunction = matterFunction, + ) : Source(matterFunction, complexityFunction) { constructor( - matter: Decimal, - complexity: Double, - matterFunction: MatterFunction, - complexityFunction: MatterFunction = matterFunction, - ) : super(matterFunction, complexityFunction) { - this.matter = matter - this.complexity = complexity - } - - constructor(json: JsonObject) : super(json) { - try { - matter = Decimal(json["matter"]?.asString ?: throw JsonSyntaxException("Missing matter value")) - } catch(err: NumberFormatException) { - throw JsonSyntaxException("Invalid matter value: ${json["matter"]}") - } - - try { - complexity = json["complexity"]?.asDouble ?: throw JsonSyntaxException("Missing complexity value") - } catch(err: NumberFormatException) { - throw JsonSyntaxException("Invalid complexity value: ${json["complexity"]}") - } - } + value: Double, + matterFunction: IMatterFunction, + complexityFunction: IMatterFunction = matterFunction + ) : this(Decimal(value.toString()), value, matterFunction, complexityFunction) override fun provideValues(): MatterManager.Result { return MatterManager.Result(MatterValue(matter, complexity)) } - - override fun toJson(): JsonObject { - return super.toJson().also { - it["type"] = "constant" - it["matter"] = matter.toString() - it["complexity"] = complexity - } - } } - class Key : Source { - val key: ResourceLocation - - constructor( - key: ResourceLocation, - matterFunction: MatterFunction, - complexityFunction: MatterFunction = matterFunction, - ) : super(matterFunction, complexityFunction) { - this.key = key - } - - constructor( - key: ItemLike, - matterFunction: MatterFunction, - complexityFunction: MatterFunction = matterFunction, - ) : super(matterFunction, complexityFunction) { - this.key = key.asItem().registryName ?: throw IllegalStateException("${key.asItem()} has no registry ID!") - } - - constructor(json: JsonObject) : super(json) { - key = ResourceLocation.tryParse(json["key"]?.asString ?: throw JsonSyntaxException("Missing key")) ?: throw JsonSyntaxException("Invalid key value: ${json["key"]}") - } - + class Value( + val id: Either>, + matterFunction: IMatterFunction, + complexityFunction: IMatterFunction = matterFunction, + ) : Source(matterFunction, complexityFunction) { override fun provideValues(): MatterManager.Result { - return MatterManager.compute(ForgeRegistries.ITEMS.getValue(key) ?: throw NullPointerException("$key does not point to anything")) - } - - override fun toJson(): JsonObject { - return super.toJson().also { - it["type"] = "key" - it["key"] = key.toString() - } + return id.map( + { + MatterManager.compute(ForgeRegistries.ITEMS.getValue(it) ?: throw NullPointerException("$it does not point to anything")) + }, + { + MatterManager.compute(it) + } + ) } } - class Tag : Source { - val tag: TagKey - - constructor( - tag: TagKey, - matterFunction: MatterFunction, - complexityFunction: MatterFunction = matterFunction, - ) : super(matterFunction, complexityFunction) { - this.tag = tag - } - - constructor(json: JsonObject) : super(json) { - tag = ItemTags.create(ResourceLocation.tryParse(json["tag"]?.asString ?: throw JsonSyntaxException("Missing tag")) ?: throw JsonSyntaxException("Invalid tag value: ${json["tag"]}")) - } - - override fun provideValues(): MatterManager.Result { - return MatterManager.compute(tag) - } - - override fun toJson(): JsonObject { - return super.toJson().also { - it["type"] = "tag" - it["tag"] = tag.location.toString() - } - } - } - - val values: List - - constructor( - key: ResourceLocation, - value: Source - ) : super(key = key, errorOnFailure = false, matter = null, complexity = null) { - values = listOf(value) - } - - constructor( - tag: TagKey, - value: Source - ) : super(tag = tag, errorOnFailure = false, matter = null, complexity = null) { - values = listOf(value) - } - - constructor( - key: ResourceLocation, - value: Stream - ) : super(key = key, errorOnFailure = false, matter = null, complexity = null) { - values = value.collect(ImmutableList.toImmutableList()) - require(values.isNotEmpty()) { "Provided compute source Stream is empty" } - } - - constructor( - tag: TagKey, - value: Stream - ) : super(tag = tag, errorOnFailure = false, matter = null, complexity = null) { - values = value.collect(ImmutableList.toImmutableList()) - require(values.isNotEmpty()) { "Provided compute source Stream is empty" } - } - - constructor( - key: ResourceLocation, - value: Collection - ) : super(key = key, errorOnFailure = false, matter = null, complexity = null) { - require(value.isNotEmpty()) { "Provided compute source list is empty" } - values = ImmutableList.copyOf(value) - } - - constructor( - tag: TagKey, - value: Collection - ) : super(tag = tag, errorOnFailure = false, matter = null, complexity = null) { - require(value.isNotEmpty()) { "Provided compute source list is empty" } - values = ImmutableList.copyOf(value) - } + val values: ImmutableList = ImmutableList.copyOf(values) internal fun tryCompute(): MatterManager.Result { var compute: IMatterValue = IMatterValue.Companion @@ -244,37 +311,7 @@ class ComputeAction : AbstractRegistryAction { return MatterManager.Result(compute) } - constructor(json: JsonObject) : super(json) { - val values = json["values"] as? JsonArray ?: throw JsonSyntaxException("Invalid `values` value: ${json["values"]}") - - this.values = values.stream().map { - it as? JsonObject ?: throw JsonSyntaxException("JsonObject expected, $it <${it::class.qualifiedName}> given") - - when (val type = it["type"]?.asString?.lowercase() ?: throw JsonSyntaxException("Missing type")) { - "tag" -> Tag(it) - "key" -> Key(it) - "constant" -> Constant(it) - else -> throw JsonSyntaxException("Invalid type provided: $type") - } - }.collect(ImmutableList.toImmutableList()) - - if (this.values.isEmpty()) { - throw JsonSyntaxException("Compute action is empty") - } - } - - override fun toJson(): JsonObject { - return super.toJson().also { - it["action"] = "compute" - it["values"] = JsonArray(values.size).also { - for (value in values) { - it.add(value.toJson()) - } - } - } - } - - override fun update(registry: MutableCollection, modifier: ResourceLocation) { + override fun doUpdate(registry: MutableCollection, source: ResourceLocation) { // do nothing } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt index 58252bdd1..f1b91b715 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/DeleteAction.kt @@ -1,60 +1,55 @@ package ru.dbotthepony.mc.otm.matter -import com.google.gson.JsonObject +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.Item -import ru.dbotthepony.mc.otm.core.set -open class DeleteAction : AbstractRegistryAction { - constructor( - tag: TagKey, - errorOnFailure: Boolean = false, - ) : super(tag = tag, errorOnFailure = errorOnFailure, matter = null, complexity = null) +open class DeleteAction( + id: Either>, + errorOnFailure: Boolean = false, +) : AbstractRegistryAction(id, errorOnFailure) { + override fun doUpdate(registry: MutableCollection, source: ResourceLocation) { + val iterator = registry.iterator() - constructor( - key: ResourceLocation, - errorOnFailure: Boolean = false, - ) : super(key = key, errorOnFailure = errorOnFailure, matter = null, complexity = null) - - constructor(json: JsonObject) : super(json) - - override fun toJson(): JsonObject { - return super.toJson().also { - it["action"] = "delete" + for (value in iterator) { + if (value.name == id) { + iterator.remove() + return + } } + + fail { "Could not find matter value with name $id (source: $source)" } } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { - if (!checkConditions()) { - return - } + override val type: Type<*> + get() = Companion - check(matter == null) { "Delete action can't have matter value (for $modifier)" } - check(complexity == null) { "Delete action can't have complexity value (for $modifier)" } - - if (key != null) { - val iterator = registry.iterator() - - for (value in iterator) { - if (value is MutableKeyEntry && value.key == key) { - iterator.remove() - return - } + companion object : Type { + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(DeleteAction::id), + Codec.BOOL.optionalFieldOf("errorOnFailure", false).forGetter(DeleteAction::errorOnFailure) + ).apply(it, ::DeleteAction) + } + } + } +} + +class BlacklistAction(id: Either>) : DeleteAction(id) { + override val type: Type<*> + get() = Companion + + companion object : Type { + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(BlacklistAction::id) + ).apply(it, ::BlacklistAction) } - - fail { "Could not find matter value with key $key" } - } else { - val iterator = registry.iterator() - - for (value in iterator) { - if (value is MutableTagEntry && value.tag == tag) { - iterator.remove() - return - } - } - - fail { "Could not find matter value with tag $tag" } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt new file mode 100644 index 000000000..b225a7c10 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterFunction.kt @@ -0,0 +1,84 @@ +package ru.dbotthepony.mc.otm.matter + +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.registry.RegistryDelegate + +interface IMatterFunction { + fun updateValue(self: Int, other: Int): Int + fun updateValue(self: Decimal, other: Decimal): Decimal + fun updateValue(self: Double, other: Double): Double + + companion object : IMatterFunction { + private val registryDelegate = RegistryDelegate("matter_function") { disableSync(); disableSaving() } + + val registryKey get() = registryDelegate.key + val registry by registryDelegate + + private val registrar = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID) + + init { + registrar.register("noop") { this } + } + + val NOOP = this + val PLUS: IMatterFunction by registrar.register("plus") { SimpleMatterFunction(Int::plus, Double::plus, Decimal::plus) } + val MINUS: IMatterFunction by registrar.register("minus") { SimpleMatterFunction(Int::minus, Double::minus, Decimal::minus) } + val DIV: IMatterFunction by registrar.register("div") { SimpleMatterFunction(Int::div, Double::div, Decimal::div) } + val MUL: IMatterFunction by registrar.register("mul") { SimpleMatterFunction(Int::times, Double::times, Decimal::times) } + val MOD: IMatterFunction by registrar.register("mod") { SimpleMatterFunction(Int::mod, Double::mod) } + val AT_LEAST: IMatterFunction by registrar.register("at_least") { SimpleMatterFunction(Int::coerceAtLeast, Double::coerceAtLeast, Decimal::coerceAtLeast) } + val AT_MOST: IMatterFunction by registrar.register("at_most") { SimpleMatterFunction(Int::coerceAtMost, Double::coerceAtMost, Decimal::coerceAtMost) } + val REPLACE: IMatterFunction by registrar.register("replace") { SimpleMatterFunction({ _, value -> value }, { _, value -> value }, { _, value -> value }) } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + bus.addListener(registryDelegate::build) + } + + override fun updateValue(self: Int, other: Int): Int { + return self + } + + override fun updateValue(self: Decimal, other: Decimal): Decimal { + return self + } + + override fun updateValue(self: Double, other: Double): Double { + return self + } + } +} + +data class SimpleMatterFunction( + val int: IntFunction = IntFunction { _, _ -> throw UnsupportedOperationException("Can't do this with Int") }, + val double: DoubleFunction = DoubleFunction { _, _ -> throw UnsupportedOperationException("Can't do this with Double") }, + val decimal: DecimalFunction = DecimalFunction { _, _ -> throw UnsupportedOperationException("Can't do this with Decimal") } +) : IMatterFunction { + fun interface IntFunction { + fun update(self: Int, value: Int): Int + } + + fun interface DoubleFunction { + fun update(self: Double, value: Double): Double + } + + fun interface DecimalFunction { + fun update(self: Decimal, value: Decimal): Decimal + } + + override fun updateValue(self: Int, other: Int): Int { + return int.update(self, other) + } + + override fun updateValue(self: Decimal, other: Decimal): Decimal { + return decimal.update(self, other) + } + + override fun updateValue(self: Double, other: Double): Double { + return double.update(self, other) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt index 7fc49dd3b..17fa4e5c2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt @@ -1,9 +1,13 @@ package ru.dbotthepony.mc.otm.matter import net.minecraft.network.FriendlyByteBuf -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.readDecimal -import ru.dbotthepony.mc.otm.core.writeDecimal +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.readDecimal +import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.util.readDouble +import ru.dbotthepony.mc.otm.core.util.writeDouble +import java.io.InputStream +import java.io.OutputStream interface IMatterValue : Comparable { val matter: Decimal @@ -24,6 +28,14 @@ interface IMatterValue : Comparable { return MatterValue(matter - other.matter, complexity - other.complexity) } + operator fun times(other: IMatterValue): IMatterValue { + return MatterValue(matter * other.matter, complexity * other.complexity) + } + + operator fun times(other: Int): IMatterValue { + return MatterValue(matter * other, complexity * other) + } + override fun compareTo(other: IMatterValue): Int { val matterComparison = matter.compareTo(other.matter) @@ -38,6 +50,8 @@ interface IMatterValue : Comparable { * ZERO */ companion object : IMatterValue { + val ZERO: IMatterValue get() = this + override val matter: Decimal get() = Decimal.ZERO override val complexity: Double @@ -51,6 +65,11 @@ fun FriendlyByteBuf.writeMatterValue(value: IMatterValue) { writeDouble(value.complexity) } +fun OutputStream.writeMatterValue(value: IMatterValue) { + writeDecimal(value.matter) + writeDouble(value.complexity) +} + fun FriendlyByteBuf.readMatterValue(): IMatterValue { val matter = readDecimal() val complexity = readDouble() @@ -62,6 +81,17 @@ fun FriendlyByteBuf.readMatterValue(): IMatterValue { } } +fun InputStream.readMatterValue(): IMatterValue { + val matter = readDecimal() + val complexity = readDouble() + + if (matter.isZero && complexity == 0.0) { + return IMatterValue.Companion + } else { + return MatterValue(matter, complexity) + } +} + data class MatterValue( override val matter: Decimal, override val complexity: Double diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt index b0f54b201..b0fccf9c5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/InsertAction.kt @@ -1,152 +1,67 @@ package ru.dbotthepony.mc.otm.matter -import com.google.gson.JsonObject +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.Item -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.probablyParallelStream -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import java.util.Optional -class InsertAction : AbstractRegistryAction { - val replaceIfExists: Boolean - val comparePriority: Boolean - - constructor( - key: ResourceLocation, - matter: Decimal?, - complexity: Double?, - priority: Int? = null, - errorOnFailure: Boolean = false, - replaceIfExists: Boolean = false, - comparePriority: Boolean = true, - ) : super(key = key, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { - this.replaceIfExists = replaceIfExists - this.comparePriority = comparePriority - } - - constructor( - tag: TagKey, - matter: Decimal?, - complexity: Double?, - priority: Int? = null, - errorOnFailure: Boolean = false, - replaceIfExists: Boolean = false, - comparePriority: Boolean = true, - ) : super(tag = tag, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { - this.replaceIfExists = replaceIfExists - this.comparePriority = comparePriority - } - - constructor(json: JsonObject) : super(json) { - this.replaceIfExists = json["replace_if_exists"]?.asBoolean ?: false - this.comparePriority = json["compare_priority"]?.asBoolean ?: true - } - - override fun toJson(): JsonObject { - checkNotNull(matter) { "Missing matter value, which is required for insert action" } - checkNotNull(complexity) { "Missing complexity value, which is required for insert action" } - - return super.toJson().also { - it["action"] = "insert" - it["replace_if_exists"] = replaceIfExists - it["compare_priority"] = comparePriority - } - } - - override fun update(registry: MutableCollection, modifier: ResourceLocation) { - checkNotNull(matter) { "Missing matter value, which is required for insert action (for $modifier)" } - checkNotNull(complexity) { "Missing complexity value, which is required for insert action (for $modifier)" } - - check(!comparePriority || priority != null) { "If compare_priority is true then priority must not be null (for $modifier)" } - - if (!checkConditions()) { - return - } - - if (key != null) { - if (replaceIfExists) { - // search & replace in parallel, if possible - val replaced = registry.probablyParallelStream().anyMatch { - if (it is MutableKeyEntry && it.key == key) { - if (!comparePriority || it.priority < priority!!) { - it.matter = matter - it.complexity = complexity - it.priority = priority ?: 0 - } - - return@anyMatch true - } else { - return@anyMatch false +class InsertAction( + id: Either>, + val matter: Decimal, + val complexity: Double, + val replaceIfExists: Boolean = false, + errorOnFailure: Boolean = false, + priority: Optional = Optional.empty(), +) : AbstractRegistryAction(id, errorOnFailure, priority) { + override fun doUpdate(registry: MutableCollection, source: ResourceLocation) { + if (replaceIfExists) { + // search & replace in parallel, if possible + val replaced = registry.stream().anyMatch { + if (it.name == id) { + if (priority.isEmpty || it.priority < priority.get()) { + it.matter = matter + it.complexity = complexity + it.priority = priority.orElse(0) } - } - if (!replaced) { - registry.add( - MutableKeyEntry( - key, - matter, - complexity, - priority ?: 0 - ) - ) - } - } else { - if (registry.probablyParallelStream().anyMatch { it is MutableKeyEntry && it.key == key }) { - fail { "Value with key $key already exists" } - return + return@anyMatch true + } else { + return@anyMatch false } + } - registry.add( - MutableKeyEntry( - key, - matter, - complexity, - priority ?: 0 - ) - ) + if (!replaced) { + registry.add(MutableRegistryEntry(id, matter, complexity, priority.orElse(0))) } } else { - if (replaceIfExists) { - // search & replace in parallel, if possible - val replaced = registry.probablyParallelStream().anyMatch { - if (it is MutableTagEntry && it.tag == tag) { - if (!comparePriority || it.priority < priority!!) { - it.matter = matter - it.complexity = complexity - it.priority = priority ?: 0 - } + if (registry.stream().anyMatch { it.name == id }) { + fail { "Value with name $id already exists (source: $source)" } + return + } - return@anyMatch true - } else { - return@anyMatch false - } - } + registry.add(MutableRegistryEntry(id, matter, complexity, priority.orElse(0))) + } + } - if (!replaced) { - registry.add( - MutableTagEntry( - tag!!, - matter, - complexity, - priority ?: 0 - ) - ) - } - } else { - if (registry.probablyParallelStream().anyMatch { it is MutableTagEntry && it.tag == tag }) { - fail { "Value with tag $tag already exists" } - return - } + override val type: Type<*> + get() = Companion - registry.add( - MutableTagEntry( - tag!!, - matter, - complexity, - priority ?: 0 - ) - ) + companion object : Type { + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(InsertAction::id), + DecimalCodec.fieldOf("matter").forGetter(InsertAction::matter), + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("complexity").forGetter(InsertAction::complexity), + Codec.BOOL.optionalFieldOf("replace", false).forGetter(InsertAction::replaceIfExists), + Codec.BOOL.optionalFieldOf("errorOnFailure", false).forGetter(InsertAction::errorOnFailure), + Codec.INT.optionalFieldOf("priority").forGetter(InsertAction::priority), + ).apply(it, ::InsertAction) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt index e2a37e01d..6f45e0283 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterDataProvider.kt @@ -1,23 +1,40 @@ package ru.dbotthepony.mc.otm.matter +import com.mojang.datafixers.util.Either import net.minecraft.data.CachedOutput -import net.minecraft.data.DataGenerator import net.minecraft.data.DataProvider +import net.minecraft.data.PackOutput import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.Item import net.minecraft.world.level.ItemLike import net.minecraftforge.data.event.GatherDataEvent -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.core.registryName -import java.util.Collections +import ru.dbotthepony.mc.otm.core.toJsonStrict +import java.util.* +import java.util.concurrent.CompletableFuture import java.util.function.Consumer +import kotlin.ConcurrentModificationException +import kotlin.collections.ArrayList +import kotlin.collections.LinkedHashMap @Suppress("FunctionName", "unused") -open class MatterDataProvider(protected val dataGenerator: DataGenerator, val namespace: String?) : DataProvider { - constructor(event: GatherDataEvent) : this(event.generator, event.modContainer.namespace) +open class MatterDataProvider(val modid: String? = null) : DataProvider { + var pathProvider: PackOutput.PathProvider by WriteOnce("You need to call bindPackOutput before registering this data provider") + private set + + constructor(output: PackOutput, modid: String? = null) : this(modid) { + bindPackOutput(output) + } + + constructor(event: GatherDataEvent) : this(event.generator.packOutput, event.modContainer.namespace) + + fun bindPackOutput(output: PackOutput) { + pathProvider = output.createPathProvider(PackOutput.Target.DATA_PACK, MatterManager.MATTER_DIRECTORY) + } - protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, MatterManager.MATTER_DIRECTORY) protected val actions = LinkedHashMap() sealed class Configuration(val name: ResourceLocation) { @@ -26,27 +43,10 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na var complexity: Double? = null var priority: Int? = null - private var _key: ResourceLocation? = null - private var _tag: TagKey? = null + var target: Either>? = null - var key: ResourceLocation? - get() = _key - set(value) { - checkNotNull(value) { "Can't set key to null" } - _tag = null - _key = value - } - - var tag: TagKey? - get() = _tag - set(value) { - checkNotNull(value) { "Can't set tag to null" } - _key = null - _tag = value - } - - protected fun checkKeyTag() { - check(_key != null || _tag != null) { "You must define either key or tag for $name" } + protected fun target(): Either> { + return checkNotNull(target) { "You must define either key or tag for $name" } } protected fun checkMatterValues() { @@ -59,75 +59,38 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na class InsertConfiguration(name: ResourceLocation) : Configuration(name) { var replaceIfExists: Boolean = false - var comparePriority: Boolean = false override fun build(): InsertAction { - checkKeyTag() checkMatterValues() check(matter != null && complexity != null) { "You must define both matter value and complexity for $name" } - if (key != null) { - return InsertAction( - key = key ?: throw ConcurrentModificationException(), - matter = matter, - complexity = complexity, - priority = priority, - errorOnFailure = errorOnFailure, - replaceIfExists = replaceIfExists, - comparePriority = comparePriority - ) - } else { - return InsertAction( - tag = tag ?: throw ConcurrentModificationException(), - matter = matter, - complexity = complexity, - priority = priority, - errorOnFailure = errorOnFailure, - replaceIfExists = replaceIfExists, - comparePriority = comparePriority - ) - } + return InsertAction( + id = target(), + matter = matter!!, + complexity = complexity!!, + priority = Optional.ofNullable(priority), + errorOnFailure = errorOnFailure, + replaceIfExists = replaceIfExists, + ) } } class DeleteConfiguration(name: ResourceLocation) : Configuration(name) { override fun build(): DeleteAction { - checkKeyTag() - check(matter == null) { "Delete action can't have matter value" } check(complexity == null) { "Delete action can't have complexity value" } - if (key != null) { - return DeleteAction( - key = key ?: throw ConcurrentModificationException(), - errorOnFailure = errorOnFailure, - ) - } else { - return DeleteAction( - tag = tag ?: throw ConcurrentModificationException(), - errorOnFailure = errorOnFailure, - ) - } + return DeleteAction(target(), errorOnFailure = errorOnFailure) } } class BlacklistConfiguration(name: ResourceLocation) : Configuration(name) { override fun build(): BlacklistAction { - checkKeyTag() - check(matter == null) { "Blacklist action can't have matter value" } check(complexity == null) { "Blacklist action can't have complexity value" } check(!errorOnFailure) { "Blacklist can't error on 'failure'" } - if (key != null) { - return BlacklistAction( - key = key ?: throw ConcurrentModificationException(), - ) - } else { - return BlacklistAction( - tag = tag ?: throw ConcurrentModificationException(), - ) - } + return BlacklistAction(target()) } } @@ -140,381 +103,406 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na } fun plus(matter: Decimal, complexity: Double): ComputeConfiguration { - values.add(ComputeAction.Constant(matter, complexity, MatterFunction.ADD)) + values.add(ComputeAction.Constant(matter, complexity, IMatterFunction.PLUS)) return this } fun minus(matter: Decimal, complexity: Double): ComputeConfiguration { - values.add(ComputeAction.Constant(matter, complexity, MatterFunction.SUBTRACT)) + values.add(ComputeAction.Constant(matter, complexity, IMatterFunction.MINUS)) return this } fun multiply(matter: Decimal, complexity: Double): ComputeConfiguration { - values.add(ComputeAction.Constant(matter, complexity, MatterFunction.MULTIPLY)) + values.add(ComputeAction.Constant(matter, complexity, IMatterFunction.MUL)) return this } fun divide(matter: Decimal, complexity: Double): ComputeConfiguration { - values.add(ComputeAction.Constant(matter, complexity, MatterFunction.DIVIDE)) + values.add(ComputeAction.Constant(matter, complexity, IMatterFunction.DIV)) return this } fun plus(key: ResourceLocation): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.ADD)) + values.add(ComputeAction.Value(Either.left(key), IMatterFunction.PLUS)) return this } fun plus(key: ItemLike): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.ADD)) + values.add(ComputeAction.Value(Either.left(key.asItem().registryName!!), IMatterFunction.PLUS)) return this } fun minus(key: ResourceLocation): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.SUBTRACT)) + values.add(ComputeAction.Value(Either.left(key), IMatterFunction.MINUS)) return this } fun minus(key: ItemLike): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.SUBTRACT)) + values.add(ComputeAction.Value(Either.left(key.asItem().registryName!!), IMatterFunction.MINUS)) return this } fun multiply(key: ResourceLocation): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.MULTIPLY)) + values.add(ComputeAction.Value(Either.left(key), IMatterFunction.MUL)) return this } fun multiply(key: ItemLike): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.MULTIPLY)) + values.add(ComputeAction.Value(Either.left(key.asItem().registryName!!), IMatterFunction.MUL)) return this } fun divide(key: ResourceLocation): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.DIVIDE)) + values.add(ComputeAction.Value(Either.left(key), IMatterFunction.DIV)) return this } fun divide(key: ItemLike): ComputeConfiguration { - values.add(ComputeAction.Key(key, MatterFunction.DIVIDE)) + values.add(ComputeAction.Value(Either.left(key.asItem().registryName!!), IMatterFunction.DIV)) return this } fun plus(tag: TagKey): ComputeConfiguration { - values.add(ComputeAction.Tag(tag, MatterFunction.ADD)) + values.add(ComputeAction.Value(Either.right(tag), IMatterFunction.PLUS)) return this } fun minus(tag: TagKey): ComputeConfiguration { - values.add(ComputeAction.Tag(tag, MatterFunction.SUBTRACT)) + values.add(ComputeAction.Value(Either.right(tag), IMatterFunction.MINUS)) return this } fun multiply(tag: TagKey): ComputeConfiguration { - values.add(ComputeAction.Tag(tag, MatterFunction.MULTIPLY)) + values.add(ComputeAction.Value(Either.right(tag), IMatterFunction.MUL)) return this } fun divide(tag: TagKey): ComputeConfiguration { - values.add(ComputeAction.Tag(tag, MatterFunction.DIVIDE)) + values.add(ComputeAction.Value(Either.right(tag), IMatterFunction.DIV)) return this } override fun build(): ComputeAction { - checkKeyTag() - check(matter == null) { "Compute action can't have matter value" } check(complexity == null) { "Compute action can't have complexity value" } - check(values.isNotEmpty()) { "Compute action is empty" } - if (key != null) { - return ComputeAction( - key = key ?: throw ConcurrentModificationException(), - value = values, - ) - } else { - return ComputeAction( - tag = tag ?: throw ConcurrentModificationException(), - value = values, - ) - } + return ComputeAction(target(), values,) } } class UpdateConfiguration(name: ResourceLocation) : Configuration(name) { - val matterFunctions = ArrayList>() - val complexityFunctions = ArrayList>() - val priorityFunctions = ArrayList>() + val matterFunctions = ArrayList() + val complexityFunctions = ArrayList() + val priorityFunctions = ArrayList() - fun addMatterFunction(function: MatterFunction, value: Decimal): UpdateConfiguration { - matterFunctions.add(BoundMatterFunction(function, value)) - matter = null + fun addMatterFunction(function: IMatterFunction, value: Decimal): UpdateConfiguration { + matterFunctions.add(UpdateAction.MatterFunction(function, value)) return this } - fun addComplexityFunction(function: MatterFunction, value: Double): UpdateConfiguration { - complexityFunctions.add(BoundMatterFunction(function, value)) - matter = null + fun addComplexityFunction(function: IMatterFunction, value: Double): UpdateConfiguration { + complexityFunctions.add(UpdateAction.ComplexityFunction(function, value)) return this } - fun addPriorityFunction(function: MatterFunction, value: Int): UpdateConfiguration { - priorityFunctions.add(BoundMatterFunction(function, value)) - matter = null + fun addPriorityFunction(function: IMatterFunction, value: Int): UpdateConfiguration { + priorityFunctions.add(UpdateAction.PriorityFunction(function, value)) return this } override fun build(): UpdateAction { - check(!(matterFunctions.isNotEmpty() && matter != null)) { "Can't have both matter value and matter value functions be defined at the same time" } - check(!(complexityFunctions.isNotEmpty() && complexity != null)) { "Can't have both complexity and complexity functions be defined at the same time" } - check(!(priorityFunctions.isNotEmpty() && priority != null)) { "Can't have both priority and priority functions be defined at the same time" } - check( - matterFunctions.isNotEmpty() || matter != null || - complexityFunctions.isNotEmpty() || complexity != null || - priorityFunctions.isNotEmpty() || priority != null + matterFunctions.isNotEmpty() || + complexityFunctions.isNotEmpty() || + priorityFunctions.isNotEmpty() ) { "Update configuration is completely empty" } - checkMatterValues() - checkKeyTag() - - if (key != null) { - return UpdateAction( - key = key ?: throw ConcurrentModificationException(), - matter = matter, - complexity = complexity, - priority = priority, - errorOnFailure = errorOnFailure, - matterFunctions = matterFunctions, - complexityFunctions = complexityFunctions, - priorityFunctions = priorityFunctions - ) - } else { - return UpdateAction( - tag = tag ?: throw ConcurrentModificationException(), - matter = matter, - complexity = complexity, - priority = priority, - errorOnFailure = errorOnFailure, - matterFunctions = matterFunctions, - complexityFunctions = complexityFunctions, - priorityFunctions = priorityFunctions - ) - } + return UpdateAction( + target(), + errorOnFailure = errorOnFailure, + matterFunctions = matterFunctions, + complexityFunctions = complexityFunctions, + priorityFunctions = priorityFunctions + ) } } - fun Scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(matterValue, complexity, configurator) fun scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, matterValue, complexity, configurator) fun scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, matterValue, complexity, configurator) - fun scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun Scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun Scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun Scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun Scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(matterValue, complexity, configurator) fun scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, matterValue, complexity, configurator) fun scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, matterValue, complexity, configurator) - fun scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity, configurator) + fun scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity, configurator) - fun scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) - fun scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue), complexity.toDouble(), configurator) + fun scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue), complexity.toDouble(), configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue), complexity.toDouble(), configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue), complexity.toDouble(), configurator) @@ -547,110 +535,142 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na fun Scope(matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(matterValue * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, matterValue * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, matterValue * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(matterValue * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, matterValue * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, matterValue * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Scope.() -> Unit) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) @@ -658,110 +678,142 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na fun Scope(matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(matterValue * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, matterValue * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, matterValue * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun Scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun Scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun Scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(matterValue * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, matterValue * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Decimal, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, matterValue * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Double, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Int, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Float, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Int, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Long, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Double, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) - fun scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) + fun scope( matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope( + Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(item: ItemLike, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(item, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) fun scope(tag: TagKey, matterValue: Float, complexity: Long, configurator: Consumer) = this@MatterDataProvider.Scope(tag, Decimal(matterValue) * this.matterValue, complexity * this.complexity, configurator) @@ -911,11 +963,11 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na } protected fun updateLocation(input: ResourceLocation, prefix: String): ResourceLocation { - if (namespace != null) { - if (input.namespace == namespace) { + if (modid != null) { + if (input.namespace == modid) { return ResourceLocation(input.namespace, prefix + input.path) } else { - return ResourceLocation(namespace, prefix + input.namespace + "/" + input.path) + return ResourceLocation(modid, prefix + input.namespace + "/" + input.path) } } else { return ResourceLocation(input.namespace, prefix + input.path) @@ -929,32 +981,32 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na // direct inheritance fun inherit(destination: ItemLike, source: ItemLike, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return compute(updateLocation(destination, "item/")) { - key = destination.asItem().registryName ?: throw ConcurrentModificationException() - add(ComputeAction.Key(source, MatterFunction.ADD)) + target = Either.left(destination.asItem().registryName!!) + add(ComputeAction.Value(Either.left(source.asItem().registryName!!), IMatterFunction.PLUS)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: ItemLike, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return compute(updateLocation(destination.location, "tag/")) { - tag = destination - add(ComputeAction.Key(source, MatterFunction.ADD)) + target = Either.right(destination) + add(ComputeAction.Value(Either.left(source.asItem().registryName!!), IMatterFunction.PLUS)) configurator.invoke(this) } } fun inherit(destination: ItemLike, source: TagKey, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return compute(updateLocation(destination, "item/")) { - key = destination.asItem().registryName ?: throw ConcurrentModificationException() - add(ComputeAction.Tag(source, MatterFunction.ADD)) + target = Either.left(destination.asItem().registryName!!) + add(ComputeAction.Value(Either.right(source), IMatterFunction.PLUS)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: TagKey, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return compute(updateLocation(destination.location, "tag/")) { - tag = destination - add(ComputeAction.Tag(source, MatterFunction.ADD)) + target = Either.right(destination) + add(ComputeAction.Value(Either.right(source), IMatterFunction.PLUS)) configurator.invoke(this) } } @@ -962,56 +1014,56 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na // multiplied inheritance fun inherit(destination: ItemLike, source: ItemLike, multiplier: Double, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(Decimal(multiplier), multiplier, MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(Decimal(multiplier), multiplier, IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: ItemLike, multiplier: Double, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(Decimal(multiplier), multiplier, MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(Decimal(multiplier), multiplier, IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: ItemLike, source: TagKey, multiplier: Double, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(Decimal(multiplier), multiplier, MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(Decimal(multiplier), multiplier, IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: TagKey, multiplier: Double, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(Decimal(multiplier), multiplier, MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(Decimal(multiplier), multiplier, IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: ItemLike, source: ItemLike, multiplier: Decimal, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(multiplier, multiplier.toDouble(), MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(multiplier, multiplier.toDouble(), IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: ItemLike, multiplier: Decimal, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(multiplier, multiplier.toDouble(), MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(multiplier, multiplier.toDouble(), IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: ItemLike, source: TagKey, multiplier: Decimal, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(multiplier, multiplier.toDouble(), MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(multiplier, multiplier.toDouble(), IMatterFunction.MUL)) configurator.invoke(this) } } fun inherit(destination: TagKey, source: TagKey, multiplier: Decimal, configurator: ComputeConfiguration.() -> Unit): ComputeAction { return inherit(destination, source) { - add(ComputeAction.Constant(multiplier, multiplier.toDouble(), MatterFunction.MULTIPLY)) + add(ComputeAction.Constant(multiplier, multiplier.toDouble(), IMatterFunction.MUL)) configurator.invoke(this) } } @@ -1079,54 +1131,54 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na fun insert(name: ItemLike, configurator: InsertConfiguration.() -> Unit): InsertAction { return insert(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { - key = name.asItem().registryName ?: throw ConcurrentModificationException() + target = Either.left(name.asItem().registryName!!) configurator.invoke(this) } } fun delete(name: ItemLike, configurator: DeleteConfiguration.() -> Unit): DeleteAction { return delete(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { - key = name.asItem().registryName ?: throw ConcurrentModificationException() + target = Either.left(name.asItem().registryName!!) configurator.invoke(this) } } fun blacklist(name: ItemLike): BlacklistAction { return blacklist(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { - key = name.asItem().registryName ?: throw ConcurrentModificationException() + target = Either.left(name.asItem().registryName!!) } } fun blacklist(name: TagKey): BlacklistAction { return blacklist(updateLocation(name.location, "tag/")) { - tag = name + target = Either.right(name) } } fun update(name: ItemLike, configurator: UpdateConfiguration.() -> Unit): UpdateAction { return update(updateLocation(name.asItem().registryName ?: throw NullPointerException("${name.asItem()} does not have registry name!"), "item/")) { - key = name.asItem().registryName ?: throw ConcurrentModificationException() + target = Either.left(name.asItem().registryName!!) configurator.invoke(this) } } fun insert(name: TagKey, configurator: InsertConfiguration.() -> Unit): InsertAction { return insert(updateLocation(name.location, "tag/")) { - tag = name + target = Either.right(name) configurator.invoke(this) } } fun delete(name: TagKey, configurator: DeleteConfiguration.() -> Unit): DeleteAction { return delete(updateLocation(name.location, "tag/")) { - tag = name + target = Either.right(name) configurator.invoke(this) } } fun update(name: TagKey, configurator: UpdateConfiguration.() -> Unit): UpdateAction { return update(updateLocation(name.location, "tag/")) { - tag = name + target = Either.right(name) configurator.invoke(this) } } @@ -1204,16 +1256,23 @@ open class MatterDataProvider(protected val dataGenerator: DataGenerator, val na protected val added = ArrayList() val addedView: List = Collections.unmodifiableList(added) - final override fun run(output: CachedOutput) { + final override fun run(output: CachedOutput): CompletableFuture<*> { addActions() + val promises = ArrayList>() + for ((key, value) in actions) { - DataProvider.saveStable(output, value.toJson(), pathProvider.json(key)) + promises.add(DataProvider.saveStable(output, AbstractRegistryAction.CODEC.toJsonStrict(value), pathProvider.json(key))) added.add(value) } + + return CompletableFuture.allOf(*promises.toTypedArray()) } override fun getName(): String { + if (modid != null) + return "Matter Data for mod $modid" + return "Matter Data" } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterFunction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterFunction.kt deleted file mode 100644 index 37b2e7a9f..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterFunction.kt +++ /dev/null @@ -1,78 +0,0 @@ -package ru.dbotthepony.mc.otm.matter - -import com.google.gson.JsonParseException -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.core.integerDivisionUp - -enum class MatterFunction { - ADD { - override fun updateValue(self: Int, other: Int): Int = self + other - override fun updateValue(self: Decimal, other: Decimal): Decimal = self + other - override fun updateValue(self: Double, other: Double): Double = self + other - }, - SUBTRACT { - override fun updateValue(self: Int, other: Int): Int = self - other - override fun updateValue(self: Decimal, other: Decimal): Decimal = self - other - override fun updateValue(self: Double, other: Double): Double = self - other - }, - MULTIPLY { - override fun updateValue(self: Int, other: Int): Int = self * other - override fun updateValue(self: Decimal, other: Decimal): Decimal = self * other - override fun updateValue(self: Double, other: Double): Double = self * other - }, - DIVIDE { - override fun updateValue(self: Int, other: Int): Int = self / other - override fun updateValue(self: Decimal, other: Decimal): Decimal = self / other - override fun updateValue(self: Double, other: Double): Double = self / other - }, - DIVIDE_UP { - override fun updateValue(self: Int, other: Int): Int = integerDivisionUp(self, other) - override fun updateValue(self: Decimal, other: Decimal): Decimal = throw JsonParseException("Integer division up is available only for integers") - override fun updateValue(self: Double, other: Double): Double = throw JsonParseException("Integer division up is available only for integers") - }, - DIVIDE_DOWN { - override fun updateValue(self: Int, other: Int): Int = integerDivisionDown(self, other) - override fun updateValue(self: Decimal, other: Decimal): Decimal = throw JsonParseException("Integer division down is available only for integers") - override fun updateValue(self: Double, other: Double): Double = throw JsonParseException("Integer division down is available only for integers") - }, - MODULO { - override fun updateValue(self: Int, other: Int): Int = self % other - override fun updateValue(self: Decimal, other: Decimal): Decimal = self % other - override fun updateValue(self: Double, other: Double): Double = self % other - }, - AT_LEAST { - override fun updateValue(self: Int, other: Int): Int = self.coerceAtLeast(other) - override fun updateValue(self: Decimal, other: Decimal): Decimal = self.coerceAtLeast(other) - override fun updateValue(self: Double, other: Double): Double = self.coerceAtLeast(other) - }, - AT_MOST { - override fun updateValue(self: Int, other: Int): Int = self.coerceAtMost(other) - override fun updateValue(self: Decimal, other: Decimal): Decimal = self.coerceAtMost(other) - override fun updateValue(self: Double, other: Double): Double = self.coerceAtMost(other) - }, - REPLACE { - override fun updateValue(self: Int, other: Int): Int = other - override fun updateValue(self: Decimal, other: Decimal): Decimal = other - override fun updateValue(self: Double, other: Double): Double = other - }, - NOOP { - override fun updateValue(self: Int, other: Int): Int = self - override fun updateValue(self: Decimal, other: Decimal): Decimal = self - override fun updateValue(self: Double, other: Double): Double = self - }, - ; - - protected abstract fun updateValue(self: Int, other: Int): Int - protected abstract fun updateValue(self: Decimal, other: Decimal): Decimal - protected abstract fun updateValue(self: Double, other: Double): Double - - fun updateValue(self: T, other: T): T { - return when (self) { - is Decimal -> updateValue(self as Decimal, other as Decimal) as T - is Double -> updateValue(self as Double, other as Double) as T - is Int -> updateValue(self as Int, other as Int) as T - else -> throw RuntimeException("Unknown number type: ${self::class.qualifiedName}") - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt index d9d2f187c..40480d309 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -8,11 +8,15 @@ import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonParseException import com.google.gson.JsonSyntaxException -import com.mojang.blaze3d.platform.InputConstants import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.context.CommandContext +import com.mojang.serialization.JsonOps +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import it.unimi.dsi.fastutil.objects.Object2BooleanFunction import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2BooleanFunction import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction @@ -33,12 +37,18 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.tags.TagKey import net.minecraft.util.profiling.ProfilerFiller import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.CraftingContainer import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.item.crafting.UpgradeRecipe import net.minecraft.world.level.ItemLike import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.crafting.IShapedRecipe import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.OnDatapackSyncEvent import net.minecraftforge.event.RegisterCommandsEvent @@ -46,13 +56,12 @@ import net.minecraftforge.event.entity.player.ItemTooltipEvent import net.minecraftforge.event.server.ServerStartedEvent import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.fml.ModList -import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.server.command.EnumArgument import org.apache.logging.log4j.LogManager -import org.lwjgl.glfw.GLFW +import ru.dbotthepony.mc.otm.config.ClientConfig import ru.dbotthepony.mc.otm.MINECRAFT_SERVER import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.OverdriveThatMatters @@ -60,108 +69,59 @@ import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.SystemTime import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive +import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.container.stream -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.util.stream +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.filterNotNull -import ru.dbotthepony.mc.otm.core.formatMatter -import ru.dbotthepony.mc.otm.core.formatMatterFull -import ru.dbotthepony.mc.otm.core.formatSiComponent -import ru.dbotthepony.mc.otm.core.formatTickDuration +import ru.dbotthepony.mc.otm.core.util.formatMatter +import ru.dbotthepony.mc.otm.core.util.formatMatterFull +import ru.dbotthepony.mc.otm.core.util.formatSiComponent +import ru.dbotthepony.mc.otm.core.util.formatTickDuration import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.isActuallyEmpty -import ru.dbotthepony.mc.otm.core.isZero +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.isZero import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.core.readItemType import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.stream +import ru.dbotthepony.mc.otm.core.util.readBinaryComponent +import ru.dbotthepony.mc.otm.core.util.readCollection +import ru.dbotthepony.mc.otm.core.util.writeBinaryComponent +import ru.dbotthepony.mc.otm.core.util.writeCollection +import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.core.writeItemType +import ru.dbotthepony.mc.otm.milliTime +import ru.dbotthepony.mc.otm.network.GenericNetworkChannel +import ru.dbotthepony.mc.otm.network.MNetworkContext import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel import ru.dbotthepony.mc.otm.registry.RegistryDelegate -import ru.dbotthepony.mc.otm.storage.ItemStackWrapper +import ru.dbotthepony.mc.otm.secondTime +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.io.DataInputStream +import java.io.DataOutputStream import java.io.File +import java.io.OutputStream import java.math.BigInteger import java.util.* -import java.util.function.Supplier +import java.util.function.BooleanSupplier import java.util.stream.Stream +import java.util.zip.Deflater +import java.util.zip.Inflater import kotlin.ConcurrentModificationException import kotlin.collections.ArrayDeque import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.collections.LinkedHashMap +import kotlin.jvm.optionals.getOrNull import kotlin.math.pow import kotlin.math.roundToInt -internal sealed class MutableEntry( - var matter: Decimal, - var complexity: Double, - var priority: Int -) { - abstract fun freeze(): Entry -} - -internal class MutableTagEntry( - val tag: TagKey, - matter: Decimal, - complexity: Double, - priority: Int -) : MutableEntry(matter, complexity, priority) { - override fun freeze(): Entry { - return TagEntry(tag, matter, complexity, priority) - } -} - -internal class MutableKeyEntry( - val key: ResourceLocation, - matter: Decimal, - complexity: Double, - priority: Int -) : MutableEntry(matter, complexity, priority) { - init { - if (key == AIR) { - throw JsonSyntaxException("you wot, can't modify $key") - } - } - - override fun freeze(): Entry { - return KeyEntry(key, matter, complexity, priority) - } - - companion object { - private val AIR = ResourceLocation("minecraft", "air") - } -} - -internal sealed class Entry( - final override val matter: Decimal, - final override val complexity: Double, - val priority: Int, -) : IMatterValue { - companion object : Entry(Decimal.ZERO, 0.0, Int.MIN_VALUE) -} - -internal class TagEntry( - val tag: TagKey, - matter: Decimal, - complexity: Double, - priority: Int, -) : Entry(matter, complexity, priority) { - val bound by lazy { - val manager = ForgeRegistries.ITEMS.tags() ?: throw NullPointerException("Forge registry of Items has no tags!") - manager.getTag(tag) - } -} - -internal class KeyEntry( - val key: ResourceLocation, - matter: Decimal, - complexity: Double, - priority: Int, -) : Entry(matter, complexity, priority) - private fun transformQuotes(it: String?): String = if (it != null) '"' + it.replace("\"", "\"\"") + '"' else "NULL" object MatterManager { @@ -170,8 +130,8 @@ object MatterManager { private val LOGGER = LogManager.getLogger() private object Registry : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), MATTER_DIRECTORY) { - val keyEntries = HashMap() - val tagEntries = LinkedHashMap, TagEntry>() + val keyEntries = HashMap() + val tagEntries = LinkedHashMap, IRegistryEntry>() val keyEntriesBlacklist = HashMap() val tagEntriesBlacklist = LinkedHashMap, BlacklistAction>() @@ -210,7 +170,7 @@ object MatterManager { .orElse(null) ?: IMatterValue.Companion } - return Entry.Companion + return IRegistryEntry.Companion } /** @@ -316,57 +276,71 @@ object MatterManager { val addOrReplace = ArrayList>() val update = ArrayList>() val delete = ArrayList>() + val sortedKeys = ArrayList(map.keys) + val inverse = Object2IntOpenHashMap() + inverse.defaultReturnValue(Int.MAX_VALUE) // выталкиваем неизвестные имена в самый конец + + for ((i, info) in ModList.get().mods.withIndex()) { + inverse[info.namespace] = i + } + + inverse[OverdriveThatMatters.MOD_ID] = -1 // загружаем файлы самого мода всегда раньше, чем всё остальное + + sortedKeys.sortWith { a, b -> inverse.getInt(a.namespace).compareTo(inverse.getInt(b.namespace)) } + + for (key in sortedKeys) { + val json = map[key]!! - for ((key, json) in map) { if (json !is JsonObject) { throw JsonParseException("Matter value $key has invalid type: ${json::class.qualifiedName} ($json)") } - when (val action = json["action"]?.asString?.lowercase() ?: throw JsonParseException("Missing `action` json element in $key. Possible values are: (INSERT, DELETE and UPDATE (case-insensitive))")) { - "insert" -> { - val value = InsertAction(json) + val result = AbstractRegistryAction.CODEC + .decode(JsonOps.INSTANCE, json) + .getOrThrow(false) { throw JsonSyntaxException("Failed to deserialize matter registry entry $key: $it") } + .first - if (value.replaceIfExists) { - addOrReplace.add(value to key) + when (result) { + is InsertAction -> { + if (result.replaceIfExists) { + addOrReplace.add(result to key) } else { - add.add(value to key) + add.add(result to key) } } - "update" -> update.add(UpdateAction(json) to key) - "delete" -> delete.add(DeleteAction(json) to key) - - "compute" -> { - try { - ComputeAction(json).also { - if (it.key != null) { - computeKeys.compute(it.key) { _, existing -> if (it.canReplace(existing)) it else existing } - } else { - computeTags.compute(it.tag!!) { _, existing -> if (it.canReplace(existing)) it else existing } - } + is UpdateAction -> update.add(result to key) + is ComputeAction -> { + result.id.map( + { + computeKeys.compute(it) { _, existing -> if (result.canReplace(existing)) result else existing } + }, + { + computeTags.compute(it) { _, existing -> if (result.canReplace(existing)) result else existing } } - } catch (err: Throwable) { - throw JsonSyntaxException("Processing $key failed", err) - } + ) } - "blacklist" -> { - val value = BlacklistAction(json) + is BlacklistAction -> { + result.id.map( + { + keyEntriesBlacklist[it] = result + }, + { + tagEntriesBlacklist[it] = result + } + ) - if (value.key != null) { - keyEntriesBlacklist[value.key] = value - } else { - tagEntriesBlacklist[value.tag!!] = value - } - - delete.add(value to key) + delete.add(result to key) } - else -> throw JsonParseException("Unknown action $action of $key") + is DeleteAction -> delete.add(result to key) + + else -> throw JsonParseException("Unknown action ${result::class.qualifiedName} in $key") } } - val registry = ArrayList() + val registry = ArrayList() for ((action, key) in add) action.update(registry, key) for ((action, key) in addOrReplace) action.update(registry, key) @@ -376,39 +350,42 @@ object MatterManager { for (key in keyEntriesBlacklist.keys) computeKeys.remove(key) for (key in tagEntriesBlacklist.keys) computeTags.remove(key) - val tags = HashMap, MutableTagEntry>() - val keys = HashMap() + val tags = HashMap, IMutableRegistryEntry>() + val keys = HashMap() for (entry in registry) { - if (entry is MutableTagEntry) { - val existing = tags[entry.tag] + entry.name.map( + { + val existing = keys[it] - if (existing == null || existing.priority < entry.priority) { - tags[entry.tag] = entry - } - } else if (entry is MutableKeyEntry) { - val existing = keys[entry.key] + if (existing == null || existing.priority < entry.priority) { + keys[it] = entry + } + }, + { + val existing = tags[it] - if (existing == null || existing.priority < entry.priority) { - keys[entry.key] = entry + if (existing == null || existing.priority < entry.priority) { + tags[it] = entry + } } - } + ) } for (key in keys.values) { - keyEntries[key.key] = key.freeze() as KeyEntry + keyEntries[key.name.left().orElseThrow()] = key.freeze() } - val tagEntries = ArrayList() + val tagEntries = ArrayList() for (tag in tags.values) { - tagEntries.add(tag.freeze() as TagEntry) + tagEntries.add(tag.freeze()) } tagEntries.sortBy { it.priority } for (entry in tagEntries) { - this.tagEntries[entry.tag] = entry + this.tagEntries[entry.name.right().orElseThrow()] = entry } } finally { profilerFiller.pop() @@ -468,24 +445,189 @@ object MatterManager { val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true - var stream = server.recipeManager.byType(findRecipeType).values.parallelStream().filter { !it.isIncomplete } + var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete } if (ignoreDamageables) { - stream = stream.filter { it.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } + stream = stream.filter { it.value.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } + } + + stream.filter { it.value.getResultItem().isNotEmpty }.map { + try { + ResolvedRecipe( + it.value.ingredients.stream() + .filter { !it.isActuallyEmpty } + .map { it.items.stream().filter { it.isNotEmpty }.map(::RecipeEntry) }, + ImmutableStack(it.value.getResultItem()), + isCritical = isCritical, + name = it.id, + allowBacktrack = allowBacktrack + ) + } catch(err: Throwable) { + throw RuntimeException("Processing simple recipe ${it.id}", err) + } + } + .filter { + if (it.inputs.isEmpty()) { + LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") + false + } else { + true + } + } + } + } + + registrar.register("crafting") { + Finder { server, data -> + val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}") + + if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) { + LOGGER.error("Invalid or missing recipe category: $location!") + return@Finder Stream.empty() + } + + val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType>? ?: throw ConcurrentModificationException() + + val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true + val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false + val isCritical = data["is_critical"]?.asBoolean ?: true + var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete } + + if (ignoreDamageables) { + stream = stream.filter { it.value.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } } stream.map { - ResolvedRecipe( - it.ingredients.stream() - .filter { !it.isActuallyEmpty } - .map { it.items.stream().map(::ImmutableStack) }, - ImmutableStack(it.resultItem), - isCritical = isCritical, - name = it.id, - allowBacktrack = allowBacktrack - ) + try { + // avoid reality snap when recipe has no output + val resultItem = it.value.getResultItem() + + if (resultItem.isEmpty) { + null + } else { + var width: Int + var height: Int + + if (it.value is IShapedRecipe<*>) { + width = (it.value as IShapedRecipe<*>).recipeWidth + height = (it.value as IShapedRecipe<*>).recipeHeight + } else { + width = 3 + height = 3 + } + + if (width * height < it.value.ingredients.size) { + width = it.value.ingredients.size.coerceAtLeast(width) + height = it.value.ingredients.size.coerceAtLeast(height) + } + + val container = CraftingContainer(object : AbstractContainerMenu(null, 0) { + override fun quickMoveStack(pPlayer: Player, pIndex: Int): ItemStack { + return ItemStack.EMPTY + } + + override fun stillValid(pPlayer: Player): Boolean { + return false + } + }, width, height) + + val realIngredients = ArrayList>() + + for (c in it.value.ingredients.indices) { + if (it.value.ingredients[c].isActuallyEmpty) { + continue + } + + for ((i, ingredient) in it.value.ingredients.withIndex()) { + if (i != c) { + container[i] = if (ingredient.isActuallyEmpty) ItemStack.EMPTY else ingredient.items.firstOrNull() ?: ItemStack.EMPTY + } + } + + val result = ArrayList() + + for (item in it.value.ingredients[c].items) { + container[c] = item + + if (!it.value.assemble(container).isEmpty) { + val residue = it.value.getRemainingItems(container) + + val thisResidue = residue[c] + + if (thisResidue.isEmpty) { + result.add(RecipeEntry(item)) + } else if (item.isNotEmpty) { + result.add(RecipeEntry(ImmutableStack(item), ImmutableStack(thisResidue))) + } else { + LOGGER.warn("Crafting table recipe ${it.id} has literally minecraft:air as item at $c") + } + } + } + + realIngredients.add(result) + } + + ResolvedRecipe( + realIngredients.stream().map { it.stream() }, + ImmutableStack(resultItem), + isCritical = isCritical, + name = it.id, + allowBacktrack = allowBacktrack + ) + } + } catch(err: Throwable) { + throw RuntimeException("Processing crafting table recipe ${it.id}", err) + } + }.filterNotNull().filter { + if (it.inputs.isEmpty()) { + LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") + false + } else { + true + } } - .filter { + } + } + + registrar.register("smithing") { + Finder { server, data -> + val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}") + + if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) { + LOGGER.error("Invalid or missing recipe category: $location!") + return@Finder Stream.empty() + } + + val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType>? ?: throw ConcurrentModificationException() + + val isCritical = data["is_critical"]?.asBoolean ?: true + val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true + + var stream = server.recipeManager.byType(findRecipeType).values.stream().filter { !it.value.isIncomplete } + stream = stream.filter { it.value is UpgradeRecipe } + + stream.filter { it.value.getResultItem().isNotEmpty }.map { + try { + val ingredients = ArrayList() + + val recipe = it.value as UpgradeRecipe + + ingredients.add(recipe.base) + ingredients.add(recipe.addition) + + ResolvedRecipe( + ingredients.stream() + .filter { !it.isActuallyEmpty } + .map { it.items.stream().filter { it.isNotEmpty }.map(::RecipeEntry) }, + ImmutableStack(it.value.resultItem), + isCritical = isCritical, + name = it.id, + allowBacktrack = allowBacktrack + ) + } catch(err: Throwable) { + throw RuntimeException("Processing smithing recipe ${it.id}", err) + } + }.filterNotNull().filter { if (it.inputs.isEmpty()) { LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") false @@ -570,7 +712,7 @@ object MatterManager { for (input1 in recipe.inputs) { for (input in input1) { - input2Recipes.computeIfAbsent(input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) + input2Recipes.computeIfAbsent(input.input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) } } } @@ -583,12 +725,14 @@ object MatterManager { private val seenItems = ArrayDeque() private var changes = false + private var iteration = 0 + private var lenientStage = false private var cachedIterationResults = Reference2ObjectOpenHashMap() private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result { val recipes = input2Recipes[item] - if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.item == item } } }) { + if (recipes.isNullOrEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.input.item == item } } }) { if (makeCommentary) comment(item, TextComponent("Item '${item.registryName}' has no recipes")) @@ -695,37 +839,147 @@ object MatterManager { inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) { var minimal: IMatterValue? = null var minimalMultiplier = 0.0 + val skips = ArrayList() + val recursiveSkips = ArrayList() + var hadResidue = false - innerInputsLoop@ for (input in inputs) { - val ivalue = determineValue(input.item) + innerInputsLoop@ for (entry in inputs) { + val ivalue = determineValue(entry.input.item) if (ivalue.isMissing) { - comment(item, TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} has no matter value")) + comment(item, TextComponent("Input $entry at input slot $i in ${recipe.formattedName} has no matter value")) - if (recipe.isCritical) { + if (recipe.isCritical && !lenientStage) { return Result.MISSING } else { - continue@recipesLoop + if (recipe.isCritical) { + skips.add(entry) + continue@innerInputsLoop + } else { + continue@recipesLoop + } } } else if (ivalue.isSkipped) { - comment(item, TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive")) + if (recipes.size == 1 && entry.input.item == recipe.output.item) { + // assume cloning and/or catalyst? + comment(item, TextComponent("Input $entry at input slot $i is assumed to be cloning/catalyst")) - if (inputs.size == 1) { - hadSkips = true - continue@recipesLoop + if (inputs.size == 1) { + continue@inputsLoop + } else { + continue@innerInputsLoop + } } else { - continue@innerInputsLoop + comment(item, TextComponent("Input $entry at input slot $i in ${recipe.formattedName} is recursive")) + recursiveSkips.add(entry) + + if (inputs.size == 1) { + hadSkips = true + continue@recipesLoop + } else { + continue@innerInputsLoop + } } } - if (minimal == null || minimal > ivalue.value!!) { - minimal = ivalue.value - minimalMultiplier = input.multiplier + var realValue = ivalue.value!! + val ivalue2 = entry.remainder?.item?.let(::determineValue) + + if (ivalue2 != null) { + if (ivalue2.isMissing) { + comment(item, TextComponent("Remainder of $entry at input slot $i in ${recipe.formattedName} has no matter value")) + + if (recipe.isCritical && !lenientStage) { + return Result.MISSING + } else { + if (recipe.isCritical) { + skips.add(entry) + continue@innerInputsLoop + } else { + continue@recipesLoop + } + } + } else if (ivalue2.isSkipped) { + comment(item, TextComponent("Remainder of $entry at input slot $i in ${recipe.formattedName} is recursive")) + recursiveSkips.add(entry) + + if (inputs.size == 1) { + hadSkips = true + continue@recipesLoop + } else { + continue@innerInputsLoop + } + } + + realValue = MatterValue(realValue.matter - ivalue2.value!!.matter, realValue.complexity - ivalue2.value.complexity) + hadResidue = true + + if (realValue.matter.isNegative || realValue.complexity < 0.0) { + comment(item, TextComponent("Entry $entry at input slot $i in ${recipe.formattedName} ended up with negative matter value or/and complexity")) + return Result.MISSING + } + } + + if (minimal == null || minimal > realValue) { + minimal = realValue + minimalMultiplier = entry.input.multiplier } } + if (skips.size > inputs.size / 2 || skips.isNotEmpty() && inputs.size == 1) { + comment(item, TextComponent("More than half inputs (${skips.joinToString(", ")}) at input slot $i in ${recipe.formattedName} have no matter values")) + return Result.MISSING + } else if (skips.isNotEmpty()) { + /** + * Эвристический анализ тегов + * + * Если у 60% <= предметов со значением материи есть тег, и он есть у всех + * предметов без значения материи, то предметы без материи игнорируются + */ + val manager = ForgeRegistries.ITEMS.tags()!! + val tagSetsPresent = Object2IntArrayMap>() + val tagSetsMissing = Object2IntArrayMap>() + + for (input in inputs) { + if (input in recursiveSkips) { + continue + } + + val list = manager.getReverseTag(input.input.item).getOrNull()?.tagKeys?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of() + + if (input !in skips) { + for (tag in list) + tagSetsPresent.computeInt(tag) { _, e -> (e ?: 0) + 1 } + } else { + for (tag in list) + tagSetsMissing.computeInt(tag) { _, e -> (e ?: 0) + 1 } + } + } + + val filtered = tagSetsPresent.object2IntEntrySet() + .filter { it.intValue.toDouble() >= (inputs.size - skips.size - recursiveSkips.size) * 0.6 } + + val result = filtered.all { tagSetsMissing.getInt(it.key) == skips.size } + + if (!result) { + comment(item, TextComponent("More than half inputs (${skips.joinToString(", ")}) at input slot $i in ${recipe.formattedName} have no matter values")) + return Result.MISSING + } else { + comment(item, TextComponent("${recipe.formattedName} with inputs at slot $i without matter values were allowed to be skipped due to next tags:")) + + for (tag in filtered) { + comment(item, TextComponent(tag.key.location.toString())) + } + } + } + + if (minimal != null && !minimal.hasMatterValue && hadResidue && inputs.size == 1) { + // ингредиент ничего не стоит ибо не потребляется в крафте + continue@inputsLoop + } + if (minimal == null || !minimal.hasMatterValue) { - comment(item, TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ", transform = { it.item.registryName.toString() }) }) (???)")) + comment(item, TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ")}) (???)")) return Result.MISSING } @@ -739,7 +993,8 @@ object MatterManager { } if (accumulatedMatter == null || accumulatedComplexity == null) { - throw RuntimeException("This piece should be unreachable") + comment(item, TextComponent("${recipe.formattedName} ended up with undefined matter value and/or complexity (all inputs skipped)")) + return Result.MISSING } if (!accumulatedMatter.isPositive || accumulatedComplexity <= 0.0) { @@ -894,6 +1149,26 @@ object MatterManager { time = SystemTime() + lenientStage = false + + while (changes) { + ops += cachedIterationResults.size + cachedIterationResults = Reference2ObjectOpenHashMap() + changes = false + iteration++ + + val iterator = toDetermine.iterator() + + for (value in iterator) { + if (determineValue(value).value?.hasMatterValue == true) { + iterator.remove() + } + } + } + + lenientStage = true + changes = true + while (changes) { ops += cachedIterationResults.size cachedIterationResults = Reference2ObjectOpenHashMap() @@ -923,8 +1198,6 @@ object MatterManager { } } - var iteration = 0 - internal fun compute(value: Item): Result { return Resolver.determineValue(value) } @@ -988,7 +1261,7 @@ object MatterManager { matter = get(value.item) if (matter.hasMatterValue && value.isDamageableItem) { - val severity = 1.0 - value.damageValue.toDouble() / value.maxDamage.toDouble() + val severity = 1.0 - value.damageValue.toDouble() / (value.maxDamage.toDouble() + 1.0) undamagedMatterValue = matter matter = MatterValue(matter.matter * severity, matter.complexity * (0.5 + severity / 2)) } else { @@ -1004,9 +1277,9 @@ object MatterManager { val drive = value.getCapability(MatteryCapability.DRIVE).orNull() - if (drive != null && drive.storageType == OverdriveThatMatters.INSTANCE.ITEM_STORAGE()) { - (drive as IMatteryDrive).stacks - .map { it.stack.stack } + if (drive != null && drive.storageType == StorageStack.ITEMS) { + (drive as IMatteryDrive).stacks + .map { it.stack.toItemStack() } .filter { !it.isEmpty } .map { get(it, level + 1, true) } .reduce(::reduce) @@ -1084,10 +1357,10 @@ object MatterManager { return getResearchAdvance(this@MatterManager.get(stack.asItem()).complexity) } - fun tooltipEvent(event: ItemTooltipEvent) { - val window = minecraft.window.window + private val formatMatterAsReadable = BooleanSupplier { if (ClientConfig.Tooltip.ALWAYS_DISPLAY_MATTER_VALUE) minecraft.window.isShiftDown else minecraft.window.isShiftDown && milliTime % 2_000L > 1_000L } - if (InputConstants.isKeyDown(window, GLFW.GLFW_KEY_LEFT_SHIFT) || InputConstants.isKeyDown(window, GLFW.GLFW_KEY_RIGHT_SHIFT)) { + fun tooltipEvent(event: ItemTooltipEvent) { + if (ClientConfig.Tooltip.ALWAYS_DISPLAY_MATTER_VALUE || minecraft.window.isShiftDown) { val matter = get(event.itemStack, accountForStackSize = false) if (matter.hasMatterValue) { @@ -1097,20 +1370,20 @@ object MatterManager { if (matter.matter != matterSized.matter || matter.complexity != matterSized.complexity) { event.toolTip.add( TranslatableComponent("otm.gui.matter.format_and_complexity2", - matter.matter.formatMatterFull(), - matterSized.matter.formatMatter(), + matter.matter.formatMatterFull(formatAsReadable = formatMatterAsReadable), + matterSized.matter.formatMatter(formatAsReadable = formatMatterAsReadable), if (matter.complexity > 1000.0) formatTickDuration(matter.complexity.roundToInt(), true) else matter.complexity.formatSiComponent(TranslatableComponent("otm.gui.ticks")), if (matterSized.complexity > 1000.0) formatTickDuration(matterSized.complexity.roundToInt(), true) else matterSized.complexity.formatSiComponent(TranslatableComponent("otm.gui.ticks")), ).withStyle(ChatFormatting.AQUA)) } else { event.toolTip.add( TranslatableComponent("otm.gui.matter.format_and_complexity", - matter.matter.formatMatterFull(), + matter.matter.formatMatterFull(formatAsReadable = formatMatterAsReadable), if (matter.complexity > 1000.0) formatTickDuration(matter.complexity.roundToInt(), true) else matter.complexity.formatSiComponent(TranslatableComponent("otm.gui.ticks")), ).withStyle(ChatFormatting.AQUA)) } } else { - event.toolTip.add(matter.matter.formatMatterFull().withStyle(ChatFormatting.AQUA)) + event.toolTip.add(matter.matter.formatMatterFull(formatAsReadable = formatMatterAsReadable).withStyle(ChatFormatting.AQUA)) } } @@ -1119,11 +1392,20 @@ object MatterManager { if (commentary != null) { if (commentary.size > 3) { - event.toolTip.add(TextComponent("...").withStyle(ChatFormatting.DARK_GRAY)) - } + var index = ((secondTime / 3L) % commentary.size).toInt() - for (i in (commentary.size - 3).coerceAtLeast(0) until commentary.size) { - event.toolTip.add(commentary[i].withStyle(ChatFormatting.DARK_GRAY)) + for (i in 0 .. 2) { + if (index == commentary.size) { + event.toolTip.add(TextComponent("=========").withStyle(ChatFormatting.DARK_GRAY)) + index = 0 + } + + event.toolTip.add(commentary[index++].withStyle(ChatFormatting.DARK_GRAY)) + } + } else { + for (comment in commentary) { + event.toolTip.add(comment.withStyle(ChatFormatting.DARK_GRAY)) + } } } } @@ -1137,12 +1419,29 @@ object MatterManager { val item: Item, val multiplier: Double = 1.0 ) { + init { + require(!multiplier.isNaN()) { "Provided stack cost multiplier is NaN (item: ${item.registryName})" } + require(!multiplier.isInfinite()) { "Provided stack cost multiplier is infinite (item: ${item.registryName})" } + } + constructor(item: Item, count: Int) : this(item, 1.0 / count.toDouble()) constructor(item: ItemStack) : this(item.item, item.count) } + data class RecipeEntry( + val input: ImmutableStack, + val remainder: ImmutableStack? = null + ) { + constructor(item: Item, count: Int) : this(ImmutableStack(item, count)) + constructor(item: ItemStack) : this(ImmutableStack(item)) + + override fun toString(): String { + return "RecipeEntry[${input.item.registryName} to ${remainder?.item?.registryName}]" + } + } + class ResolvedRecipe( - inputs: Stream>, + inputs: Stream>, val output: ImmutableStack, val transformMatterValue: (Decimal) -> Decimal = { it }, val transformComplexity: (Double) -> Double = { it }, @@ -1160,25 +1459,42 @@ object MatterManager { * (e.g. 4 rabbit hides -> 1 leather), we can assume that we can determine matter value of ingredients based on matter value of output * * "Backtrack" can happen if everything of the next holds true: - * * All recipes with item in question contains only that item; and + * * All recipes with item in question contains only that item; * * All recipes allow backtracking */ val allowBacktrack: Boolean = true, ) { - val inputs: List> = inputs - .map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } + val inputs: List> = inputs + .map { it.filter { it.input.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } .filter { it.isNotEmpty() } .collect(ImmutableList.toImmutableList()) - constructor( - inputs: Collection>, - output: ImmutableStack, - transformMatterValue: (Decimal) -> Decimal = { it }, - transformComplexity: (Double) -> Double = { it }, - ) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity) - val formattedName: String get() = if (name == null) "One of recipes" else "Recipe '$name'" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ResolvedRecipe) return false + + if (output != other.output) return false + if (transformMatterValue != other.transformMatterValue) return false + if (transformComplexity != other.transformComplexity) return false + if (isCritical != other.isCritical) return false + if (allowBacktrack != other.allowBacktrack) return false + if (inputs != other.inputs) return false + + return true + } + + override fun hashCode(): Int { + var result = output.hashCode() + result = 31 * result + transformMatterValue.hashCode() + result = 31 * result + transformComplexity.hashCode() + result = 31 * result + isCritical.hashCode() + result = 31 * result + allowBacktrack.hashCode() + result = 31 * result + inputs.hashCode() + return result + } } fun interface Finder { @@ -1193,7 +1509,7 @@ object MatterManager { * * You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential. */ - fun find(server: MinecraftServer, json: JsonObject): Stream + fun find(server: MinecraftServer, data: JsonObject): Stream } /** @@ -1204,7 +1520,18 @@ object MatterManager { return Registry.direct(value) } + /** + * Access recipe finders registry + * + * @throws IllegalStateException if calling too early + */ @JvmStatic val recipeFinders get() = Resolver.delegate.get() + + /** + * Access recipe finders registry key + * + * Use this with your [DeferredRegister] + */ @JvmStatic val recipeFindersKey get() = Resolver.delegate.key private val commentary = Reference2ObjectOpenHashMap>() @@ -1366,7 +1693,7 @@ object MatterManager { matterValues[item] = value } - RegistryNetworkChannel.send(PacketDistributor.ALL.noArg(), SyncPacket(matterValues, commentary)) + syncRegistry(PacketDistributor.ALL.noArg()) } fun onDataPackSync(event: OnDatapackSyncEvent) { @@ -1374,64 +1701,211 @@ object MatterManager { return if (event.player == null) { - RegistryNetworkChannel.send(PacketDistributor.ALL.noArg(), SyncPacket(matterValues, commentary)) + syncRegistry(PacketDistributor.ALL.noArg()) } else { - RegistryNetworkChannel.send(PacketDistributor.PLAYER.with { event.player ?: throw ConcurrentModificationException() }, SyncPacket(matterValues, commentary)) + syncRegistry(PacketDistributor.PLAYER.with { event.player!! }) } } fun onServerStarted(event: ServerStartedEvent) { check(Resolver.ready) { "Recipe resolver is not ready somehow" } check(Registry.ready) { "Matter registry is not ready somehow" } - finishUpIfRequiredAndPossible(event.server ?: throw NullPointerException("what.")) + finishUpIfRequiredAndPossible(event.server) } fun get(value: Item): IMatterValue { return matterValues[value] ?: IMatterValue.Companion } + private val receivedPackets = ArrayList() + + private fun syncRegistry(distributor: PacketDistributor.PacketTarget) { + val time = SystemTime() + val stream = FastByteArrayOutputStream() + val data = DataOutputStream(stream) + + var commentsSize = commentary.size + data.writeInt(matterValues.size) + + for ((k, v) in matterValues) { + data.writeItemType(k) + data.writeMatterValue(v) + + val comment = commentary[k] + + if (comment != null) { + commentsSize-- + data.write(1) + data.writeCollection(comment, OutputStream::writeBinaryComponent) + } else { + data.write(0) + } + } + + data.writeInt(commentsSize) + + for ((k, v) in commentary) { + if (!matterValues.containsKey(k)) { + data.writeItemType(k) + data.writeCollection(v, OutputStream::writeBinaryComponent) + } + } + + val deflater = Deflater(5) + deflater.setInput(stream.array, 0, stream.length) + deflater.finish() + + val chunks = ArrayList() + var totalSize = 0 + var first = true + + while (true) { + val bytes = ByteArray(2 shl 20 - 1024) + val written = deflater.deflate(bytes) + + if (written == 0) { + break + } else { + totalSize += written + chunks.add(SyncPacket(bytes, written, if (first) { first = false; FIRST } else NORMAL)) + } + } + + if (chunks.size == 1) { + chunks[0].mode = FIRST_AND_LAST + } else if(chunks.size > 1) { + chunks.last().mode = LAST + } + + LOGGER.debug("Encoding matter registry packet took ${time.millis}ms, (${stream.length} bytes total, $totalSize bytes compressed)") + + for (chunk in chunks) { + GenericNetworkChannel.send(distributor, chunk) + } + } + + private fun playRegistryPackets() { + val time = SystemTime() + var totalCompressedSize = 0 + + for (chunk in receivedPackets) { + totalCompressedSize += chunk.length + } + + if (totalCompressedSize == 0) { + return // what. + } + + val compressed = ByteArray(totalCompressedSize) + var pointer = 0 + + for (chunk in receivedPackets) { + for (i in 0 until chunk.length) { + compressed[pointer++] = chunk.payload[i] + } + } + + receivedPackets.clear() + + val chunks = ArrayList() + var size = 0 + val inflater = Inflater() + inflater.setInput(compressed) + + while (!inflater.finished()) { + val chunk = ByteArray(2 shl 16) + val inflated = inflater.inflate(chunk) + + if (inflated == 0) { + break + } else { + size += inflated + + if (size >= 1 shl 24 /* 16 MiB */) { + throw IndexOutOfBoundsException("Pipe Bomb") + } + + if (inflated == 2 shl 16) { + chunks.add(chunk) + } else { + chunks.add(chunk.copyOfRange(0, inflated)) + } + } + } + + val spliced = ByteArray(size) + var pointer2 = 0 + + for (chunk in chunks) { + for (i in chunk.indices) { + spliced[pointer2++] = chunk[i] + } + } + + val stream = FastByteArrayInputStream(spliced) + val data = DataInputStream(stream) + + val valuesSize = data.readInt() + + matterValues.clear() + commentary.clear() + + for (i in 0 until valuesSize) { + val type = data.readItemType() + check(matterValues.put(type, data.readMatterValue()) == null) { "Duplicate item type $type" } + + if (data.read() > 0) { + commentary[type] = data.readCollection { readBinaryComponent()!! } + } + } + + val commentsSize = data.readInt() + + for (i in 0 until commentsSize) { + val type = data.readItemType() + check(commentary.put(type, data.readCollection { readBinaryComponent()!! }) == null) { "Duplicate commentary item type $type" } + } + + LOGGER.debug("Decoding matter registry packets took ${time.millis}ms ($totalCompressedSize bytes compressed, $size bytes total)") + } + fun readSyncPacket(buff: FriendlyByteBuf): SyncPacket { LOGGER.info("Received matter registry packet, ${buff.readableBytes()} bytes in size") - val time = SystemTime() - - val result = SyncPacket( - buff.readMap(FriendlyByteBuf::readItemType, FriendlyByteBuf::readMatterValue), - buff.readMap(FriendlyByteBuf::readItemType) { self -> self.readCollection(::ArrayList, FriendlyByteBuf::readComponent) } - ) - - LOGGER.debug("Reading matter registry packet took ${time.millis}ms") - - return result + val mode = buff.readByte() + val bytes = ByteArray(buff.readableBytes()) + buff.readBytes(bytes) + return SyncPacket(bytes, bytes.size, mode.toInt()) } - class SyncPacket( - val values: Map, - val comments: Map> - ) : MatteryPacket { + private const val FIRST = 0 + private const val NORMAL = 1 + private const val LAST = 2 + private const val FIRST_AND_LAST = 3 + + class SyncPacket(val payload: ByteArray, val length: Int, var mode: Int) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { - val time = SystemTime() - buff.writeMap(values, FriendlyByteBuf::writeItemType, FriendlyByteBuf::writeMatterValue) - buff.writeMap(comments, FriendlyByteBuf::writeItemType) { self, value -> self.writeCollection(value, FriendlyByteBuf::writeComponent) } - LOGGER.debug("Encoding matter registry packet took ${time.millis}ms, written total ${buff.writerIndex() - 1} bytes") + buff.writeByte(mode) + buff.writeBytes(payload, 0, length) } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { if (SERVER_IS_LIVE) return // singleplayer or LAN host - val time = SystemTime() - - matterValues.clear() - matterValues.putAll(values) - - commentary.clear() - - for ((k, v) in comments) { - commentary[k] = ArrayList(v.size).also { it.addAll(v) } + if (mode == FIRST) { + receivedPackets.clear() + receivedPackets.add(this) + } else if (mode == LAST) { + receivedPackets.add(this) + playRegistryPackets() + } else if (mode == FIRST_AND_LAST) { + receivedPackets.clear() + receivedPackets.add(this) + playRegistryPackets() + } else { + receivedPackets.add(this) } - - LOGGER.debug("Updating matter registry from packet took ${time.millis}ms") } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt new file mode 100644 index 000000000..ddab4fed1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/RegistryEntries.kt @@ -0,0 +1,60 @@ +package ru.dbotthepony.mc.otm.matter + +import com.mojang.datafixers.util.Either +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.TagKey +import net.minecraft.world.item.Item +import ru.dbotthepony.mc.otm.core.math.Decimal + +interface IRegistryEntry : IMatterValue { + val name: Either> + override val matter: Decimal + override val complexity: Double + val priority: Int + + fun freeze(): IRegistryEntry { + return this + } + + companion object : IRegistryEntry { + override fun freeze(): IRegistryEntry { + return this + } + + override val name: Either> = Either.left(ResourceLocation("minecraft", "air")) + override val matter: Decimal + get() = Decimal.ZERO + override val complexity: Double + get() = 0.0 + override val priority: Int + get() = 0 + } +} + +interface IMutableRegistryEntry : IRegistryEntry { + override var matter: Decimal + override var complexity: Double + override var priority: Int + + override fun freeze(): IRegistryEntry { + throw UnsupportedOperationException() + } +} + +data class RegistryEntry( + override val name: Either>, + override val matter: Decimal, + override val complexity: Double, + override val priority: Int, +) : IRegistryEntry + +data class MutableRegistryEntry( + override val name: Either>, + override var matter: Decimal, + override var complexity: Double, + override var priority: Int, +) : IMutableRegistryEntry { + override fun freeze(): IRegistryEntry { + return RegistryEntry(name, matter, complexity, priority) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt index 5a255cf50..6f6748ecf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/UpdateAction.kt @@ -1,195 +1,92 @@ package ru.dbotthepony.mc.otm.matter import com.google.common.collect.ImmutableList -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParseException +import com.mojang.datafixers.util.Either +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey import net.minecraft.world.item.Item -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.data.stream +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.simpleCodec -class UpdateAction : AbstractRegistryAction { - companion object { - private inline fun deserializeFunctionTree(input: JsonElement?, name: String): List> { - if (input == null) { - return listOf() - } - - if (input !is JsonArray) { - throw JsonParseException("Expected $name to be JsonArray, ${input::class.qualifiedName} given") - } - - return input.stream().map { - if (it !is JsonObject) { - throw JsonParseException("All elements in function tree must be JsonObjects, ${it::class.qualifiedName} given") - } - - val type = it["type"]?.asString ?: throw JsonParseException("Invalid `type` value in function object") - val value = it["value"] ?: throw JsonParseException("Missing `value` value in function object") - - val parsedValue: T = BoundMatterFunction.getValue(value) - val fn: MatterFunction - - try { - fn = MatterFunction.valueOf(type.uppercase()) - } catch (err: NoSuchElementException) { - throw JsonParseException("No such function: $type", err) - } - - return@map BoundMatterFunction(fn, parsedValue) - }.collect(ImmutableList.toImmutableList()) - } - } - - val matterFunctions: List> - val complexityFunctions: List> - val priorityFunctions: List> - - constructor(json: JsonObject) : super(json) { - matterFunctions = deserializeFunctionTree(json["matter_functions"], "matter_functions") - complexityFunctions = deserializeFunctionTree(json["complexity_functions"], "complexity_functions") - priorityFunctions = deserializeFunctionTree(json["priority_functions"], "priority_functions") - } - - constructor( - tag: TagKey, - matter: Decimal? = null, - complexity: Double? = null, - priority: Int? = null, - errorOnFailure: Boolean = false, - matterFunctions: List> = ImmutableList.of(), - complexityFunctions: List> = ImmutableList.of(), - priorityFunctions: List> = ImmutableList.of(), - ) : super(tag = tag, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { - check(matter != null || complexity != null || priority != null || matterFunctions.isNotEmpty() || complexityFunctions.isNotEmpty() || priorityFunctions.isNotEmpty()) { - "Can't update existing entries without anything specified" - } - - this.matterFunctions = ImmutableList.copyOf(matterFunctions) - this.complexityFunctions = ImmutableList.copyOf(complexityFunctions) - this.priorityFunctions = ImmutableList.copyOf(priorityFunctions) - } - - constructor( - key: ResourceLocation, - matter: Decimal? = null, - complexity: Double? = null, - priority: Int? = null, - errorOnFailure: Boolean = false, - matterFunctions: List> = ImmutableList.of(), - complexityFunctions: List> = ImmutableList.of(), - priorityFunctions: List> = ImmutableList.of(), - ) : super(key = key, matter = matter, complexity = complexity, priority = priority, errorOnFailure = errorOnFailure) { - check(matter != null || complexity != null || priority != null || matterFunctions.isNotEmpty() || complexityFunctions.isNotEmpty() || priorityFunctions.isNotEmpty()) { - "Can't update existing entries without anything specified" - } - - this.matterFunctions = ImmutableList.copyOf(matterFunctions) - this.complexityFunctions = ImmutableList.copyOf(complexityFunctions) - this.priorityFunctions = ImmutableList.copyOf(priorityFunctions) - } - - override fun toJson(): JsonObject { - return super.toJson().also { - it["action"] = "update" - - if (matterFunctions.isNotEmpty()) { - it["matter_functions"] = JsonArray().also { - for (fn in matterFunctions) { - it.add(fn.toJson()) - } - } - } - - if (complexityFunctions.isNotEmpty()) { - it["complexity_functions"] = JsonArray().also { - for (fn in complexityFunctions) { - it.add(fn.toJson()) - } - } - } - - if (priorityFunctions.isNotEmpty()) { - it["priority_functions"] = JsonArray().also { - for (fn in priorityFunctions) { - it.add(fn.toJson()) - } - } +class UpdateAction( + id: Either>, + matterFunctions: Collection, + complexityFunctions: Collection, + priorityFunctions: Collection, + errorOnFailure: Boolean = false, +) : AbstractRegistryAction(id, errorOnFailure) { + data class MatterFunction(val function: IMatterFunction, val value: Decimal) { + companion object { + val CODEC: Codec by lazy { + simpleCodec(::MatterFunction, MatterFunction::function, IMatterFunction.registry.codec, MatterFunction::value, DecimalCodec) } } } - private fun apply(value: MutableEntry, modifier: ResourceLocation) { - if (matterFunctions.isNotEmpty()) - value.matter = matterFunctions.apply(value.matter) - else if (matter != null) - value.matter = matter - - if (complexityFunctions.isNotEmpty()) - value.complexity = complexityFunctions.apply(value.complexity) - else if (complexity != null) - value.complexity = complexity - - if (priorityFunctions.isNotEmpty()) - value.priority = priorityFunctions.apply(value.priority) - else if (priority != null) - value.priority = priority + data class ComplexityFunction(val function: IMatterFunction, val value: Double) { + companion object { + val CODEC: Codec by lazy { + simpleCodec(::ComplexityFunction, ComplexityFunction::function, IMatterFunction.registry.codec, ComplexityFunction::value, Codec.DOUBLE) + } + } } - override fun update(registry: MutableCollection, modifier: ResourceLocation) { - if(!( - matter != null || - complexity != null || - priority != null || - matterFunctions.isNotEmpty() || - complexityFunctions.isNotEmpty() || - priorityFunctions.isNotEmpty() - )) { - throw JsonParseException("Can't update existing entries without anything specified (for $modifier)") - } - - if (matter != null && matterFunctions.isNotEmpty()) { - throw JsonParseException("Can't have both matter and matter functions specified at the same time (for $modifier)") - } - - if (complexity != null && complexityFunctions.isNotEmpty()) { - throw JsonParseException("Can't have both complexity and complexity functions specified at the same time (for $modifier)") - } - - if (priority != null && priorityFunctions.isNotEmpty()) { - throw JsonParseException("Can't have both priority and priority functions specified at the same time (for $modifier)") - } - - if (!checkConditions()) { - return - } - - if (key != null) { - val iterator = registry.iterator() - - for (value in iterator) { - if (value is MutableKeyEntry && value.key == key) { - apply(value, modifier) - return - } + data class PriorityFunction(val function: IMatterFunction, val value: Int) { + companion object { + val CODEC: Codec by lazy { + simpleCodec(::PriorityFunction, PriorityFunction::function, IMatterFunction.registry.codec, PriorityFunction::value, Codec.INT) } - - fail { "Could not find matter value with key $key" } - } else { - val iterator = registry.iterator() - - for (value in iterator) { - if (value is MutableTagEntry && value.tag == tag) { - apply(value, modifier) - return - } - } - - fail { "Could not find matter value with tag $tag" } } } + + companion object : Type { + override val codec: Codec by lazy { + RecordCodecBuilder.create { + it.group( + TargetCodec.fieldOf("id").forGetter(UpdateAction::id), + Codec.list(MatterFunction.CODEC).fieldOf("matterFunctions").forGetter(UpdateAction::matterFunctions), + Codec.list(ComplexityFunction.CODEC).fieldOf("complexityFunctions").forGetter(UpdateAction::complexityFunctions), + Codec.list(PriorityFunction.CODEC).fieldOf("priorityFunctions").forGetter(UpdateAction::priorityFunctions), + Codec.BOOL.optionalFieldOf("errorOnFailure", false).forGetter(UpdateAction::errorOnFailure) + ).apply(it, ::UpdateAction) + } + } + } + + val matterFunctions: ImmutableList = ImmutableList.copyOf(matterFunctions) + val complexityFunctions: ImmutableList = ImmutableList.copyOf(complexityFunctions) + val priorityFunctions: ImmutableList = ImmutableList.copyOf(priorityFunctions) + + override val type: Type<*> + get() = Companion + + private fun apply(value: IMutableRegistryEntry, modifier: ResourceLocation) { + matterFunctions.forEach { + value.matter = it.function.updateValue(value.matter, it.value) + } + + complexityFunctions.forEach { + value.complexity = it.function.updateValue(value.complexity, it.value) + } + + priorityFunctions.forEach { + value.priority = it.function.updateValue(value.priority, it.value) + } + } + + override fun doUpdate(registry: MutableCollection, source: ResourceLocation) { + val iterator = registry.iterator() + + for (value in iterator) { + if (value.name == id) { + apply(value, source) + return + } + } + + fail { "Could not find matter value with name $id" } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/BatteryBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/BatteryBankMenu.kt deleted file mode 100644 index 9cd7da508..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/BatteryBankMenu.kt +++ /dev/null @@ -1,36 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import net.minecraft.world.Container -import kotlin.jvm.JvmOverloads -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.block.entity.BatteryBankBlockEntity -import net.minecraft.world.SimpleContainer -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.core.orNull -import ru.dbotthepony.mc.otm.registry.MMenus - -class BatteryBankMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: BatteryBankBlockEntity? = null, -) : MatteryMenu(MMenus.BATTERY_BANK, p_38852_, inventory, tile) { - val powerLevel: LevelGaugeWidget - override val storageSlots: List - - init { - val container: Container = tile?.container ?: SimpleContainer(BatteryBankBlockEntity.CAPACITY) - powerLevel = LevelGaugeWidget(this, tile?.getCapability(MatteryCapability.ENERGY)?.orNull()) - - storageSlots = ImmutableList(BatteryBankBlockEntity.CAPACITY) { - val slot = BatterySlot(container, it) - addSlot(slot) - return@ImmutableList slot - } - - addInventorySlots() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/CargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/CargoCrateMenu.kt deleted file mode 100644 index 65352ffdd..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/CargoCrateMenu.kt +++ /dev/null @@ -1,42 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.CargoCrateBlockEntity -import ru.dbotthepony.mc.otm.registry.MMenus - -class CargoCrateMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: CargoCrateBlockEntity? = null -) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory, tile) { - override val storageSlots: List - - private val trackedPlayerOpen = !inventory.player.isSpectator - - init { - val container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) - - storageSlots = ImmutableList(CargoCrateBlockEntity.CAPACITY) { - val slot = MatterySlot(container, it) - addSlot(slot) - return@ImmutableList slot - } - - if (trackedPlayerOpen) { - tile?.onPlayerOpen() - } - - addInventorySlots() - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - - if (trackedPlayerOpen) { - (tile as? CargoCrateBlockEntity)?.onPlayerClose() - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ChemicalGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ChemicalGeneratorMenu.kt deleted file mode 100644 index 11dbdd27a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ChemicalGeneratorMenu.kt +++ /dev/null @@ -1,55 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.ForgeHooks -import net.minecraftforge.common.capabilities.ForgeCapabilities -import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, tile: ChemicalGeneratorBlockEntity? = null) - : MatteryMenu(MMenus.CHEMICAL_GENERATOR, id, inv, tile) { - - val container = tile?.container ?: SimpleContainer(3) - - val fuelSlot = object : MatterySlot(container, ChemicalGeneratorBlockEntity.SLOT_INPUT) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return ForgeHooks.getBurnTime(p_40231_, null) > 0 - } - } - - val residueSlot = object : MatterySlot(container, ChemicalGeneratorBlockEntity.SLOT_RESIDUE) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return false - } - } - - val batterySlot = object : MatterySlot(container, ChemicalGeneratorBlockEntity.SLOT_BATTERY) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return p_40231_.getCapability(ForgeCapabilities.ENERGY).isPresent - } - } - - val progress = ProgressGaugeWidget(this) { 1f - tile!!.workTicks.toFloat() / tile.workTicksTotal } - val energy = LevelGaugeWidget(this, tile?.energy) - var burnTime by mSynchronizer.int() - - override val storageSlots = listOf( - addSlot(fuelSlot), - addSlot(batterySlot), - addSlot(residueSlot), - ) - - init { - addInventorySlots() - } - - override fun broadcastChanges() { - super.broadcastChanges() - progress.updateServer() - burnTime = (tile as ChemicalGeneratorBlockEntity).workTicks - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveRackMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveRackMenu.kt deleted file mode 100644 index 7b31075c2..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveRackMenu.kt +++ /dev/null @@ -1,27 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.storage.DriveRackBlockEntity -import ru.dbotthepony.mc.otm.registry.MMenus - -class DriveRackMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: DriveRackBlockEntity? = null -) : MatteryPoweredMenu(MMenus.DRIVE_RACK, p_38852_, inventory, tile) { - override val storageSlots: List - - init { - val container = tile?.container ?: SimpleContainer(4) - - storageSlots = ImmutableList(4) { - val slot = DriveSlot(container, it) - addSlot(slot) - slot - } - - addInventorySlots() - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt deleted file mode 100644 index 81ee77c47..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.kt +++ /dev/null @@ -1,173 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack -import net.minecraftforge.common.capabilities.ForgeCapabilities -import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive -import ru.dbotthepony.mc.otm.container.ItemFilter -import ru.dbotthepony.mc.otm.core.ifPresentK -import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem -import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider -import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget -import ru.dbotthepony.mc.otm.registry.MMenus -import ru.dbotthepony.mc.otm.storage.ITEM_STORAGE -import ru.dbotthepony.mc.otm.storage.ItemStackWrapper -import ru.dbotthepony.mc.otm.storage.PoweredVirtualComponent - -class DriveViewerMenu @JvmOverloads constructor( - containerID: Int, - inventory: Inventory, - tile: DriveViewerBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.DRIVE_VIEWER, containerID, inventory, tile -), INetworkedItemViewProvider { - override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) - val driveSlot: MatterySlot - - private val powered: PoweredVirtualComponent? - private var lastDrive: IMatteryDrive? = null - - init { - val container = tile?.container ?: SimpleContainer(1) - - driveSlot = object : MatterySlot(container, 0) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return itemStack.getCapability(MatteryCapability.DRIVE).isPresent - } - } - - if (tile != null) { - powered = PoweredVirtualComponent( - ItemStackWrapper::class.java, - tile.getCapability(MatteryCapability.ENERGY).resolve().get() - ) - - this.networkedItemView.setComponent(powered) - } else { - powered = null - } - - addSlot(driveSlot) - addInventorySlots() - } - - override val storageSlots: List = ImmutableList.of(driveSlot, batterySlot) - - val driveFilter = ItemFilter(PortableCondensationDriveItem.MAX_FILTERS) { self, _, _, _ -> - if (tile?.container?.get(0)?.item is PortableCondensationDriveItem) { - tile.container[0].getOrCreateTag().put(PortableCondensationDriveItem.FILTER_PATH, self.serializeNBT()) - } - } - - val driveFilterSlots = addFilterSlots(driveFilter) - - val isWhitelist = BooleanPlayerInputWidget(this) - val matchTag = BooleanPlayerInputWidget(this) - val matchNBT = BooleanPlayerInputWidget(this) - - init { - if (tile == null) { - isWhitelist.asClient() - matchTag.asClient() - matchNBT.asClient() - } - } - - override fun broadcastChanges() { - super.broadcastChanges() - - if (tile != null) { - val itemStack = (tile as DriveViewerBlockEntity).container.getItem(0) - - val prev = lastDrive - lastDrive = null - - if (!itemStack.isEmpty) { - itemStack.getCapability(MatteryCapability.DRIVE).ifPresentK { - if (it.storageType === ITEM_STORAGE) { - lastDrive = it as IMatteryDrive - } - } - } - - if (prev != lastDrive) { - prev?.let(powered!!::remove) - this.networkedItemView.clear() - lastDrive?.let(powered!!::add) - - if (lastDrive != null) { - val filter = (itemStack.item as? PortableCondensationDriveItem)?.getFilterSettings(itemStack) - - if (filter != null) { - driveFilter.copyFrom(filter) - driveFilter.isLocked = false - - isWhitelist.withProperty(driveFilter::isWhitelist) - matchTag.withProperty(driveFilter::matchTag) - matchNBT.withProperty(driveFilter::matchNBT) - } else { - driveFilter.clear() - driveFilter.isLocked = true - - isWhitelist.withoutAnything() - matchTag.withoutAnything() - matchNBT.withoutAnything() - } - } else { - driveFilter.clear() - driveFilter.isLocked = true - - isWhitelist.withoutAnything() - matchTag.withoutAnything() - matchNBT.withoutAnything() - } - } - - this.networkedItemView.network() - } - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - - if (lastDrive != null) - powered?.remove(lastDrive!!) - - this.networkedItemView.removed() - } - - override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { - val slot = slots[slotIndex] - val item = slot.item - - if (item.isEmpty || item.getCapability(MatteryCapability.DRIVE).isPresent || item.getCapability(ForgeCapabilities.ENERGY).isPresent) - return super.quickMoveStack(ply, slotIndex) - - val powered = powered - if (lastDrive == null || powered == null) - return ItemStack.EMPTY - - val remaining = powered.insertStack(ItemStackWrapper(item), false) - - if (remaining.count.toInt() == item.count) - return ItemStack.EMPTY - - if (remaining.isEmpty) { - val copy = item.copy() - slot.set(ItemStack.EMPTY) - return copy - } - - val copy = item.copy() - item.shrink(remaining.count.toInt()) - slot.setChanged() - return copy - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyCounterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyCounterMenu.kt deleted file mode 100644 index 827b45194..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyCounterMenu.kt +++ /dev/null @@ -1,78 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.core.Direction -import kotlin.jvm.JvmOverloads -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import net.minecraft.world.level.block.Block -import ru.dbotthepony.mc.otm.block.EnergyCounterBlock -import ru.dbotthepony.mc.otm.block.entity.EnergyCounterBlockEntity -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.menu.widget.NumberPlayerInputWidget -import ru.dbotthepony.mc.otm.menu.widget.OneWayPlayerInputWidget -import ru.dbotthepony.mc.otm.registry.MMenus -import java.math.BigDecimal - -class EnergyCounterMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: EnergyCounterBlockEntity? = null -) : MatteryMenu(MMenus.ENERGY_COUNTER, p_38852_, inventory, tile) { - var passed by mSynchronizer.fraction() - var average by mSynchronizer.fraction() - var last20Ticks by mSynchronizer.fraction() - var lastTick by mSynchronizer.fraction() - - val switchDirection: OneWayPlayerInputWidget - var inputDirection by mSynchronizer.enum(Direction::class.java) - - val maxIO = NumberPlayerInputWidget(this) - - // TODO: Graph and proper networking for it - private var ticksPassed = 0 - - init { - if (tile == null) { - switchDirection = OneWayPlayerInputWidget(this) - maxIO.asClient() - } else { - switchDirection = OneWayPlayerInputWidget(this) { - tile.level?.setBlock(tile.blockPos, tile.blockState.setValue(EnergyCounterBlock.INPUT_DIRECTION, tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION).opposite), Block.UPDATE_ALL) - } - - maxIO.withSupplier { - return@withSupplier if (tile.ioLimit == null) MINUS_ONE else tile.ioLimit!!.toBigDecmial() - }.withConsumer { - if (it.signum() < 0) { - tile.ioLimit = null - } else { - tile.ioLimit = Decimal(it) - } - } - } - } - - override fun broadcastChanges() { - if (tile is EnergyCounterBlockEntity) { - passed = tile.passed - average = tile.calcAverage(20) - lastTick = tile.lastTick - - if (ticksPassed == 0) { - last20Ticks = tile.sumHistory(20) - } - - ticksPassed = (ticksPassed + 1) % 20 - inputDirection = tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - } - - super.broadcastChanges() - } - - - override val storageSlots: Collection get() = emptySet() - - companion object { - private val MINUS_ONE = -BigDecimal.ONE - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyServoMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyServoMenu.kt deleted file mode 100644 index eb8e679a8..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/EnergyServoMenu.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.block.entity.EnergyServoBlockEntity -import ru.dbotthepony.mc.otm.capability.energy -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class EnergyServoMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: EnergyServoBlockEntity? = null -) : MatteryMenu(MMenus.ENERGY_SERVO, p_38852_, inventory, tile) { - val dischargeSlot: MatterySlot - val chargeSlot: MatterySlot - - val powerGauge = LevelGaugeWidget(this, tile?.energy) - - init { - val container = tile?.container ?: SimpleContainer(2) - - dischargeSlot = object : MatterySlot(container, EnergyServoBlockEntity.SLOT_DISCHARGE) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return super.mayPlace(p_40231_) && (p_40231_.energy?.let { it.extractEnergy(Int.MAX_VALUE, true) > 0 } ?: false) - } - } - - chargeSlot = object : MatterySlot(container, EnergyServoBlockEntity.SLOT_CHARGE) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return super.mayPlace(p_40231_) && (p_40231_.energy?.let { it.receiveEnergy(Int.MAX_VALUE, true) > 0 } ?: false) - } - } - - addSlot(dischargeSlot) - addSlot(chargeSlot) - addInventorySlots() - } - - override val storageSlots: Collection = ImmutableList.of(dischargeSlot, chargeSlot) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt deleted file mode 100644 index 042c23baf..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExoPackInventoryMenu.kt +++ /dev/null @@ -1,237 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import com.mojang.datafixers.util.Pair -import net.minecraft.core.NonNullList -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.* -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.compat.curios.curiosSlots -import ru.dbotthepony.mc.otm.container.iterator -import ru.dbotthepony.mc.otm.network.ExoSuitCarriedPacket -import ru.dbotthepony.mc.otm.network.ExoSuitMenuInitPacket -import ru.dbotthepony.mc.otm.network.ExoSuitSlotPacket -import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel - -class ExoPackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) { - override val storageSlots: Collection get() = listOf() - - val craftingGrid: CraftingContainer - val craftingSlots: List - - init { - if (capability.isExoPackCraftingUpgraded) { - craftingGrid = CraftingContainer(this, 3, 3) - } else { - craftingGrid = CraftingContainer(this, 2, 2) - } - - val builder = ImmutableList.builder() - - for (i in 0 until craftingGrid.containerSize) { - builder.add(MatterySlot(craftingGrid, i)) - } - - craftingSlots = builder.build() - } - - val craftingResultContainer = ResultContainer() - val craftingResultSlot = ResultSlot(capability.ply, craftingGrid, craftingResultContainer, 0, 0, 0) - - init { - addSlot(craftingResultSlot) - - if (capability.ply is ServerPlayer) { - addSlotListener(capability.ply.containerListener) - } - } - - val armorSlots: List> = makeArmorSlots() - - val offhandSlot = object : InventorySlot(capability.ply.inventory, 40) { - override fun getNoItemIcon(): Pair { - return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_SHIELD) - } - } - - val allAccessibleSlots: List - - init { - addInventorySlots(autoFrame = false) - - craftingSlots.forEach(this::addSlot) - addSlot(offhandSlot) - - for ((a, b) in armorSlots) { - addSlot(a) - if (b != null) addSlot(b) - } - - val builder = ImmutableList.builder() - - builder.addAll(playerInventorySlots) - builder.addAll(craftingSlots) - builder.add(offhandSlot) - - allAccessibleSlots = builder.build() - } - - val curiosSlots: List> = ImmutableList.copyOf(ply.curiosSlots) - - init { - for ((a, b) in curiosSlots) { - addSlot(a) - storage2Inventory(a) - - if (b != null) { - addSlot(b) - storage2Inventory(b) - } - } - } - - override fun slotsChanged(container: Container) { - super.slotsChanged(container) - - if (container == craftingGrid) { - CraftingMenu.slotChangedCraftingGrid(this, capability.ply.level, capability.ply, craftingGrid, craftingResultContainer) - } - } - - override fun setSynchronizer(synchronizer: ContainerSynchronizer) { - if (synchronizer == Companion) { - super.setSynchronizer(synchronizer) - } - } - - override fun removed(player: Player) { - if (player != capability.ply) { - throw RuntimeException("what.") - } - - super.removed(player) - - craftingResultContainer.clearContent() - - if (!player.level.isClientSide) { - val iterator = craftingGrid.iterator() - - for (itemStack in iterator) { - val leftover = moveItemStackToSlots(itemStack, playerInventorySlots) - - if (!leftover.isEmpty) { - player.drop(leftover, true) - } - - iterator.remove() - } - } - } - - init { - if (capability.ply is ServerPlayer) { - setSynchronizer(Companion) - } - } - - override fun stillValid(player: Player): Boolean { - return true - } - - override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { - if (!slots[slotIndex].hasItem() || !slots[slotIndex].mayPickup(ply)) { - return ItemStack.EMPTY - } - - if (!autoRegisteredStorageSlots) { - autoRegisteredStorageSlots = true - - for (slot in playerCombinedInventorySlots) { - quickMoveMapping[slot] = ::playerHotbarSlots - } - - for (slot in playerHotbarSlots) { - quickMoveMapping[slot] = ::playerCombinedInventorySlots - } - - for (slot in craftingSlots) { - quickMoveMapping[slot] = ::playerInventorySlots - } - - for (slot in armorSlots) { - quickMoveMapping[slot.first] = ::playerInventorySlots - - if (slot.second != null) { - quickMoveMapping[slot.first] = ::playerInventorySlots - } - } - - quickMoveMapping[offhandSlot] = ::playerInventorySlots - } - - if (slotIndex == craftingResultSlot.index) { - val item = craftingResultSlot.item - val leftover = moveItemStackToSlots(item, playerInventorySlots2, simulate = true) - - if (leftover.isEmpty) { - val copy = item.copy() - moveItemStackToSlots(item, playerInventorySlots2, simulate = false) - item.count = 0 - craftingResultSlot.onTake(ply, copy) - return copy - } - - return ItemStack.EMPTY - } - - return super.quickMoveStack(ply, slotIndex) - } - - override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean { - return p_38909_.container != craftingResultContainer && super.canTakeItemForPickAll(p_38908_, p_38909_) - } - - companion object : ContainerSynchronizer { - // ServerPlayer#nextContainerCounter - // this.containerCounter = this.containerCounter % 100 + 1; - // значение ID никогда не будет больше 100 - const val CONTAINER_ID: Int = 101 - - override fun sendInitialData(container: AbstractContainerMenu, itemStacks: NonNullList, carried: ItemStack, remoteDataSlots: IntArray) { - return sendInitialData(container as ExoPackInventoryMenu, itemStacks, carried, remoteDataSlots) - } - - override fun sendSlotChange(container: AbstractContainerMenu, slotId: Int, itemStack: ItemStack) { - return sendSlotChange(container as ExoPackInventoryMenu, slotId, itemStack) - } - - override fun sendCarriedChange(container: AbstractContainerMenu, itemStack: ItemStack) { - return sendCarriedChange(container as ExoPackInventoryMenu, itemStack) - } - - override fun sendDataChange(container: AbstractContainerMenu, dataSlotId: Int, shortData: Int) { - return sendDataChange(container as ExoPackInventoryMenu, dataSlotId, shortData) - } - - fun sendInitialData(container: ExoPackInventoryMenu, itemStacks: NonNullList, carried: ItemStack, remoteDataSlots: IntArray) { - MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExoSuitMenuInitPacket(itemStacks, carried, container.incrementStateId())) - } - - fun sendSlotChange(container: ExoPackInventoryMenu, slotId: Int, itemStack: ItemStack) { - if (container.slots[slotId].container != container.ply.inventory || container.ply.containerMenu is ExoPackInventoryMenu) - MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExoSuitSlotPacket(slotId, itemStack, container.stateId)) - } - - fun sendCarriedChange(container: ExoPackInventoryMenu, itemStack: ItemStack) { - MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExoSuitCarriedPacket(itemStack, container.stateId)) - } - - fun sendDataChange(container: ExoPackInventoryMenu, dataSlotId: Int, shortData: Int) { - throw UnsupportedOperationException("This should never be called. Dumbass") - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt new file mode 100644 index 000000000..49c95010e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt @@ -0,0 +1,260 @@ +package ru.dbotthepony.mc.otm.menu + +import com.google.common.collect.ImmutableList +import net.minecraft.core.NonNullList +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.Container +import net.minecraft.world.entity.ExperienceOrb +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.* +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.compat.curios.curiosSlots +import ru.dbotthepony.mc.otm.container.util.slotIterator +import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.network.ExopackCarriedPacket +import ru.dbotthepony.mc.otm.network.ExopackMenuInitPacket +import ru.dbotthepony.mc.otm.network.ExopackSlotPacket +import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel + +class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) { + val craftingGrid: CraftingContainer + val craftingSlots: List + + init { + if (capability.isExopackCraftingUpgraded) { + craftingGrid = CraftingContainer(this, 3, 3) + } else { + craftingGrid = CraftingContainer(this, 2, 2) + } + + val builder = ImmutableList.builder() + + for (i in 0 until craftingGrid.containerSize) { + builder.add(MatterySlot(craftingGrid, i)) + } + + craftingSlots = builder.build() + } + + val craftingResultContainer = ResultContainer() + val craftingResultSlot = ResultSlot(capability.ply, craftingGrid, craftingResultContainer, 0, 0, 0) + + init { + addSlot(craftingResultSlot) + + if (capability.ply is ServerPlayer) { + addSlotListener(capability.ply.containerListener) + } + } + + init { + addInventorySlots(autoFrame = false) + + for (slot in playerCombinedInventorySlots) { + mapQuickMove(slot, playerHotbarSlots) + } + + for (slot in playerHotbarSlots) { + mapQuickMove(slot, playerCombinedInventorySlots) + } + + for (slot in craftingSlots) { + mapQuickMove(slot, playerInventorySlots) + } + + craftingSlots.forEach(this::addSlot) + } + + val armorSlots = makeArmorSlots() + val curiosSlots: ImmutableList> = ImmutableList.copyOf(player.curiosSlots) + + init { + for ((a, b) in curiosSlots) { + addSlot(a) + mapQuickMoveToInventory(a) + + if (b != null) { + addSlot(b) + mapQuickMoveToInventory(b) + } + } + } + + private fun popFurnaceExp() { + if (capability.isExopackSmeltingInstalled && capability.exopackSmelterExperience >= 1f && !player.level.isClientSide) { + val whole = capability.exopackSmelterExperience.toInt() + capability.exopackSmelterExperience -= whole + ExperienceOrb.award(player.level as ServerLevel, player.position(), whole) + } + } + + val furnaceInputs: List = capability.smelters.map { + object : MatterySlot(it.input, 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && capability.isExopackSmeltingInstalled + } + } + } + + val furnaceOutputs: List = capability.smelters.map { + object : OutputSlot(it.output, 0, onTake = { popFurnaceExp() }) { + override fun mayPickup(player: Player): Boolean { + return super.mayPickup(player) && capability.isExopackSmeltingInstalled + } + } + } + + val furnaceProgress = capability.smelters.map { ProgressGaugeWidget(mSynchronizer, it) } + val furnaceMenuOpenState = InstantBooleanInput(this) + + init { + addStorageSlot(furnaceInputs, condition = furnaceMenuOpenState) + addStorageSlot(furnaceOutputs, condition = furnaceMenuOpenState) + } + + val enderChestSlots: List + val enderChestOpenState = InstantBooleanInput(this) + val playerEnderSortSettings = IItemStackSortingSettings.inputs(this, capability.enderSortingSettings) + val sortEnderChest: SortInput? + + init { + if (capability.isExopackEnderAccessInstalled) { + enderChestSlots = makeSlots(player.enderChestInventory) { a, b -> + MatterySlot(a, b).also { + addStorageSlot(it, condition = enderChestOpenState) + } + } + + sortEnderChest = SortInput(player.enderChestInventory, playerEnderSortSettings) + } else { + enderChestSlots = listOf() + sortEnderChest = null + } + } + + private var isRemoved = false + + override fun slotsChanged(container: Container) { + if (isRemoved) + return // кому это интересно + + super.slotsChanged(container) + + if (container == craftingGrid) { + CraftingMenu.slotChangedCraftingGrid(this, capability.ply.level, capability.ply, craftingGrid, craftingResultContainer) + } + } + + override fun setSynchronizer(synchronizer: ContainerSynchronizer) { + if (synchronizer == Companion) { + super.setSynchronizer(synchronizer) + } + } + + fun actuallyRemoved() { + isRemoved = true + removed(capability.ply) + } + + override fun removed(player: Player) { + if (player != capability.ply) { + throw RuntimeException("what.") + } + + super.removed(player) + + craftingResultContainer.clearContent() + + if (!player.level.isClientSide) { + for (slot in craftingGrid.slotIterator()) { + val leftover = moveItemStackToSlots(slot.item, playerInventorySlots) + + if (!leftover.isEmpty) { + player.drop(leftover, true) + } + + slot.remove() + } + } + } + + init { + if (capability.ply is ServerPlayer) { + setSynchronizer(Companion) + } + } + + override fun stillValid(player: Player): Boolean { + return true + } + + override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { + if (!slots[slotIndex].hasItem() || !slots[slotIndex].mayPickup(ply)) { + return ItemStack.EMPTY + } + + if (slotIndex == craftingResultSlot.index) { + val item = craftingResultSlot.item + val leftover = moveItemStackToSlots(item, playerInventorySlots, simulate = true) + + if (leftover.isEmpty) { + val copy = item.copy() + moveItemStackToSlots(item, playerInventorySlots, simulate = false) + item.count = 0 + craftingResultSlot.onTake(ply, copy) + return copy + } + + return ItemStack.EMPTY + } + + return super.quickMoveStack(ply, slotIndex) + } + + override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean { + return slot.container != craftingResultContainer && super.canTakeItemForPickAll(itemStack, slot) + } + + companion object : ContainerSynchronizer { + // ServerPlayer#nextContainerCounter + // this.containerCounter = this.containerCounter % 100 + 1; + // значение ID никогда не будет больше 100 + const val CONTAINER_ID: Int = 101 + + override fun sendInitialData(container: AbstractContainerMenu, itemStacks: NonNullList, carried: ItemStack, remoteDataSlots: IntArray) { + return sendInitialData(container as ExopackInventoryMenu, itemStacks, carried, remoteDataSlots) + } + + override fun sendSlotChange(container: AbstractContainerMenu, slotId: Int, itemStack: ItemStack) { + return sendSlotChange(container as ExopackInventoryMenu, slotId, itemStack) + } + + override fun sendCarriedChange(container: AbstractContainerMenu, itemStack: ItemStack) { + return sendCarriedChange(container as ExopackInventoryMenu, itemStack) + } + + override fun sendDataChange(container: AbstractContainerMenu, dataSlotId: Int, shortData: Int) { + return sendDataChange(container as ExopackInventoryMenu, dataSlotId, shortData) + } + + fun sendInitialData(container: ExopackInventoryMenu, itemStacks: NonNullList, carried: ItemStack, remoteDataSlots: IntArray) { + MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId())) + } + + fun sendSlotChange(container: ExopackInventoryMenu, slotId: Int, itemStack: ItemStack) { + if (container.slots[slotId].container != container.player.inventory || container.player.containerMenu is ExopackInventoryMenu) + MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId)) + } + + fun sendCarriedChange(container: ExopackInventoryMenu, itemStack: ItemStack) { + MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId)) + } + + fun sendDataChange(container: ExopackInventoryMenu, dataSlotId: Int, shortData: Int) { + throw UnsupportedOperationException("This should never be called. Dumbass") + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt new file mode 100644 index 000000000..8a6a4fe87 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ISortingSettings.kt @@ -0,0 +1,124 @@ +package ru.dbotthepony.mc.otm.menu + +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.core.nbt.getBoolean +import ru.dbotthepony.mc.otm.core.nbt.mapString +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.ItemSorter +import ru.dbotthepony.mc.otm.core.util.ItemStackSorter +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback + +interface IBaseSortingSettings : INBTSerializable { + var isAscending: Boolean + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["isAscending"] = isAscending + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + nbt ?: return + isAscending = nbt.getBoolean("isAscending", true) + } +} + +interface IItemStackSortingSettings : IBaseSortingSettings { + data class Impl( + override var isAscending: Boolean = true, + override var sorting: ItemStackSorter = ItemStackSorter.DEFAULT, + ) : IItemStackSortingSettings + + var sorting: ItemStackSorter + + override fun serializeNBT(): CompoundTag { + return super.serializeNBT().also { + it["sorting"] = sorting.name + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + nbt ?: return + super.deserializeNBT(nbt) + sorting = nbt.mapString("sorting", ItemStackSorter::valueOf, ItemStackSorter.DEFAULT) + } + + val actualComparator: Comparator get() { + if (isAscending) { + return sorting + } else { + return sorting.reversed() + } + } + + companion object { + fun inputs(menu: MatteryMenu, observer: Runnable? = null) : IItemStackSortingSettings { + return object : IItemStackSortingSettings { + override var isAscending: Boolean by BooleanInputWithFeedback(menu).also { if (observer != null) it.addListener { observer.run() } } + override var sorting: ItemStackSorter by EnumInputWithFeedback(menu).also { if (observer != null) it.addListener { observer.run() } } + } + } + + fun inputs(menu: MatteryMenu, parent: IItemStackSortingSettings?, observer: Runnable? = null) : IItemStackSortingSettings { + if (parent == null) + return inputs(menu) + + return object : IItemStackSortingSettings { + override var isAscending: Boolean by BooleanInputWithFeedback(menu, parent::isAscending).also { if (observer != null) it.addListener { observer.run() } } + override var sorting: ItemStackSorter by EnumInputWithFeedback(menu, parent::sorting).also { if (observer != null) it.addListener { observer.run() } } + } + } + } +} + +interface IItemSortingSettings : IBaseSortingSettings { + data class Impl( + override var isAscending: Boolean = true, + override var sorting: ItemSorter = ItemSorter.DEFAULT, + ) : IItemSortingSettings + + var sorting: ItemSorter + + override fun serializeNBT(): CompoundTag { + return super.serializeNBT().also { + it["sorting"] = sorting.name + } + } + + override fun deserializeNBT(nbt: CompoundTag?) { + nbt ?: return + super.deserializeNBT(nbt) + sorting = nbt.mapString("sorting", ItemSorter::valueOf, ItemSorter.DEFAULT) + } + + val actualComparator: Comparator get() { + if (isAscending) { + return sorting + } else { + return sorting.reversed() + } + } + + companion object { + fun inputs(menu: MatteryMenu, observer: Runnable? = null) : IItemSortingSettings { + return object : IItemSortingSettings { + override var isAscending: Boolean by BooleanInputWithFeedback(menu).also { if (observer != null) it.addListener { observer.run() } } + override var sorting: ItemSorter by EnumInputWithFeedback(menu).also { if (observer != null) it.addListener { observer.run() } } + } + } + + fun inputs(menu: MatteryMenu, parent: IItemSortingSettings?, observer: Runnable? = null) : IItemSortingSettings { + if (parent == null) + return inputs(menu) + + return object : IItemSortingSettings { + override var isAscending: Boolean by BooleanInputWithFeedback(menu, parent::isAscending).also { if (observer != null) it.addListener { observer.run() } } + override var sorting: ItemSorter by EnumInputWithFeedback(menu, parent::sorting).also { if (observer != null) it.addListener { observer.run() } } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.kt deleted file mode 100644 index 63199e3da..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.kt +++ /dev/null @@ -1,285 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.Container -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack -import net.minecraftforge.network.PacketDistributor -import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity -import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings -import ru.dbotthepony.mc.otm.container.get -import ru.dbotthepony.mc.otm.core.nonEmpty -import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider -import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel -import ru.dbotthepony.mc.otm.registry.MMenus -import ru.dbotthepony.mc.otm.storage.* -import java.util.* - - -private class ResultSlot(container: Container) : MatterySlot(container, 0) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return false - } - - override fun remove(p_40227_: Int): ItemStack { - if (container[0].count != p_40227_) { - return ItemStack.EMPTY - } - - return super.remove(p_40227_) - } - - override fun safeTake(p_150648_: Int, p_150649_: Int, p_150650_: Player): ItemStack { - val container = container - - if (container is ItemMonitorBlockEntity.CraftingResultContainer && p_150650_ is ServerPlayer) { - container.craftingPlayer = p_150650_ - } - - val result: ItemStack - - try { - result = super.safeTake(p_150648_, p_150649_, p_150650_) - } finally { - if (container is ItemMonitorBlockEntity.CraftingResultContainer) { - container.craftingPlayer = null - } - } - - return result - } - - override fun tryRemove(a: Int, b: Int, c: Player): Optional { - val container = container - - if (container is ItemMonitorBlockEntity.CraftingResultContainer && c is ServerPlayer) { - container.craftingPlayer = c - } - - val result: Optional - - try { - result = super.tryRemove(a, b, c) - } finally { - if (container is ItemMonitorBlockEntity.CraftingResultContainer) { - container.craftingPlayer = null - } - } - - return result - } -} - -class ItemMonitorMenu @JvmOverloads constructor( - containerId: Int, - inventory: Inventory, - tile: ItemMonitorBlockEntity? = null -) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider { - override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) - val settings: ItemMonitorPlayerSettings = tile?.getSettings(inventory.player as ServerPlayer) ?: ItemMonitorPlayerSettings() - private var settingsNetworked = false - - val craftingResult: MatterySlot - val craftingSlots: List - - init { - if (tile != null) { - networkedItemView.setComponent(tile.poweredView) - craftingResult = ResultSlot(tile.craftingResultContainer) - craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(tile.craftingGrid, it) }.asList()) - } else { - craftingResult = ResultSlot(SimpleContainer(1)) - val container = SimpleContainer(9) - craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(container, it) }.asList()) - } - - val slots = craftingSlots.map(this::addSlot) - addSlot(craftingResult) - - addInventorySlots() - } - - override fun broadcastFullState() { - super.broadcastFullState() - MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings) - settingsNetworked = true - } - - fun sendSettingsToServer() { - MenuNetworkChannel.sendToServer(settings) - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - networkedItemView.removed() - } - - override fun broadcastChanges() { - super.broadcastChanges() - networkedItemView.network() - - if (!settingsNetworked) { - MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings) - settingsNetworked = true - } - } - - override val storageSlots: List = listOf(batterySlot) - - override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { - if (playerInventorySlots.any { it.index == slotIndex }) { - if (tile == null) { - return ItemStack.EMPTY - } - - val slot = slots[slotIndex] - - if (slot.item.isEmpty) { - return ItemStack.EMPTY - } - - val leftover = networkedItemView.provider?.insertStack(ItemStackWrapper(slot.item), false)?.stack ?: slot.item - - if (leftover.count == slot.item.count) { - return ItemStack.EMPTY - } - - val old = slot.item.copy() - slot.item.count = leftover.count - return old - } else if (slotIndex in craftingSlots[0].index .. craftingSlots[craftingSlots.size - 1].index) { - // from crafting grid to inventory - val item = slots[slotIndex].item - - if (item.isEmpty) { - return ItemStack.EMPTY - } - - var remainder = moveItemStackToSlots(item, playerInventorySlots) - slots[slotIndex].set(remainder) - - if (remainder.isEmpty) { - return item - } - - remainder = networkedItemView.provider?.insertStack(ItemStackWrapper(remainder), false)?.stack ?: remainder - slots[slotIndex].set(remainder) - - if (remainder.isEmpty) { - return item - } - - return if (remainder.count != item.count) item else ItemStack.EMPTY - } else if (slotIndex == craftingResult.index) { - // quickcraft... god damn it - if (!craftingResult.hasItem()) { - return ItemStack.EMPTY - } - - val item = craftingResult.item - - val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY - - if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) { - // recipe has changed - return ItemStack.EMPTY - } - - if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) { - if (tile.howMuchPlayerCrafted(ply) > 0) { - return ItemStack.EMPTY - } - } else { - var hasUnstackables = false - var maxStack = 64 - - if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.FULL) { - for (gridItem in tile.craftingGrid.iterator().nonEmpty()) { - if (!gridItem.isStackable) { - hasUnstackables = true - break - } - } - } else { - maxStack = 0 - - for (gridItem in tile.craftingGrid.iterator().nonEmpty()) { - maxStack = maxStack.coerceAtLeast(gridItem.maxStackSize) - } - } - - if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || hasUnstackables) { - val count = tile.howMuchPlayerCrafted(ply) - - if (count > 0 && (count + 1) * craftingResult.item.count > craftingResult.item.maxStackSize) { - return ItemStack.EMPTY - } - } else { - val count = tile.howMuchPlayerCrafted(ply) - - if (count > 0 && count >= maxStack) { - return ItemStack.EMPTY - } - } - } - - tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer - - try { - when (settings.resultTarget) { - ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM -> { - val wrapper = ItemStackWrapper(item) - var remaining = tile.poweredView?.insertStack(wrapper, true)?.stack ?: return ItemStack.EMPTY - - if (remaining.isEmpty) { - tile.poweredView!!.insertStack(wrapper, false) - craftingResult.remove(item.count) - return item - } - - remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate = true) - - if (remaining.isEmpty) { - remaining = tile.poweredView!!.insertStack(wrapper, false).stack - moveItemStackToSlots(remaining, playerInventorySlots, simulate = false) - craftingResult.remove(item.count) - return item - } - - return ItemStack.EMPTY - } - - ItemMonitorPlayerSettings.ResultTarget.MIXED, ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> { - var remaining = moveItemStackToSlots(item, playerInventorySlots, simulate = true) - - if (remaining.isEmpty) { - moveItemStackToSlots(item, playerInventorySlots, simulate = false) - craftingResult.remove(item.count) - return item - } - - val wrapper = ItemStackWrapper(remaining) - remaining = tile.poweredView?.insertStack(wrapper, true)?.stack ?: return ItemStack.EMPTY - - if (remaining.isEmpty) { - moveItemStackToSlots(item, playerInventorySlots, simulate = false) - tile.poweredView!!.insertStack(wrapper, false).stack - craftingResult.remove(item.count) - return item - } - - return ItemStack.EMPTY - } - } - } finally { - tile.craftingResultContainer.craftingPlayer = null - } - } - - return super.quickMoveStack(ply, slotIndex) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt deleted file mode 100644 index afca430f3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterBottlerMenu.kt +++ /dev/null @@ -1,64 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.core.orNull -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterBottlerMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: MatterBottlerBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.MATTER_BOTTLER, p_38852_, inventory, tile -) { - val workFlow: BooleanPlayerInputWidget - - val progressWidget: ProgressGaugeWidget - val matterWidget: LevelGaugeWidget - - override val storageSlots: List - - init { - val container = tile?.container ?: SimpleContainer(6) - - if (tile == null) { - progressWidget = ProgressGaugeWidget(this) - matterWidget = LevelGaugeWidget(this) - workFlow = BooleanPlayerInputWidget(this).asClient() - } else { - progressWidget = ProgressGaugeWidget(this) { tile.getWorkProgress() } - matterWidget = LevelGaugeWidget(this, tile.matter) - workFlow = BooleanPlayerInputWidget(this, tile::isBottling) - } - - storageSlots = ImmutableList(6) { index -> - object : MatterySlot(container, index) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - val cap = p_40231_.getCapability(MatteryCapability.MATTER).orNull() ?: return false - - if (workFlow.value) { - return index < 3 && cap.allowsReceive - } else { - return index >= 3 && cap.allowsExtract - } - } - } - } - - storageSlots.forEach(this::addSlot) - addInventorySlots() - } - - override fun broadcastChanges() { - super.broadcastChanges() - workFlow.value = (tile as MatterBottlerBlockEntity).isBottling - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterCapacitorBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterCapacitorBankMenu.kt deleted file mode 100644 index 759e5a326..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterCapacitorBankMenu.kt +++ /dev/null @@ -1,46 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.matter.MatterCapacitorBankBlockEntity -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterCapacitorBankMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: MatterCapacitorBankBlockEntity? = null -) : MatteryMenu( - MMenus.MATTER_CAPACITOR_BANK, p_38852_, inventory, tile -) { - val matterGauge: LevelGaugeWidget - val totalMatterGauge: LevelGaugeWidget - - override val storageSlots: List - - init { - if (tile == null) { - matterGauge = LevelGaugeWidget(this) - totalMatterGauge = LevelGaugeWidget(this) - } else { - matterGauge = LevelGaugeWidget(this, tile) - totalMatterGauge = LevelGaugeWidget(this, { - tile.matterGraph?.getMatterStorageLevel() ?: Decimal.ZERO - }, { - tile.matterGraph?.getMatterStorageMaxLevel() ?: Decimal.ZERO - }) - } - - val container = tile?.container ?: SimpleContainer(2 * 6) - - storageSlots = ImmutableList(2 * 6) { - val slot = MatterContainerInputSlot(container, it) - addSlot(slot) - slot - } - - addInventorySlots() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.kt deleted file mode 100644 index 9156eb5bb..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.kt +++ /dev/null @@ -1,56 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import kotlin.jvm.JvmOverloads -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.block.entity.matter.MatterDecomposerBlockEntity -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import net.minecraft.world.SimpleContainer -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.core.orNull -import ru.dbotthepony.mc.otm.matter.MatterManager -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterDecomposerMenu @JvmOverloads constructor( - containerID: Int, - inventory: Inventory, - tile: MatterDecomposerBlockEntity? = null -) : MatteryPoweredMenu(MMenus.MATTER_DECOMPOSER, containerID, inventory, tile) { - val input: MatterySlot - val outputMain: MachineOutputSlot - val outputStacking: MachineOutputSlot - val progressWidget: ProgressGaugeWidget - val matterWidget: LevelGaugeWidget - - init { - val container = tile?.container ?: SimpleContainer(3) - - // Вход - input = object : MatterySlot(container, 0) { - override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) - } - - // Выход - outputMain = MachineOutputSlot(container, 1) - outputStacking = MachineOutputSlot(container, 2) - - matterWidget = LevelGaugeWidget(this, tile?.getCapability(MatteryCapability.MATTER)?.orNull()) - - if (tile == null) { - progressWidget = ProgressGaugeWidget(this) - } else { - progressWidget = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) - } - - addInventorySlots() - } - - override val storageSlots: List = ImmutableList.of( - addSlot(outputMain), - addSlot(outputStacking), - addSlot(input), - ) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.kt deleted file mode 100644 index 04f6ae207..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.kt +++ /dev/null @@ -1,303 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.Slot -import net.minecraftforge.network.NetworkEvent -import net.minecraftforge.network.PacketDistributor -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.block.entity.matter.MatterPanelBlockEntity -import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphListener -import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph -import ru.dbotthepony.mc.otm.network.* -import ru.dbotthepony.mc.otm.registry.MMenus -import java.util.* -import java.util.function.Consumer -import java.util.function.Supplier - -class CancelTaskPacket(val id: UUID) : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - buff.writeUUID(id) - } - - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val sender = context.sender!! - (sender.containerMenu as? MatterPanelMenu)?.receiveTaskCancel(sender, id) - } - } - - companion object { - fun read(buff: FriendlyByteBuf): CancelTaskPacket { - return CancelTaskPacket(buff.readUUID()) - } - } -} - -class PatternsChangePacket(val isUpdate: Boolean, val patterns: Collection) : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - buff.writeBoolean(isUpdate) - buff.writeInt(patterns.size) - - for (pattern in patterns) { - pattern.write(buff) - } - } - - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return@enqueueWork - - if (isUpdate) { - menu.networkPatternsUpdated(patterns) - } else { - menu.networkPatternsRemoved(patterns) - } - } - } - - companion object { - fun read(buff: FriendlyByteBuf): PatternsChangePacket { - val isUpdate = buff.readBoolean() - val size = buff.readInt() - - return PatternsChangePacket(isUpdate, ArrayList(size).also { - for (i in 0 until size) it.add(PatternState.read(buff) ?: throw NullPointerException("Unable to read PatternState from network at $i")) }) - } - } -} - -class TasksChangePacket(val isUpdate: Boolean, val tasks: Collection>) : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - buff.writeBoolean(isUpdate) - buff.writeInt(tasks.size) - - for (task in tasks) { - task.write(buff) - } - } - - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return@enqueueWork - - if (isUpdate) { - menu.networkTasksUpdated(tasks) - } else { - menu.networkTasksRemoved(tasks) - } - } - } - - companion object { - fun read(buff: FriendlyByteBuf): TasksChangePacket { - val isUpdate = buff.readBoolean() - val size = buff.readInt() - - return TasksChangePacket(isUpdate, ArrayList>(size).also { - for (i in 0 until size) it.add(buff.readReplicationTask() ?: throw NullPointerException("Unable to read Replication Task from network at $i")) }) - } - } -} - -class ReplicationRequestPacket(val id: UUID, amount: Int) : MatteryPacket { - val amount = amount.coerceAtLeast(1).coerceAtMost(99999) - - override fun write(buff: FriendlyByteBuf) { - buff.writeUUID(id) - buff.writeInt(amount) - } - - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val sender = context.sender ?: return@enqueueWork - val menu = sender.containerMenu as? MatterPanelMenu ?: return@enqueueWork - - menu.requestReplication(sender, id, amount) - } - } - - companion object { - fun read(buff: FriendlyByteBuf): ReplicationRequestPacket { - return ReplicationRequestPacket(buff.readUUID(), buff.readInt()) - } - } -} - -class MatterPanelMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: MatterPanelBlockEntity? = null -) : MatteryMenu(MMenus.MATTER_PANEL, p_38852_, inventory, tile), IMatterGraphListener { - fun taskUpdated(task: IReplicationTask<*>) { - sendNetwork(TasksChangePacket(true, listOf(task))) - } - - fun taskRemoved(task: IReplicationTask<*>) { - sendNetwork(TasksChangePacket(false, listOf(task))) - } - - // client code - val patterns = ArrayList() - val tasks = ArrayList>() - var changeset = 0 - - fun networkPatternsUpdated(patterns: Collection) { - changeset++ - - for (pattern in patterns) { - val index = this.patterns.indexOfFirst(pattern::matchId) - - if (index != -1) { - this.patterns[index] = pattern - } else { - this.patterns.add(pattern) - } - } - } - - fun networkPatternsRemoved(patterns: Collection) { - changeset++ - - for (pattern in patterns) { - this.patterns.remove(pattern) - } - } - - fun networkTasksUpdated(tasks: Collection>) { - changeset++ - - for (task in tasks) { - val index = this.tasks.indexOfFirst(task::matchId) - - if (index != -1) { - this.tasks[index] = task - } else { - this.tasks.add(task) - } - - updateWatcher?.accept(task) - } - } - - fun networkTasksRemoved(tasks: Collection>) { - changeset++ - - for (task in tasks) { - this.tasks.remove(task) - deleteWatcher?.accept(task) - } - } - - private var deleteWatcher: Consumer>? = null - private var updateWatcher: Consumer>? = null - - fun networkTaskWatcher(updateWatcher: Consumer>, deleteWatcher: Consumer>) { - this.deleteWatcher = deleteWatcher - this.updateWatcher = updateWatcher - } - - // server code - fun requestReplication(ply: ServerPlayer, id: UUID, count: Int) { - if (ply.isSpectator) { - return - } - - val tile = tile as MatterPanelBlockEntity? ?: return - - val graph = tile.matterGraph ?: return - val state = graph.getPattern(id) - - if (state == null) { - LOGGER.error("Received replication request from {} of {}, but it is not found in grid", ply, id) - return - } - - tile.addTask(state, count) - } - - fun receiveTaskCancel(ply: ServerPlayer, id: UUID) { - if (!ply.isSpectator) - (tile as MatterPanelBlockEntity?)?.removeTask(id) - } - - fun requestTaskCancel(id: UUID) { - if (minecraft.player?.isSpectator != true) - MenuNetworkChannel.sendToServer(CancelTaskPacket(id)) - } - - override fun onPatternAdded(state: IPatternState) { - sendNetwork(PatternsChangePacket(true, listOf(state))) - } - - override fun onPatternRemoved(state: IPatternState) { - sendNetwork(PatternsChangePacket(false, listOf(state))) - } - - override fun onPatternUpdated(newState: IPatternState, oldState: IPatternState) { - sendNetwork(PatternsChangePacket(true, listOf(newState))) - } - - private var initialSend = false - - private var listeningGrid: MatterNetworkGraph? = null - - init { - if (tile != null) { - listeningGrid = tile.matterGraph - tile.attachMenu(this) - listeningGrid?.addListener(this) - } - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - (tile as MatterPanelBlockEntity?)?.deatachMenu(this) - listeningGrid?.removeListener(this) - } - - override fun broadcastChanges() { - super.broadcastChanges() - if (!initialSend) fullPatternBroadcast() - } - - private fun sendNetwork(packet: Any) { - MenuNetworkChannel.send(PacketDistributor.PLAYER.with { inventory.player as ServerPlayer }, packet) - } - - fun fullPatternBroadcast() { - if (inventory.player.level.isClientSide) { - initialSend = true - return - } - - val tile = tile as MatterPanelBlockEntity? - - if (tile != null) { - val grid = tile.matterGraph - - if (grid != null) { - initialSend = true - sendNetwork(PatternsChangePacket(true, grid.patterns.toList())) - } - - sendNetwork(TasksChangePacket(true, tile.allReplicationTasks.toList())) - } - } - - override val storageSlots: Collection - get() = emptyList() - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterRecyclerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterRecyclerMenu.kt deleted file mode 100644 index fc9a02bb1..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterRecyclerMenu.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.block.entity.matter.MatterRecyclerBlockEntity -import ru.dbotthepony.mc.otm.item.MatterDustItem -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterRecyclerMenu @JvmOverloads constructor( - containerID: Int, - inventory: Inventory, - tile: MatterRecyclerBlockEntity? = null -) : MatteryPoweredMenu(MMenus.MATTER_RECYCLER, containerID, inventory, tile) { - val input: MatterySlot - val progress: ProgressGaugeWidget - val matter = LevelGaugeWidget(this, tile?.matter) - - init { - val container = tile?.container ?: SimpleContainer(1) - - input = object : MatterySlot(container, 0) { - override fun mayPlace(p_40231_: ItemStack): Boolean { - return p_40231_.item is MatterDustItem && (p_40231_.item as MatterDustItem).getMatterValue(p_40231_) != null - } - } - - if (tile == null) { - progress = ProgressGaugeWidget(this) - } else { - progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) - } - - addSlot(input) - addInventorySlots() - } - - override val storageSlots: Collection = ImmutableList.of(input) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.kt deleted file mode 100644 index 39e16d5d3..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.kt +++ /dev/null @@ -1,43 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import kotlin.jvm.JvmOverloads -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import net.minecraft.world.SimpleContainer -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterReplicatorMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: MatterReplicatorBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.MATTER_REPLICATOR, p_38852_, inventory, tile -) { - val matter: LevelGaugeWidget - val progress: ProgressGaugeWidget - override val storageSlots: List - - init { - val container = tile?.container ?: SimpleContainer(5) - - storageSlots = ImmutableList(5) { - val slot = MachineOutputSlot(container, it) - addSlot(slot) - return@ImmutableList slot - } - - if (tile != null) { - matter = LevelGaugeWidget(this, tile.matter) - progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) - } else { - matter = LevelGaugeWidget(this) - progress = ProgressGaugeWidget(this) - } - - addInventorySlots() - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.kt deleted file mode 100644 index 5c1384149..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import kotlin.jvm.JvmOverloads -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import net.minecraft.world.SimpleContainer -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.matter.MatterManager -import ru.dbotthepony.mc.otm.registry.MMenus - -class MatterScannerMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: MatterScannerBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.MATTER_SCANNER, p_38852_, inventory, tile -) { - val input: MatterySlot - val progress: ProgressGaugeWidget - val patterns: LevelGaugeWidget - - init { - val container = tile?.container ?: SimpleContainer(1) - - input = object : MatterySlot(container, 0, 64, 38) { - override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) - } - - addSlot(input) - - if (tile != null) { - progress = ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) - patterns = LevelGaugeWidget(this, - { Decimal(tile.matterGraph?.patternCount ?: 0L) }, - { Decimal(tile.matterGraph?.patternCapacity ?: 0L) }) - } else { - progress = ProgressGaugeWidget(this) - patterns = LevelGaugeWidget(this) - } - - addInventorySlots() - } - - override val storageSlots: List = listOf(input) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index 8c77b7a51..ac48cb38a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -2,76 +2,221 @@ package ru.dbotthepony.mc.otm.menu import com.google.common.collect.ImmutableList import com.mojang.datafixers.util.Pair +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ReferenceArrayList +import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.* +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.InventoryMenu +import net.minecraft.world.inventory.MenuType +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraftforge.network.PacketDistributor +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots +import ru.dbotthepony.mc.otm.compat.curios.curiosSlots import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot +import ru.dbotthepony.mc.otm.container.IMatteryContainer import ru.dbotthepony.mc.otm.container.ItemFilter -import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot -import ru.dbotthepony.mc.otm.menu.widget.AbstractWidget -import ru.dbotthepony.mc.otm.network.FieldSynchronizer +import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.computeSortedIndices +import ru.dbotthepony.mc.otm.container.sortWithIndices +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet +import ru.dbotthepony.mc.otm.core.collect.ConditionalSet +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec +import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec +import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec +import ru.dbotthepony.mc.otm.core.util.CollectionStreamCodec +import ru.dbotthepony.mc.otm.core.util.IStreamCodec +import ru.dbotthepony.mc.otm.core.util.ItemStackValueCodec +import ru.dbotthepony.mc.otm.core.util.ItemValueCodec +import ru.dbotthepony.mc.otm.core.util.NullValueCodec +import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec +import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.network.MNetworkContext +import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuNetworkChannel +import ru.dbotthepony.mc.otm.network.SetCarriedPacket +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import ru.dbotthepony.mc.otm.network.synchronizer.IField +import ru.dbotthepony.mc.otm.network.synchronizer.IMutableBooleanField +import java.io.DataInputStream +import java.io.DataOutputStream +import java.math.BigDecimal import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer +import java.util.function.DoubleSupplier +import java.util.function.IntSupplier +import java.util.function.Predicate -abstract class MatteryMenu @JvmOverloads protected constructor( +data class PlayerSlot(val functional: A, val cosmetic: B? = null) + +data class EquipmentSlots( + val armorSlots: List>, + val curiosSlots: List> +) + +/** + * [openState] **is clientside only**, attempting to use it on server will result + * in classloading exceptions. + */ +data class UpgradeSlots( + val slots: List, + val allowedTypes: Set, + val openState: GetterSetter, + val currentStats: IMatteryUpgrade +) + +abstract class MatteryMenu( menuType: MenuType<*>?, containerId: Int, val inventory: Inventory, val tile: BlockEntity? = null ) : AbstractContainerMenu(menuType, containerId) { + /** + * Server->Client synchronizer + */ val mSynchronizer = FieldSynchronizer() - val ply: Player get() = inventory.player + val player: Player get() = inventory.player - private val _matteryWidgets = ArrayList() - val matteryWidgets: List = Collections.unmodifiableList(_matteryWidgets) + private val _playerInventorySlots = ArrayList() + private val _playerHotbarSlots = ArrayList() + private val _playerCombinedInventorySlots = ArrayList() + private val _exopackChargeSlots = ArrayList() - private val _playerInventorySlots = ArrayList() - private val _playerInventorySlots2 = ArrayList() - private val _playerHotbarSlots = ArrayList() - private val _playerMainSlots = ArrayList() - private val _playerExoSuitSlots = ArrayList() - private val _playerCombinedInventorySlots = ArrayList() + private val playerInputs = ArrayList>() + + class PlayerInputPacket(val containerId: Int, val inputId: Int, val payload: ByteArray) : MatteryPacket { + constructor(buff: FriendlyByteBuf) : this(buff.readVarInt(), buff.readVarInt(), ByteArray(buff.readableBytes()).also { buff.readBytes(it) }) + + override fun write(buff: FriendlyByteBuf) { + buff.writeVarInt(containerId) + buff.writeVarInt(inputId) + buff.writeBytes(payload) + } + + override fun play(context: MNetworkContext) { + val menu = context.sender?.containerMenu as? MatteryMenu ?: return + if (menu.containerId != containerId || !menu.stillValid(context.sender)) return + val input = menu.playerInputs.getOrNull(inputId) ?: return + if (!input.test(context.sender)) return + input.invoke(input.codec.read(DataInputStream(FastByteArrayInputStream(payload)))) + } + } /** - * inventory + exosuit + hotbar (in this order) + * Client->Server input */ - val playerInventorySlots: List = Collections.unmodifiableList(_playerInventorySlots) + inner class PlayerInput(val codec: IStreamCodec, allowSpectators: Boolean = false, val handler: (V) -> Unit) : Consumer, Predicate { + val id = playerInputs.size + var allowSpectators by mSynchronizer.bool(allowSpectators).property + + init { + playerInputs.add(this) + } + + private val filters = ArrayList>() + + init { + filters.add { allowSpectators || !it.isSpectator } + } + + fun filter(predicate: Predicate): PlayerInput { + filters.add(predicate) + return this + } + + override fun test(player: Player?): Boolean { + if (player == null) return false + return filters.all { it.test(player) } + } + + override fun accept(value: V) { + if (test(minecraft.player as Player?)) { + val stream = FastByteArrayOutputStream() + codec.write(DataOutputStream(stream), value) + MenuNetworkChannel.sendToServer(PlayerInputPacket(containerId, id, stream.array.copyOfRange(0, stream.length))) + } + } + + internal fun invoke(value: Any?) { + handler.invoke(value as V) + } + } + + inner class SortInput(val container: Container, val settings: IItemStackSortingSettings) { + val input = PlayerInput(CollectionStreamCodec(VarIntValueCodec, ::IntArrayList)) { + container.sortWithIndices(it) + } + + fun clientInput() { + input.accept(container.computeSortedIndices(settings.actualComparator)) + } + } + + fun oneWayInput(allowSpectators: Boolean = false, handler: () -> Unit): PlayerInput { + return PlayerInput(NullValueCodec, allowSpectators) { + handler.invoke() + } + } + + fun bigDecimalInput(allowSpectators: Boolean = false, handler: (BigDecimal) -> Unit) = PlayerInput(BigDecimalValueCodec, allowSpectators, handler) + fun booleanInput(allowSpectators: Boolean = false, handler: (Boolean) -> Unit) = PlayerInput(BooleanValueCodec, allowSpectators, handler) + fun itemInput(allowSpectators: Boolean = false, handler: (Item) -> Unit) = PlayerInput(ItemValueCodec, allowSpectators, handler) + fun itemStackInput(allowSpectators: Boolean = false, handler: (ItemStack) -> Unit) = PlayerInput(ItemStackValueCodec, allowSpectators, handler) + fun nullableItemInput(allowSpectators: Boolean = false, handler: (Item?) -> Unit) = PlayerInput(ItemValueCodec.nullable, allowSpectators, handler) + fun stringInput(allowSpectators: Boolean = false, handler: (String) -> Unit) = PlayerInput(BinaryStringCodec, allowSpectators, handler) + fun intInput(allowSpectators: Boolean = false, handler: (Int) -> Unit) = PlayerInput(VarIntValueCodec, allowSpectators, handler) /** - * hotbar + inventory + exosuit (in this order) + * hotbar + inventory + Exopack (in this order) */ - val playerInventorySlots2: List = Collections.unmodifiableList(_playerInventorySlots2) + val playerInventorySlots: List = Collections.unmodifiableList(_playerInventorySlots) /** * hotbar only */ - val playerHotbarSlots: List = Collections.unmodifiableList(_playerHotbarSlots) + val playerHotbarSlots: List = Collections.unmodifiableList(_playerHotbarSlots) /** - * inventory only + * inventory + Exopack (in this order) */ - val playerMainSlots: List = Collections.unmodifiableList(_playerMainSlots) + val playerCombinedInventorySlots: List = Collections.unmodifiableList(_playerCombinedInventorySlots) - /** - * exosuit only - */ - val playerExoSuitSlots: List = Collections.unmodifiableList(_playerExoSuitSlots) + val exopackChargeSlots: List = Collections.unmodifiableList(_exopackChargeSlots) - /** - * inventory + exosuit (in this order) - */ - val playerCombinedInventorySlots: List = Collections.unmodifiableList(_playerCombinedInventorySlots) + val exopackPowerLevel = ProfiledLevelGaugeWidget>(mSynchronizer) + + var sortInventoryInput: SortInput? = null + private set + + val playerSortSettings = IItemStackSortingSettings.inputs(this, player.matteryPlayer?.sortingSettings) + + var offhandSlot: InventorySlot? = null + protected set var autoCreateInventoryFrame = true private set @@ -79,74 +224,78 @@ abstract class MatteryMenu @JvmOverloads protected constructor( protected val lockedInventorySlots: MutableSet = HashSet() protected open fun isInventorySlotLocked(index: Int): Boolean = lockedInventorySlots.contains(index) - private val _filterSlots = ArrayList() - val filterSlots: List = Collections.unmodifiableList(_filterSlots) - protected var inventorySlotIndexStart = 0 protected var inventorySlotIndexEnd = 0 - private val playerPacketDistributor = PacketDistributor.PLAYER.with { ply as ServerPlayer } - - fun addFilterSlots(slots: ItemFilter): List { - val result = ArrayList(slots.size) + fun addFilterSlots(slots: ItemFilter): List> { + val result = ArrayList>(slots.size) for (i in 0 until slots.size) { - _filterSlots.add(ItemFilterNetworkSlot(i, _filterSlots.size, slots).also(result::add)) + result.add(GetterSetter.of( + mSynchronizer.computedItem { slots[i] }, + itemStackInput { slots[i] = it } + )) } return result } - fun addFilterSlots(amount: Int): List { - val result = ArrayList(amount) + fun addFilterSlots(amount: Int): List> { + val result = ArrayList>(amount) for (i in 0 until amount) { - _filterSlots.add(ItemFilterNetworkSlot(i, _filterSlots.size, null).also(result::add)) + result.add(GetterSetter.of( + mSynchronizer.computedItem { ItemStack.EMPTY }, + itemStackInput { throw UnsupportedOperationException() } + )) } return result } - fun addWidget(widget: AbstractWidget): Int { - val indexOf = _matteryWidgets.indexOf(widget) + fun addFilterSlots(slots: ItemFilter?, amount: Int): List> { + if (slots != null && amount != slots.size) + throw IllegalStateException("Provided ItemFiler has different amount of slots than expected: ${slots.size} != $amount") - if (indexOf == -1) { - _matteryWidgets.add(widget) - return _matteryWidgets.size - 1 - } - - return indexOf + if (slots == null) + return addFilterSlots(amount) + else + return addFilterSlots(slots) } - fun getWidget(index: Int): AbstractWidget? { - if (index !in _matteryWidgets.indices) { - return null - } - - return _matteryWidgets[index] - } - - open inner class InventorySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { + open inner class InventorySlot(container: Container, index: Int, addFilter: Boolean = false) : UserFilteredSlot(container, index, 0, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && !isInventorySlotLocked(index) + return !isInventorySlotLocked(index) && super.mayPlace(itemStack) } override fun mayPickup(player: Player): Boolean { - return super.mayPickup(player) && !isInventorySlotLocked(index) + return !isInventorySlotLocked(index) && super.mayPickup(player) } - override fun isSameInventory(other: Slot): Boolean { - if (container === inventory || container === ply.matteryPlayer?.exoPackContainer) - return other.container === inventory || other.container === ply.matteryPlayer?.exoPackContainer + var chargeFlag: GetterSetter? = null + private set - return super.isSameInventory(other) + init { + val mattery = player.matteryPlayer!! + + if (addFilter) { + val mContainer = container as IMatteryContainer + + filter = GetterSetter.of( + getter = { mContainer.getSlotFilter(slotIndex) }, + setter = nullableItemInput(true) { mContainer.setSlotFilter(slotIndex, it) }::accept + ) + } + + chargeFlag = GetterSetter.of( + getter = { slotIndex in mattery.slotsChargeFlag }, + setter = booleanInput(true) { if (it) mattery.slotsChargeFlag.add(slotIndex) else mattery.slotsChargeFlag.remove(slotIndex) }::accept + ) } } - open inner class EquipmentSlot(container: Container, index: Int, val type: net.minecraft.world.entity.EquipmentSlot, x: Int = 0, y: Int = 0) : InventorySlot(container, index, x, y) { - constructor(type: net.minecraft.world.entity.EquipmentSlot, x: Int = 0, y: Int = 0) : this( - inventory, 34 + type.ordinal, type, x, y - ) + open inner class EquipmentSlot(container: Container, index: Int, val type: net.minecraft.world.entity.EquipmentSlot) : InventorySlot(container, index) { + constructor(type: net.minecraft.world.entity.EquipmentSlot) : this(inventory, 34 + type.ordinal, type) override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && itemStack.canEquip(type, inventory.player) @@ -165,94 +314,81 @@ abstract class MatteryMenu @JvmOverloads protected constructor( } } - protected fun addInventorySlots(autoFrame: Boolean = !ply.isSpectator) { + protected fun addInventorySlots(autoFrame: Boolean = !player.isSpectator) { check(_playerInventorySlots.isEmpty()) { "Already created inventory slots" } + val mattery = player.matteryPlayer ?: return + autoCreateInventoryFrame = autoFrame - for (i in 9 .. 35) { - val slot = InventorySlot(inventory, i) - - _playerInventorySlots.add(slot) - _playerInventorySlots2.add(slot) - _playerMainSlots.add(slot) - _playerCombinedInventorySlots.add(slot) - inventory2Storage(slot) - addSlot(slot) - } - - val mattery = ply.matteryPlayer - - if (mattery != null && mattery.hasExoPack) { - for (i in 0 until mattery.exoPackContainer.containerSize) { - val slot = InventorySlot(mattery.exoPackContainer, i) - - _playerInventorySlots.add(slot) - _playerInventorySlots2.add(slot) - _playerExoSuitSlots.add(slot) - _playerCombinedInventorySlots.add(slot) - inventory2Storage(slot) - addSlot(slot) + offhandSlot = object : InventorySlot(inventory, 40) { + override fun getNoItemIcon(): Pair { + return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_SHIELD) } } - for (i in 0..8) { - val slot = InventorySlot(inventory, i) + mapQuickMoveToInventory(offhandSlot!!) + addSlot(offhandSlot!!) + + for (i in 0 until if (mattery.hasExopack) mattery.combinedInventory.containerSize else mattery.wrappedItemInventory.containerSize) { + if (i in Inventory.INVENTORY_SIZE until player.inventory.containerSize) continue + + val slot = InventorySlot(mattery.combinedInventory, i, true) - addSlot(slot) - _playerInventorySlots2.add(i, slot) _playerInventorySlots.add(slot) - _playerHotbarSlots.add(slot) - inventory2Storage(slot) + + if (i <= 8) + _playerHotbarSlots.add(slot) + else + _playerCombinedInventorySlots.add(slot) + + mapQuickMove(slot, equipmentSlots) + mapQuickMoveToExternal(slot) + addSlot(slot) } + + if (mattery.hasExopack) { + _exopackChargeSlots.add(BatterySlot(mattery.exopackEnergy.parent, 0).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) + + for (i in 0 until mattery.exopackChargeSlots.containerSize) + _exopackChargeSlots.add(ChargeSlot(mattery.exopackChargeSlots, i).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) + + exopackPowerLevel.with(mattery.exopackEnergy) + } + + sortInventoryInput = SortInput(mattery.inventoryAndExopackNoHotbar, playerSortSettings) + } + + private var broadcastOnce = false + + protected open fun beforeBroadcast() { + + } + + protected fun matteryBroadcast() { + beforeBroadcast() + + val payload = mSynchronizer.collectNetworkPayload() + + if (payload != null) { + if (broadcastOnce) { + MenuNetworkChannel.send(player, MenuFieldPacket(containerId, payload)) + } else { + MenuNetworkChannel.sendNow(player, MenuFieldPacket(containerId, payload)) + } + } + + broadcastOnce = true } override fun broadcastChanges() { - for (widget in _matteryWidgets) { - widget.updateServer() - } - - val payload = mSynchronizer.collectNetworkPayload() - - if (payload != null) { - MenuNetworkChannel.send(ply, MenuFieldPacket(payload)) - } - super.broadcastChanges() - - val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } - - for (slot in _filterSlots) { - val packet = slot.sendChanges() - - if (packet != null) { - MenuNetworkChannel.send(consumer, packet) - } - } + matteryBroadcast() } override fun broadcastFullState() { - for (widget in _matteryWidgets) { - widget.updateServer() - } - mSynchronizer.invalidate() - val payload = mSynchronizer.collectNetworkPayload() - - if (payload != null) { - MenuNetworkChannel.send(ply, MenuFieldPacket(payload)) - } - super.broadcastFullState() - - val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } - - for (slot in _filterSlots) { - val packet = slot.sendChanges(true) - - if (packet != null) { - MenuNetworkChannel.send(consumer, packet) - } - } + matteryBroadcast() } override fun stillValid(player: Player): Boolean { @@ -267,42 +403,146 @@ abstract class MatteryMenu @JvmOverloads protected constructor( return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0 } - abstract val storageSlots: Collection - protected val quickMoveMapping = Reference2ObjectOpenHashMap Collection?>() - - protected fun storage2Inventory(slot: Slot) { - quickMoveMapping[slot] = ::playerInventorySlots + fun syncCarried() { + setRemoteCarried(carried.copy()) + MenuNetworkChannel.send(player as ServerPlayer, SetCarriedPacket(carried)) } - protected fun inventory2Storage(slot: Slot) { - quickMoveMapping[slot] = ::storageSlots + fun syncCarried(stack: ItemStack) { + carried = stack + syncCarried() } - protected var autoRegisteredStorageSlots = false + private val externalSlots = ConditionalSet() + private val quickMoveMapping = Reference2ObjectOpenHashMap>>() + + override fun addSlot(pSlot: Slot): Slot { + if (pSlot in slots) { + return pSlot + } + + if (pSlot is UserFilteredSlot && !pSlot.hasSetFilter) { + val container = pSlot.container + + val input: PlayerInput + val field: IField + + if (container is IMatteryContainer) { + input = PlayerInput(ItemValueCodec.nullable, handler = { container.setSlotFilter(pSlot.slotIndex, it) }) + field = mSynchronizer.ComputedField(getter = { container.getSlotFilter(pSlot.slotIndex) }, ItemValueCodec.nullable) + } else { + input = PlayerInput(ItemValueCodec.nullable, handler = { throw UnsupportedOperationException() }) + field = mSynchronizer.ComputedField(getter = { null }, ItemValueCodec.nullable) + } + + pSlot.filter = GetterSetter.of(getter = field::value, setter = input::accept) + } + + return super.addSlot(pSlot) + } + + fun addSlot(slots: Iterable) { + slots.forEach(::addSlot) + } + + /** + * Adds slot to "storage slots" list (utilized by quick move) and calls [addSlot] + * + * [condition] allows to specify when slot is invisible on GUI (hence being ignored by quick move) + */ + protected fun addStorageSlot(slot: T, addMapping: Boolean = true, prepend: Boolean = false, condition: BooleanSupplier = BooleanSupplier { true }): T { + if (!externalSlots.actuallyContains(slot)) { + addSlot(slot) + + if (prepend) + externalSlots.replaceFirst(slot, condition) + else + externalSlots.replace(slot, condition) + + if (addMapping) { + mapQuickMove(slot, equipmentSlots) + mapQuickMoveToInventory(slot) + } + } + + return slot + } + + protected fun addStorageSlot(slot: Iterable, addMapping: Boolean = true, prepend: Boolean = false, condition: BooleanSupplier = BooleanSupplier { true }) { + for (value in slot) + addStorageSlot(value, addMapping, prepend, condition) + } + + /** + * Marks slot as "storage" - shift clicking it will move its contents to Player's inventory + */ + protected fun mapQuickMoveToInventory(slot: Slot, prepend: Boolean = false) { + mapQuickMove(slot, playerInventorySlots, prepend = prepend) + } + + /** + * Marks slot as "inventory" - shift clicking it will move its contents to menu's storage slots + */ + protected fun mapQuickMoveToExternal(slot: Slot, prepend: Boolean = false) { + mapQuickMove(slot, externalSlots, prepend = prepend) + } + + protected fun mapQuickMove(slot: Slot, target: Collection, prepend: Boolean = false, condition: BooleanSupplier = BooleanSupplier { true }) { + val listing = quickMoveMapping.computeIfAbsent(slot, Reference2ObjectFunction { ReferenceArrayList(1) /* ReferenceArrayList ибо мы используем его в некотором смысле как множество */ }) + listing.remove(target) + + if (prepend) + listing.add(0, target) + else + listing.add(target) + } + + protected fun mapQuickMove(slot: Slot, vararg target: Collection) { + for (value in target) { + mapQuickMove(slot, value) + } + } // This method receive Player interactor and slot_index where Shift + Right click occurred // It shall return item stack that got moved override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { - if (!slots[slotIndex].hasItem() || !slots[slotIndex].mayPickup(ply)) { + val slot = slots[slotIndex] + + if (!slot.hasItem() || !slot.mayPickup(ply)) { return ItemStack.EMPTY } - if (!autoRegisteredStorageSlots) { - autoRegisteredStorageSlots = true + val target = quickMoveMapping[slot] ?: return ItemStack.EMPTY + val copy = slot.item.copy() + var any = false - for (slot in storageSlots) { - storage2Inventory(slot) + if (target.any { it.any { it is UserFilteredSlot && it.filter != null } }) { + for (collection in target) { + if (moveItemStackTo(slot, collection, onlyFiltered = true)) { + any = true + + if (!slot.hasItem()) { + return copy + } + } } } - val slot = slots[slotIndex] - val target = quickMoveMapping[slot]?.invoke() ?: return ItemStack.EMPTY - val copy = slots[slotIndex].item.copy() + for (collection in target) { + if (moveItemStackTo(slot, collection)) { + any = true - if (moveItemStackTo(slots[slotIndex], target)) + if (!slot.hasItem()) { + return copy + } + } + } + + if (any) { return copy - else + } else { return ItemStack.EMPTY + } } fun quickMoveToInventory(stack: ItemStack, simulate: Boolean): ItemStack { @@ -313,11 +553,11 @@ abstract class MatteryMenu @JvmOverloads protected constructor( return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate) } - override fun canTakeItemForPickAll(p_38908_: ItemStack, p_38909_: Slot): Boolean { - if (p_38909_ is EquipmentSlot) + override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean { + if (slot is EquipmentSlot) return false - return super.canTakeItemForPickAll(p_38908_, p_38909_) && (p_38909_ !is MatterySlot || p_38909_.canTakeItemForPickAll()) && !p_38909_.isCurioSlot + return super.canTakeItemForPickAll(itemStack, slot) && (slot !is MatterySlot || slot.canTakeItemForPickAll()) && !slot.isCurioSlot } override fun moveItemStackTo( @@ -336,49 +576,50 @@ abstract class MatteryMenu @JvmOverloads protected constructor( return true } - fun moveItemStackTo( - item: ItemStack, - slots: Collection - ): Boolean { - val remainder = moveItemStackToSlots(item, slots) - - if (remainder.count == item.count) { - return false - } - - item.count = remainder.count - return true - } - fun moveItemStackTo( source: Slot, - slots: Collection + slots: Collection, + onlyFiltered: Boolean = false ): Boolean { - val remainder = moveItemStackToSlots(source.item, slots) + val remainder = moveItemStackToSlots(source.item, slots, onlyFiltered = onlyFiltered) if (remainder.count == source.item.count) { return false } + val copy = source.item.copy() + if (remainder.isEmpty) { source.set(ItemStack.EMPTY) + source.onTake(player, copy) } else { + copy.count = source.item.count - remainder.count source.item.count = remainder.count - source.setChanged() + source.onTake(player, copy) } return true } - fun moveItemStackToSlots(item: ItemStack, slots: Collection, simulate: Boolean = false): ItemStack { + fun moveItemStackToSlots(item: ItemStack, slots: Collection, simulate: Boolean = false, onlyFiltered: Boolean = false): ItemStack { + if (item.isEmpty) { + return ItemStack.EMPTY + } + val copy = item.copy() // first pass - stack with existing slots if (copy.isStackable) { for (slot in slots) { + if (onlyFiltered && (slot !is UserFilteredSlot || !slot.test(item))) { + continue + } else if (!onlyFiltered && slot is UserFilteredSlot && !slot.test(item)) { + continue + } + val limit = slot.getMaxStackSize(copy) - if (limit > slot.item.count && ItemStack.isSameItemSameTags(slot.item, copy) && slot.mayPlace(item)) { + if (limit > slot.item.count && slot.mayPlace(item) && ItemStack.isSameItemSameTags(slot.item, copy)) { val newCount = (slot.item.count + copy.count).coerceAtMost(limit) val diff = newCount - slot.item.count copy.count -= diff @@ -397,6 +638,12 @@ abstract class MatteryMenu @JvmOverloads protected constructor( // second pass - drop stack into first free slot for (slot in slots) { + if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) { + continue + } else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) { + continue + } + val limit = slot.getMaxStackSize(copy) if (!slot.hasItem() && slot.mayPlace(item)) { @@ -427,26 +674,137 @@ abstract class MatteryMenu @JvmOverloads protected constructor( require(finalSlot < slots.size) { "Final slot $finalSlot is bigger than total size of array of ${slots.size}" } val slots = ArrayList(finalSlot - initialSlot + 1) + var filters = false for (i in (if (inverse) finalSlot downTo initialSlot else initialSlot .. finalSlot)) { - slots.add(slots[i]) + val slot = slots[i] + slots.add(slot) + + if (slot is InventorySlot && slot.filter != null) { + filters = true + } + } + + if (filters) { + return moveItemStackToSlots(moveItemStackToSlots(item, slots, simulate, onlyFiltered = true), slots, simulate, onlyFiltered = false) } return moveItemStackToSlots(item, slots, simulate) } - public override fun addDataSlots(p_38885_: ContainerData) { - super.addDataSlots(p_38885_) - } + private var armorSlots: ImmutableList>? = null + private var curiosSlots: ImmutableList>? = null + private val equipmentSlots = ArrayList() - fun makeArmorSlots(): List> { - val cosmetic = ply.cosmeticArmorSlots + fun makeArmorSlots(mapMoveToExternal: Boolean = false): List> { + if (armorSlots != null) { + return armorSlots!! + } + + val cosmetic = player.cosmeticArmorSlots return ImmutableList.of( - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD) to cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.HEAD), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.CHEST) to cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.CHEST), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.LEGS) to cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.LEGS), - EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.FEET) to cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.FEET), + PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.HEAD)), + PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.CHEST), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.CHEST)), + PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.LEGS), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.LEGS)), + PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.FEET), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.FEET)), + ).also { + armorSlots = it + + for ((a, b) in it) { + equipmentSlots.add(a) + addSlot(a) + if (b != null) addSlot(b) + + if (mapMoveToExternal) { + mapQuickMoveToExternal(a) + } + + mapQuickMoveToInventory(a) + + if (b != null) { + if (mapMoveToExternal) { + mapQuickMoveToExternal(b) + } + + mapQuickMoveToInventory(b) + } + } + } + } + + fun makeEquipmentSlots(mapMoveToExternal: Boolean = false): EquipmentSlots { + return EquipmentSlots( + armorSlots = makeArmorSlots(mapMoveToExternal), + curiosSlots = curiosSlots ?: ImmutableList.copyOf(player.curiosSlots).also { + for ((a, b) in it) { + equipmentSlots.add(a) + addSlot(a) + if (b != null) addSlot(b) + + if (mapMoveToExternal) { + mapQuickMoveToExternal(a) + } + + mapQuickMoveToInventory(a) + + if (b != null) { + if (mapMoveToExternal) { + mapQuickMoveToExternal(b) + } + + mapQuickMoveToInventory(b) + } + } + }.also { curiosSlots = it } + ) + } + + fun makeUpgradeSlots(count: Int, container: UpgradeContainer?): UpgradeSlots { + if (container != null) { + require(count == container.containerSize) { "Upgrade container size ${container.containerSize} does not match with provided size $count" } + } + + val allowedTypes = EnumMap(UpgradeType::class.java) + + for (value in UpgradeType.ALL) { + allowedTypes[value] = mSynchronizer.bool() + + if (container != null) { + allowedTypes[value]!!.boolean = value in container.allowedUpgrades + } + } + + val syncContainer = container ?: SimpleContainer(count) + + val isOpen = InstantBooleanInput(this) + + return UpgradeSlots( + slots = immutableList(count) { + object : MatterySlot(syncContainer, it) { + init { + mapQuickMoveToInventory(this) + } + + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.UPGRADE).map { it.upgradeTypes.any { allowedTypes[it]!!.boolean } }.orElse(false) + } + } + }.also { for (i in it.indices.reversed()) addStorageSlot(it[i], prepend = true, condition = isOpen) }, + + allowedTypes = ConditionalEnumSet(allowedTypes), + openState = isOpen, + currentStats = object : IMatteryUpgrade { + override val upgradeTypes: Set = setOf() + override val speedBonus: Double by mSynchronizer.computedDouble(DoubleSupplier { container?.speedBonus ?: 0.0 }).property + override val processingItems: Int by mSynchronizer.computedInt(IntSupplier { container?.processingItems ?: 0 }).property + override val energyStorageFlat: Decimal by mSynchronizer.computedDecimal { container?.energyStorageFlat ?: Decimal.ZERO } + override val energyStorage: Decimal by mSynchronizer.computedDecimal { container?.energyStorage ?: Decimal.ZERO } + override val energyConsumed: Decimal by mSynchronizer.computedDecimal { container?.energyConsumed ?: Decimal.ZERO } + override val energyThroughputFlat: Decimal by mSynchronizer.computedDecimal { container?.energyThroughputFlat ?: Decimal.ZERO } + override val energyThroughput: Decimal by mSynchronizer.computedDecimal { container?.energyThroughput ?: Decimal.ZERO } + override val failureMultiplier: Double by mSynchronizer.computedDouble(DoubleSupplier { container?.failureMultiplier ?: 1.0 }).property + } ) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt index 2f2f226a6..8f0e8ca81 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt @@ -5,6 +5,8 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import net.minecraft.world.SimpleContainer +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback abstract class MatteryPoweredMenu protected constructor( menuType: MenuType<*>, @@ -12,18 +14,16 @@ abstract class MatteryPoweredMenu protected constructor( inventory: Inventory, tile: MatteryPoweredBlockEntity? = null ) : MatteryMenu(menuType, containerID, inventory, tile) { - val powerWidget: LevelGaugeWidget - val batterySlot: BatterySlot + val energyWidget = LevelGaugeWidget(this, tile?.energy) + val batterySlot = BatterySlot(tile?.batteryContainer ?: SimpleContainer(1), 0) + val redstoneConfig = EnumInputWithFeedback(this) init { - if (tile == null) { - powerWidget = LevelGaugeWidget(this) - batterySlot = BatterySlot(SimpleContainer(1), 0) - } else { - powerWidget = LevelGaugeWidget(this, tile.energy) - batterySlot = BatterySlot(tile.batteryContainer, 0) + if (tile != null) { + redstoneConfig.with(tile.redstoneControl::redstoneSetting) } addSlot(batterySlot) + mapQuickMoveToInventory(batterySlot) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MinecartCargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MinecartCargoCrateMenu.kt deleted file mode 100644 index e5d07b566..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MinecartCargoCrateMenu.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.Container -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.entity.player.Player -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.CargoCrateBlockEntity -import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate -import ru.dbotthepony.mc.otm.registry.MMenus - -class MinecartCargoCrateMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - val cart: MinecartCargoCrate? = null -) : MatteryMenu(MMenus.CARGO_CRATE, p_38852_, inventory) { - override val storageSlots: List - - private val trackedPlayerOpen = !inventory.player.isSpectator - - init { - val container = cart as Container? ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) - - storageSlots = ImmutableList(CargoCrateBlockEntity.CAPACITY) { - val slot = MatterySlot(container, it) - addSlot(slot) - return@ImmutableList slot - } - - if (trackedPlayerOpen) { - cart?.onPlayerOpen() - } - - addInventorySlots() - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - - if (trackedPlayerOpen) { - cart?.onPlayerClose() - } - } - - override fun stillValid(player: Player): Boolean { - return cart?.stillValid(player) ?: true - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt deleted file mode 100644 index 7b4990b01..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PatternStorageMenu.kt +++ /dev/null @@ -1,46 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import ru.dbotthepony.mc.otm.core.ImmutableList -import ru.dbotthepony.mc.otm.block.entity.matter.PatternStorageBlockEntity -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class PatternStorageMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: PatternStorageBlockEntity? = null -) : MatteryMenu( - MMenus.PATTERN_STORAGE, p_38852_, inventory, tile -) { - val storedThis: LevelGaugeWidget - val storedGrid: LevelGaugeWidget - - override val storageSlots: List - - init { - if (tile == null) { - storedThis = LevelGaugeWidget(this) - storedGrid = LevelGaugeWidget(this) - } else { - storedThis = LevelGaugeWidget(this, tile) - storedGrid = LevelGaugeWidget(this, { - Decimal(tile.matterGraph?.patternCount ?: 0) - }, { - Decimal(tile.matterGraph?.patternCapacity ?: 0) - }) - } - - val patterns = tile?.patternContainer ?: SimpleContainer(2 * 4) - - storageSlots = ImmutableList(2 * 4) { - PatternSlot(patterns, it) - } - - storageSlots.forEach(this::addSlot) - - addInventorySlots() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PlatePressMenu.kt deleted file mode 100644 index a4fc2680b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/PlatePressMenu.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import com.google.common.collect.ImmutableList -import net.minecraft.world.SimpleContainer -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.block.entity.PlatePressBlockEntity -import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class PlatePressMenu @JvmOverloads constructor( - containerID: Int, - inventory: Inventory, - tile: PlatePressBlockEntity? = null -) : MatteryPoweredMenu(MMenus.PLATE_PRESS, containerID, inventory, tile) { - val container = tile?.container ?: SimpleContainer(2) - - val inputSlot = MatterySlot(container, PlatePressBlockEntity.SLOT_INPUT) - val outputSlot = MachineOutputSlot(container, PlatePressBlockEntity.SLOT_OUTPUT) - - override val storageSlots: List = ImmutableList.of(inputSlot, outputSlot) - - val progressGauge = if (tile != null) ProgressGaugeWidget(this, tile::workProgress, tile::isUnableToProcess) else ProgressGaugeWidget(this) - - init { - addSlot(inputSlot) - addSlot(outputSlot) - addInventorySlots() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt index c2e8374a9..be03785a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -1,16 +1,38 @@ package ru.dbotthepony.mc.otm.menu +import com.google.common.collect.ImmutableList import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy -import ru.dbotthepony.mc.otm.capability.matter.MatterDirection import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.container.IMatteryContainer +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.runOnClient +import java.util.function.Predicate -open class MatterySlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) { +/** + * Make slots for single container + */ +inline fun makeSlots(container: C, initializer: (C, Int) -> S): ImmutableList { + return immutableList(container.containerSize) { initializer.invoke(container, it) } +} + +/** + * Make slots for list of containers with single slot in them + */ +inline fun makeSlots(containers: List?, size: Int, initializer: (Container, Int) -> S): ImmutableList { + return immutableList(size) { initializer(containers?.get(it) ?: SimpleContainer(1), 0) } +} + +open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) { var ignoreSpectators = true override fun mayPickup(player: Player): Boolean { @@ -24,32 +46,100 @@ open class MatterySlot @JvmOverloads constructor(container: Container, index: In open fun canTakeItemForPickAll(): Boolean { return true } -} -open class MachineOutputSlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return false + override fun getMaxStackSize(): Int { + val container = container + + if (container is IMatteryContainer) { + return container.getMaxStackSize(slotIndex, ItemStack.EMPTY) + } else { + return super.getMaxStackSize() + } + } + + override fun getMaxStackSize(itemStack: ItemStack): Int { + val container = container + + if (container is IMatteryContainer) { + return container.getMaxStackSize(slotIndex, itemStack) + } else { + return super.getMaxStackSize(itemStack) + } } } -open class BatterySlot @JvmOverloads constructor(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y), Predicate { + var hasSetFilter = false + private set + + var filter: GetterSetter? = null + set(value) { + hasSetFilter = true + field = value + } + + override fun canTakeItemForPickAll(): Boolean { + return filter?.get() == null + } + + override fun test(t: ItemStack): Boolean { + return filter?.get() == null || filter?.get() == t.item + } + + fun isSameFilter(other: Slot): Boolean { + if (other !is UserFilteredSlot) + return filter?.get() == null + + return ( + (other.filter == null && filter == null) || + (other.filter != null && filter != null && other.filter!!.get() == filter!!.get()) + ) + } + + override fun isSameInventory(other: Slot): Boolean { + return isSameFilter(other) && super.isSameInventory(other) + } +} + +open class OutputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: (ItemStack) -> Unit = {}) : MatterySlot(container, index, x, y) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return false + } + + override fun onTake(pPlayer: Player, pStack: ItemStack) { + super.onTake(pPlayer, pStack) + this.onTake.invoke(pStack) + } + + override fun onQuickCraft(pStack: ItemStack, pAmount: Int) { + super.onQuickCraft(pStack, pAmount) + this.onTake.invoke(pStack) + } +} + +open class BatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && (itemStack.energy?.canExtract() ?: false) } } -open class MatterContainerInputSlot @JvmOverloads constructor( +open class ChargeSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && (itemStack.energy?.canReceive() ?: false) + } +} + +open class MatterContainerInputSlot( container: Container, index: Int, x: Int = 0, y: Int = 0, - val direction: MatterDirection = MatterDirection.BIDIRECTIONAL + val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL ) : MatterySlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { val handler = itemStack.getCapability(MatteryCapability.MATTER).resolve() if (handler.isEmpty) return false - val direction = handler.get().direction - return super.mayPlace(itemStack) && (direction == MatterDirection.BIDIRECTIONAL || this.direction == direction) + return super.mayPlace(itemStack) && this.direction.test(handler.get().matterFlow) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt deleted file mode 100644 index f7dfd875b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageBusMenu.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.block.entity.storage.StorageBusBlockEntity -import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class StorageBusMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: StorageBusBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.STORAGE_BUS, p_38852_, inventory, tile -) { - val busFilterSlots: List - val busFilterState: BooleanPlayerInputWidget - - init { - if (tile != null) { - busFilterSlots = addFilterSlots(tile.filter) - busFilterState = BooleanPlayerInputWidget(this, tile.filter::isWhitelist) - } else { - busFilterSlots = addFilterSlots(StorageBusBlockEntity.MAX_FILTERS) - busFilterState = BooleanPlayerInputWidget(this).asClient() - } - - addInventorySlots() - } - - override val storageSlots: Collection = listOf(batterySlot) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt deleted file mode 100644 index 777118f53..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageExporterMenu.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.block.entity.storage.StorageExporterBlockEntity -import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class StorageExporterMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: StorageExporterBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.STORAGE_EXPORTER, p_38852_, inventory, tile -) { - val busFilterSlots: List - val busFilterState: BooleanPlayerInputWidget - - init { - if (tile != null) { - busFilterSlots = addFilterSlots(tile.filter) - busFilterState = BooleanPlayerInputWidget(this, tile.filter::isWhitelist) - } else { - busFilterSlots = addFilterSlots(StorageExporterBlockEntity.MAX_FILTERS) - busFilterState = BooleanPlayerInputWidget(this).asClient() - } - - addInventorySlots() - } - - override val storageSlots: List = listOf(batterySlot) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageImporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageImporterMenu.kt deleted file mode 100644 index 554153aef..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StorageImporterMenu.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dbotthepony.mc.otm.menu - -import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot -import ru.dbotthepony.mc.otm.block.entity.storage.StorageImporterBlockEntity -import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputWidget -import ru.dbotthepony.mc.otm.registry.MMenus - -class StorageImporterMenu @JvmOverloads constructor( - p_38852_: Int, - inventory: Inventory, - tile: StorageImporterBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.STORAGE_IMPORTER, p_38852_, inventory, tile -) { - val busFilterSlots: List - val busFilterState: BooleanPlayerInputWidget - - init { - if (tile != null) { - busFilterSlots = addFilterSlots(tile.filter) - busFilterState = BooleanPlayerInputWidget(this, tile.filter::isWhitelist) - } else { - busFilterSlots = addFilterSlots(StorageImporterBlockEntity.MAX_FILTERS) - busFilterState = BooleanPlayerInputWidget(this).asClient() - } - - addInventorySlots() - } - - override val storageSlots: List = listOf(batterySlot) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt index 251540c89..8ded60070 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt @@ -1,8 +1,8 @@ package ru.dbotthepony.mc.otm.menu.data import com.mojang.blaze3d.platform.InputConstants -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.client.Minecraft import net.minecraft.client.gui.screens.Screen import net.minecraft.network.FriendlyByteBuf @@ -11,39 +11,42 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.ClickAction import net.minecraft.world.inventory.ClickType import net.minecraft.world.item.ItemStack -import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.addSorted +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.map import ru.dbotthepony.mc.otm.core.readBigInteger import ru.dbotthepony.mc.otm.core.writeBigInteger import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.* -import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.storage.* import java.math.BigInteger import java.util.* import java.util.function.Supplier +import kotlin.collections.ArrayList interface INetworkedItemViewProvider { val networkedItemView: NetworkedItemView } -class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket { +private fun all(stack: ItemStack): Int = stack.maxStackSize.coerceAtMost(stack.count) +private fun half(stack: ItemStack): Int = (stack.maxStackSize.coerceAtMost(stack.count) / 2).coerceAtLeast(1) + +data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { buff.writeInt(stackID) buff.writeEnum(type) buff.writeEnum(action) } - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val sender = context.sender ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val sender = context.sender ?: return - if (!sender.isSpectator) { - sender.resetLastActionTime() - (sender.containerMenu as? INetworkedItemViewProvider)?.networkedItemView?.playerInteract(this) - } + if (!sender.isSpectator) { + sender.resetLastActionTime() + (sender.containerMenu as? INetworkedItemViewProvider)?.networkedItemView?.playerInteract(this) } } @@ -54,144 +57,85 @@ class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: } } -object ClearItemViewPacket : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - // NO-OP +abstract class NetworkedItemViewPacket : MatteryPacket { + final override fun play(context: MNetworkContext) { + val get = Minecraft.getInstance().player?.containerMenu ?: return + val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No NetworkedItemView is present in currently open menu") + action(view) } - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - (minecraft.player?.containerMenu as? INetworkedItemViewProvider)?.networkedItemView?.clear() - } - } + protected abstract fun action(view: NetworkedItemView) +} - fun read(buff: FriendlyByteBuf): ClearItemViewPacket { - return ClearItemViewPacket - } +object ClearItemViewPacket : NetworkedItemViewPacket() { + override fun write(buff: FriendlyByteBuf) {} - fun send(ply: ServerPlayer) { - MenuNetworkChannel.send(ply, this) + override fun action(view: NetworkedItemView) { + view.clear() } } -class StackAddPacket(val containerId: Int, val id: Int, val stack: ItemStackWrapper) : MatteryPacket { +class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(containerId) - buff.writeInt(id) - buff.writeBigItem(stack) + buff.writeInt(stackId) + stack.write(buff) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != containerId) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $containerId") - - if (view.localState.containsKey(id)) { - throw IllegalStateException("Item tracker $containerId already has stack with id of $id") - } - - val state = NetworkedItemView.NetworkedItem(id, stack) - view.localState[id] = state - - /*val iterator = view.sortedView.iterator().withIndex() - var lastCompare = 0 - var hit = false - - while (iterator.hasNext()) { - val (i, existing) = iterator.next() - val cmp = view.sorter.compare(existing.stack, stack) - - if (cmp != lastCompare && lastCompare != 0) { - hit = true - view.sortedView.add(i, state) - } else if (cmp != lastCompare) { - lastCompare = cmp - } - } - - if (!hit) {*/ - view.sortedView.add(state) - //} - - view.resort() + override fun action(view: NetworkedItemView) { + if (view.id2tuple.containsKey(stackId)) { + throw IllegalStateException("NetworkedItemView $view already contains stack with id $stackId") } + + val tuple = NetworkedItemView.Tuple(stackId, stack) + view.id2tuple[stackId] = tuple + view.sortedView.addSorted(tuple, view.sorter.map(NetworkedItemView.Tuple::stack)) } companion object { fun read(buffer: FriendlyByteBuf): StackAddPacket { - val containerId = buffer.readInt() val id = buffer.readInt() - val item = buffer.readBigItem() - return StackAddPacket(containerId, id, item) + val item = StorageStack.ITEMS.read(buffer) + return StackAddPacket(id, item) } } } -class StackChangePacket(val id: Int, val stackID: Int, val newCount: BigInteger) : MatteryPacket { +class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeInt(stackID) + buff.writeInt(stackId) buff.writeBigInteger(newCount) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != id) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") - val state = view.localState[stackID] ?: throw IllegalStateException("No such stack with id $stackID in $view") - - state.stack.count = newCount - view.resort() - } + override fun action(view: NetworkedItemView) { + val tuple = view.id2tuple[stackId] ?: throw IllegalStateException("No such stack with id $stackId in $view") + tuple.stack = tuple.stack.copy(newCount) + view.resort() } companion object { fun read(buffer: FriendlyByteBuf): StackChangePacket { - val id = buffer.readInt() val stackID = buffer.readInt() val newCount = buffer.readBigInteger() - return StackChangePacket(id, stackID, newCount) + return StackChangePacket(stackID, newCount) } } } -class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket { +class StackRemovePacket(val stackId: Int) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeInt(stackID) + buff.writeInt(stackId) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != id) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") - val obj = view.localState.remove(stackID) ?: throw IllegalStateException("No such stack with id $stackID in $view") - view.sortedView.remove(obj) - view.resort() - } + override fun action(view: NetworkedItemView) { + val obj = view.id2tuple.remove(stackId) ?: throw IllegalStateException("No such stack with id $stackId in $view") + view.sortedView.remove(obj) + view.resort() } companion object { fun read(buffer: FriendlyByteBuf): StackRemovePacket { - val id = buffer.readInt() val stackID = buffer.readInt() - return StackRemovePacket(id, stackID) + return StackRemovePacket(stackID) } } } @@ -199,129 +143,105 @@ class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket { /** * Creates a virtual, slotless container for Player to interaction with. */ -open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: Boolean) : IStorageEventConsumer { - data class NetworkedItem constructor(val id: Int, val stack: ItemStackWrapper, val upstreamId: UUID? = null) - - override val storageType: StorageStackType - get() = ITEM_STORAGE - - // this (how client see and interact with) - val localState = Int2ObjectOpenHashMap() - - val sortedView = LinkedList() - var sorter: Comparator = NAME_SORTER - - companion object { - val NAME_SORTER = Comparator { o1, o2 -> - val cmp = o1.displayName.string.compareTo(o2.displayName.string) - - if (cmp != 0) - return@Comparator cmp - - return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) - } - - val COUNT_SORTER = Comparator { o1, o2 -> - val cmp = o1.count.compareTo(o2.count) - - if (cmp != 0) - return@Comparator cmp - - return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) +class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Boolean) : IStorageEventConsumer { + data class Tuple(val networkId: Int, var stack: ItemStorageStack, val upstreamId: UUID? = null) : Supplier { + override fun get(): ItemStorageStack { + return stack } } + override val storageType: StorageStack.Type + get() = StorageStack.ITEMS + + val id2tuple = Int2ObjectOpenHashMap() + val sortedView = ArrayList() + var sorter: Comparator = ItemStorageStackSorter.DEFAULT + set(value) { + if (field != value) { + field = value + resort() + } + } + + val itemCount get() = id2tuple.size + + private var nextItemID = 0 + private val uuid2tuple = Object2ObjectOpenHashMap() + private val networkBacklog = ArrayList() + + operator fun get(id: Int): Tuple? = id2tuple[id] + fun resort() { - sortedView.sortWith { a, b -> - return@sortWith sorter.compare(a.stack.item, b.stack.item) + if (isRemote) { + sortedView.sortWith(sorter.map(Tuple::stack)) } } - // parent (e.g. VirtualComponent) - protected val upstreamState = HashMap() - protected val networkBacklog = ArrayList() + var component: IStorageComponent? = null + set(provider) { + if (provider === field) return - operator fun get(id: Int): NetworkedItem? = localState[id] - - var provider: IStorageComponent? = null - private set + field?.removeListenerAndNotify(this) + field = provider + provider?.addListenerAndNotify(this) + } fun mouseClick(index: Int, mouseButton: Int) { - if (minecraft.player?.isSpectator == true) { - return - } + if (minecraft.player?.isSpectator == true) return - val list = sortedView - - val action = + MenuNetworkChannel.sendToServer(ItemViewInteractPacket( + sortedView.getOrNull(index)?.networkId ?: -1, + if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP, if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY - - val type = - if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP - - MenuNetworkChannel.sendToServer( - ItemViewInteractPacket(if (index >= list.size) -1 else list[index].id, type, action) - ) - } - - fun setComponent(provider: IStorageComponent?) { - if (provider === this.provider) return - - this.provider?.removeListenerAuto(this) - this.provider = provider - provider?.addListenerAuto(this) + )) } fun removed() { - provider?.removeListenerAuto(this) + component?.removeListenerAndNotify(this) } - val itemCount get() = localState.values.size + override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { + check(!uuid2tuple.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" } - override fun addStack(stack: ItemStackWrapper, id: UUID, provider: IStorageProvider) { - check(!upstreamState.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" } + val state = Tuple(nextItemID++, stack, id) - val state = NetworkedItem(nextItemID++, stack.copy(), id) - - this.localState[state.id] = state - upstreamState[id] = state - network { StackAddPacket(menu.containerId, state.id, state.stack) } + this.id2tuple[state.networkId] = state + uuid2tuple[id] = state + network { StackAddPacket(state.networkId, state.stack) } } - override fun changeStack(stack: ItemStackWrapper, id: UUID, oldCount: BigInteger) { - val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") - get.stack.count = stack.count - network { StackChangePacket(menu.containerId, get.id, stack.count) } + override fun onStackChanged(stack: ItemStorageStack, id: UUID) { + val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") + get.stack = stack + network { StackChangePacket(get.networkId, stack.count) } } - protected fun network(fn: () -> Any) { - if (!remote) { + override fun onStackRemoved(id: UUID) { + val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") + uuid2tuple.remove(id) + id2tuple.remove(get.networkId) + network { StackRemovePacket(get.networkId) } + } + + private inline fun network(fn: () -> Any) { + if (!isRemote) { networkBacklog.add(fn()) } } - override fun removeStack(stack: ItemStackWrapper, id: UUID) { - val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") - upstreamState.remove(id) - localState.remove(get.id) - network { StackRemovePacket(menu.containerId, get.id) } - } - - protected var nextItemID = 0 - fun clear() { sortedView.clear() - upstreamState.clear() - localState.clear() + uuid2tuple.clear() + id2tuple.clear() - if (!remote) { + if (!isRemote) { networkBacklog.clear() networkBacklog.add(ClearItemViewPacket) } } fun network() { - check(!remote) { "Not a server" } + check(!isRemote) { "Not a server" } val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } for (packet in networkBacklog) { @@ -332,82 +252,41 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: } fun playerInteract(packet: ItemViewInteractPacket) { - val provider = provider ?: return - - val click = packet.type - val action = packet.action - val stackId = packet.stackID - - if (click == ClickType.CLONE) { - if (stackId < 0 || !ply.abilities.instabuild) return - - val state = get(stackId) ?: return - val copy = state.stack.stack.also { it.count = it.maxStackSize } - - ply.containerMenu.carried = copy - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(ply.containerMenu.carried)) - ply.containerMenu.setRemoteCarried(ply.containerMenu.carried.copy()) + val component = component ?: return + val (stackId, type, action) = packet + if (type == ClickType.CLONE) { + if (!ply.abilities.instabuild) return + menu.syncCarried((get(stackId) ?: return).stack.toItemStack().also { it.count = it.maxStackSize }) return } - if (click == ClickType.QUICK_MOVE && stackId > -1) { - val state = get(stackId) ?: return + // забираем из системы с зажатым shift + if (type == ClickType.QUICK_MOVE) { + val tuple = get(stackId) ?: return + val stack = tuple.stack.toItemStack() - val amount = - if (action == ClickAction.PRIMARY) - state.stack.item.maxStackSize - else - 1.coerceAtLeast(state.stack.item.maxStackSize / 2) - - val extracted = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), true) - - if (!extracted.isEmpty) { - val remaining = menu.quickMoveToInventory(extracted.stack, false) - - if (remaining.count != extracted.count.toInt()) { - provider.extractStack(state.upstreamId, (extracted.count.toInt() - remaining.count).toBigInteger(), false) - } - } + var amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack) + amount -= menu.quickMoveToInventory(tuple.stack.toItemStack(amount), true).count + if (amount == 0) return + menu.quickMoveToInventory(component.extractStack(tuple.upstreamId!!, amount.toBigInteger(), false).toItemStack(), false) return } - if (!menu.carried.isEmpty && click != ClickType.QUICK_MOVE) { - // try to put + if (menu.carried.isNotEmpty) { if (action == ClickAction.PRIMARY) { - val carried = menu.carried - val amount = carried.count - - if (amount == carried.count) { - val stack = provider.insertStack(ItemStackWrapper(menu.carried), false).stack - menu.carried = stack - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) - } - } else { - val copy = menu.carried.copy() - copy.count = 1 - - if (provider.insertStack(ItemStackWrapper(copy), false).isEmpty) { - menu.carried.shrink(1) - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) - } + menu.syncCarried(component.insertStack(ItemStorageStack(menu.carried), false).toItemStack()) + } else if (component.insertStack(ItemStorageStack(menu.carried.copyWithCount(1)), false).isEmpty) { + menu.carried.shrink(1) + menu.syncCarried() } } else if (stackId > -1) { val state = get(stackId) ?: return - - val amount = - if (action == ClickAction.PRIMARY) - state.stack.item.maxStackSize - else - (state.stack.stack.count / 2).coerceAtMost(state.stack.item.maxStackSize / 2).coerceAtLeast(1) - - val extracted = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), false) - menu.carried = extracted.stack - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) + val stack = state.stack.toItemStack() + val amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack) + menu.carried = component.extractStack(state.upstreamId!!, amount.toBigInteger(), false).toItemStack() + menu.syncCarried() } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt new file mode 100644 index 000000000..eb438a19b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.menu.decorative + +import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.container.sort +import ru.dbotthepony.mc.otm.core.util.NullValueCodec +import ru.dbotthepony.mc.otm.menu.IItemStackSortingSettings +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.UserFilteredSlot +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.registry.MMenus + +class CargoCrateMenu( + containerId: Int, + inventory: Inventory, + tile: CargoCrateBlockEntity? = null +) : MatteryMenu(MMenus.CARGO_CRATE, containerId, inventory, tile) { + val actualContainer: Container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) + val storageSlots = makeSlots(actualContainer, ::UserFilteredSlot) + private val trackedPlayerOpen = !inventory.player.isSpectator + + val sort = SortInput(actualContainer, playerSortSettings) + + init { + if (trackedPlayerOpen) { + tile?.onPlayerOpen() + } + + addStorageSlot(storageSlots) + addInventorySlots() + } + + override fun removed(p_38940_: Player) { + super.removed(p_38940_) + + if (trackedPlayerOpen) { + (tile as? CargoCrateBlockEntity)?.onPlayerClose() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt new file mode 100644 index 000000000..401057a94 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.mc.otm.menu.decorative + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity +import ru.dbotthepony.mc.otm.capability.isNotEmpty +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.FluidGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlockEntity? = null) : MatteryMenu(MMenus.FLUID_TANK, containerId, inventory, tile) { + val fluid = FluidGaugeWidget(mSynchronizer, tile?.fluid) + val equipment = makeEquipmentSlots(true) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val redstoneConfig = EnumInputWithFeedback(this) + val fluidConfig = FluidConfigPlayerInput(this, tile?.fluidConfig) + + val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && + itemStack + .getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM) + .map { it.isNotEmpty } + .orElse(false) + } + } + + val fillInput = object : MatterySlot(tile?.fillInput ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && + (if (itemStack.count <= 1) itemStack else itemStack.copyWithCount(1)) + .getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM) + .map { + if (fluid.fluid.isEmpty) + it.tanks > 0 + else + it.fill(fluid.fluid, IFluidHandler.FluidAction.SIMULATE) > 0 + } + .orElse(false) + } + } + + val output = OutputSlot(tile?.output ?: SimpleContainer(1), 0) + + init { + // сначала слот на заполнение из бака + addStorageSlot(fillInput) + addStorageSlot(drainInput) + addStorageSlot(output) + addInventorySlots() + + if (tile != null) { + redstoneConfig.with(tile.redstoneControl::redstoneSetting) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/HoloSignMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/HoloSignMenu.kt new file mode 100644 index 000000000..bc3562c0e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/HoloSignMenu.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.menu.decorative + +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.inventory.Slot +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.StringInputWithFeedback +import ru.dbotthepony.mc.otm.registry.MMenus + +class HoloSignMenu @JvmOverloads constructor( + containerId: Int, + inventory: Inventory, + tile: HoloSignBlockEntity? = null +) : MatteryMenu(MMenus.HOLO_SIGN, containerId, inventory, tile) { + val text = StringInputWithFeedback(this) + val locked = BooleanInputWithFeedback(this) + val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java) + + init { + text.filter { it.isCreative || !locked.value } + redstone.filter { it.isCreative || !locked.value } + locked.filter { it.isCreative } + + if (tile != null) { + text.withConsumer { if (tile.isLocked) tile.signText = it else tile.signText = HoloSignBlockEntity.truncate(it) }.withSupplier(tile::signText) + locked.with(tile::isLocked) + redstone.with(tile.redstoneControl::redstoneSetting) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt new file mode 100644 index 000000000..b4aa8d957 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt @@ -0,0 +1,48 @@ +package ru.dbotthepony.mc.otm.menu.decorative + +import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.container.sort +import ru.dbotthepony.mc.otm.core.util.NullValueCodec +import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.registry.MMenus + +class MinecartCargoCrateMenu( + containerId: Int, + inventory: Inventory, + val cart: MinecartCargoCrate? = null +) : MatteryMenu(MMenus.CARGO_CRATE, containerId, inventory) { + val actualContainer: Container = cart ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) + val storageSlots = makeSlots(actualContainer, ::MatterySlot) + + private val trackedPlayerOpen = !inventory.player.isSpectator + + val sort = SortInput(actualContainer, playerSortSettings) + + init { + if (trackedPlayerOpen) { + cart?.onPlayerOpen(inventory.player) + } + + addStorageSlot(storageSlots) + addInventorySlots() + } + + override fun removed(p_38940_: Player) { + super.removed(p_38940_) + + if (trackedPlayerOpen) { + cart?.onPlayerClose(inventory.player) + } + } + + override fun stillValid(player: Player): Boolean { + return cart?.stillValid(player) ?: true + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt new file mode 100644 index 000000000..d61b81d0b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt @@ -0,0 +1,149 @@ +package ru.dbotthepony.mc.otm.menu.decorative + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.* +import net.minecraft.world.level.material.Fluids +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.core.ISubscriptable +import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.maybe +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.util.ResourceLocationValueCodec +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.recipe.AbstractPainterRecipe +import ru.dbotthepony.mc.otm.registry.MMenus +import ru.dbotthepony.mc.otm.registry.MRecipes +import java.util.* +import java.util.function.IntConsumer +import kotlin.collections.ArrayList + +class PainterMenu( + containerId: Int, inventory: Inventory, tile: PainterBlockEntity? = null +) : MatteryMenu(MMenus.PAINTER, containerId, inventory, tile) { + val dyeStored = (DyeColor.entries.toMutableList().also { it.add(0, null) }).associateWith { dye -> + mSynchronizer.ComputedIntField({ tile?.dyeStored(dye) ?: 0 }).also { it.addListener(IntConsumer { rescan() }) } + } + + val dyeStoredDirect = SupplierMap(dyeStored) + val itemConfig = ItemConfigPlayerInput(this, tile?.config) + + val inputContainer = MatteryContainer(::rescan, 1) + val outputContainer = MatteryContainer(1) + private var lastRecipe: AbstractPainterRecipe? = null + var selectedRecipe by mSynchronizer.Field(null, ResourceLocationValueCodec.nullable).also { it.addListener { rescan() } } + + val isBulk = BooleanInputWithFeedback(this, tile?.let { it::isBulk }) + + val selectRecipe = PlayerInput(ResourceLocationValueCodec) { + selectedRecipe = it + } + + val inputSlot = object : MatterySlot(inputContainer, 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + if (!itemStack.isEmpty && itemStack.item is DyeableArmorItem) { + return super.mayPlace(itemStack) + } + + return super.mayPlace(itemStack) && inventory.player.level.recipeManager.byType(MRecipes.PAINTER).values.any { it.value.matches(itemStack) } + } + } + + val outputSlot = object : MatterySlot(outputContainer, 0) { + override fun tryRemove(p_150642_: Int, p_150643_: Int, p_150644_: Player): Optional { + rescan() + return super.tryRemove(p_150642_, p_150643_, p_150644_) + } + + override fun onTake(player: Player, itemStack: ItemStack) { + if (itemStack.isNotEmpty) { + lastRecipe?.value?.dyes?.let { tile?.takeDyes(it) } + + if (isBulk.value) { + val found = player.matteryPlayer!!.inventoryAndExopack + .slotIterator() + .filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameTags(it.item, inputSlot.item) } + .maybe() + + if (found != null) { + found.remove(1) + rescan() + } else { + inputContainer.removeItem(0, 1) + } + } else { + inputContainer.removeItem(0, 1) + } + } + + super.onTake(player, itemStack) + } + + override fun mayPlace(itemStack: ItemStack): Boolean { + return false + } + } + + val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && (( + itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().map { + dyeStoredDirect[null]!! < PainterBlockEntity.MAX_WATER_STORAGE && it.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE).isNotEmpty + }.orElse(false) + ) || (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false)) + } + } + + init { + addSlot(outputSlot) + addSlot(dyeSlot) + + addStorageSlot(dyeSlot) + addStorageSlot(inputSlot) + mapQuickMoveToInventory(outputSlot) + mapQuickMoveToInventory(dyeSlot) + + addInventorySlots() + } + + override fun removed(p_38940_: Player) { + super.removed(p_38940_) + clearContainer(p_38940_, inputContainer) + } + + val listeners = ISubscriptable.Impl() + val possibleRecipes = ArrayList() + + private fun rescan() { + possibleRecipes.clear() + possibleRecipes.addAll(inventory.player.level.recipeManager.byType(MRecipes.PAINTER).values.iterator().filter { it.value.matches(inputContainer[0]) }) + + listeners.accept(Unit) + if (tile !is PainterBlockEntity) return + + if (inputContainer.isEmpty || selectedRecipe == null) { + outputContainer.clearContent() + } else { + val recipe = inventory.player.level.recipeManager.byKey(selectedRecipe!!).get() as AbstractPainterRecipe? + + if (recipe == null || !recipe.value.canCraft(dyeStoredDirect) || !recipe.value.matches(inputContainer, inventory.player.level)) { + outputContainer.clearContent() + } else { + outputContainer[0] = recipe.value.assemble(inputContainer) + lastRecipe = recipe + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt new file mode 100644 index 000000000..66c9fbfc8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt @@ -0,0 +1,87 @@ +package ru.dbotthepony.mc.otm.menu.input + +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.ISubscriptable +import ru.dbotthepony.mc.otm.core.SentientGetterSetter +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.synchronizer.IField +import java.util.function.Consumer +import java.util.function.Predicate +import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 + +/** + * Represents Server to Client synchronization and Client to Server input + * + * Getting and setting values should ONLY be done clientside + */ +interface IPlayerInputWithFeedback : SentientGetterSetter, Predicate + +/** + * Represents Server to Client synchronization and Client to Server input + * + * Getting and setting values should ONLY be done clientside + */ +abstract class AbstractPlayerInputWithFeedback : IPlayerInputWithFeedback { + abstract val input: MatteryMenu.PlayerInput + abstract val field: IField + + final override fun addListener(listener: Consumer): ISubscriptable.L { + return field.addListener(listener) + } + + val value: V get() = this.field.value + + final override fun get(): V { + return value + } + + final override fun accept(t: V) { + input.accept(t) + } + + override fun test(player: Player?) = input.test(player) + fun filter(filter: Predicate) = input.filter(filter) + + var supplier: (() -> V)? = null + var consumer: ((V) -> Unit)? = null + + fun withSupplier(func: () -> V): AbstractPlayerInputWithFeedback { + supplier = func + return this + } + + fun withSupplier(func: Supplier): AbstractPlayerInputWithFeedback { + supplier = func::get + return this + } + + fun withConsumer(func: (V) -> Unit): AbstractPlayerInputWithFeedback { + consumer = func + return this + } + + fun withConsumer(func: Consumer): AbstractPlayerInputWithFeedback { + consumer = func::accept + return this + } + + fun with(state: KMutableProperty0): AbstractPlayerInputWithFeedback { + withConsumer { state.set(it) } + withSupplier { state.get() } + return this + } + + fun with(state: GetterSetter): AbstractPlayerInputWithFeedback { + withConsumer(state) + withSupplier(state) + return this + } + + fun clear(): AbstractPlayerInputWithFeedback { + supplier = null + consumer = null + return this + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt new file mode 100644 index 000000000..b90ab2934 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.util.function.BooleanSupplier +import kotlin.reflect.KMutableProperty0 + +class BooleanInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback() { + override val input = menu.booleanInput(allowSpectators) { consumer?.invoke(it) } + override val field = menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false }) + + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0?) : this(menu, allowSpectators) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter?) : this(menu, allowSpectators) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: KMutableProperty0?) : this(menu) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: GetterSetter?) : this(menu) { + if (state != null) + with(state) + } + + fun switchValue() { + accept(!value) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnergyConfigPlayerInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnergyConfigPlayerInput.kt new file mode 100644 index 000000000..0205c1d57 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnergyConfigPlayerInput.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +/** + * [allowPull] and [allowPush] controls whenever player is allowed to change these options + */ +class EnergyConfigPlayerInput(val menu: MatteryMenu, config: MatteryDeviceBlockEntity.ConfigurableEnergy<*>? = null, val allowPull: Boolean = false, val allowPush: Boolean = false) { + inner class Piece(val side: RelativeSide) { + val pull = BooleanInputWithFeedback(menu) + val push = BooleanInputWithFeedback(menu) + val input = EnumInputWithFeedback(menu) + + var possibleModes by menu.mSynchronizer.enum(FlowDirection::class.java) + private set + + var default by menu.mSynchronizer.enum(FlowDirection.NONE) + + init { + pull.filter { allowPull && possibleModes.isSupertype(FlowDirection.INPUT) } + push.filter { allowPush && possibleModes.isSupertype(FlowDirection.OUTPUT) } + } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableEnergy<*>.Piece) { + possibleModes = config.possibleModes + pull.with(config::automatePull) + push.with(config::automatePush) + input.withSupplier { config.energyFlow }.withConsumer { if (possibleModes.isSupertype(it)) config.energyFlow = it } + } + } + + val pieces = immutableMap { for (side in RelativeSide.values()) put(side, Piece(side)) } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableEnergy<*>) { + for ((side, v) in config.pieces) { + pieces[side]!!.with(v) + pieces[side]!!.default = config.defaults[side]!! + } + } + + init { + if (config != null) { + with(config) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt new file mode 100644 index 000000000..8d26f352e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import kotlin.reflect.KMutableProperty0 + +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, allowedValues = allowedValues) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: KMutableProperty0?, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, state, allowedValues = allowedValues) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter?, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, state, allowedValues = allowedValues) + +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, allowedValues = allowedValues) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0?, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state, allowedValues = allowedValues) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter?, allowedValues: Set? = null) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state, allowedValues = allowedValues) + +class EnumInputWithFeedback>(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean = false, val allowedValues: Set? = null) : AbstractPlayerInputWithFeedback() { + val codec = EnumValueCodec(clazz) + private val default = codec.values.first() + + override val input = menu.PlayerInput(codec, allowSpectators) { if (allowedValues == null || it in allowedValues) consumer?.invoke(it) } + override val field = menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) + + constructor(menu: MatteryMenu, clazz: Class, state: KMutableProperty0?, allowedValues: Set? = null) : this(menu, clazz, allowedValues = allowedValues) { + if (state != null) { + with(state) + } + } + + constructor(menu: MatteryMenu, clazz: Class, state: GetterSetter?, allowedValues: Set? = null) : this(menu, clazz, allowedValues = allowedValues) { + if (state != null) { + with(state) + } + } + + constructor(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean, state: KMutableProperty0?, allowedValues: Set? = null) : this(menu, clazz, allowSpectators, allowedValues = allowedValues) { + if (state != null) { + with(state) + } + } + + constructor(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean, state: GetterSetter?, allowedValues: Set? = null) : this(menu, clazz, allowSpectators, allowedValues = allowedValues) { + if (state != null) { + with(state) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt new file mode 100644 index 000000000..b7c33511e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/FluidConfigPlayerInput.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +/** + * [allowPull] and [allowPush] controls whenever player is allowed to change these options + */ +class FluidConfigPlayerInput(val menu: MatteryMenu, config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>? = null, val allowPull: Boolean = false, val allowPush: Boolean = false) { + var possibleModes by menu.mSynchronizer.enum(FlowDirection::class.java) + private set + + inner class Piece(val side: RelativeSide) { + val pull = BooleanInputWithFeedback(menu) + val push = BooleanInputWithFeedback(menu) + val input = EnumInputWithFeedback(menu) + + var default by menu.mSynchronizer.enum(FlowDirection.NONE) + + init { + pull.filter { allowPull && possibleModes.isSupertype(FlowDirection.INPUT) } + push.filter { allowPush && possibleModes.isSupertype(FlowDirection.OUTPUT) } + } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>.Piece, parent: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>) { + pull.with(config::automatePull) + push.with(config::automatePush) + input.withSupplier { config.flow }.withConsumer { if (parent.possibleModes.isSupertype(it)) config.flow = it } + } + } + + val pieces = immutableMap { for (side in RelativeSide.values()) put(side, Piece(side)) } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableFluidHandler<*>) { + possibleModes = config.possibleModes + + for ((side, v) in config.pieces) { + pieces[side]!!.with(v, config) + pieces[side]!!.default = config.defaults[side]!! + } + } + + init { + if (config != null) { + with(config) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/InstantBooleanInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/InstantBooleanInput.kt new file mode 100644 index 000000000..2b1b7e85c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/InstantBooleanInput.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.util.function.BooleanSupplier + +class InstantBooleanInput(menu: MatteryMenu) : GetterSetter, BooleanSupplier { + var value = false + private set + + val input = menu.booleanInput(true) { value = it } + + override fun get(): Boolean { + return value + } + + override fun getAsBoolean(): Boolean { + return value + } + + override fun accept(t: Boolean) { + value = t + input.accept(t) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/IntInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/IntInputWithFeedback.kt new file mode 100644 index 000000000..5f1e9bfcd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/IntInputWithFeedback.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import java.util.function.IntSupplier +import kotlin.reflect.KMutableProperty0 + +class IntInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback() { + override val input = menu.intInput(allowSpectators) { consumer?.invoke(it) } + override val field = menu.mSynchronizer.computedInt(IntSupplier { supplier?.invoke() ?: 0 }) + + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0?) : this(menu, allowSpectators) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter?) : this(menu, allowSpectators) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: KMutableProperty0?) : this(menu) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: GetterSetter?) : this(menu) { + if (state != null) + with(state) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemConfigPlayerInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemConfigPlayerInput.kt new file mode 100644 index 000000000..8c6216f03 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemConfigPlayerInput.kt @@ -0,0 +1,55 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.core.immutableMap +import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.menu.MatteryMenu + +/** + * [allowPull] and [allowPush] controls whenever player is allowed to change these options + */ +class ItemConfigPlayerInput(val menu: MatteryMenu, config: MatteryDeviceBlockEntity.ConfigurableItemHandler? = null, val allowPull: Boolean = false, val allowPush: Boolean = false) { + private val allowedFlags = MatteryDeviceBlockEntity.ItemHandlerMode.values().map { menu.mSynchronizer.bool() to it } + fun isAllowed(value: MatteryDeviceBlockEntity.ItemHandlerMode) = allowedFlags[value.ordinal].first.boolean + + inner class Piece(val side: RelativeSide) { + fun isAllowed(value: MatteryDeviceBlockEntity.ItemHandlerMode) = this@ItemConfigPlayerInput.isAllowed(value) + + val pull = BooleanInputWithFeedback(menu) + val push = BooleanInputWithFeedback(menu) + val input = EnumInputWithFeedback(menu) + + var default by menu.mSynchronizer.enum(MatteryDeviceBlockEntity.ItemHandlerMode.DISABLED) + + init { + pull.filter { allowPull && isAllowed(MatteryDeviceBlockEntity.ItemHandlerMode.INPUT) } + push.filter { allowPush && isAllowed(MatteryDeviceBlockEntity.ItemHandlerMode.OUTPUT) } + } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableItemHandler.Piece) { + pull.with(config::automatePull) + push.with(config::automatePush) + input.withSupplier { config.mode }.withConsumer { if (isAllowed(it)) config.mode = it } + } + } + + val pieces = immutableMap { for (side in RelativeSide.values()) put(side, Piece(side)) } + + fun with(config: MatteryDeviceBlockEntity.ConfigurableItemHandler) { + for ((f, v) in allowedFlags) { + f.boolean = v in config.possibleViews + } + + for ((side, v) in config.pieces) { + pieces[side]!!.with(v) + pieces[side]!!.default = config.defaults[side]!! + } + } + + init { + if (config != null) { + with(config) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt new file mode 100644 index 000000000..617bd9dfc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.mc.otm.menu.input + +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import kotlin.reflect.KMutableProperty0 + +class StringInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback() { + override val input = menu.stringInput { consumer?.invoke(it.replace('\u0000', ' ')) } + override val field = menu.mSynchronizer.computedString { supplier?.invoke() ?: "" } + + constructor(menu: MatteryMenu, state: KMutableProperty0) : this(menu) { + with(state) + } + + constructor(menu: MatteryMenu, state: GetterSetter) : this(menu) { + with(state) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt new file mode 100644 index 000000000..0e0ad51f7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import com.google.common.collect.ImmutableList +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage +import ru.dbotthepony.mc.otm.capability.matter.canExtractMatter +import ru.dbotthepony.mc.otm.capability.matter.canReceiveMatter +import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterBottlerMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: MatterBottlerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_BOTTLER, p_38852_, inventory, tile) { + val workFlow = BooleanInputWithFeedback(this) + val progressWidget = ProgressGaugeWidget(this) + val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + + val storageSlots: ImmutableList = makeSlots(CombinedContainer(tile?.bottling ?: SimpleContainer(3), tile?.unbottling ?: SimpleContainer(3))) { it, index -> + object : MatterySlot(it, index) { + override fun mayPlace(itemStack: ItemStack): Boolean { + val cap = itemStack.getCapability(MatteryCapability.MATTER).orNull() ?: return false + + if (workFlow.value) { + return index < 3 && cap.canReceiveMatter + } else { + return index >= 3 && cap.canExtractMatter + } + } + }.also(this::addStorageSlot) + } + + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + init { + if (tile != null) { + progressWidget.with(tile::workProgress) + workFlow.with(tile::isBottling) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt new file mode 100644 index 000000000..fe1b4cbf3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.block.entity.matter.MatterCapacitorBankBlockEntity +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.MatterContainerInputSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterCapacitorBankMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: MatterCapacitorBankBlockEntity? = null +) : MatteryMenu(MMenus.MATTER_CAPACITOR_BANK, p_38852_, inventory, tile) { + val matterGauge = LevelGaugeWidget(this) + val totalMatterGauge = LevelGaugeWidget(this) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + + val storageSlots: List + + init { + if (tile != null) { + matterGauge.with(tile) + totalMatterGauge.with({ + tile.matterNode.graph.getMatterStorageLevel() + }, { + tile.matterNode.graph.getMatterStorageMaxLevel() + }) + } + + val container = tile?.container ?: SimpleContainer(2 * 6) + + storageSlots = immutableList(2 * 6) { + addStorageSlot(MatterContainerInputSlot(container, it)) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt new file mode 100644 index 000000000..f2eb883a4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import kotlin.jvm.JvmOverloads +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.matter.MatterDecomposerBlockEntity +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import net.minecraft.world.SimpleContainer +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterDecomposerMenu @JvmOverloads constructor( + containerID: Int, + inventory: Inventory, + tile: MatterDecomposerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_DECOMPOSER, containerID, inventory, tile) { + val input = object : MatterySlot(tile?.inputContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) + } + + val outputMain: OutputSlot + val outputStacking: OutputSlot + val progressWidget = ProgressGaugeWidget(this, tile) + val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val upgrades = makeUpgradeSlots(4, tile?.upgrades) + + init { + val container = tile?.outputContainer ?: SimpleContainer(2) + + // Выход + outputMain = OutputSlot(container, 0) + outputStacking = OutputSlot(container, 1) + + addStorageSlot(outputMain) + addStorageSlot(outputStacking) + addStorageSlot(input) + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt new file mode 100644 index 000000000..582044b64 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt @@ -0,0 +1,131 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.block.entity.matter.MatterEntanglerBlockEntity +import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer +import ru.dbotthepony.mc.otm.container.ShadowCraftingContainer +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.value +import ru.dbotthepony.mc.otm.item.IQuantumLinked +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus +import ru.dbotthepony.mc.otm.registry.MRecipes +import java.util.* + +class MatterEntanglerMenu( + containerId: Int, inventory: Inventory, tile: MatterEntanglerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_ENTANGLER, containerId, inventory, tile) { + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy) + val profiledMatter = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + + val progress = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0)) + + val inputs: List = makeSlots(tile?.inputs ?: object : MatteryCraftingContainer(3, 3) { + override fun getMaxStackSize(): Int { + return 1 + } + }) { it, i -> + object : MatterySlot(it, i) { + override fun mayPlace(itemStack: ItemStack): Boolean { + val shadow = ShadowCraftingContainer.shadow(it, i, itemStack) + val level = player.level + + return super.mayPlace(itemStack) && (level ?: return false) + .recipeManager + .byType(MRecipes.MATTER_ENTANGLER) + .values + .any { it.value.preemptivelyMatches(shadow, level) } + } + } + } + + val outputs = makeSlots(tile?.output ?: SimpleContainer(1)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + private val entangling: Container = if (tile == null) object : SimpleContainer(2) { + override fun getMaxStackSize(): Int { + return 1 + } + } else object : MatteryContainer(::rescan, 2) { + override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return 1 + } + } + + private fun rescan() { + if (player is ServerPlayer && entangling[0].item is IQuantumLinked && entangling[1].item == entangling[0].item && entangling[0].isNotEmpty && entangling[1].isNotEmpty) { + entanglingC.container[0] = (entangling[0].item as IQuantumLinked).merge(entangling[0], entangling[1]) + } else if (player is ServerPlayer) { + entanglingC.container[0] = ItemStack.EMPTY + } + } + + private inner class EntanglingInputSlot(index: Int) : MatterySlot(entangling, index) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && itemStack.item is IQuantumLinked + } + } + + val entanglingA: MatterySlot = EntanglingInputSlot(0) + val entanglingB: MatterySlot = EntanglingInputSlot(1) + val entanglingC: MatterySlot = object : MatterySlot(SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return false + } + + override fun canTakeItemForPickAll(): Boolean { + return false + } + + override fun tryRemove(p_150642_: Int, p_150643_: Int, p_150644_: Player): Optional { + rescan() + return super.tryRemove(p_150642_, p_150643_, p_150644_) + } + + override fun onTake(p_150645_: Player, p_150646_: ItemStack) { + if (p_150646_.isNotEmpty) { + entangling.removeItem(0, 1) + entangling.removeItem(1, 1) + } + + super.onTake(p_150645_, p_150646_) + } + } + + override fun removed(p_38940_: Player) { + super.removed(p_38940_) + clearContainer(p_38940_, entangling) + } + + init { + addSlot(outputs) + addSlot(entanglingA) + addSlot(entanglingB) + addSlot(entanglingC) + mapQuickMoveToInventory(entanglingA) + mapQuickMoveToInventory(entanglingB) + mapQuickMoveToInventory(entanglingC) + outputs.forEach(::mapQuickMoveToInventory) + addStorageSlot(inputs) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt new file mode 100644 index 000000000..0def6a9c5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt @@ -0,0 +1,345 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.block.entity.matter.MatterPanelBlockEntity +import ru.dbotthepony.mc.otm.capability.matter.* +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.addSorted +import ru.dbotthepony.mc.otm.core.map +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec +import ru.dbotthepony.mc.otm.core.util.NullValueCodec +import ru.dbotthepony.mc.otm.core.util.writeCollection +import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphListener +import ru.dbotthepony.mc.otm.graph.matter.MatterGraph +import ru.dbotthepony.mc.otm.menu.IItemSortingSettings +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.network.* +import ru.dbotthepony.mc.otm.registry.MMenus +import java.util.* +import java.util.function.Predicate + +class CancelTaskPacket(val id: UUID) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeUUID(id) + } + + override fun play(context: MNetworkContext) { + val sender = context.sender!! + (sender.containerMenu as? MatterPanelMenu)?.receiveTaskCancel(sender, id) + } + + companion object { + fun read(buff: FriendlyByteBuf): CancelTaskPacket { + return CancelTaskPacket(buff.readUUID()) + } + } +} + +class PatternsChangePacket(val isUpdate: Boolean, val patterns: Collection) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeBoolean(isUpdate) + buff.writeCollection(patterns, PatternState::write) + } + + override fun play(context: MNetworkContext) { + val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return + + if (isUpdate) { + menu.networkPatternsUpdated(patterns) + } else { + menu.networkPatternsRemoved(patterns) + } + } + + companion object { + fun read(buff: FriendlyByteBuf): PatternsChangePacket { + return PatternsChangePacket(buff.readBoolean(), buff.readCollection(::ArrayList, PatternState::read)) + } + } +} + +class TasksChangePacket(val isUpdate: Boolean, val tasks: Collection) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeBoolean(isUpdate) + buff.writeCollection(tasks, ReplicationTask::write) + } + + override fun play(context: MNetworkContext) { + val menu = minecraft.player?.containerMenu as? MatterPanelMenu ?: return + + if (isUpdate) { + menu.networkTasksUpdated(tasks) + } else { + menu.networkTasksRemoved(tasks) + } + } + + companion object { + fun read(buff: FriendlyByteBuf): TasksChangePacket { + return TasksChangePacket(buff.readBoolean(), buff.readCollection(::ArrayList, ReplicationTask::read)) + } + } +} + +class ReplicationRequestPacket(val id: UUID, amount: Int) : MatteryPacket { + val amount = amount.coerceAtLeast(1).coerceAtMost(99999) + + override fun write(buff: FriendlyByteBuf) { + buff.writeUUID(id) + buff.writeInt(amount) + } + + override fun play(context: MNetworkContext) { + val sender = context.sender ?: return + val menu = sender.containerMenu as? MatterPanelMenu ?: return + + menu.requestReplication(sender, id, amount) + } + + companion object { + fun read(buff: FriendlyByteBuf): ReplicationRequestPacket { + return ReplicationRequestPacket(buff.readUUID(), buff.readInt()) + } + } +} + +class MatterPanelMenu( + containerId: Int, + inventory: Inventory, + tile: MatterPanelBlockEntity? = null +) : MatteryMenu(MMenus.MATTER_PANEL, containerId, inventory, tile), IMatterGraphListener { + fun taskUpdated(task: ReplicationTask) { + sendNetwork(TasksChangePacket(true, listOf(task))) + } + + fun taskRemoved(task: ReplicationTask) { + sendNetwork(TasksChangePacket(false, listOf(task))) + } + + private fun updateComparators() { + var p = settings.sorting.map(PatternState::item) + var t = settings.sorting.map(ReplicationTask::item) + + if (!settings.isAscending) { + p = p.reversed() + t = t.reversed() + } + + patternComparator = p + taskComparator = t + } + + val settings = IItemSortingSettings.inputs(this, tile?.getPlayerSettings(player), ::updateComparators) + + var isProvidingTasks by BooleanInputWithFeedback(this, tile?.let { it::isProvidingTasks }) + val cancelAll = PlayerInput(NullValueCodec) { tile?.dropAllTasks() } + + val totalMatterStored: Decimal by mSynchronizer.ComputedField( + getter = { tile?.matterNode?.graph?.getMatterStorageLevel() ?: Decimal.ZERO }, + codec = DecimalValueCodec, + ) + + var filter: Predicate = Predicate { true } + set(value) { + field = value + patternsFiltered.clear() + tasksFiltered.clear() + + for (pattern in patterns) { + if (value.test(pattern.item)) { + patternsFiltered.add(pattern) + } + } + + for (task in tasks) { + if (value.test(task.item)) { + tasksFiltered.add(task) + } + } + } + + private var patternComparator = settings.sorting.map(PatternState::item) + set(value) { + if (value != field) { + field = value + patterns.sortWith(value) + patternsFiltered.sortWith(value) + } + } + + private var taskComparator = settings.sorting.map(ReplicationTask::item) + set(value) { + if (value != field) { + field = value + tasks.sortWith(value) + tasksFiltered.sortWith(value) + } + } + + private val patterns = ArrayList() + private val tasks = ArrayList() + + val patternsFiltered = ArrayList() + val tasksFiltered = ArrayList() + + fun networkPatternsUpdated(patterns: Collection) { + for (pattern in patterns) { + val index = this.patterns.indexOfFirst(pattern::matchId) + + if (index != -1) { + this.patterns[index] = pattern + } else { + this.patterns.addSorted(pattern, patternComparator) + } + + if (filter.test(pattern.item)) { + val index = this.patternsFiltered.indexOfFirst(pattern::matchId) + + if (index != -1) { + this.patternsFiltered[index] = pattern + } else { + this.patternsFiltered.addSorted(pattern, patternComparator) + } + } + } + } + + fun networkPatternsRemoved(patterns: Collection) { + for (pattern in patterns) { + this.patterns.remove(pattern) + } + } + + fun networkTasksUpdated(tasks: Collection) { + for (task in tasks) { + val index = this.tasks.indexOfFirst(task::matchId) + + if (index != -1) { + this.tasks[index] = task + } else { + this.tasks.addSorted(task, taskComparator) + } + + if (filter.test(task.item)) { + val index = this.tasksFiltered.indexOfFirst(task::matchId) + + if (index != -1) { + this.tasksFiltered[index] = task + } else { + this.tasksFiltered.addSorted(task, taskComparator) + } + } + } + } + + fun networkTasksRemoved(tasks: Collection) { + for (task in tasks) { + val index = this.tasks.indexOfFirst(task::matchId) + + if (index != -1) { + this.tasks.removeAt(index) + + if (filter.test(task.item)) { + val index = this.tasksFiltered.indexOfFirst(task::matchId) + + if (index != -1) { + this.tasksFiltered.removeAt(index) + } + } + } + } + } + + // server code + fun requestReplication(ply: ServerPlayer, id: UUID, count: Int) { + if (ply.isSpectator) { + return + } + + val tile = tile as MatterPanelBlockEntity? ?: return + + val state = tile.matterNode.graph.getPattern(id) + + if (state == null) { + LOGGER.error("Received replication request from {} of {}, but it is not found in grid", ply, id) + return + } + + tile.addTask(state, count) + } + + fun receiveTaskCancel(ply: ServerPlayer, id: UUID) { + if (!ply.isSpectator) + (tile as MatterPanelBlockEntity?)?.removeTask(id) + } + + fun requestTaskCancel(id: UUID) { + if (minecraft.player?.isSpectator != true) + MenuNetworkChannel.sendToServer(CancelTaskPacket(id)) + } + + override fun onPatternAdded(state: PatternState) { + sendNetwork(PatternsChangePacket(true, listOf(state))) + } + + override fun onPatternRemoved(state: PatternState) { + sendNetwork(PatternsChangePacket(false, listOf(state))) + } + + override fun onPatternUpdated(newState: PatternState, oldState: PatternState) { + sendNetwork(PatternsChangePacket(true, listOf(newState))) + } + + private var initialSend = false + private var listeningGrid: MatterGraph? = null + + init { + if (tile != null) { + listeningGrid = tile.matterNode.graph + tile.attachMenu(this) + listeningGrid!!.addListener(this) + } + } + + override fun removed(p_38940_: Player) { + super.removed(p_38940_) + (tile as MatterPanelBlockEntity?)?.detachMenu(this) + listeningGrid?.removeListener(this) + } + + override fun broadcastChanges() { + super.broadcastChanges() + if (!initialSend) fullPatternBroadcast() + } + + private fun sendNetwork(packet: Any) { + MenuNetworkChannel.send(inventory.player, packet) + } + + fun fullPatternBroadcast() { + if (inventory.player.level.isClientSide) { + initialSend = true + return + } + + val tile = tile as MatterPanelBlockEntity? + + if (tile != null) { + val grid = tile.matterNode.graph + initialSend = true + sendNetwork(PatternsChangePacket(true, grid.patterns.toList())) + sendNetwork(TasksChangePacket(true, tile.allReplicationTasks.toList())) + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt new file mode 100644 index 000000000..f019cbbea --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterReconstructorMenu( + containerId: Int, + inventory: Inventory, + tile: MatterReconstructorBlockEntity? = null +) : MatteryPoweredMenu(MMenus.ITEM_REPAIER, containerId, inventory, tile) { + val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + val slot = object : MatterySlot(tile?.repairContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.isRepairable && itemStack.isDamaged && super.mayPlace(itemStack) + } + } + + val equipment = makeEquipmentSlots(mapMoveToExternal = true) + val progress = ProgressGaugeWidget(this) + + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + init { + addStorageSlot(slot) + addInventorySlots() + + if (tile != null) { + progress.with(tile::visualProgress, tile::isUnableToProcess) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt new file mode 100644 index 000000000..c374abe83 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.block.entity.matter.MatterRecyclerBlockEntity +import ru.dbotthepony.mc.otm.item.matter.MatterDustItem +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterRecyclerMenu @JvmOverloads constructor( + containerID: Int, + inventory: Inventory, + tile: MatterRecyclerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_RECYCLER, containerID, inventory, tile) { + val input = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.item is MatterDustItem && (itemStack.item as MatterDustItem).getMatterValue(itemStack) != null + } + } + + val progress = ProgressGaugeWidget(this, tile) + val matter = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + init { + addStorageSlot(input) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt new file mode 100644 index 000000000..8c50152b7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.server.level.ServerPlayer +import kotlin.jvm.JvmOverloads +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import net.minecraft.world.SimpleContainer +import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus +import ru.dbotthepony.mc.otm.triggers.TakeItemOutOfReplicatorTrigger + +class MatterReplicatorMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: MatterReplicatorBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_REPLICATOR, p_38852_, inventory, tile) { + val matter = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) + val progress = ProgressGaugeWidget(this, tile) + val storageSlots: List + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + init { + val container = CombinedContainer(tile?.outputContainer ?: SimpleContainer(3), tile?.dustContainer ?: SimpleContainer(2)) + + storageSlots = immutableList(5) { + addStorageSlot(OutputSlot(container, it, onTake = { + if (inventory.player is ServerPlayer && it.isNotEmpty) + TakeItemOutOfReplicatorTrigger.trigger(inventory.player as ServerPlayer, it) })) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt new file mode 100644 index 000000000..48c500c0d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt @@ -0,0 +1,49 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import kotlin.jvm.JvmOverloads +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import net.minecraft.world.SimpleContainer +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class MatterScannerMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: MatterScannerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.MATTER_SCANNER, p_38852_, inventory, tile) { + val input: MatterySlot + val progress = ProgressGaugeWidget(this, tile) + val patterns = LevelGaugeWidget(this) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val upgrades = makeUpgradeSlots(2, tile?.upgrades) + + init { + val container = tile?.container ?: SimpleContainer(1) + + input = object : MatterySlot(container, 0, 64, 38) { + override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) + } + + addStorageSlot(input) + + if (tile != null) { + patterns.with( + { Decimal(tile.matterNode.graph.patternCount) }, + { Decimal(tile.matterNode.graph.patternCapacity) }) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt new file mode 100644 index 000000000..0edc9a82f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt @@ -0,0 +1,42 @@ +package ru.dbotthepony.mc.otm.menu.matter + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.block.entity.matter.PatternStorageBlockEntity +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.PatternSlot +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class PatternStorageMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: PatternStorageBlockEntity? = null +) : MatteryMenu(MMenus.PATTERN_STORAGE, p_38852_, inventory, tile) { + val storedThis = LevelGaugeWidget(this, tile) + val storedGrid = LevelGaugeWidget(this) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + + val storageSlots: List + + init { + if (tile != null) { + storedGrid.with({ + Decimal(tile.matterNode.graph.patternCount) + }, { + Decimal(tile.matterNode.graph.patternCapacity) + }) + } + + val patterns = tile?.container ?: SimpleContainer(2 * 4) + + storageSlots = immutableList(2 * 4) { + addStorageSlot(PatternSlot(patterns, it)) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt new file mode 100644 index 000000000..4b46e738c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.mc.otm.menu.storage + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.storage.DriveRackBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.menu.DriveSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class DriveRackMenu @JvmOverloads constructor( + containerId: Int, + inventory: Inventory, + tile: DriveRackBlockEntity? = null +) : MatteryPoweredMenu(MMenus.DRIVE_RACK, containerId, inventory, tile) { + val storageSlots = makeSlots(tile?.container ?: SimpleContainer(4), ::DriveSlot) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority }) + val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority }) + val mode = EnumInputWithFeedback(this, tile?.let { it::mode }, FlowDirection.WITHOUT_NONE) + + init { + addStorageSlot(storageSlots) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt new file mode 100644 index 000000000..9a24e4021 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt @@ -0,0 +1,163 @@ +package ru.dbotthepony.mc.otm.menu.storage + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive +import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter +import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.storage.StorageStack +import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent + +class DriveViewerMenu( + containerID: Int, + inventory: Inventory, + tile: DriveViewerBlockEntity? = null +) : MatteryPoweredMenu(MMenus.DRIVE_VIEWER, containerID, inventory, tile), INetworkedItemViewProvider { + override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) + + val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.getCapability(MatteryCapability.DRIVE).isPresent + } + } + + private val powered: PoweredVirtualComponent? + private var lastDrive: IMatteryDrive? = null + val profiledEnergy = ProfiledLevelGaugeWidget>(this, energyWidget) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + + val settings = object : DriveViewerBlockEntity.ISettings { + override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::sorting }).also { it.addListener { changes() } } + override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::isAscending }).also { it.addListener { changes() } } + + private fun changes() { + if (isAscending) { + networkedItemView.sorter = sorting + } else { + networkedItemView.sorter = sorting.reversed() + } + } + } + + init { + if (tile != null) { + profiledEnergy.with(tile.energy) + powered = PoweredVirtualComponent(StorageStack.ITEMS, tile::energy) + this.networkedItemView.component = powered + } else { + powered = null + } + + addStorageSlot(driveSlot) + addInventorySlots() + } + + var drivePresent by mSynchronizer.bool().property + + private fun getFilter(): ItemFilter? { + val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) + return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack) + } + + val driveFilterSlots = immutableList(PortableCondensationDriveItem.MAX_FILTERS) { i -> + GetterSetter.of( + mSynchronizer.computedItem { getFilter()?.get(i) ?: ItemStack.EMPTY }, + itemStackInput { getFilter()?.set(i, it) } + ) + } + + private fun make(mapper: (ItemFilter) -> GetterSetter): GetterSetter { + return GetterSetter.of( + { getFilter()?.let(mapper)?.get() ?: false }, + { getFilter()?.let(mapper)?.accept(it) } + ) + } + + val isWhitelist = BooleanInputWithFeedback(this, make { it::isWhitelist.asGetterSetter() }).also { it.filter { drivePresent } } + val matchTag = BooleanInputWithFeedback(this, make { it::matchTag.asGetterSetter() }).also { it.filter { drivePresent } } + val matchNBT = BooleanInputWithFeedback(this, make { it::matchNBT.asGetterSetter() }).also { it.filter { drivePresent } } + + override fun broadcastChanges() { + super.broadcastChanges() + + if (tile != null) { + val itemStack = (tile as DriveViewerBlockEntity).container.getItem(0) + + val prev = lastDrive + lastDrive = null + + if (!itemStack.isEmpty) { + itemStack.getCapability(MatteryCapability.DRIVE).ifPresentK { + if (it.storageType == StorageStack.ITEMS) { + lastDrive = it as IMatteryDrive + } + } + } + + drivePresent = lastDrive != null + + if (prev != lastDrive) { + prev?.let(powered!!::remove) + this.networkedItemView.clear() + lastDrive?.let(powered!!::add) + } + + this.networkedItemView.network() + } + } + + override fun removed(player: Player) { + super.removed(player) + lastDrive?.let { powered?.remove(it) } + this.networkedItemView.removed() + } + + override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { + val slot = slots[slotIndex] + val item = slot.item + + if (item.isEmpty || item.getCapability(MatteryCapability.DRIVE).isPresent || item.getCapability(ForgeCapabilities.ENERGY).isPresent) + return super.quickMoveStack(ply, slotIndex) + + val powered = powered + if (lastDrive == null || powered == null) + return ItemStack.EMPTY + + val remaining = powered.insertStack(ItemStorageStack(item), false) + + if (remaining.count.toInt() == item.count) + return ItemStack.EMPTY + + if (remaining.isEmpty) { + val copy = item.copy() + slot.set(ItemStack.EMPTY) + return copy + } + + val copy = item.copy() + item.shrink(remaining.count.toInt()) + slot.setChanged() + return copy + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt new file mode 100644 index 000000000..512424acd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt @@ -0,0 +1,234 @@ +package ru.dbotthepony.mc.otm.menu.storage + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.block.entity.storage.IItemMonitorPlayerSettings +import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity +import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.reduce +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus +import ru.dbotthepony.mc.otm.storage.* +import java.util.* + +private class ResultSlot(container: Container) : MatterySlot(container, 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return false + } + + override fun remove(p_40227_: Int): ItemStack { + if (container[0].count != p_40227_) { + return ItemStack.EMPTY + } + + return super.remove(p_40227_) + } + + override fun safeTake(p_150648_: Int, p_150649_: Int, p_150650_: Player): ItemStack { + val container = container + + if (container is ItemMonitorBlockEntity.CraftingResultContainer && p_150650_ is ServerPlayer) { + container.craftingPlayer = p_150650_ + } + + val result: ItemStack + + try { + result = super.safeTake(p_150648_, p_150649_, p_150650_) + } finally { + if (container is ItemMonitorBlockEntity.CraftingResultContainer) { + container.craftingPlayer = null + } + } + + return result + } + + override fun tryRemove(a: Int, b: Int, c: Player): Optional { + val container = container + + if (container is ItemMonitorBlockEntity.CraftingResultContainer && c is ServerPlayer) { + container.craftingPlayer = c + } + + val result: Optional + + try { + result = super.tryRemove(a, b, c) + } finally { + if (container is ItemMonitorBlockEntity.CraftingResultContainer) { + container.craftingPlayer = null + } + } + + return result + } +} + +class ItemMonitorMenu( + containerId: Int, + inventory: Inventory, + tile: ItemMonitorBlockEntity? = null +) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider { + override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val settings = object : IItemMonitorPlayerSettings { + override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ingredientPriority }) + override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::resultTarget }) + override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::craftingAmount }) + override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::sorting }).also { it.addListener { changes() } } + override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } } + + private fun changes() { + if (ascendingSort) { + networkedItemView.sorter = sorting + } else { + networkedItemView.sorter = sorting.reversed() + } + } + } + + val craftingResult: MatterySlot + val craftingSlots: List + + init { + if (tile != null) { + networkedItemView.component = tile.poweredView + } + + craftingResult = ResultSlot(tile?.craftingResultContainer ?: SimpleContainer(1)) + craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatterySlot) + craftingSlots.forEach(this::addSlot) + addSlot(craftingResult) + + addInventorySlots() + } + + override fun removed(player: Player) { + super.removed(player) + networkedItemView.removed() + } + + override fun broadcastChanges() { + super.broadcastChanges() + networkedItemView.network() + } + + private fun moveCrafting(view: IStorageComponent, simulate: Boolean): Boolean { + val itemStack = craftingResult.item + var remaining: ItemStack + + if (settings.resultTarget == ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM) { + remaining = view.insertStack(ItemStorageStack(itemStack), simulate).toItemStack() + remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate) + } else { + remaining = moveItemStackToSlots(itemStack, playerInventorySlots, simulate) + remaining = view.insertStack(ItemStorageStack(remaining), simulate).toItemStack() + } + + if (!simulate && remaining.isNotEmpty) { + player.spawnAtLocation(remaining) + } + + if (!simulate) { + craftingResult.remove(itemStack.count) + } + + return remaining.count != itemStack.count + } + + override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { + if (playerInventorySlots.any { it.index == slotIndex }) { + if (tile == null) return ItemStack.EMPTY + val slot = slots[slotIndex] + if (slot.item.isEmpty) return ItemStack.EMPTY + + val leftover = networkedItemView.component?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item + + if (leftover.count == slot.item.count) { + return ItemStack.EMPTY + } + + val old = slot.item.copy() + slot.item.count = leftover.count + return old + } else if (craftingSlots.any { it.index == slotIndex }) { + // from crafting grid to inventory + val item = slots[slotIndex].item + if (item.isEmpty) return ItemStack.EMPTY + + var remainder = item + + when (settings.ingredientPriority) { + ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> { + remainder = networkedItemView.component?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder + } + + else -> {} + } + + remainder = moveItemStackToSlots(remainder, playerInventorySlots) + + slots[slotIndex].set(remainder) + return if (remainder.count != item.count) item else ItemStack.EMPTY + } else if (slotIndex == craftingResult.index) { + var item = craftingResult.item + if (item.isEmpty) return ItemStack.EMPTY + val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY + + if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) { + // рецепт изменился + return ItemStack.EMPTY + } + + val crafted = tile.howMuchPlayerCrafted(ply) + + if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) { + if (crafted > 0) { + return ItemStack.EMPTY + } + } else { + if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || tile.craftingGrid.any { !it.isStackable }) { + if (crafted > 0 && (crafted + 1) * item.count > item.maxStackSize) { + return ItemStack.EMPTY + } + } else { + if (crafted > 0 && crafted >= tile.craftingGrid.iterator().map { it.maxStackSize }.reduce(Int.MAX_VALUE, Int::coerceAtMost)) { + return ItemStack.EMPTY + } + } + } + + tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer + + try { + if (moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, true)) { + item = item.copy() + moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, false) + return item + } else { + return ItemStack.EMPTY + } + } finally { + tile.craftingResultContainer.craftingPlayer = null + } + } + + return super.quickMoveStack(ply, slotIndex) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt new file mode 100644 index 000000000..fa06e69be --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.menu.storage + +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.storage.StorageBusBlockEntity +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class StorageBusMenu( + containerId: Int, + inventory: Inventory, + tile: StorageBusBlockEntity? = null +) : MatteryPoweredMenu(MMenus.STORAGE_BUS, containerId, inventory, tile) { + val busFilterSlots = addFilterSlots(tile?.filter, StorageBusBlockEntity.MAX_FILTERS) + val busFilterState = BooleanInputWithFeedback(this, tile?.let { it.filter::isWhitelist }) + val insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority }) + val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority }) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val mode = EnumInputWithFeedback(this, tile?.let { it::mode }, FlowDirection.WITHOUT_NONE) + + init { + addStorageSlot(batterySlot) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt new file mode 100644 index 000000000..f0573601a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.menu.storage + +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class StorageImporterExporterMenu( + containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null +) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile) { + val filterSlots = addFilterSlots(tile?.filter, AbstractStorageImportExport.MAX_FILTERS) + val isWhitelist = BooleanInputWithFeedback(this, tile?.let { it.filter::isWhitelist }) + val matchNBT = BooleanInputWithFeedback(this, tile?.let { it.filter::matchNBT }) + val matchTag = BooleanInputWithFeedback(this, tile?.let { it.filter::matchTag }) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + + init { + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StoragePowerSupplierMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StoragePowerSupplierMenu.kt similarity index 71% rename from src/main/kotlin/ru/dbotthepony/mc/otm/menu/StoragePowerSupplierMenu.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StoragePowerSupplierMenu.kt index 0e7483177..4e4a2bc00 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/StoragePowerSupplierMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StoragePowerSupplierMenu.kt @@ -1,8 +1,8 @@ -package ru.dbotthepony.mc.otm.menu +package ru.dbotthepony.mc.otm.menu.storage import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.inventory.Slot import ru.dbotthepony.mc.otm.block.entity.storage.StoragePowerSupplierBlockEntity +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu import ru.dbotthepony.mc.otm.registry.MMenus class StoragePowerSupplierMenu @JvmOverloads constructor( @@ -10,21 +10,20 @@ class StoragePowerSupplierMenu @JvmOverloads constructor( inventory: Inventory, tile: StoragePowerSupplierBlockEntity? = null ) : MatteryPoweredMenu(MMenus.STORAGE_POWER_SUPPLIER, p_38852_, inventory, tile) { - var totalTransferred by mSynchronizer.fraction() + var totalTransferred by mSynchronizer.decimal() var activeNodes by mSynchronizer.int() init { + addStorageSlot(batterySlot) addInventorySlots() } override fun broadcastChanges() { if (tile is StoragePowerSupplierBlockEntity) { totalTransferred = tile.powerPassed - activeNodes = tile.cell.storageGraph?.powerDemandingNodes?.size ?: 0 + activeNodes = tile.cell.graph.powerDemandingNodes.size } super.broadcastChanges() } - - override val storageSlots: List = listOf(batterySlot) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidChargerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidChargerMenu.kt new file mode 100644 index 000000000..23f9071b3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidChargerMenu.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerMiddleBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerTopBlockEntity +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class AndroidChargerMenu : MatteryPoweredMenu { + val energyConfig: EnergyConfigPlayerInput + val profiledEnergy: ProfiledLevelGaugeWidget<*> + + constructor( + p_38852_: Int, + inventory: Inventory, + tile: AndroidChargerBlockEntity? = null + ) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile) { + energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energyConfig?.energy, energyWidget) + } + + constructor( + p_38852_: Int, + inventory: Inventory, + tile: AndroidChargerTopBlockEntity? = null + ) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile?.lastTileEntity) { + energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.lastTileEntity?.energyConfig?.energy, energyWidget) + } + + constructor( + p_38852_: Int, + inventory: Inventory, + tile: AndroidChargerMiddleBlockEntity? = null + ) : super(MMenus.ANDROID_CHARGER, p_38852_, inventory, tile?.lastTileEntity) { + energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.lastTileEntity?.energyConfig?.energy, energyWidget) + } + + init { + addStorageSlot(batterySlot) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt similarity index 62% rename from src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt index c1a8c141c..301f24d27 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/AndroidStationMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt @@ -1,21 +1,21 @@ -package ru.dbotthepony.mc.otm.menu +package ru.dbotthepony.mc.otm.menu.tech -import com.google.common.collect.ImmutableList import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraftforge.common.capabilities.ForgeCapabilities -import ru.dbotthepony.mc.otm.block.entity.AndroidStationBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability -import ru.dbotthepony.mc.otm.capability.energy -import ru.dbotthepony.mc.otm.capability.matteryEnergy import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus -import java.util.* import kotlin.reflect.KMutableProperty0 class AndroidStationMenu @JvmOverloads constructor( @@ -24,8 +24,8 @@ class AndroidStationMenu @JvmOverloads constructor( tile: AndroidStationBlockEntity? = null ) : MatteryPoweredMenu(MMenus.ANDROID_STATION, containerID, inventory, tile) { private fun container(target: (MatteryPlayerCapability) -> KMutableProperty0): Container { - if (ply is ServerPlayer) - return PartContainer(target.invoke(ply.matteryPlayer ?: throw NullPointerException("OTM player capability is missing"))) + if (player is ServerPlayer) + return PartContainer(target.invoke(player.matteryPlayer ?: throw NullPointerException("OTM player capability is missing"))) else return SimpleContainer(1) } @@ -53,10 +53,10 @@ class AndroidStationMenu @JvmOverloads constructor( } override fun removeItem(p_18942_: Int, p_18943_: Int): ItemStack { - if (p_18942_ != 0 || powerWidget.level < AndroidStationBlockEntity.ENERGY_PER_OPERATION || item.isEmpty) + if (p_18942_ != 0 || energyWidget.level < MachinesConfig.AndroidStation.ENERGY_PER_OPERATION || item.isEmpty) return ItemStack.EMPTY - (tile as AndroidStationBlockEntity).energy.extractEnergyInner(AndroidStationBlockEntity.ENERGY_PER_OPERATION, false) + (tile as AndroidStationBlockEntity).energy.extractEnergy(MachinesConfig.AndroidStation.ENERGY_PER_OPERATION, false) return removeItemNoUpdate(p_18942_) } @@ -73,7 +73,7 @@ class AndroidStationMenu @JvmOverloads constructor( if (p_18944_ != 0) return - (tile as AndroidStationBlockEntity).energy.extractEnergyInner(AndroidStationBlockEntity.ENERGY_PER_OPERATION, false) + (tile as AndroidStationBlockEntity).energy.extractEnergy(MachinesConfig.AndroidStation.ENERGY_PER_OPERATION, false) item = p_18945_ } @@ -93,18 +93,18 @@ class AndroidStationMenu @JvmOverloads constructor( private inner class AndroidSlot(container: Container, private val condition: (ItemStack) -> Boolean) : MatterySlot(container, 0) { override fun mayPickup(player: Player): Boolean { if (tile is AndroidStationBlockEntity) { - return super.mayPickup(player) && tile.energy.batteryLevel >= AndroidStationBlockEntity.ENERGY_PER_OPERATION + return super.mayPickup(player) && tile.energy.batteryLevel >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION } - return super.mayPickup(player) && powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_OPERATION + return super.mayPickup(player) && energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION } - override fun mayPlace(p_40231_: ItemStack): Boolean { + override fun mayPlace(itemStack: ItemStack): Boolean { if (tile is AndroidStationBlockEntity) { - return tile.energy.batteryLevel >= AndroidStationBlockEntity.ENERGY_PER_OPERATION && condition.invoke(p_40231_) + return tile.energy.batteryLevel >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION && condition.invoke(itemStack) } - return powerWidget.level >= AndroidStationBlockEntity.ENERGY_PER_OPERATION && condition.invoke(p_40231_) + return energyWidget.level >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION && condition.invoke(itemStack) } } @@ -112,28 +112,13 @@ class AndroidStationMenu @JvmOverloads constructor( it.getCapability(ForgeCapabilities.ENERGY).isPresent } - override val storageSlots: ImmutableList - - val armorSlots = makeArmorSlots() + val equipment = makeEquipmentSlots() + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) init { - for ((a, b) in armorSlots) { - addSlot(a) - if (b != null) addSlot(b) - } - addInventorySlots() - - addSlot(androidBattery) - - val builder = ImmutableList.builder() - - for ((a, _) in armorSlots) { - builder.add(a) - } - - builder.add(androidBattery) - storageSlots = builder.build() + addStorageSlot(androidBattery) } override fun stillValid(player: Player): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt new file mode 100644 index 000000000..af4335ec6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.world.Container +import kotlin.jvm.JvmOverloads +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import net.minecraft.world.SimpleContainer +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.menu.BatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class BatteryBankMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: BatteryBankBlockEntity? = null, +) : MatteryMenu(MMenus.BATTERY_BANK, p_38852_, inventory, tile) { + val powerLevel = ProfiledLevelGaugeWidget(this, tile?.energyConfig?.energy) + val storageSlots: List + val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java) + val energyConfig = EnergyConfigPlayerInput(this, allowPull = false, allowPush = true) + val itemConfig = ItemConfigPlayerInput(this, allowPull = false, allowPush = false) + + init { + if (tile != null) { + redstone.with(tile.redstoneControl::redstoneSetting) + energyConfig.with(tile.energyConfig) + itemConfig.with(tile.itemConfig) + } + + val container: Container = tile?.container ?: SimpleContainer(BatteryBankBlockEntity.CAPACITY) + + storageSlots = immutableList(BatteryBankBlockEntity.CAPACITY) { + addStorageSlot(BatterySlot(container, it)) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt new file mode 100644 index 000000000..5239bc527 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt @@ -0,0 +1,77 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import net.minecraftforge.common.ForgeHooks +import net.minecraftforge.common.capabilities.ForgeCapabilities +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.tech.ChemicalGeneratorBlockEntity +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, tile: ChemicalGeneratorBlockEntity? = null) + : MatteryMenu(MMenus.CHEMICAL_GENERATOR, id, inv, tile) { + + val redstoneConfig = EnumInputWithFeedback(this) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPush = true) + + init { + if (tile != null) { + redstoneConfig.with(tile.redstoneControl::redstoneSetting) + } + } + + val fuelSlot = object : MatterySlot(tile?.fuelContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return ForgeHooks.getBurnTime(itemStack, null) > 0 + } + } + + val residueSlot = object : MatterySlot(tile?.residueContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return false + } + } + + val batterySlot = object : MatterySlot(tile?.batteryContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + itemStack.getCapability(ForgeCapabilities.ENERGY).ifPresentK { + return it.canReceive() + } + + return false + } + } + + val progress = ProgressGaugeWidget(this) + val energy = ProfiledLevelGaugeWidget(this, tile?.energy) + var burnTime by mSynchronizer.int().property + + init { + addStorageSlot(fuelSlot) + addStorageSlot(batterySlot) + addStorageSlot(residueSlot) + + if (tile != null) { + progress.with { if (tile.workTicksTotal == 0) 0f else 1f - tile.workTicks.toFloat() / tile.workTicksTotal } + } + } + + init { + addInventorySlots() + } + + override fun broadcastChanges() { + super.broadcastChanges() + burnTime = (tile as ChemicalGeneratorBlockEntity).workTicks + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt new file mode 100644 index 000000000..23ed2311a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.tech.CobblerBlockEntity +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class CobblerMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: CobblerBlockEntity? = null +) : MatteryMenu(MMenus.COBBLESTONE_GENERATOR, p_38852_, inventory, tile) { + val storageSlots = (tile?.container ?: SimpleContainer(CobblerBlockEntity.CONTAINER_SIZE)).let { c -> immutableList(c.containerSize) { addStorageSlot(OutputSlot(c, it)) } } + val redstone = EnumInputWithFeedback(this) + val itemConfig = ItemConfigPlayerInput(this) + + val progress = ProgressGaugeWidget(this) + + init { + if (tile != null) { + progress.with(tile.jobEventLoops[0]) + redstone.with(tile.redstoneControl::redstoneSetting) + itemConfig.with(tile.itemConfig) + } + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt new file mode 100644 index 000000000..6d98b6857 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt @@ -0,0 +1,77 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.core.Direction +import kotlin.jvm.JvmOverloads +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.level.block.Block +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.registry.MMenus +import java.math.BigDecimal + +class EnergyCounterMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: EnergyCounterBlockEntity? = null +) : MatteryMenu(MMenus.ENERGY_COUNTER, p_38852_, inventory, tile) { + var passed by mSynchronizer.decimal() + var average by mSynchronizer.decimal() + var last20Ticks by mSynchronizer.decimal() + var lastTick by mSynchronizer.decimal() + var maxIO by mSynchronizer.bigDecimal() + + val switchDirection = oneWayInput { + if (tile is EnergyCounterBlockEntity) + tile.level?.setBlock(tile.blockPos, tile.blockState.setValue(EnergyCounterBlock.INPUT_DIRECTION, tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION).opposite), Block.UPDATE_ALL) + } + + val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java) + + init { + if (tile != null) { + redstone.with(tile.redstoneControl::redstoneSetting) + } + } + + var inputDirection by mSynchronizer.enum(Direction::class.java) + + val maxIOInput = bigDecimalInput { + if (tile is EnergyCounterBlockEntity) { + if (it.signum() < 0) { + tile.ioLimit = null + } else { + tile.ioLimit = Decimal(it) + } + } + } + + // TODO: Graph and proper networking for it + private var ticksPassed = 0 + + override fun beforeBroadcast() { + super.beforeBroadcast() + + if (tile is EnergyCounterBlockEntity) { + passed = tile.passed + average = tile.calcAverage(20) + lastTick = tile.lastTick + + if (ticksPassed == 0) { + last20Ticks = tile.sumHistory(20) + } + + ticksPassed = (ticksPassed + 1) % 20 + inputDirection = tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) + + maxIO = tile.ioLimit?.toBigDecmial() ?: -BigDecimal.ONE + } + } + + companion object { + private val MINUS_ONE = -BigDecimal.ONE + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt new file mode 100644 index 000000000..e336e01c9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt @@ -0,0 +1,54 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyServoBlockEntity +import ru.dbotthepony.mc.otm.capability.energy +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class EnergyServoMenu @JvmOverloads constructor( + p_38852_: Int, + inventory: Inventory, + tile: EnergyServoBlockEntity? = null +) : MatteryMenu(MMenus.ENERGY_SERVO, p_38852_, inventory, tile) { + val dischargeSlot = object : MatterySlot(tile?.discharge ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && (itemStack.energy?.canExtract() ?: false) + } + } + + val chargeSlot = object : MatterySlot(tile?.charge ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && (itemStack.energy?.canReceive() ?: false) + } + } + + val equipment = makeEquipmentSlots(mapMoveToExternal = true) + + val powerGauge = ProfiledLevelGaugeWidget(this, tile?.energy) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true, allowPush = true) + val redstoneConfig = EnumInputWithFeedback(this) + + init { + if (tile != null) { + redstoneConfig.with(tile.redstoneControl::redstoneSetting) + } + } + + init { + // charge then discharge for charging to take priority over discharging + addStorageSlot(chargeSlot) + addStorageSlot(dischargeSlot) + + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt new file mode 100644 index 000000000..e4acd946a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt @@ -0,0 +1,112 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.enchantment.Enchantments +import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel +import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem +import ru.dbotthepony.mc.otm.item.EssenceServoItem +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MMenus + +class EssenceStorageMenu @JvmOverloads constructor( + containerID: Int, + inventory: Inventory, + tile: EssenceStorageBlockEntity? = null +) : MatteryMenu(MMenus.ESSENCE_STORAGE, containerID, inventory, tile) { + val experienceStored by mSynchronizer.ComputedLongField(getter = { tile?.experienceStored ?: 0L }).property + val redstoneConfig = EnumInputWithFeedback(this) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) + + val capsuleSlot = object : MatterySlot(tile?.capsuleContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.item is EssenceCapsuleItem && super.mayPlace(itemStack) + } + } + + val servoSlot = object : MatterySlot(tile?.servoContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.item == MItems.ESSENCE_SERVO && super.mayPlace(itemStack) + } + } + + val mendingSlot = object : MatterySlot(tile?.mendingContainer ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.isDamaged && itemStack.getEnchantmentLevel(Enchantments.MENDING) > 0 + } + } + + val storeLevels = intInput { + if (it > 0) { + val ply = player as ServerPlayer + tile!! + + if (it == 1) { + EssenceServoItem.storeLevel(ply, tile) + } else { + if (ply.experienceProgress > 0f) { + EssenceServoItem.storeLevel(ply, tile) + } + + if (ply.experienceLevel > 0) { + val old = ply.experienceLevel + val new = (old - it).coerceAtLeast(0) + + tile.experienceStored += getTotalXpRequiredForLevel(old) - getTotalXpRequiredForLevel(new) + ply.setExperienceLevels(new) + } + } + } + } + + val dispenseLevels = intInput { + if (it > 0) { + val ply = player as ServerPlayer + tile!! + + if (it == 1) { + EssenceServoItem.dispenseLevel(ply, tile) + } else { + var i = 0 + + while (i++ < it && tile.experienceStored > 0L) { + EssenceServoItem.dispenseLevel(ply, tile) + } + } + } + } + + init { + storeLevels.filter { + it.isCreative || it.matteryPlayer?.isAndroid == true || servoSlot.item.item == MItems.ESSENCE_SERVO //|| it.itemsStream(true).anyMatch { it.item == MItems.ESSENCE_SERVO } + } + + storeLevels.filter { + it.experienceProgress > 0f || it.experienceLevel > 0 + } + + dispenseLevels.filter { + it.isCreative || it.matteryPlayer?.isAndroid == true || servoSlot.item.item == MItems.ESSENCE_SERVO //|| it.itemsStream(true).anyMatch { it.item == MItems.ESSENCE_SERVO } + } + + dispenseLevels.filter { (tile?.experienceStored ?: experienceStored) > 0L } + + if (tile != null) { + redstoneConfig.with(tile.redstoneControl::redstoneSetting) + } + + addStorageSlot(capsuleSlot) + addStorageSlot(servoSlot) + addStorageSlot(mendingSlot) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt new file mode 100644 index 000000000..35269557b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class PlatePressMenu( + containerID: Int, + inventory: Inventory, + tile: PlatePressBlockEntity? = null +) : MatteryPoweredMenu(MMenus.PLATE_PRESS, containerID, inventory, tile) { + val inputSlot = MatterySlot(tile?.inputContainer ?: SimpleContainer(1), 0) + val outputSlot = OutputSlot(tile?.outputContainer ?: SimpleContainer(1), 0) { tile?.experience?.popExperience(player as ServerPlayer) } + + val progressGauge = ProgressGaugeWidget(this, tile) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + + init { + addStorageSlot(inputSlot) + addStorageSlot(outputSlot) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt new file mode 100644 index 000000000..e547464ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt @@ -0,0 +1,69 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.inventory.MenuType +import ru.dbotthepony.mc.otm.block.entity.tech.PoweredFurnaceBlockEntity +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class PoweredFurnaceMenu( + type: MenuType, + containerID: Int, + inventory: Inventory, + tile: PoweredFurnaceBlockEntity? = null +) : MatteryPoweredMenu(type, containerID, inventory, tile) { + val inputSlots = makeSlots(tile?.inputs, 2, ::MatterySlot) + val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> OutputSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } } + + val progressGauge = immutableList(2) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) } + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val balanceInputs = BooleanInputWithFeedback(this) + + val upgrades = makeUpgradeSlots(2, tile?.upgrades) + + init { + if (tile != null) balanceInputs.with(tile::balanceInputs) + addStorageSlot(inputSlots) + addStorageSlot(outputSlots) + addInventorySlots() + } + + companion object { + fun furnace( + containerID: Int, + inventory: Inventory, + tile: PoweredFurnaceBlockEntity? = null + ) : PoweredFurnaceMenu { + return PoweredFurnaceMenu(MMenus.POWERED_FURNACE, containerID, inventory, tile) + } + + fun blasting( + containerID: Int, + inventory: Inventory, + tile: PoweredFurnaceBlockEntity? = null + ) : PoweredFurnaceMenu { + return PoweredFurnaceMenu(MMenus.POWERED_BLAST_FURNACE, containerID, inventory, tile) + } + + fun smoking( + containerID: Int, + inventory: Inventory, + tile: PoweredFurnaceBlockEntity? = null + ) : PoweredFurnaceMenu { + return PoweredFurnaceMenu(MMenus.POWERED_SMOKER, containerID, inventory, tile) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt new file mode 100644 index 000000000..244b9bc13 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.menu.tech + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity +import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget +import ru.dbotthepony.mc.otm.registry.MMenus + +class TwinPlatePressMenu( + containerID: Int, + inventory: Inventory, + tile: PlatePressBlockEntity? = null +) : MatteryPoweredMenu(MMenus.TWIN_PLATE_PRESS, containerID, inventory, tile) { + val inputSlots = makeSlots(tile?.inputContainer ?: SimpleContainer(2), ::MatterySlot) + val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(2)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } + + val progressGauge0 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0)) + val progressGauge1 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(1)) + val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true) + val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + + val balanceInputs = BooleanInputWithFeedback(this) + + val upgrades = makeUpgradeSlots(4, tile?.upgrades) + + init { + if (tile != null) { + balanceInputs.with(tile::balanceInputs) + } + } + + init { + addStorageSlot(inputSlots) + addStorageSlot(outputSlots) + addInventorySlots() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.kt deleted file mode 100644 index e040090f1..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.widget - -import net.minecraft.world.inventory.ContainerData -import ru.dbotthepony.mc.otm.menu.MatteryMenu - -abstract class AbstractWidget(val menu: MatteryMenu,) { - val slotID: Int = menu.addWidget(this) - - abstract fun updateServer() - - protected fun addDataSlots(slots: ContainerData) { - menu.addDataSlots(slots) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt deleted file mode 100644 index da5988ed8..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/BooleanPlayerInputWidget.kt +++ /dev/null @@ -1,101 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.widget - -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.world.entity.player.Player -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel -import ru.dbotthepony.mc.otm.network.sender -import java.util.function.Supplier -import kotlin.reflect.KMutableProperty0 - -class BooleanPlayerInputPacket(val id: Int, val value: Boolean) : MatteryPacket { - override fun play(context: Supplier) { - context.get().packetHandled = true - - context.get().enqueueWork { - ((context.get().sender?.containerMenu as? MatteryMenu)?.getWidget(id) as? BooleanPlayerInputWidget)?.userInput(value, context.sender!!) - } - } - - override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeBoolean(value) - } - - companion object { - fun read(buff: FriendlyByteBuf): BooleanPlayerInputPacket { - return BooleanPlayerInputPacket(buff.readInt(), buff.readBoolean()) - } - } -} - -class BooleanPlayerInputWidget(menu: MatteryMenu) : AbstractWidget(menu) { - var value by menu.mSynchronizer.bool() - var ignoreSpectators by menu.mSynchronizer.bool(true) - - constructor(menu: MatteryMenu, state: KMutableProperty0) : this(menu) { - withClicker { state.set(it) } - withSupplier { state.get() } - } - - var supplier: (() -> Boolean)? = null - var clicker: ((Boolean) -> Unit)? = null - - fun withSupplier(func: () -> Boolean): BooleanPlayerInputWidget { - supplier = func - return this - } - - fun withClicker(func: (Boolean) -> Unit): BooleanPlayerInputWidget { - clicker = func - return this - } - - fun withProperty(state: KMutableProperty0): BooleanPlayerInputWidget { - withClicker { state.set(it) } - withSupplier { state.get() } - return this - } - - fun withoutAnything(): BooleanPlayerInputWidget { - supplier = null - clicker = null - return this - } - - fun asClient(): BooleanPlayerInputWidget { - supplier = null - clicker = { - MenuNetworkChannel.sendToServer(BooleanPlayerInputPacket(slotID, it)) - } - - return this - } - - fun userInput(newValue: Boolean, ply: Player? = null) { - if (ply?.isSpectator == true && ignoreSpectators) { - return - } - - val clicker = clicker - - if (clicker != null) { - clicker.invoke(newValue) - updateServer() - } - } - - fun switchValue(ply: Player? = null) { - userInput(!value, ply) - } - - override fun updateServer() { - val supplier = supplier - - if (supplier != null) { - value = supplier.invoke() - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt new file mode 100644 index 000000000..ca7d3b7cd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/FluidGaugeWidget.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.menu.widget + +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import java.util.function.IntSupplier +import java.util.function.Supplier + +class FluidGaugeWidget(synchronizer: FieldSynchronizer) { + constructor(menu: MatteryMenu) : this(menu.mSynchronizer) + + var maxCapacitySupplier = IntSupplier { 0 } + var fluidSupplier = Supplier { FluidStack.EMPTY!! } + + val maxCapacity by synchronizer.ComputedIntField({ maxCapacitySupplier.asInt }) + val fluid by synchronizer.ComputedField({ fluidSupplier.get() }, FluidStackValueCodec) + + val percentage: Float get() { + if (maxCapacity <= 0 || fluid.isEmpty) { + return 0f + } + + return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f) + } + + constructor(synchronizer: FieldSynchronizer, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) { + if (fluid != null) { + with(fluid, tank) + } + } + + constructor(menu: MatteryMenu, fluid: IFluidHandler?, tank: Int = 0) : this(menu) { + if (fluid != null) { + with(fluid, tank) + } + } + + fun with(fluid: IFluidHandler, tank: Int = 0): FluidGaugeWidget { + maxCapacitySupplier = IntSupplier { fluid.getTankCapacity(tank) } + fluidSupplier = Supplier { fluid[tank] } + return this + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt index df67049fb..eddd1a7ba 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt @@ -1,66 +1,118 @@ package ru.dbotthepony.mc.otm.menu.widget -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IMatterHandler +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer @Suppress("unused") -class LevelGaugeWidget(menu: MatteryMenu) : AbstractWidget(menu) { - var levelProvider = { Decimal.ONE } - var maxLevelProvider = {Decimal.ONE } +class LevelGaugeWidget(synchronizer: FieldSynchronizer) { + constructor(menu: MatteryMenu) : this(menu.mSynchronizer) - var level by menu.mSynchronizer.fraction() - private set - var maxLevel by menu.mSynchronizer.fraction() - private set + var levelProvider = { Decimal.ONE } + var maxLevelProvider = { Decimal.ONE } + + val level by synchronizer.ComputedField(getter = { levelProvider.invoke() }, codec = DecimalValueCodec) + val maxLevel by synchronizer.ComputedField(getter = { maxLevelProvider.invoke() }, codec = DecimalValueCodec) constructor( menu: MatteryMenu, power: IMatteryEnergyStorage? - ) : this(menu) { - if (power == null) return - - this.levelProvider = power::batteryLevel - this.maxLevelProvider = power::maxBatteryLevel + ) : this(menu.mSynchronizer) { + if (power != null) { + with(power) + } } constructor( menu: MatteryMenu, - matter: IMatterHandler? - ) : this(menu) { - if (matter == null) return - - this.levelProvider = matter::storedMatter - this.maxLevelProvider = matter::maxStoredMatter + matter: IMatterStorage? + ) : this(menu.mSynchronizer) { + if (matter != null) { + with(matter) + } } constructor( menu: MatteryMenu, patterns: IPatternStorage? - ) : this(menu) { - if (patterns == null) return - - this.levelProvider = {Decimal(patterns.storedPatterns)} - this.maxLevelProvider = {Decimal(patterns.patternCapacity)} + ) : this(menu.mSynchronizer) { + if (patterns != null) { + with(patterns) + } } constructor( menu: MatteryMenu, level: () -> Decimal, maxLevel: () -> Decimal, - ) : this(menu) { + ) : this(menu.mSynchronizer) { this.levelProvider = level this.maxLevelProvider = maxLevel } - override fun updateServer() { - level = levelProvider.invoke() - maxLevel = maxLevelProvider.invoke() + constructor( + synchronizer: FieldSynchronizer, + power: IMatteryEnergyStorage? + ) : this(synchronizer) { + if (power != null) { + with(power) + } } - fun level(): Decimal = level - fun maxLevel(): Decimal = maxLevel - fun percentage(): Float = level.percentage(maxLevel) + constructor( + synchronizer: FieldSynchronizer, + matter: IMatterStorage? + ) : this(synchronizer) { + if (matter != null) { + with(matter) + } + } + + constructor( + synchronizer: FieldSynchronizer, + patterns: IPatternStorage? + ) : this(synchronizer) { + if (patterns != null) { + with(patterns) + } + } + + constructor( + synchronizer: FieldSynchronizer, + level: () -> Decimal, + maxLevel: () -> Decimal, + ) : this(synchronizer) { + this.levelProvider = level + this.maxLevelProvider = maxLevel + } + + val percentage: Float get() = level.percentage(maxLevel) + + fun with(level: () -> Decimal, maxLevel: () -> Decimal): LevelGaugeWidget { + this.levelProvider = level + this.maxLevelProvider = maxLevel + return this + } + + fun with(patterns: IPatternStorage): LevelGaugeWidget { + this.levelProvider = { Decimal(patterns.storedPatterns) } + this.maxLevelProvider = { Decimal(patterns.patternCapacity) } + return this + } + + fun with(power: IMatteryEnergyStorage): LevelGaugeWidget { + this.levelProvider = power::batteryLevel + this.maxLevelProvider = power::maxBatteryLevel + return this + } + + fun with(matter: IMatterStorage): LevelGaugeWidget { + this.levelProvider = matter::storedMatter + this.maxLevelProvider = matter::maxStoredMatter + return this + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/NumberPlayerInputWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/NumberPlayerInputWidget.kt deleted file mode 100644 index 7489a7e97..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/NumberPlayerInputWidget.kt +++ /dev/null @@ -1,92 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.widget - -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.world.entity.player.Player -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.network.* -import java.math.BigDecimal -import java.util.function.Supplier - -class NumberPlayerInputPacket(val id: Int, val value: BigDecimal) : MatteryPacket { - override fun play(context: Supplier) { - context.get().packetHandled = true - - context.get().enqueueWork { - ((context.get().sender?.containerMenu as? MatteryMenu)?.getWidget(id) as? NumberPlayerInputWidget)?.userInput(value, context.sender!!) - } - } - - override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeDecimal(value) - } - - companion object { - fun read(buff: FriendlyByteBuf): NumberPlayerInputPacket { - return NumberPlayerInputPacket(buff.readInt(), buff.readDecimal()) - } - } -} - -class NumberPlayerInputWidget(menu: MatteryMenu) : AbstractWidget(menu) { - var value by menu.mSynchronizer.bigDecimal() - var ignoreSpectators by menu.mSynchronizer.bool(true) - - var supplier: (() -> BigDecimal)? = null - var consumer: ((BigDecimal) -> Unit)? = null - var minValue: BigDecimal? = null - var maxValue: BigDecimal? = null - - fun withSupplier(func: () -> BigDecimal): NumberPlayerInputWidget { - supplier = func - return this - } - - fun withConsumer(func: (BigDecimal) -> Unit): NumberPlayerInputWidget { - consumer = func - return this - } - - fun asClient(): NumberPlayerInputWidget { - supplier = null - - consumer = { - MenuNetworkChannel.sendToServer(NumberPlayerInputPacket(slotID, it)) - } - - return this - } - - fun userInput(newValue: BigDecimal, ply: Player? = null) { - if (ply?.isSpectator == true && ignoreSpectators) { - return - } - - val consumer = consumer - - if (consumer != null) { - @Suppress("name_shadowing") - var newValue = newValue - - if (minValue != null) { - newValue = newValue.max(minValue!!) - } - - if (maxValue != null) { - newValue = newValue.min(maxValue!!) - } - - consumer.invoke(newValue) - updateServer() - } - } - - override fun updateServer() { - val supplier = supplier - - if (supplier != null) { - value = supplier.invoke() - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/OneWayPlayerInputWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/OneWayPlayerInputWidget.kt deleted file mode 100644 index 203eae69c..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/OneWayPlayerInputWidget.kt +++ /dev/null @@ -1,53 +0,0 @@ -package ru.dbotthepony.mc.otm.menu.widget - -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.world.entity.player.Player -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.network.MatteryPacket -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel -import ru.dbotthepony.mc.otm.network.sender -import ru.dbotthepony.mc.otm.runIfClient -import java.util.function.Supplier - -class OneWayPlayerInputPacket(val id: Int) : MatteryPacket { - override fun play(context: Supplier) { - context.get().packetHandled = true - - context.get().enqueueWork { - ((context.get().sender?.containerMenu as? MatteryMenu)?.getWidget(id) as? OneWayPlayerInputWidget)?.userInput(context.sender!!) - } - } - - override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - } - - companion object { - fun read(buff: FriendlyByteBuf): OneWayPlayerInputPacket { - return OneWayPlayerInputPacket(buff.readInt()) - } - } -} - -class OneWayPlayerInputWidget(menu: MatteryMenu, clicker: (() -> Unit)? = null) : AbstractWidget(menu) { - val clicker: () -> Unit - var ignoreSpectators by menu.mSynchronizer.bool(true) - - init { - if (clicker != null) { - this.clicker = clicker - } else { - this.clicker = { MenuNetworkChannel.sendToServer(OneWayPlayerInputPacket(slotID)) } - } - } - - fun userInput(ply: Player? = null) { - if (ply?.isSpectator != true || !ignoreSpectators) { - clicker.invoke() - } - } - - override fun updateServer() {} -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt new file mode 100644 index 000000000..486168417 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProfiledLevelGaugeWidget.kt @@ -0,0 +1,62 @@ +package ru.dbotthepony.mc.otm.menu.widget + +import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage +import ru.dbotthepony.mc.otm.core.collect.SupplierList +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer + +class ProfiledLevelGaugeWidget

>(synchronizer: FieldSynchronizer, val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) { + var parent: P? = null + private set + + val historyReceive = immutableList(AbstractProfiledStorage.HISTORY_SIZE) { + synchronizer.ComputedField({ parent?.historyReceive?.get(it) ?: Decimal.ZERO }, DecimalValueCodec) + } + + val historyTransfer = immutableList(AbstractProfiledStorage.HISTORY_SIZE) { + synchronizer.ComputedField({ parent?.historyTransfer?.get(it) ?: Decimal.ZERO }, DecimalValueCodec) + } + + val directHistoryReceive = SupplierList(historyReceive) + val directHistoryTransfer = SupplierList(historyTransfer) + + val tick by synchronizer.ComputedIntField({ parent?.tick ?: 0 }).property + val lastTickReceive by synchronizer.ComputedField({ parent?.lastTickReceive ?: Decimal.ZERO }, DecimalValueCodec) + val lastTickTransfer by synchronizer.ComputedField({ parent?.lastTickTransfer ?: Decimal.ZERO }, DecimalValueCodec) + + constructor(synchronizer: FieldSynchronizer, storage: P?, gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) : this(synchronizer, gauge = gauge) { + if (storage != null) { + with(storage) + } + } + + constructor(menu: MatteryMenu, storage: P?, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, storage, gauge = gauge) + constructor(menu: MatteryMenu, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, gauge = gauge) + + constructor(menu: MatteryPoweredMenu, storage: P?) : this(menu.mSynchronizer, storage, menu.energyWidget) + constructor(menu: MatteryPoweredMenu) : this(menu.mSynchronizer, menu.energyWidget) + + fun with(storage: P): ProfiledLevelGaugeWidget

{ + if (storage is IMatterStorage) + gauge.with(storage) + else if (storage is IMatteryEnergyStorage) + gauge.with(storage) + + parent = storage + return this + } + + val weightedTransfer: Decimal get() { + return AbstractProfiledStorage.calcWeightedAverage(directHistoryTransfer, tick) + } + + val weightedReceive: Decimal get() { + return AbstractProfiledStorage.calcWeightedAverage(directHistoryReceive, tick) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt index 035afb526..6ca06cff3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/ProgressGaugeWidget.kt @@ -1,35 +1,100 @@ package ru.dbotthepony.mc.otm.menu.widget +import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop +import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.core.FloatSupplier import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import java.util.function.BooleanSupplier @Suppress("unused") -class ProgressGaugeWidget(menu: MatteryMenu) : AbstractWidget(menu) { - var progress = {0f} - var stuck = {false} - var progressContainer by menu.mSynchronizer.short() - var stuckContainer by menu.mSynchronizer.bool() +class ProgressGaugeWidget(synchronizer: FieldSynchronizer) { + constructor(menu: MatteryMenu) : this(menu.mSynchronizer) + + var progressSupplier: FloatSupplier = FloatSupplier { 0f } + var stuckSupplier: BooleanSupplier = BooleanSupplier { false } + + val percentage by synchronizer.ComputedFloatField(getter = { progressSupplier.getAsFloat() }).property + val isStuck by synchronizer.ComputedBooleanField(getter = { stuckSupplier.asBoolean }).property constructor( menu: MatteryMenu, - progress: () -> Float + progress: FloatSupplier ) : this(menu) { - this.progress = progress + this.progressSupplier = progress } constructor( menu: MatteryMenu, - progress: () -> Float, - stuck: () -> Boolean, + blockEntity: MatteryWorkerBlockEntity<*>? ) : this(menu) { - this.progress = progress - this.stuck = stuck + if (blockEntity != null) { + with(blockEntity) + } } - override fun updateServer() { - progressContainer = (Math.max(Math.min(progress.invoke(), 1f), 0f) * 10_000f).toInt().toShort() - stuckContainer = stuck.invoke() + constructor( + menu: MatteryMenu, + job: MachineJobEventLoop<*>? + ) : this(menu) { + if (job != null) { + with(job) + } } - fun isStuck(): Boolean = stuckContainer - fun percentage(): Float = progressContainer / 10_000f + constructor( + synchronizer: FieldSynchronizer, + progress: FloatSupplier + ) : this(synchronizer) { + this.progressSupplier = progress + } + + constructor( + synchronizer: FieldSynchronizer, + blockEntity: MatteryWorkerBlockEntity<*>? + ) : this(synchronizer) { + if (blockEntity != null) { + with(blockEntity) + } + } + + constructor( + synchronizer: FieldSynchronizer, + job: MachineJobEventLoop<*>? + ) : this(synchronizer) { + if (job != null) { + with(job) + } + } + + constructor( + menu: MatteryMenu, + progress: FloatSupplier, + stuck: BooleanSupplier, + ) : this(menu) { + this.progressSupplier = progress + this.stuckSupplier = stuck + } + + fun with(progress: FloatSupplier): ProgressGaugeWidget { + this.progressSupplier = progress + this.stuckSupplier = BooleanSupplier { false } + return this + } + + fun with(progress: FloatSupplier, stuck: BooleanSupplier): ProgressGaugeWidget { + this.progressSupplier = progress + this.stuckSupplier = stuck + return this + } + + fun with(blockEntity: MatteryWorkerBlockEntity<*>): ProgressGaugeWidget { + with(blockEntity.jobEventLoops[0]::workProgress, blockEntity.jobEventLoops[0]::isUnableToProcess) + return this + } + + fun with(job: MachineJobEventLoop<*>): ProgressGaugeWidget { + with(job::workProgress, job::isUnableToProcess) + return this + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt deleted file mode 100644 index 9e9427b2a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt +++ /dev/null @@ -1,1121 +0,0 @@ -package ru.dbotthepony.mc.otm.network - -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import it.unimi.dsi.fastutil.io.FastByteArrayInputStream -import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream -import it.unimi.dsi.fastutil.objects.ObjectArraySet -import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.ReferenceArraySet -import net.minecraft.world.item.ItemStack -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.secondTime -import java.io.DataInputStream -import java.io.DataOutputStream -import java.io.InputStream -import java.lang.ref.WeakReference -import java.math.BigDecimal -import java.util.* -import java.util.function.Consumer -import java.util.function.Supplier -import kotlin.ConcurrentModificationException -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.properties.ReadOnlyProperty -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KMutableProperty0 -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty0 - -interface FieldAccess { - fun read(): V - fun write(value: V) -} - -fun interface FieldGetter { - fun invoke(field: FieldAccess): V -} - -fun interface FieldSetter { - fun invoke(value: V, field: FieldAccess, setByRemote: Boolean) -} - -sealed interface IField : ReadOnlyProperty, Supplier, () -> V { - fun observe(): Boolean - fun markDirty() - fun markDirty(endpoint: FieldSynchronizer.Endpoint) - val value: V - val name: String - - fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint) - fun read(stream: DataInputStream, payloadSize: Int) { - read(stream) - } - - fun read(stream: DataInputStream) - - override fun getValue(thisRef: Any, property: KProperty<*>): V { - return this.value - } - - override fun get(): V { - return value - } - - override fun invoke(): V { - return value - } -} - -sealed interface IMutableField : IField, ReadWriteProperty, Consumer { - override var value: V - - override fun setValue(thisRef: Any, property: KProperty<*>, value: V) { - this.value = value - } - - override fun accept(t: V) { - value = t - } - - override fun getValue(thisRef: Any, property: KProperty<*>): V { - return super.getValue(thisRef, property) - } -} - -data class MapChangeset( - val action: MapAction, - val key: K?, - val value: V? -) - -enum class MapAction { - CLEAR, ADD, REMOVE -} - -/** - * Universal one-to-many value synchronizer, allowing to synchronize values from server to client - * anywhere, where input/output streams are supported - */ -@Suppress("unused") -class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCallback: Boolean) { - constructor() : this(Runnable {}, false) - constructor(callback: Runnable) : this(callback, false) - - private val fields = ArrayList>(0) - private val observers = ArrayList>(0) - - private var nextFieldID = 0 - - /** - * returns dummy field name - */ - fun nextFieldName() = "___field_${nextFieldID++}" - private var mappingVersion = 0 - - val hasObservers: Boolean get() = observers.isNotEmpty() - val isEmpty: Boolean get() = fields.isEmpty() - val isNotEmpty: Boolean get() = fields.isNotEmpty() - - var hasChanges: Boolean = false - private set(value) { - if (value != field) { - field = value - - if (value && !alwaysCallCallback) { - callback.run() - } - } - - if (alwaysCallCallback && value) { - callback.run() - } - } - - fun markClean() { - hasChanges = false - } - - fun byte(getter: () -> Byte, name: String = nextFieldName()) = ComputedField(getter, ByteValueCodec, name) - fun bool(getter: () -> Boolean, name: String = nextFieldName()) = ComputedField(getter, BooleanValueCodec, name) - fun short(getter: () -> Short, name: String = nextFieldName()) = ComputedField(getter, ShortValueCodec, name) - fun long(getter: () -> Long, name: String = nextFieldName()) = ComputedField(getter, VarLongValueCodec, name) - fun fixedLong(getter: () -> Long, name: String = nextFieldName()) = ComputedField(getter, LongValueCodec, name) - fun float(getter: () -> Float, name: String = nextFieldName()) = ComputedField(getter, FloatValueCodec, name) - fun double(getter: () -> Double, name: String = nextFieldName()) = ComputedField(getter, DoubleValueCodec, name) - fun uuid(getter: () -> UUID, name: String = nextFieldName()) = ComputedField(getter, UUIDValueCodec, name) - fun int(getter: () -> Int, name: String = nextFieldName()) = ComputedField(getter, VarIntValueCodec, name) - fun fixedInt(getter: () -> Int, name: String = nextFieldName()) = ComputedField(getter, IntValueCodec, name) - fun fraction(getter: () -> Decimal, name: String = nextFieldName()) = ComputedField(getter, ImpreciseFractionValueCodec, name) - fun bigDecimal(getter: () -> BigDecimal, name: String = nextFieldName()) = ComputedField(getter, BigDecimalValueCodec, name) - fun item(getter: () -> ItemStack, name: String = nextFieldName()) = ComputedField(getter, ItemStackValueCodec, name) - - fun byte(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, ByteValueCodec, name) - fun bool(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, BooleanValueCodec, name) - fun short(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, ShortValueCodec, name) - fun long(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, VarLongValueCodec, name) - fun fixedLong(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, LongValueCodec, name) - fun float(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, FloatValueCodec, name) - fun double(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, DoubleValueCodec, name) - fun uuid(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, UUIDValueCodec, name) - fun int(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, VarIntValueCodec, name) - fun fixedInt(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, IntValueCodec, name) - fun fraction(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, ImpreciseFractionValueCodec, name) - fun bigDecimal(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, BigDecimalValueCodec, name) - fun item(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, ItemStackValueCodec, name) - - fun byte(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, ByteValueCodec, name) - fun bool(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, BooleanValueCodec, name) - fun short(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, ShortValueCodec, name) - fun long(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, VarLongValueCodec, name) - fun fixedLong(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, LongValueCodec, name) - fun float(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, FloatValueCodec, name) - fun double(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, DoubleValueCodec, name) - fun uuid(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, UUIDValueCodec, name) - fun int(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, VarIntValueCodec, name) - fun fixedInt(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, IntValueCodec, name) - fun fraction(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, ImpreciseFractionValueCodec, name) - fun bigDecimal(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, BigDecimalValueCodec, name) - fun item(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, ItemStackValueCodec, name) - - fun > enum(type: Class, getter: () -> T, name: String = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name) - inline fun > enum(noinline getter: () -> T, name: String = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name) - - fun > enum(type: Class, getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name) - inline fun > enum(getter: KProperty0, name: String = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name) - - fun > enum(type: Class, getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(type), name) - inline fun > enum(getter: Supplier, name: String = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(T::class.java), name) - - fun byte( - value: Byte = 0, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, ByteValueCodec, getter, setter, name = name) - } - - fun bool( - value: Boolean = false, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, BooleanValueCodec, getter, setter, name = name) - } - - fun short( - value: Short = 0, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, ShortValueCodec, getter, setter, name = name) - } - - fun long( - value: Long = 0L, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, VarLongValueCodec, getter, setter, name = name) - } - - fun fixedLong( - value: Long = 0L, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, LongValueCodec, getter, setter, name = name) - } - - fun float( - value: Float = 0f, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, FloatValueCodec, getter, setter, name = name) - } - - fun double( - value: Double = 0.0, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, DoubleValueCodec, getter, setter, name = name) - } - - fun uuid( - value: UUID = UUID(0L, 0L), - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, UUIDValueCodec, getter, setter, name = name) - } - - fun int( - value: Int = 0, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, VarIntValueCodec, getter, setter, name = name) - } - - fun fixedInt( - value: Int = 0, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, IntValueCodec, getter, setter, name = name) - } - - fun fraction( - value: Decimal = Decimal.ZERO, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, ImpreciseFractionValueCodec, getter, setter, name = name) - } - - fun bigDecimal( - value: BigDecimal = BigDecimal.ZERO, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, BigDecimalValueCodec, getter, setter, name = name) - } - - fun > enum( - type: Class, - value: T = type.enumConstants[0], - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, EnumValueCodec(type), getter, setter, name = name) - } - - fun > enum( - value: T, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - name: String = nextFieldName(), - ): Field { - return Field(value, EnumValueCodec(value::class.java), getter, setter, name = name) - } - - fun item( - value: ItemStack = ItemStack.EMPTY, - getter: FieldGetter? = null, - setter: FieldSetter? = null, - observe: Boolean = true, - name: String = nextFieldName(), - ): Field { - return Field(value, ItemStackValueCodec, getter, setter, isObserver = observe, name = name) - } - - fun item( - delegate: KMutableProperty0, - name: String = nextFieldName(), - ): ObservedField { - return ObservedField(delegate, ItemStackValueCodec, name = name) - } - - fun map( - keyCodec: IStreamCodec, - valueCodec: IStreamCodec, - callback: ((changes: Collection>) -> Unit)? = null, - backingMap: MutableMap = HashMap(), - observingBackingMap: MutableMap? = null, - name: String = nextFieldName(), - ): Map { - return Map( - keyCodec = keyCodec, - valueCodec = valueCodec, - callback = callback, - backingMap = backingMap, - observingBackingMap = observingBackingMap, - name = name, - ) - } - - private var endpointsMaxCapacity = 1 - private val endpoints = ArrayList>(1) - val defaultEndpoint = Endpoint() - - private var nextEndpointsCleanup = secondTime - - private fun notifyEndpoints(dirtyField: AbstractField<*>) { - hasChanges = true - - forEachEndpoint { - it.addDirtyField(dirtyField) - } - } - - private inline fun forEachEndpoint(execute: (Endpoint) -> Unit) { - nextEndpointsCleanup = secondTime + 60 - - synchronized(endpoints) { - endpoints.forValidRefs { execute.invoke(it) } - - if (endpoints.size < endpointsMaxCapacity / 2) { - endpoints.trimToSize() - endpointsMaxCapacity = endpoints.size - } - } - } - - inner class Endpoint { - init { - synchronized(endpoints) { - endpoints.add(WeakReference(this)) - endpointsMaxCapacity = endpointsMaxCapacity.coerceAtLeast(endpoints.size) - - if (secondTime >= nextEndpointsCleanup) { - nextEndpointsCleanup = secondTime + 60 - - val iterator = endpoints.listIterator() - - for (value in iterator) { - if (value.get() == null) { - iterator.remove() - } - } - - if (endpoints.size < endpointsMaxCapacity / 2) { - endpoints.trimToSize() - endpointsMaxCapacity = endpoints.size - } - } - } - } - - private val dirtyFields = ReferenceArraySet>(fields.size) - - // use LinkedList because it is ensured memory is freed on LinkedList#clear - private val mapBacklogs = Reference2ObjectOpenHashMap, LinkedList Unit>>>() - - var unused: Boolean = false - private set - - private var mappingVersion = -1 - - fun invalidateMappings() { - mappingVersion = -1 - } - - fun markUnused() { - require(this === defaultEndpoint) { "This is not a default endpoint" } - if (unused) return - unused = true - mapBacklogs.clear() - dirtyFields.clear() - - val iterator = endpoints.listIterator() - - for (value in iterator) { - if (value.get() === this) { - iterator.remove() - } - } - } - - init { - markDirty() - } - - fun markDirty() { - for (field in fields) { - field.markDirty(this) - } - } - - internal fun addDirtyField(field: AbstractField<*>) { - if (unused) { - return - } - - dirtyFields.add(field) - } - - internal fun getMapBacklog(map: Map): LinkedList Unit>> { - if (unused) { - return LinkedList() - } - - return mapBacklogs.computeIfAbsent(map, Reference2ObjectFunction { - LinkedList() - }) - } - - fun collectNetworkPayload(): FastByteArrayOutputStream? { - if (unused || dirtyFields.isEmpty()) { - return null - } - - val stream = FastByteArrayOutputStream() - - if (mappingVersion != this@FieldSynchronizer.mappingVersion) { - stream.write(1) - - for (field in fields) { - check(field.id > 0) { "This should never happen: $field maps to invalid ID: ${field.id}!" } - stream.writeVarIntLE(field.id) - val bytes = field.name.toByteArray(Charsets.UTF_8) - stream.writeVarIntLE(bytes.size) - stream.write(bytes) - } - - stream.writeVarIntLE(0) - mappingVersion = this@FieldSynchronizer.mappingVersion - } else { - stream.write(0) - } - - for (field in dirtyFields) { - val id = field.id - check(id > 0) { "This should never happen: $field maps to invalid ID: $id!" } - stream.writeVarIntLE(id) - - val innerStream = FastByteArrayOutputStream() - val dataStream = DataOutputStream(innerStream) - - field.write(dataStream, this) - - stream.writeVarIntLE(innerStream.length) - stream.write(innerStream.array, 0, innerStream.length) - } - - dirtyFields.clear() - stream.write(0) - - return stream - } - } - - private val boundEndpoints = WeakHashMap() - - fun computeEndpointFor(obj: Any): Endpoint { - return boundEndpoints.computeIfAbsent(obj) { Endpoint() } - } - - fun removeEndpointFor(obj: Any): Endpoint? { - return boundEndpoints.remove(obj) - } - - fun endpointFor(obj: Any): Endpoint? { - return boundEndpoints[obj] - } - - @Suppress("LeakingThis") - abstract inner class AbstractField( - final override val name: String = nextFieldName(), - ) : IField { - var id: Int = fields.size + 1 - - init { - check(!fields.any { it.name == name }) { "Duplicate field name $name" } - fields.add(this) - mappingVersion++ - } - } - - inner class Field( - private var field: V, - private val codec: IStreamCodec, - private val getter: FieldGetter? = null, - private val setter: FieldSetter? = null, - isObserver: Boolean = false, - name: String = nextFieldName(), - ) : AbstractField(name), IMutableField { - private var remote: V = codec.copy(field) - - init { - if (isObserver) { - observers.add(this) - } - } - - private var isDirty = false - - private val access = object : FieldAccess { - override fun read(): V { - return field - } - - override fun write(value: V) { - if (!isDirty && !codec.compare(remote, value)) { - notifyEndpoints(this@Field) - isDirty = true - } - - this@Field.field = value - } - } - - override fun observe(): Boolean { - if (!isDirty && !codec.compare(remote, field)) { - notifyEndpoints(this@Field) - isDirty = true - } - - return isDirty - } - - override var value: V - get() { - val getter = this.getter - - if (getter != null) { - return getter.invoke(access) - } - - return this.field - } - set(value) { - val setter = this.setter - - if (setter != null) { - setter.invoke(value, access, false) - return - } - - if (this.field == value) { - return - } - - if (!isDirty && !codec.compare(remote, value)) { - notifyEndpoints(this@Field) - isDirty = true - } - - this.field = value - } - - override fun markDirty() { - notifyEndpoints(this@Field) - isDirty = true - } - - override fun markDirty(endpoint: Endpoint) { - endpoint.addDirtyField(this) - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - codec.write(stream, field) - isDirty = false - remote = codec.copy(field) - } - - override fun read(stream: DataInputStream) { - val value = codec.read(stream) - val setter = this.setter - - if (setter != null) { - setter.invoke(value, access, true) - return - } - - this.field = value - } - } - - inner class ComputedField( - private val getter: () -> V, - private val codec: IStreamCodec, - name: String = nextFieldName(), - ) : AbstractField(name), IField { - private var remote: V? = null - private var clientValue: V? = null - private var isDirty = false - - init { - observers.add(this) - } - - override fun observe(): Boolean { - if (!isDirty && (remote == null || !codec.compare(remote ?: throw ConcurrentModificationException(), value))) { - notifyEndpoints(this) - isDirty = true - remote = codec.copy(value) - } - - return isDirty - } - - override fun markDirty() { - notifyEndpoints(this) - isDirty = true - } - - override fun markDirty(endpoint: Endpoint) { - endpoint.addDirtyField(this) - } - - override val value: V - get() = clientValue ?: getter.invoke() - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - val value = value - codec.write(stream, value) - isDirty = false - } - - override fun read(stream: DataInputStream) { - clientValue = codec.read(stream) - } - } - - inner class ObservedField : AbstractField, IMutableField { - private val codec: IStreamCodec - private val getter: () -> V - private val setter: (V) -> Unit - private var remote: V - - override var value: V - get() = getter.invoke() - set(value) { setter.invoke(value) } - - constructor(field: KMutableProperty0, codec: IStreamCodec, name: String = nextFieldName()) : super(name) { - this.codec = codec - getter = field::get - setter = field::set - remote = codec.copy(value) - } - - constructor(getter: () -> V, setter: (V) -> Unit, codec: IStreamCodec, name: String = nextFieldName()) : super(name) { - this.codec = codec - this.getter = getter - this.setter = setter - remote = codec.copy(value) - } - - private var isDirty = false - - init { - observers.add(this) - } - - override fun observe(): Boolean { - if (!isDirty && !codec.compare(remote, value)) { - notifyEndpoints(this) - isDirty = true - remote = codec.copy(value) - } - - return isDirty - } - - override fun markDirty() { - notifyEndpoints(this) - isDirty = true - } - - override fun markDirty(endpoint: Endpoint) { - endpoint.addDirtyField(this) - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - val value = value - codec.write(stream, value) - isDirty = false - } - - override fun read(stream: DataInputStream) { - this.value = codec.read(stream) - } - } - - inner class Map( - private val keyCodec: IStreamCodec, - private val valueCodec: IStreamCodec, - private val backingMap: MutableMap, - private val observingBackingMap: MutableMap? = null, - private val callback: ((changes: Collection>) -> Unit)? = null, - name: String = nextFieldName(), - ) : AbstractField>(name), IField> { - private var isDirty = false - private var sentAllValues = false - private var isRemote = false - - init { - if (observingBackingMap != null) - observers.add(this) - } - - private fun pushBacklog(key: Any?, value: (DataOutputStream) -> Unit) { - forEachEndpoint { - val list = it.getMapBacklog(this) - val iterator = list.listIterator() - - for (pair in iterator) { - if (pair.first == key) { - iterator.remove() - } - } - - list.addLast(key to value) - } - } - - private fun clearBacklog() { - forEachEndpoint { - it.getMapBacklog(this).clear() - } - } - - override fun observe(): Boolean { - if (isRemote) { - return false - } - - val observingBackingMap = observingBackingMap - - if (observingBackingMap != null) { - for ((key, value) in backingMap) { - val remoteValue = observingBackingMap[key] ?: throw ConcurrentModificationException("Backing map of $this was modified externally, or $value missed a modification") - - if (!valueCodec.compare(value, remoteValue)) { - val valueCopy = valueCodec.copy(value) - - pushBacklog(key) { - it.write(MapAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - } - - observingBackingMap[key] = valueCopy - - if (!isDirty) { - notifyEndpoints(this) - isDirty = true - } - } - } - } - - return isDirty - } - - override fun markDirty() { - if (isRemote) { - return - } - - if (endpoints.isEmpty()) { - val observingBackingMap = observingBackingMap ?: return - - for ((key, value) in backingMap) - observingBackingMap[key] = valueCodec.copy(value) - - return - } - - isDirty = true - val backlogs = LinkedList Unit>>>() - - forEachEndpoint { - it.addDirtyField(this) - val value = it.getMapBacklog(this) - backlogs.add(value) - value.clear() - value.add(null to ClearBacklogEntry) - } - - for ((key, value) in backingMap) { - val valueCopy = valueCodec.copy(value) - - val action = { it: DataOutputStream -> - it.write(MapAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - } - - for (backlog in backlogs) { - backlog.add(key to action) - } - - observingBackingMap?.put(key, valueCopy) - } - } - - override fun markDirty(endpoint: Endpoint) { - if (isRemote) { - return - } - - val backlog = endpoint.getMapBacklog(this) - - backlog.clear() - backlog.add(null to ClearBacklogEntry) - - for ((key, value) in backingMap) { - val valueCopy = valueCodec.copy(value) - - backlog.add(key to { - it.write(MapAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - }) - } - } - - override val value: MutableMap = object : ProxiedMap(backingMap) { - override fun onClear() { - if (isRemote) { - return - } - - if (endpoints.isEmpty()) { - return - } - - observingBackingMap?.clear() - - forEachEndpoint { endpoint -> - endpoint.getMapBacklog(this@Map).also { - it.clear() - it.add(null to ClearBacklogEntry) - } - } - - if (!isDirty) { - notifyEndpoints(this@Map) - isDirty = true - } - - hasChanges = true - } - - override fun onValueAdded(key: K, value: V) { - if (isRemote) { - return - } - - if (endpoints.isEmpty()) { - return - } - - val valueCopy = valueCodec.copy(value) - - pushBacklog(key) { - @Suppress("BlockingMethodInNonBlockingContext") // false positive - it.write(MapAction.ADD.ordinal + 1) - keyCodec.write(it, key) - valueCodec.write(it, valueCopy) - } - - observingBackingMap?.put(key, valueCopy) - - if (!isDirty) { - notifyEndpoints(this@Map) - isDirty = true - } - - hasChanges = true - } - - override fun onValueRemoved(key: K, value: V) { - if (isRemote) { - return - } - - if (endpoints.isEmpty()) { - return - } - - val keyCopy = keyCodec.copy(key) - - pushBacklog(key) { - @Suppress("BlockingMethodInNonBlockingContext") // false positive - it.write(MapAction.REMOVE.ordinal + 1) - keyCodec.write(it, keyCopy) - } - - observingBackingMap?.remove(key) - - if (!isDirty) { - notifyEndpoints(this@Map) - isDirty = true - } - - hasChanges = true - } - } - - override fun write(stream: DataOutputStream, endpoint: Endpoint) { - sentAllValues = false - isDirty = false - - val iterator = endpoint.getMapBacklog(this).listIterator() - - for (entry in iterator) { - entry.second.invoke(stream) - iterator.remove() - } - - stream.write(0) - } - - override fun read(stream: DataInputStream) { - if (!isRemote) { - isRemote = true - clearBacklog() - observingBackingMap?.clear() - } - - isDirty = false - - val changeset = LinkedList>() - var readAction = stream.read() - 1 - - while (readAction != -1) { - if (readAction >= MapActionList.size) { - throw IndexOutOfBoundsException("Unknown map action with ID $readAction") - } - - when (MapActionList[readAction]) { - MapAction.CLEAR -> { - backingMap.clear() - changeset.add(ClearMapChangeset) - } - - MapAction.ADD -> { - val key = keyCodec.read(stream) - val value = valueCodec.read(stream) - backingMap[key] = value - changeset.add(MapChangeset(MapAction.ADD, key, value)) - } - - MapAction.REMOVE -> { - val key = keyCodec.read(stream) - backingMap.remove(key) - changeset.add(MapChangeset(MapAction.REMOVE, key, null)) - } - } - - readAction = stream.read() - 1 - } - - if (changeset.size != 0) { - callback?.invoke(changeset) - } - } - } - - /** - * marks all fields dirty, invalidates mappings for each endpoint - */ - fun invalidate() { - for (field in fields) { - field.markDirty() - } - - forEachEndpoint { - it.invalidateMappings() - } - } - - /** - * Observe changes of all fields with backing computation lambda - */ - fun observe(): Boolean { - var changes = false - - if (observers.isNotEmpty()) { - for (field in observers) { - changes = field.observe() || changes - } - } - - return changes - } - - /** - * [defaultEndpoint]#collectNetworkPayload - */ - fun collectNetworkPayload(): FastByteArrayOutputStream? { - check(!defaultEndpoint.unused) { "Default endpoint is not used" } - observe() - val values = defaultEndpoint.collectNetworkPayload() - markClean() - return values - } - - private val idToField = Int2ObjectOpenHashMap>() - private val missingFields = ObjectArraySet() - private val missingFieldsMap = Int2ObjectArrayMap() - - fun applyNetworkPayload(stream: InputStream): Int { - if (stream.read() > 0) { - idToField.clear() - missingFieldsMap.clear() - - var fieldId = stream.readVarIntLE() - - while (fieldId != 0) { - val size = stream.readVarIntLE() - val nameBytes = ByteArray(size) - stream.read(nameBytes) - val name = String(nameBytes, Charsets.UTF_8) - - val findField = fields.firstOrNull { it.name == name } - - if (findField == null) { - if (missingFields.add(name)) { - LOGGER.error("Unable to find field $name in $this, expect issues!") - } - - missingFieldsMap[fieldId] = name - } else { - idToField[fieldId] = findField - findField.id = fieldId - } - - fieldId = stream.readVarIntLE() - } - } - - var fieldId = stream.readVarIntLE() - var i = 0 - - while (fieldId != 0) { - val field = idToField[fieldId] - val payloadSize = stream.readVarIntLE() - - if (field == null) { - LOGGER.error("Unable to read field $fieldId (${missingFieldsMap[fieldId]}) because we don't know anything about it! Skipping $payloadSize bytes", IllegalStateException("Unknown field $fieldId")) - stream.skipNBytes(payloadSize.toLong()) - continue - } - - val bytes = ByteArray(payloadSize) - stream.read(bytes) - field.read(DataInputStream(FastByteArrayInputStream(bytes)), payloadSize) - fieldId = stream.readVarIntLE() - i++ - } - - return i - } - - companion object { - private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(MapAction.CLEAR.ordinal + 1) } - private val MapActionList = MapAction.values() - private val ClearMapChangeset = MapChangeset(MapAction.CLEAR, null, null) - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt index be695f2e8..9b9197192 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/GenericNetworkChannel.kt @@ -1,13 +1,144 @@ package ru.dbotthepony.mc.otm.network +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import net.minecraft.core.BlockPos +import net.minecraft.core.particles.ParticleTypes +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.server.level.ServerPlayer +import net.minecraft.util.RandomSource +import net.minecraft.world.level.Level import net.minecraftforge.network.NetworkDirection +import net.minecraftforge.network.PacketDistributor +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.android.AndroidResearchManager +import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket +import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.onceClient import ru.dbotthepony.mc.otm.item.QuantumBatteryItem +import ru.dbotthepony.mc.otm.matter.MatterManager +import java.util.* +import kotlin.collections.ArrayList -object GenericNetworkChannel : MatteryNetworkChannel( - version = "1", - name = "generic" -) { - fun register() { - add(QuantumBatteryItem.ChargePacket::class.java, QuantumBatteryItem.Companion::readPacket, NetworkDirection.PLAY_TO_CLIENT) +class SmokeParticlesPacket(val x: Double, val y: Double, val z: Double) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeDouble(x) + buff.writeDouble(y) + buff.writeDouble(z) + } + + override fun play(context: MNetworkContext) { + minecraft.player?.level?.let { + makeSmoke(x, y, z, it.random, it) + } + } + + companion object { + fun read(buff: FriendlyByteBuf): SmokeParticlesPacket { + return SmokeParticlesPacket(buff.readDouble(), buff.readDouble(), buff.readDouble()) + } + + fun makeSmoke(x: Double, y: Double, z: Double, random: RandomSource, level: Level) { + for (i in 0 .. random.nextInt(4, 8)) + level.addParticle(ParticleTypes.POOF, x + random.nextDouble() * 0.5 - 0.25, y + random.nextDouble() * 0.5 - 0.25, z + random.nextDouble() * 0.5 - 0.25, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02, random.nextGaussian() * 0.02) + } + } +} + +class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val validBytes: Int) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeBlockPos(position) + buff.writeBytes(buffer, 0, validBytes) + } + + private fun execute() { + val level = minecraft.player?.level + + if (level == null) { + LOGGER.error("Received BlockEntitySyncPacket before we are in valid level.") + + onceClient { + execute() + } + + return + } + + val blockEntity = level.getBlockEntity(position) + + if (blockEntity == null) { + LOGGER.warn("Putting BlockEntitySyncPacket received for $position into backlog because there is literally no block entity there!") + LOGGER.warn("This is CERTAINLY a bug in one of optimizing mods you have or server has installed!") + LOGGER.warn("This can cause memory leak.") + + backlog.computeIfAbsent(level) { Object2ObjectOpenHashMap() } + .computeIfAbsent(position, Object2ObjectFunction { ArrayList() }) + .add(this) + + return + } else if (blockEntity !is MatteryBlockEntity) { + LOGGER.warn("Dropping BlockEntitySyncPacket received for $position, because there is $blockEntity which is not MatteryBlockEntity!") + backlog[level]?.remove(position) + return + } + + val packets = backlog[level]?.remove(position) + + try { + if (packets != null) { + for (packet in packets) { + blockEntity.synchronizer.read(FastByteArrayInputStream(packet.buffer, 0, packet.validBytes)) + } + } + + blockEntity.synchronizer.read(FastByteArrayInputStream(buffer, 0, validBytes)) + } catch(err: Throwable) { + LOGGER.error("Exception while reading synchronized BlockEntity data!\nPosition: $position\nBlock: ${level.getBlockState(position)}\nBlock entity: $blockEntity", err) + } + } + + override fun play(context: MNetworkContext) { + execute() + } + + companion object { + private val backlog = WeakHashMap>>() + + fun read(buff: FriendlyByteBuf): BlockEntitySyncPacket { + val position = buff.readBlockPos() + val size = buff.readableBytes() + val array = ByteArray(size) + buff.readBytes(array) + return BlockEntitySyncPacket(position, array, size) + } + + private val LOGGER = LogManager.getLogger() + } +} + +object GenericNetworkChannel : MatteryNetworkChannel( + version = 5, + name = "generic" +) { + fun makeSmoke(x: Double, y: Double, z: Double, level: Level) { + send(PacketDistributor.NEAR.with { PacketDistributor.TargetPoint(x, y, z, 64.0, level.dimension()) }, SmokeParticlesPacket(x, y, z)) + } + + fun makeSmoke(excluded: ServerPlayer, x: Double, y: Double, z: Double) { + send(PacketDistributor.NEAR.with { PacketDistributor.TargetPoint(excluded, x, y, z, 64.0, excluded.level.dimension()) }, SmokeParticlesPacket(x, y, z)) + } + + fun register() { + add(QuantumBatteryItem.ChargePacket::class.java, QuantumBatteryItem.Companion::readPacket, NetworkDirection.PLAY_TO_CLIENT) + + add(BlockEntitySyncPacket::class.java, BlockEntitySyncPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) + add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) + + add(AndroidResearchManager.SyncPacket::class.java, AndroidResearchManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT, handleOnMainThread = false) + add(MatterManager.SyncPacket::class.java, MatterManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT) + + add(SmokeParticlesPacket::class, SmokeParticlesPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt index 00f8ae47a..71358da52 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryNetworkChannel.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.network -import java.util.function.Function import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer @@ -11,53 +10,45 @@ import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.NetworkRegistry import net.minecraftforge.network.PacketDistributor import net.minecraftforge.network.simple.SimpleChannel +import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.OverdriveThatMatters -import java.math.BigDecimal -import java.math.BigInteger -import java.util.Optional +import ru.dbotthepony.mc.otm.core.GetterSetter import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.locks.LockSupport import java.util.function.BiConsumer +import java.util.function.Function +import java.util.function.Predicate import java.util.function.Supplier import kotlin.reflect.KClass -fun Supplier.enqueueWork(lambda: Runnable): CompletableFuture = get().enqueueWork(lambda) -fun Supplier.enqueueWork(lambda: () -> Unit): CompletableFuture = get().enqueueWork(lambda) -var Supplier.packetHandled: Boolean - get() = get().packetHandled - set(value) { get().packetHandled = value } +class MNetworkContext(val sender: ServerPlayer?, packetHandled: GetterSetter, private val enqueuer: (Runnable) -> CompletableFuture<*>) { + var packetHandled by packetHandled -val Supplier.sender: ServerPlayer? - get() = get().sender - -fun FriendlyByteBuf.writeDecimal(value: BigDecimal) { - writeInt(value.scale()) - writeByteArray(value.unscaledValue().toByteArray()) -} - -fun FriendlyByteBuf.readDecimal(): BigDecimal { - val scale = readInt() - val bytes = readByteArray() - - return BigDecimal(BigInteger(bytes), scale) + fun enqueueWork(callback: Runnable): CompletableFuture<*> { + return enqueuer(callback) + } } interface MatteryPacket { fun write(buff: FriendlyByteBuf) - fun play(context: Supplier) + fun play(context: MNetworkContext) } -abstract class MatteryNetworkChannel(val version: String, val name: String) { - val channel: SimpleChannel = NetworkRegistry.newSimpleChannel( - ResourceLocation(OverdriveThatMatters.MOD_ID, name), - { version }, - { it == version }, - { it == version }, - ) +abstract class MatteryNetworkChannel(val version: Int, val name: String) { + val channel: SimpleChannel = NetworkRegistry + .newSimpleChannel(ResourceLocation(OverdriveThatMatters.MOD_ID, name), { version.toString() }, { it == version.toString() }, { it == version.toString() }) - fun sendToServer(packet: Any) = channel.sendToServer(packet) + fun sendToServer(packet: Any) = channel.send(PacketDistributor.SERVER.noArg(), packet) fun send(ply: Player, packet: Any) { + if (ply is ServerPlayer) { + queue.add(Task(channel, PacketDistributor.PLAYER.with { ply }, packet)) + } + } + + fun sendNow(ply: Player, packet: Any) { if (ply is ServerPlayer) { channel.send(PacketDistributor.PLAYER.with { ply }, packet) } @@ -68,7 +59,7 @@ abstract class MatteryNetworkChannel(val version: String, val name: String) { return } - channel.send(PacketDistributor.TRACKING_ENTITY.with { entity }, packet) + queue.add(Task(channel, PacketDistributor.TRACKING_ENTITY.with { entity }, packet)) } fun sendTrackingAndSelf(entity: Entity, packet: Any) { @@ -76,10 +67,16 @@ abstract class MatteryNetworkChannel(val version: String, val name: String) { return } - channel.send(PacketDistributor.TRACKING_ENTITY_AND_SELF.with { entity }, packet) + queue.add(Task(channel, PacketDistributor.TRACKING_ENTITY_AND_SELF.with { entity }, packet)) } - fun send(distributor: PacketDistributor.PacketTarget, packet: Any) = channel.send(distributor, packet) + fun send(distributor: PacketDistributor.PacketTarget, packet: Any) { + queue.add(Task(channel, distributor, packet)) + } + + fun sendNow(distributor: PacketDistributor.PacketTarget, packet: Any) { + channel.send(distributor, packet) + } private var nextNetworkPacketID = 0 @@ -87,30 +84,104 @@ abstract class MatteryNetworkChannel(val version: String, val name: String) { packetClass: Class, writer: BiConsumer, reader: Function, - handler: BiConsumer>, - direction: NetworkDirection? = null + handler: (T, MNetworkContext) -> Unit, + direction: NetworkDirection? = null, + handleOnMainThread: Boolean = true, ) { if (nextNetworkPacketID >= 256) { throw IndexOutOfBoundsException("Network message ID overflow!") } - @Suppress("INACCESSIBLE_TYPE") - channel.registerMessage(nextNetworkPacketID++, packetClass, writer, reader, handler, if (direction == null) Optional.empty() else Optional.of(direction)) + val builder = channel.messageBuilder(packetClass, nextNetworkPacketID++) + val bridgeHandler = BiConsumer> { a, b -> handler(a, MNetworkContext(b.get().sender, GetterSetter.of({ b.get().packetHandled }, { b.get().packetHandled = it }), b.get()::enqueueWork)) } + + if (handleOnMainThread) { + builder.consumerMainThread(bridgeHandler) + } else { + builder.consumerNetworkThread(bridgeHandler) + } + + builder.encoder(writer) + builder.decoder(reader) + builder.add() } fun add( packetClass: Class, reader: Function, - direction: NetworkDirection? = null + direction: NetworkDirection? = null, + handleOnMainThread: Boolean = true, ) { - add(packetClass, MatteryPacket::write, reader, MatteryPacket::play, direction) + add(packetClass, MatteryPacket::write, reader, MatteryPacket::play, direction, handleOnMainThread) } fun add( packetClass: KClass, reader: Function, - direction: NetworkDirection? = null + direction: NetworkDirection? = null, + handleOnMainThread: Boolean = true, ) { - add(packetClass.java, MatteryPacket::write, reader, MatteryPacket::play, direction) + add(packetClass.java, MatteryPacket::write, reader, MatteryPacket::play, direction, handleOnMainThread) + } + + private data class Task(val channel: SimpleChannel, val target: PacketDistributor.PacketTarget, val packet: Any) + + companion object { + private val logger = LogManager.getLogger() + private var thread: Thread? = null + + private val queue = ConcurrentLinkedQueue() + + @Volatile + private var interrupt = false + + /** + * Reason behind separate thread is that Minecraft connection class does not allow to enqueue messages, + * nor does Forge. This leads to extensive calls to Netty Channel eventLoop().execute(), which is somehow resource + * heavy, as it calls wakeup on Windows, eventFdWrite on Linux, etc., when all we want to do is to enqueue packet. + * + * Concurrent safety - Minecraft Channel (send method) does not do any writes (unless player is unconnected, in which case it uses thread safe queue), hence it is thread safe. + * OTM packets are always dispatched in correct order to client. + * Netty eventLoop executor is thread safe by definition. + */ + private fun run() { + while (!interrupt) { + val task = queue.poll() + + if (task == null) { + LockSupport.park() + } else { + try { + task.channel.send(task.target, task.packet) + } catch(err: Throwable) { + logger.error("Error executing network dispatcher task", err) + } + } + } + + logger.debug("Overdrive That Matters Packet Dispatcher thread exited gracefully") + } + + internal fun onServerPostTick() { + if (queue.isNotEmpty()) { + LockSupport.unpark(thread) + } + } + + internal fun onServerStarting() { + interrupt = false + check(thread?.isAlive != true) { "Already having network dispatcher thread, ServerStartingEvent was fired twice!" } + thread = Thread(this::run, "Overdrive That Matters Network Dispatcher").also { it.isDaemon = true; it.start() } + } + + internal fun onServerStopping() { + interrupt = true + LockSupport.unpark(thread) + } + + internal fun onServerStopped() { + interrupt = true + LockSupport.unpark(thread) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt index 606d5745b..c79c1c8fe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerNetworkChannel.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.network import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream +import net.minecraft.core.particles.ParticleTypes import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket @@ -9,7 +10,6 @@ import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import net.minecraftforge.network.NetworkDirection.PLAY_TO_CLIENT import net.minecraftforge.network.NetworkDirection.PLAY_TO_SERVER -import net.minecraftforge.network.NetworkEvent import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.android.AndroidActiveFeature import ru.dbotthepony.mc.otm.android.AndroidFeatureType @@ -26,14 +26,19 @@ import ru.dbotthepony.mc.otm.client.render.GlitchRenderer import ru.dbotthepony.mc.otm.client.render.ShockwaveRenderer import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.Vector -import ru.dbotthepony.mc.otm.menu.AndroidStationMenu -import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.core.math.Vector +import ru.dbotthepony.mc.otm.core.math.component1 +import ru.dbotthepony.mc.otm.core.math.component2 +import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.toRadians +import ru.dbotthepony.mc.otm.core.position +import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu +import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu import ru.dbotthepony.mc.otm.registry.AndroidFeatures import ru.dbotthepony.mc.otm.registry.MRegistry import java.io.ByteArrayInputStream import java.util.UUID -import java.util.function.Supplier class MatteryPlayerFieldPacket(val bytes: ByteArray, val length: Int, val isPublic: Boolean, val target: UUID? = null) : MatteryPacket { constructor(stream: FastByteArrayOutputStream, isPublic: Boolean, target: UUID? = null) : this(stream.array, stream.length, isPublic, target) @@ -48,22 +53,19 @@ class MatteryPlayerFieldPacket(val bytes: ByteArray, val length: Int, val isPubl buff.writeBytes(bytes, 0, length) } - override fun play(context: Supplier) { - context.packetHandled = true - context.get().enqueueWork { - val player: MatteryPlayerCapability + override fun play(context: MNetworkContext) { + val player: MatteryPlayerCapability - if (target != null) { - player = minecraft.level?.players()?.firstOrNull { it.uuid == target }?.matteryPlayer ?: return@enqueueWork - } else { - player = minecraft.player?.matteryPlayer ?: return@enqueueWork - } + if (target != null) { + player = minecraft.level?.players()?.firstOrNull { it.uuid == target }?.matteryPlayer ?: return + } else { + player = minecraft.player?.matteryPlayer ?: return + } - if (isPublic) { - player.publicSynchronizer.applyNetworkPayload(ByteArrayInputStream(bytes, 0, length)) - } else { - player.synchronizer.applyNetworkPayload(ByteArrayInputStream(bytes, 0, length)) - } + if (isPublic) { + player.publicSynchronizer.read(ByteArrayInputStream(bytes, 0, length)) + } else { + player.synchronizer.read(ByteArrayInputStream(bytes, 0, length)) } } @@ -86,18 +88,15 @@ class AndroidResearchRequestPacket(val type: AndroidResearchType) : MatteryPacke buff.writeUtf(type.id.toString()) } - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val ply = context.get().sender ?: return@enqueueWork - if (ply.isSpectator) return@enqueueWork - val android = ply.matteryPlayer ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val ply = context.sender ?: return + if (ply.isSpectator) return + val android = ply.matteryPlayer ?: return - if (!android.isAndroid || ply.containerMenu !is AndroidStationMenu) - return@enqueueWork + if (!android.isAndroid || ply.containerMenu !is AndroidStationMenu) + return - android.getResearch(type).research() - } + android.getResearch(type).research() } companion object { @@ -114,15 +113,10 @@ class AndroidResearchSyncPacket(val type: AndroidResearchType, val dataList: Fas buff.writeBytes(dataList.array, 0, dataList.length) } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { dataBytes ?: throw NullPointerException("No data bytes array is present") - - context.get().packetHandled = true - context.get().enqueueWork { - val android = minecraft.player?.matteryPlayer ?: return@enqueueWork - - android.getResearch(type).applyNetworkPayload(ByteArrayInputStream(dataBytes)) - } + val android = minecraft.player?.matteryPlayer ?: return + android.getResearch(type).applyNetworkPayload(ByteArrayInputStream(dataBytes)) } companion object { @@ -142,15 +136,10 @@ class AndroidFeatureSyncPacket(val type: AndroidFeatureType<*>, val dataList: Fa buff.writeBytes(dataList.array, 0, dataList.length) } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { dataBytes ?: throw NullPointerException("No data bytes array is present") - - context.get().packetHandled = true - context.get().enqueueWork { - val android = minecraft.player?.matteryPlayer ?: return@enqueueWork - - android.computeIfAbsent(type).applyNetworkPayload(ByteArrayInputStream(dataBytes)) - } + val android = minecraft.player?.matteryPlayer ?: return + android.computeIfAbsent(type).applyNetworkPayload(ByteArrayInputStream(dataBytes)) } companion object { @@ -168,13 +157,9 @@ class AndroidFeatureRemovePacket(val type: AndroidFeatureType<*>) : MatteryPacke buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type)) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val android = minecraft.player?.matteryPlayer ?: return@enqueueWork - - android.removeFeature(type) - } + override fun play(context: MNetworkContext) { + val android = minecraft.player?.matteryPlayer ?: return + android.removeFeature(type) } companion object { @@ -195,7 +180,7 @@ class PlayerIterationPacket(val iteration: Int, val deathLog: List) { + override fun play(context: MNetworkContext) { context.packetHandled = true MatteryGUI.iteration = iteration @@ -221,76 +206,69 @@ class PlayerIterationPacket(val iteration: Int, val deathLog: List) { - context.packetHandled = true - context.enqueueWork { - val mattery = minecraft.player?.matteryPlayer ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val mattery = minecraft.player?.matteryPlayer ?: return - if (mattery.hasExoPack) { - mattery.exoPackMenu.carried = itemStack - mattery.exoPackMenu.stateId = containerState - } + if (mattery.hasExopack) { + mattery.exoPackMenu.carried = itemStack + mattery.exoPackMenu.stateId = containerState } } companion object { - fun read(buff: FriendlyByteBuf): ExoSuitCarriedPacket { - return ExoSuitCarriedPacket(buff.readItem(), buff.readInt()) + fun read(buff: FriendlyByteBuf): ExopackCarriedPacket { + return ExopackCarriedPacket(buff.readItem(), buff.readInt()) } } } -class ExoSuitSlotPacket(val slotId: Int, val itemStack: ItemStack, val containerState: Int) : MatteryPacket { +class ExopackSlotPacket(val slotId: Int, val itemStack: ItemStack, val containerState: Int) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { buff.writeInt(slotId) buff.writeItem(itemStack) buff.writeInt(containerState) } - override fun play(context: Supplier) { - context.packetHandled = true - + override fun play(context: MNetworkContext) { if (slotId < 0) { LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId) return } - context.enqueueWork { - val mattery = minecraft.player?.matteryPlayer ?: return@enqueueWork + val mattery = minecraft.player?.matteryPlayer ?: return - if (mattery.hasExoPack) { - if (slotId >= mattery.exoPackMenu.slots.size) { - LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId) - return@enqueueWork - } - - // don't duplicate data - // really. - if (mattery.exoPackMenu.slots[slotId].container == minecraft.player?.inventory && minecraft.player?.containerMenu !is ExoPackInventoryMenu) - return@enqueueWork - - mattery.exoPackMenu.slots[slotId].set(itemStack) - mattery.exoPackMenu.stateId = containerState + if (mattery.hasExopack) { + if (slotId >= mattery.exoPackMenu.slots.size) { + LOGGER.error("Unknown slot with ID {} in exosuit menu", slotId) + return } + + // don't duplicate data + // really. + if (mattery.exoPackMenu.slots[slotId].container == minecraft.player?.inventory && minecraft.player?.containerMenu !is ExopackInventoryMenu) + return + + mattery.exoPackMenu.slots[slotId].set(itemStack) + mattery.exoPackMenu.stateId = containerState } } companion object { private val LOGGER = LogManager.getLogger() - fun read(buff: FriendlyByteBuf): ExoSuitSlotPacket { - return ExoSuitSlotPacket(buff.readInt(), buff.readItem(), buff.readInt()) + fun read(buff: FriendlyByteBuf): ExopackSlotPacket { + return ExopackSlotPacket(buff.readInt(), buff.readItem(), buff.readInt()) } } } -class ExoSuitMenuInitPacket(val slots: List, val carried: ItemStack, val containerState: Int) : MatteryPacket { +class ExopackMenuInitPacket(val slots: List, val carried: ItemStack, val containerState: Int) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { buff.writeInt(slots.size) @@ -302,19 +280,16 @@ class ExoSuitMenuInitPacket(val slots: List, val carried: ItemStack, buff.writeInt(containerState) } - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val mattery = minecraft.player?.matteryPlayer ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val mattery = minecraft.player?.matteryPlayer ?: return - if (mattery.hasExoPack) { - mattery.exoPackMenu.initializeContents(containerState, slots, carried) - } + if (mattery.hasExopack) { + mattery.exoPackMenu.initializeContents(containerState, slots, carried) } } companion object { - fun read(buff: FriendlyByteBuf): ExoSuitMenuInitPacket { + fun read(buff: FriendlyByteBuf): ExopackMenuInitPacket { val size = buff.readInt() val slots = ArrayList(size) @@ -325,23 +300,20 @@ class ExoSuitMenuInitPacket(val slots: List, val carried: ItemStack, val carried = buff.readItem() val containerState = buff.readInt() - return ExoSuitMenuInitPacket(slots, carried, containerState) + return ExopackMenuInitPacket(slots, carried, containerState) } } } -object ExoSuitMenuOpen : MatteryPacket { +object ExopackMenuOpen : MatteryPacket { override fun write(buff: FriendlyByteBuf) {} - override fun play(context: Supplier) { - context.packetHandled = true - context.enqueueWork { - val player = context.sender ?: return@enqueueWork - val mattery = player.matteryPlayer ?: return@enqueueWork + override fun play(context: MNetworkContext) { + val player = context.sender ?: return + val mattery = player.matteryPlayer ?: return - if (mattery.hasExoPack) { - player.containerMenu = mattery.exoPackMenu - } + if (mattery.hasExopack) { + player.containerMenu = mattery.exoPackMenu } } } @@ -352,28 +324,24 @@ class SwitchAndroidFeaturePacket(val type: AndroidFeatureType<*>, val newState: buff.writeBoolean(newState) } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { + val matteryPlayer = context.sender?.matteryPlayer ?: return - context.enqueueWork { - val matteryPlayer = context.sender?.matteryPlayer ?: return@enqueueWork + if (!matteryPlayer.isAndroid) { + return + } - if (!matteryPlayer.isAndroid) { - return@enqueueWork - } + val feature = matteryPlayer.getFeature(type) ?: return - val feature = matteryPlayer.getFeature(type) ?: return@enqueueWork - - if (feature is AndroidActiveFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) { - matteryPlayer.features - .map { it as? AndroidActiveFeature } - .filter { it != null } - .filter { it !== feature && it!!.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || it.allowToSwitchByPlayerWhileSpectator) } - .forEach { it!!.isActive = false } - feature.isActive = newState - } else if (feature is AndroidSwitchableFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) { - feature.isActive = newState - } + if (feature is AndroidActiveFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) { + matteryPlayer.features + .map { it as? AndroidActiveFeature } + .filter { it != null } + .filter { it !== feature && it!!.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || it.allowToSwitchByPlayerWhileSpectator) } + .forEach { it!!.isActive = false } + feature.isActive = newState + } else if (feature is AndroidSwitchableFeature && feature.allowToSwitchByPlayer && (!matteryPlayer.ply.isSpectator || feature.allowToSwitchByPlayerWhileSpectator)) { + feature.isActive = newState } } @@ -389,21 +357,17 @@ class ActivateAndroidFeaturePacket(val type: AndroidFeatureType<*>) : MatteryPac buff.writeInt(MRegistry.ANDROID_FEATURES.getID(type)) } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { + val matteryPlayer = context.sender?.matteryPlayer ?: return - context.enqueueWork { - val matteryPlayer = context.sender?.matteryPlayer ?: return@enqueueWork + if (!matteryPlayer.isAndroid || matteryPlayer.ply.isSpectator) { + return + } - if (!matteryPlayer.isAndroid || matteryPlayer.ply.isSpectator) { - return@enqueueWork - } + val feature = matteryPlayer.getFeature(type) as? AndroidActiveFeature ?: return - val feature = matteryPlayer.getFeature(type) as? AndroidActiveFeature ?: return@enqueueWork - - if (feature.isActive || feature.allowToSwitchByPlayer) { - feature.activate(false) - } + if (feature.isActive || feature.allowToSwitchByPlayer) { + feature.activate(false) } } @@ -423,30 +387,26 @@ class PickItemFromInventoryPacket( buff.writeVarInt(sourceExosuitSlot) } - override fun play(context: Supplier) { - context.packetHandled = true + override fun play(context: MNetworkContext) { + val player = context.sender ?: return + val mattery = player.matteryPlayer ?: return - context.enqueueWork { - val player = context.sender ?: return@enqueueWork - val mattery = player.matteryPlayer ?: return@enqueueWork - - if (!mattery.hasExoPack || sourceExosuitSlot !in 0 until mattery.exoPackContainer.containerSize) { - return@enqueueWork - } - - if (!Inventory.isHotbarSlot(targetHotbarSlot)) { - return@enqueueWork - } - - player.inventory.selected = targetHotbarSlot - val existingItem = player.inventory[targetHotbarSlot].copy() - val inventoryItem = mattery.exoPackContainer[sourceExosuitSlot].copy() - - player.inventory[targetHotbarSlot] = if (inventoryItem.isEmpty) ItemStack.EMPTY else inventoryItem - mattery.exoPackContainer[sourceExosuitSlot] = if (existingItem.isEmpty) ItemStack.EMPTY else existingItem - - player.connection.send(ClientboundSetCarriedItemPacket(targetHotbarSlot)) + if (!mattery.hasExopack || sourceExosuitSlot !in 0 until mattery.exopackContainer.containerSize) { + return } + + if (!Inventory.isHotbarSlot(targetHotbarSlot)) { + return + } + + player.inventory.selected = targetHotbarSlot + val existingItem = player.inventory[targetHotbarSlot].copy() + val inventoryItem = mattery.exopackContainer[sourceExosuitSlot].copy() + + player.inventory[targetHotbarSlot] = if (inventoryItem.isEmpty) ItemStack.EMPTY else inventoryItem + mattery.exopackContainer[sourceExosuitSlot] = if (existingItem.isEmpty) ItemStack.EMPTY else existingItem + + player.connection.send(ClientboundSetCarriedItemPacket(targetHotbarSlot)) } companion object { @@ -461,7 +421,7 @@ class GlitchPacket(val millis: Long) : MatteryPacket { buff.writeVarLong(millis) } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { context.packetHandled = true GlitchRenderer.glitchFor(millis) } @@ -480,7 +440,7 @@ class ShockwaveEffectPacket(val pos: Vector) : MatteryPacket { buff.writeDouble(pos.z) } - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { context.packetHandled = true ShockwaveRenderer.handle(this) } @@ -492,26 +452,114 @@ class ShockwaveEffectPacket(val pos: Vector) : MatteryPacket { } } -object DisplayExosuitPacket : MatteryPacket { +object DisplayExopackPacket : MatteryPacket { override fun write(buff: FriendlyByteBuf) {} - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { context.packetHandled = true - context.sender?.matteryPlayer?.displayExoPack = true + context.sender?.matteryPlayer?.isExopackVisible = true } } -object HideExosuitPacket : MatteryPacket { +object HideExopackPacket : MatteryPacket { override fun write(buff: FriendlyByteBuf) {} - override fun play(context: Supplier) { + override fun play(context: MNetworkContext) { context.packetHandled = true - context.sender?.matteryPlayer?.displayExoPack = false + context.sender?.matteryPlayer?.isExopackVisible = false + } +} + +object EnableExopackGlowPacket : MatteryPacket { + override fun write(buff: FriendlyByteBuf) {} + + override fun play(context: MNetworkContext) { + context.packetHandled = true + context.sender?.matteryPlayer?.exopackGlows = true + } +} + +object DisableExopackGlowPacket : MatteryPacket { + override fun write(buff: FriendlyByteBuf) {} + + override fun play(context: MNetworkContext) { + context.packetHandled = true + context.sender?.matteryPlayer?.exopackGlows = false + } +} + +object ResetExopackColorPacket : MatteryPacket { + override fun write(buff: FriendlyByteBuf) {} + + override fun play(context: MNetworkContext) { + context.packetHandled = true + context.sender?.matteryPlayer?.exopackColor = null + } +} + +data class SetExopackColorPacket(val color: RGBAColor) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeFloat(color.red) + buff.writeFloat(color.green) + buff.writeFloat(color.blue) + } + + override fun play(context: MNetworkContext) { + context.packetHandled = true + context.sender?.matteryPlayer?.exopackColor = color + } + + companion object { + fun read(buff: FriendlyByteBuf): SetExopackColorPacket { + return SetExopackColorPacket(RGBAColor(buff.readFloat(), buff.readFloat(), buff.readFloat())) + } + } +} + +data class ExopackSmokePacket(val player: UUID) : MatteryPacket { + override fun write(buff: FriendlyByteBuf) { + buff.writeUUID(player) + } + + override fun play(context: MNetworkContext) { + context.packetHandled = true + // minecraft.player?.level?.getPlayerByUUID(player)?.matteryPlayer?.spawnExopackSmoke = true + + minecraft.player?.level?.getPlayerByUUID(player)?.let { ply -> + if (ply != minecraft.player || minecraft.gameRenderer.mainCamera.isDetached) { + var (x, y, z) = ply.position + + y += 1.5 + val deg = toRadians(ply.yBodyRot + 90f) + + x += kotlin.math.cos(deg) * -0.4 + z += kotlin.math.sin(deg) * -0.4 + + val level = ply.level + val random = level.random + + for (i in 0 .. random.nextInt(2, 4)) + level.addParticle( + ParticleTypes.SMOKE, + x + random.nextDouble() * 0.4 - 0.2, + y + random.nextDouble() * 0.4 - 0.2, + z + random.nextDouble() * 0.4 - 0.2, + random.nextGaussian() * 0.02, + random.nextGaussian() * 0.02, + random.nextGaussian() * 0.02) + } + } + } + + companion object { + fun read(buff: FriendlyByteBuf): ExopackSmokePacket { + return ExopackSmokePacket(buff.readUUID()) + } } } object MatteryPlayerNetworkChannel : MatteryNetworkChannel( - version = "1", + version = 7, name = "player" ) { fun register() { @@ -524,11 +572,12 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel( add(PlayerIterationPacket::class, PlayerIterationPacket.Companion::read, PLAY_TO_CLIENT) - add(ExoSuitCarriedPacket::class, ExoSuitCarriedPacket.Companion::read, PLAY_TO_CLIENT) - add(ExoSuitSlotPacket::class, ExoSuitSlotPacket.Companion::read, PLAY_TO_CLIENT) - add(ExoSuitMenuInitPacket::class, ExoSuitMenuInitPacket.Companion::read, PLAY_TO_CLIENT) + add(ExopackCarriedPacket::class, ExopackCarriedPacket.Companion::read, PLAY_TO_CLIENT) + add(ExopackSlotPacket::class, ExopackSlotPacket.Companion::read, PLAY_TO_CLIENT) + add(ExopackMenuInitPacket::class, ExopackMenuInitPacket.Companion::read, PLAY_TO_CLIENT) + add(ExopackSmokePacket::class, ExopackSmokePacket.Companion::read, PLAY_TO_CLIENT) - add(ExoSuitMenuOpen::class, { ExoSuitMenuOpen }, PLAY_TO_SERVER) + add(ExopackMenuOpen::class, { ExopackMenuOpen }, PLAY_TO_SERVER) add(SwitchAndroidFeaturePacket::class, SwitchAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER) add(ActivateAndroidFeaturePacket::class, ActivateAndroidFeaturePacket.Companion::read, PLAY_TO_SERVER) @@ -541,7 +590,11 @@ object MatteryPlayerNetworkChannel : MatteryNetworkChannel( add(GlitchPacket::class, GlitchPacket.Companion::read, PLAY_TO_CLIENT) add(ShockwaveEffectPacket::class, ShockwaveEffectPacket.Companion::read, PLAY_TO_CLIENT) - add(DisplayExosuitPacket::class, { DisplayExosuitPacket }, PLAY_TO_SERVER) - add(HideExosuitPacket::class, { HideExosuitPacket }, PLAY_TO_SERVER) + add(DisplayExopackPacket::class, { DisplayExopackPacket }, PLAY_TO_SERVER) + add(HideExopackPacket::class, { HideExopackPacket }, PLAY_TO_SERVER) + add(EnableExopackGlowPacket::class, { EnableExopackGlowPacket }, PLAY_TO_SERVER) + add(DisableExopackGlowPacket::class, { DisableExopackGlowPacket }, PLAY_TO_SERVER) + add(ResetExopackColorPacket::class, { ResetExopackColorPacket }, PLAY_TO_SERVER) + add(SetExopackColorPacket::class, SetExopackColorPacket.Companion::read, PLAY_TO_SERVER) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt index d09bb9034..dacc8ad7f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt @@ -4,45 +4,46 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import net.minecraft.network.FriendlyByteBuf import net.minecraft.world.item.ItemStack import net.minecraftforge.network.NetworkDirection -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings +import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.compat.InventoryScrollPacket -import ru.dbotthepony.mc.otm.container.ItemFilterSlotPacket -import ru.dbotthepony.mc.otm.menu.CancelTaskPacket +import ru.dbotthepony.mc.otm.compat.vanilla.InventoryScrollPacket +import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import ru.dbotthepony.mc.otm.menu.matter.CancelTaskPacket import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.PatternsChangePacket -import ru.dbotthepony.mc.otm.menu.ReplicationRequestPacket -import ru.dbotthepony.mc.otm.menu.TasksChangePacket +import ru.dbotthepony.mc.otm.menu.matter.PatternsChangePacket +import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket +import ru.dbotthepony.mc.otm.menu.matter.TasksChangePacket import ru.dbotthepony.mc.otm.menu.data.ClearItemViewPacket import ru.dbotthepony.mc.otm.menu.data.ItemViewInteractPacket import ru.dbotthepony.mc.otm.menu.data.StackAddPacket import ru.dbotthepony.mc.otm.menu.data.StackChangePacket import ru.dbotthepony.mc.otm.menu.data.StackRemovePacket -import ru.dbotthepony.mc.otm.menu.widget.BooleanPlayerInputPacket -import ru.dbotthepony.mc.otm.menu.widget.NumberPlayerInputPacket -import ru.dbotthepony.mc.otm.menu.widget.OneWayPlayerInputPacket import java.io.ByteArrayInputStream -import java.util.function.Supplier -class MenuFieldPacket(val bytes: ByteArray, val length: Int) : MatteryPacket { - constructor(stream: FastByteArrayOutputStream) : this(stream.array, stream.length) +class MenuFieldPacket(val containerId: Int, val bytes: ByteArray, val length: Int) : MatteryPacket { + constructor(containerId: Int, stream: FastByteArrayOutputStream) : this(containerId, stream.array, stream.length) override fun write(buff: FriendlyByteBuf) { + buff.writeVarInt(containerId) buff.writeBytes(bytes, 0, length) } - override fun play(context: Supplier) { - context.packetHandled = true - context.get().enqueueWork { - (minecraft.player?.containerMenu as? MatteryMenu)?.mSynchronizer?.applyNetworkPayload(ByteArrayInputStream(bytes, 0, length)) + override fun play(context: MNetworkContext) { + if (containerId == ExopackInventoryMenu.CONTAINER_ID) { + minecraft.player?.matteryPlayer?.exoPackMenu?.mSynchronizer?.read(ByteArrayInputStream(bytes, 0, length)) + } else { + val menu = minecraft.player?.containerMenu as? MatteryMenu ?: return + + if (menu.containerId == containerId) + menu.mSynchronizer.read(ByteArrayInputStream(bytes, 0, length)) } } companion object { fun read(buff: FriendlyByteBuf): MenuFieldPacket { + val containerId = buff.readVarInt() val readable = buff.readableBytes() - return MenuFieldPacket(ByteArray(readable).also(buff::readBytes), readable) + return MenuFieldPacket(containerId, ByteArray(readable).also(buff::readBytes), readable) } } } @@ -52,9 +53,8 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket { buff.writeItem(item) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { minecraft.player?.containerMenu?.carried = item } + override fun play(context: MNetworkContext) { + minecraft.player?.containerMenu?.carried = item } companion object { @@ -65,32 +65,24 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket { } object MenuNetworkChannel : MatteryNetworkChannel( - version = "1", + version = 5, name = "menu" ) { fun register() { add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) - add(ItemFilterSlotPacket::class.java, ItemFilterSlotPacket.Companion::read) - // networked view - add(ClearItemViewPacket::class.java, ClearItemViewPacket::read, NetworkDirection.PLAY_TO_CLIENT) + // networked item view + add(ClearItemViewPacket::class.java, { ClearItemViewPacket }, NetworkDirection.PLAY_TO_CLIENT) add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(StackRemovePacket::class.java, StackRemovePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) - // container data + // Menu data + // Server->Client add(MenuFieldPacket::class.java, MenuFieldPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) - - // Player input in menus - add(NumberPlayerInputPacket::class.java, NumberPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) - add(OneWayPlayerInputPacket::class.java, OneWayPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) - add(BooleanPlayerInputPacket::class.java, BooleanPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) - - // menu specific - - // Item monitor - add(ItemMonitorPlayerSettings::class.java, ItemMonitorPlayerSettings.Companion::read) + // Client->Server + add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER) // matter panel menu add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt deleted file mode 100644 index 8106c4a41..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/RegistryNetworkChannel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package ru.dbotthepony.mc.otm.network - -import net.minecraftforge.network.NetworkDirection -import ru.dbotthepony.mc.otm.android.AndroidResearchManager -import ru.dbotthepony.mc.otm.matter.MatterManager - -object RegistryNetworkChannel : MatteryNetworkChannel( - version = "2", - name = "registry" -) { - fun register() { - add(AndroidResearchManager.SyncPacket::class.java, AndroidResearchManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT) - add(MatterManager.SyncPacket::class.java, MatterManager::readSyncPacket, NetworkDirection.PLAY_TO_CLIENT) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt index 694b593c6..6c54049f6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WeaponNetworkChannel.kt @@ -5,7 +5,7 @@ import ru.dbotthepony.mc.otm.item.weapon.WeaponFireInputPacket import ru.dbotthepony.mc.otm.item.weapon.WeaponScopePacket object WeaponNetworkChannel : MatteryNetworkChannel( - version = "1", + version = 3, name = "weapon" ) { fun register() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt deleted file mode 100644 index 6eac71721..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/WorldNetworkChannel.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ru.dbotthepony.mc.otm.network - -import it.unimi.dsi.fastutil.io.FastByteArrayInputStream -import net.minecraft.core.BlockPos -import net.minecraft.network.FriendlyByteBuf -import net.minecraftforge.network.NetworkDirection -import net.minecraftforge.network.NetworkEvent -import ru.dbotthepony.mc.otm.android.feature.ItemEntityDataPacket -import ru.dbotthepony.mc.otm.block.entity.SynchronizedBlockEntity -import ru.dbotthepony.mc.otm.client.minecraft -import java.util.function.Supplier - -class BlockEntitySyncPacket(val position: BlockPos, val buffer: ByteArray, val validBytes: Int) : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - buff.writeBlockPos(position) - buff.writeBytes(buffer, 0, validBytes) - } - - override fun play(context: Supplier) { - context.packetHandled = true - - context.enqueueWork { - val level = minecraft.player?.level ?: return@enqueueWork - val blockEntity = level.getBlockEntity(position) as? SynchronizedBlockEntity ?: return@enqueueWork - blockEntity.synchronizer.applyNetworkPayload(FastByteArrayInputStream(buffer, 0, validBytes)) - } - } - - companion object { - fun read(buff: FriendlyByteBuf): BlockEntitySyncPacket { - val position = buff.readBlockPos() - val size = buff.readableBytes() - val array = ByteArray(size) - buff.readBytes(array) - return BlockEntitySyncPacket(position, array, size) - } - } -} - -object WorldNetworkChannel : MatteryNetworkChannel( - version = "2", - name = "world" -) { - fun register() { - add(BlockEntitySyncPacket::class.java, BlockEntitySyncPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) - add(ItemEntityDataPacket::class.java, ItemEntityDataPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/ChangesetAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/ChangesetAction.kt new file mode 100644 index 000000000..870e44ea7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/ChangesetAction.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +enum class ChangesetAction { + CLEAR, ADD, REMOVE +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldAccess.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldAccess.kt new file mode 100644 index 000000000..c43201a31 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldAccess.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +interface FieldAccess { + fun read(): V + fun write(value: V) +} + +interface FloatFieldAccess : FieldAccess { + override fun write(value: Float) + @Deprecated("Use type specific method", replaceWith = ReplaceWith("this.readFloat()")) + override fun read() = readFloat() + fun readFloat(): Float +} + +interface DoubleFieldAccess : FieldAccess { + override fun write(value: Double) + @Deprecated("Use type specific method", replaceWith = ReplaceWith("this.readDouble()")) + override fun read() = readDouble() + fun readDouble(): Double +} + +interface IntFieldAccess : FieldAccess { + override fun write(value: Int) + @Deprecated("Use type specific method", replaceWith = ReplaceWith("this.readInt()")) + override fun read() = readInt() + fun readInt(): Int +} + +interface LongFieldAccess : FieldAccess { + override fun write(value: Long) + @Deprecated("Use type specific method", replaceWith = ReplaceWith("this.readLong()")) + override fun read() = readLong() + fun readLong(): Long +} + +interface BooleanFieldAccess : FieldAccess { + override fun write(value: Boolean) + @Deprecated("Use type specific method", replaceWith = ReplaceWith("this.readBoolean()")) + override fun read() = readBoolean() + fun readBoolean(): Boolean +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldGetter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldGetter.kt new file mode 100644 index 000000000..4389e1ca3 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldGetter.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +fun interface FieldGetter { + fun invoke(field: FieldAccess): V +} + +fun interface FloatFieldGetter : FieldGetter { + fun invoke(field: FloatFieldAccess): Float + + @Deprecated("Use type specific invoke") + override fun invoke(field: FieldAccess): Float { + return invoke(field as FloatFieldAccess) + } +} + +fun interface DoubleFieldGetter : FieldGetter { + fun invoke(field: DoubleFieldAccess): Double + + @Deprecated("Use type specific invoke") + override fun invoke(field: FieldAccess): Double { + return invoke(field as DoubleFieldAccess) + } +} + +fun interface IntFieldGetter : FieldGetter { + fun invoke(field: IntFieldAccess): Int + + @Deprecated("Use type specific invoke") + override fun invoke(field: FieldAccess): Int { + return invoke(field as IntFieldAccess) + } +} + +fun interface LongFieldGetter : FieldGetter { + fun invoke(field: LongFieldAccess): Long + + @Deprecated("Use type specific invoke") + override fun invoke(field: FieldAccess): Long { + return invoke(field as LongFieldAccess) + } +} + +fun interface BooleanFieldGetter : FieldGetter { + fun invoke(field: BooleanFieldAccess): Boolean + + @Deprecated("Use type specific invoke") + override fun invoke(field: FieldAccess): Boolean { + return invoke(field as BooleanFieldAccess) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSetter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSetter.kt new file mode 100644 index 000000000..3a5e4fb40 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSetter.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +fun interface FieldSetter { + fun invoke(value: V, access: FieldAccess, setByRemote: Boolean) +} + +fun interface FloatFieldSetter : FieldSetter { + fun invoke(value: Float, access: FloatFieldAccess, setByRemote: Boolean) + + @Deprecated("Use type specific invoke") + override fun invoke(value: Float, access: FieldAccess, setByRemote: Boolean) { + invoke(value, access as FloatFieldAccess, setByRemote) + } +} + +fun interface DoubleFieldSetter : FieldSetter { + fun invoke(value: Double, access: DoubleFieldAccess, setByRemote: Boolean) + + @Deprecated("Use type specific invoke") + override fun invoke(value: Double, access: FieldAccess, setByRemote: Boolean) { + invoke(value, access as DoubleFieldAccess, setByRemote) + } +} + +fun interface IntFieldSetter : FieldSetter { + fun invoke(value: Int, access: IntFieldAccess, setByRemote: Boolean) + + @Deprecated("Use type specific invoke") + override fun invoke(value: Int, access: FieldAccess, setByRemote: Boolean) { + invoke(value, access as IntFieldAccess, setByRemote) + } +} + +fun interface LongFieldSetter : FieldSetter { + fun invoke(value: Long, access: LongFieldAccess, setByRemote: Boolean) + + @Deprecated("Use type specific invoke") + override fun invoke(value: Long, access: FieldAccess, setByRemote: Boolean) { + invoke(value, access as LongFieldAccess, setByRemote) + } +} + +fun interface BooleanFieldSetter : FieldSetter { + fun invoke(value: Boolean, access: BooleanFieldAccess, setByRemote: Boolean) + + @Deprecated("Use type specific invoke") + override fun invoke(value: Boolean, access: FieldAccess, setByRemote: Boolean) { + invoke(value, access as BooleanFieldAccess, setByRemote) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt new file mode 100644 index 000000000..c9dd51db8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt @@ -0,0 +1,2190 @@ +@file:Suppress("DeprecatedCallableAddReplaceWith") + +package ru.dbotthepony.mc.otm.network.synchronizer + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer +import it.unimi.dsi.fastutil.floats.FloatConsumer +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ReferenceArraySet +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.* +import ru.dbotthepony.mc.otm.core.collect.ProxiedMap +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec +import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec +import ru.dbotthepony.mc.otm.core.util.ByteValueCodec +import ru.dbotthepony.mc.otm.core.util.DecimalValueCodec +import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.core.util.IStreamCodec +import ru.dbotthepony.mc.otm.core.util.ItemStackValueCodec +import ru.dbotthepony.mc.otm.core.util.ShortValueCodec +import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec +import ru.dbotthepony.mc.otm.core.util.readVarIntLE +import ru.dbotthepony.mc.otm.core.util.readVarLongLE +import ru.dbotthepony.mc.otm.core.util.writeVarIntLE +import ru.dbotthepony.mc.otm.core.util.writeVarLongLE +import ru.dbotthepony.mc.otm.secondTime +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.InputStream +import java.lang.ref.WeakReference +import java.math.BigDecimal +import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer +import java.util.function.DoubleConsumer +import java.util.function.DoubleSupplier +import java.util.function.IntConsumer +import java.util.function.IntSupplier +import java.util.function.LongConsumer +import java.util.function.LongSupplier +import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 + +/** + * Universal, one-to-many value synchronizer, allowing to synchronize values from server to client + * anywhere, where input/output streams are supported + */ +@Suppress("unused", "BlockingMethodInNonBlockingContext") +class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCallback: Boolean) { + constructor() : this(Runnable {}, false) + constructor(callback: Runnable) : this(callback, false) + + private var freeSlots = 0 + // почему не удалять поля напрямую? + // чтоб не возникло проблем в состоянии гонки + // формируем пакет -> удаляем поле по обе стороны -> клиент принимает пакет -> клиент считывает неверные данные + // конечно, всё равно всё сломается если было удалено поле, которое находится в пакете + // но если поля нет в пакете, то всё окей + private val fields = ArrayList?>(0) + private val observers = ArrayList>(0) + + private var nextFieldID = 0 + + val hasObservers: Boolean get() = observers.isNotEmpty() + + var isEmpty: Boolean = true + private set + + val isNotEmpty: Boolean get() = !isEmpty + + var isDirty: Boolean = false + private set(value) { + if (value != field) { + field = value + + if (value && !alwaysCallCallback) { + callback.run() + } + } + + if (alwaysCallCallback && value) { + callback.run() + } + } + + fun markClean() { + isDirty = false + } + + fun computedByte(getter: () -> Byte) = ComputedField(getter, ByteValueCodec) + fun computedBool(getter: BooleanSupplier) = ComputedBooleanField(getter) + fun computedShort(getter: () -> Short) = ComputedField(getter, ShortValueCodec) + fun computedLong(getter: LongSupplier) = ComputedLongField(getter) + fun computedFixedLong(getter: LongSupplier) = ComputedFixedLongField(getter) + fun computedFloat(getter: FloatSupplier) = ComputedFloatField(getter) + fun computedDouble(getter: DoubleSupplier) = ComputedDoubleField(getter) + fun computedUuid(getter: () -> UUID) = ComputedField(getter, UUIDValueCodec) + fun computedInt(getter: IntSupplier) = ComputedIntField(getter) + fun computedFixedInt(getter: IntSupplier) = ComputedFixedIntField(getter) + fun computedDecimal(getter: () -> Decimal) = ComputedField(getter, DecimalValueCodec) + fun computedBigDecimal(getter: () -> BigDecimal) = ComputedField(getter, BigDecimalValueCodec) + fun computedItem(getter: () -> ItemStack) = ComputedField(getter, ItemStackValueCodec) + fun computedString(getter: () -> String) = ComputedField(getter, BinaryStringCodec) + + fun computedByte(getter: KProperty0) = ComputedField(getter, ByteValueCodec) + fun computedBool(getter: KProperty0) = ComputedBooleanField(getter::get) + fun computedShort(getter: KProperty0) = ComputedField(getter, ShortValueCodec) + fun computedLong(getter: KProperty0) = ComputedLongField(getter::get) + fun computedFixedLong(getter: KProperty0) = ComputedFixedLongField(getter::get) + fun computedFloat(getter: KProperty0) = ComputedFloatField(getter::get) + fun computedDouble(getter: KProperty0) = ComputedDoubleField(getter::get) + fun computedUuid(getter: KProperty0) = ComputedField(getter, UUIDValueCodec) + fun computedInt(getter: KProperty0) = ComputedIntField(getter::get) + fun computedFixedInt(getter: KProperty0) = ComputedFixedIntField(getter::get) + fun computedDecimal(getter: KProperty0) = ComputedField(getter, DecimalValueCodec) + fun computedBigDecimal(getter: KProperty0) = ComputedField(getter, BigDecimalValueCodec) + fun computedItem(getter: KProperty0) = ComputedField(getter, ItemStackValueCodec) + fun computedString(getter: KProperty0) = ComputedField(getter, BinaryStringCodec) + + fun computedByte(getter: Supplier) = ComputedField(getter::get, ByteValueCodec) + fun computedBool(getter: Supplier) = ComputedBooleanField(getter::get) + fun computedShort(getter: Supplier) = ComputedField(getter::get, ShortValueCodec) + fun computedLong(getter: Supplier) = ComputedLongField(getter::get) + fun computedFixedLong(getter: Supplier) = ComputedFixedLongField(getter::get) + fun computedFloat(getter: Supplier) = ComputedFloatField(getter::get) + fun computedDouble(getter: Supplier) = ComputedDoubleField(getter::get) + fun computedUuid(getter: Supplier) = ComputedField(getter::get, UUIDValueCodec) + fun computedInt(getter: Supplier) = ComputedIntField(getter::get) + fun computedFixedInt(getter: Supplier) = ComputedFixedIntField(getter::get) + fun computedDecimal(getter: Supplier) = ComputedField(getter::get, DecimalValueCodec) + fun computedBigDecimal(getter: Supplier) = ComputedField(getter::get, BigDecimalValueCodec) + fun computedItem(getter: Supplier) = ComputedField(getter::get, ItemStackValueCodec) + fun computedString(getter: Supplier) = ComputedField(getter::get, BinaryStringCodec) + + fun > computedEnum(type: Class, getter: () -> T) = ComputedField(getter, EnumValueCodec(type)) + inline fun > computedEnum(noinline getter: () -> T) = ComputedField(getter, EnumValueCodec(T::class.java)) + + fun > computedEnum(type: Class, getter: KProperty0) = ComputedField(getter, EnumValueCodec(type)) + inline fun > computedEnum(getter: KProperty0) = ComputedField(getter, EnumValueCodec(T::class.java)) + + fun > computedEnum(type: Class, getter: Supplier) = ComputedField(getter::get, EnumValueCodec(type)) + inline fun > computedEnum(getter: Supplier) = ComputedField(getter::get, EnumValueCodec(T::class.java)) + + @JvmOverloads + fun byte( + value: Byte = 0, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, ByteValueCodec, getter, setter) + } + + @JvmOverloads + fun bool( + value: Boolean = false, + getter: BooleanFieldGetter? = null, + setter: BooleanFieldSetter? = null, + ): BooleanField { + return BooleanField(value, getter, setter) + } + + @JvmOverloads + fun short( + value: Short = 0, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, ShortValueCodec, getter, setter) + } + + @JvmOverloads + fun long( + value: Long = 0L, + getter: LongFieldGetter? = null, + setter: LongFieldSetter? = null, + ): LongField { + return LongField(value, getter, setter) + } + + @JvmOverloads + fun fixedLong( + value: Long = 0L, + getter: LongFieldGetter? = null, + setter: LongFieldSetter? = null, + ): FixedLongField { + return FixedLongField(value, getter, setter) + } + + @JvmOverloads + fun float( + value: Float = 0f, + getter: FloatFieldGetter? = null, + setter: FloatFieldSetter? = null, + ): FloatField { + return FloatField(value, getter, setter) + } + + @JvmOverloads + fun double( + value: Double = 0.0, + getter: DoubleFieldGetter? = null, + setter: DoubleFieldSetter? = null, + ): DoubleField { + return DoubleField(value, getter, setter) + } + + @JvmOverloads + fun uuid( + value: UUID = UUID(0L, 0L), + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, UUIDValueCodec, getter, setter) + } + + @JvmOverloads + fun int( + value: Int = 0, + getter: IntFieldGetter? = null, + setter: IntFieldSetter? = null, + ): IntField { + return IntField(value, getter, setter) + } + + @JvmOverloads + fun string( + value: String = "", + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, BinaryStringCodec, getter, setter) + } + + @JvmOverloads + fun fixedInt( + value: Int = 0, + getter: IntFieldGetter? = null, + setter: IntFieldSetter? = null, + ): FixedIntField { + return FixedIntField(value, getter, setter) + } + + @JvmOverloads + fun decimal( + value: Decimal = Decimal.ZERO, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, DecimalValueCodec, getter, setter) + } + + @JvmOverloads + fun bigDecimal( + value: BigDecimal = BigDecimal.ZERO, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, BigDecimalValueCodec, getter, setter) + } + + @JvmOverloads + fun > enum( + type: Class, + value: T = type.enumConstants[0], + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, EnumValueCodec(type), getter, setter) + } + + @JvmOverloads + fun > enum( + value: T, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + ): Field { + return Field(value, EnumValueCodec(value::class.java), getter, setter) + } + + @JvmOverloads + fun item( + value: ItemStack = ItemStack.EMPTY, + getter: FieldGetter? = null, + setter: FieldSetter? = null, + observe: Boolean = true, + ): Field { + return Field(value, ItemStackValueCodec, getter, setter, isObserver = observe) + } + + fun item( + delegate: KMutableProperty0, + ): ObservedField { + return ObservedField(delegate, ItemStackValueCodec) + } + + private var endpointsMaxCapacity = 1 + private val endpoints = ArrayList>(1) + val defaultEndpoint = Endpoint() + + private var nextEndpointsCleanup = secondTime + + private fun notifyEndpoints(dirtyField: AbstractField<*>) { + isDirty = true + + forEachEndpoint { + it.addDirtyField(dirtyField) + } + } + + private inline fun forEachEndpoint(execute: (Endpoint) -> Unit) { + nextEndpointsCleanup = secondTime + 60 + + synchronized(endpoints) { + endpoints.forValidRefs { execute.invoke(it) } + + if (endpoints.size < endpointsMaxCapacity / 2) { + endpoints.trimToSize() + endpointsMaxCapacity = endpoints.size + } + } + } + + inner class Endpoint { + init { + synchronized(endpoints) { + endpoints.add(WeakReference(this)) + endpointsMaxCapacity = endpointsMaxCapacity.coerceAtLeast(endpoints.size) + + if (secondTime >= nextEndpointsCleanup) { + nextEndpointsCleanup = secondTime + 60 + + val iterator = endpoints.listIterator() + + for (value in iterator) { + if (value.get() == null) { + iterator.remove() + } + } + + if (endpoints.size < endpointsMaxCapacity / 2) { + endpoints.trimToSize() + endpointsMaxCapacity = endpoints.size + } + } + } + } + + private val dirtyFields = ReferenceArraySet>(4) + + // use LinkedList because it is ensured memory is freed on LinkedList#clear + private val mapBacklogs = Reference2ObjectOpenHashMap, LinkedList Unit>>>() + private val setBacklogs = Reference2ObjectOpenHashMap, LinkedList Unit>>>() + + var unused: Boolean = false + private set + + fun markUnused() { + require(this === defaultEndpoint) { "This is not a default endpoint" } + if (unused) return + unused = true + mapBacklogs.clear() + dirtyFields.clear() + + val iterator = endpoints.listIterator() + + for (value in iterator) { + if (value.get() === this) { + iterator.remove() + } + } + } + + init { + markDirty() + } + + fun markDirty() { + for (field in fields) { + field?.markDirty(this) + } + } + + internal fun addDirtyField(field: AbstractField<*>) { + if (unused) { + return + } + + dirtyFields.add(field) + } + + internal fun removeDirtyField(field: AbstractField<*>) { + dirtyFields.remove(field) + } + + internal fun getMapBacklog(map: Map): LinkedList Unit>> { + if (unused) { + return LinkedList() + } + + return mapBacklogs.computeIfAbsent(map, Reference2ObjectFunction { + LinkedList() + }) + } + + internal fun removeMapBacklog(map: Map) { + mapBacklogs.remove(map) + } + + internal fun getSetBacklog(set: Set): LinkedList Unit>> { + if (unused) { + return LinkedList() + } + + return setBacklogs.computeIfAbsent(set, Reference2ObjectFunction { + LinkedList() + }) + } + + internal fun removeSetBacklog(set: Set) { + setBacklogs.remove(set) + } + + fun collectNetworkPayload(): FastByteArrayOutputStream? { + if (unused || dirtyFields.isEmpty()) { + return null + } + + val stream = FastByteArrayOutputStream() + val dataStream = DataOutputStream(stream) + + for (field in dirtyFields) { + stream.writeVarIntLE(field.id) + field.write(dataStream, this) + } + + dirtyFields.clear() + stream.write(0) + + return stream + } + } + + private val boundEndpoints = WeakHashMap() + + fun computeEndpointFor(obj: Any): Endpoint { + return boundEndpoints.computeIfAbsent(obj) { Endpoint() } + } + + fun removeEndpointFor(obj: Any): Endpoint? { + return boundEndpoints.remove(obj) + } + + fun endpointFor(obj: Any): Endpoint? { + return boundEndpoints[obj] + } + + @Suppress("LeakingThis") + abstract inner class AbstractField : IField { + val id: Int + + init { + if (freeSlots > 0) { + var found = -1 + + for (i in fields.indices) { + if (fields[i] == null) { + fields[i] = this + found = i + 1 + freeSlots-- + break + } + } + + if (found == -1) { + throw RuntimeException("freeSlots = $freeSlots but no null entries in field list!") + } else { + id = found + } + } else { + fields.add(this) + id = fields.size + isEmpty = false + } + } + + final override var isRemoved = false + private set + + protected var isDirty = false + + override fun remove() { + if (isRemoved) + return + + isRemoved = true + freeSlots++ + fields[id - 1] = null + observers.remove(this) + isEmpty = fields.all { it == null } + + while (fields[fields.size - 1] == null) { + fields.removeAt(fields.size - 1) + freeSlots-- + } + + forEachEndpoint { + it.removeDirtyField(this) + } + } + + override fun markDirty(endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + endpoint.addDirtyField(this) + } + + override fun markDirty() { + check(!isRemoved) { "Field was removed" } + notifyEndpoints(this@AbstractField) + isDirty = true + } + } + + /** + * Networked variable with backing field holding immutable value + */ + inner class Field( + private var field: V, + private val codec: IStreamCodec, + private val getter: FieldGetter? = null, + private val setter: FieldSetter? = null, + isObserver: Boolean = false, + ) : AbstractField(), IMutableField { + private var remote: V = codec.copy(field) + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } + + init { + if (isObserver) { + observers.add(this) + } + } + + private val access = object : FieldAccess { + override fun read(): V { + return field + } + + override fun write(value: V) { + if (!isDirty && !codec.compare(remote, value)) { + notifyEndpoints(this@Field) + isDirty = true + } + + this@Field.field = value + subs.accept(value) + } + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + if (!isDirty && !codec.compare(remote, field)) { + notifyEndpoints(this@Field) + isDirty = true + } + + return isDirty + } + + override var value: V + get() { + val getter = this.getter + + if (getter != null) { + return getter.invoke(access) + } + + return this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else { + if (!isDirty && !codec.compare(remote, value)) { + notifyEndpoints(this@Field) + isDirty = true + } + + this.field = value + subs.accept(value) + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + codec.write(stream, field) + isDirty = false + remote = codec.copy(field) + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = codec.read(stream) + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + subs.accept(value) + } + } + } + + abstract inner class PrimitiveField : AbstractField() { + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + return isDirty + } + } + + /** + * Type specific field, storing primitive [Float] directly + */ + inner class FloatField(field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField(), IMutableFloatField { + private val subs = IFloatSubcripable.Impl() + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + + override val property = object : IMutableFloatProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Float { + return this@FloatField.float + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Float) { + this@FloatField.float = value + } + } + + private val access = object : FloatFieldAccess { + override fun readFloat(): Float { + return this@FloatField.field + } + + override fun write(value: Float) { + if (!isDirty && value != this@FloatField.field) { + notifyEndpoints(this@FloatField) + isDirty = true + } + + this@FloatField.field = value + } + } + + override var float: Float + get() { + return getter?.invoke(access) ?: this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else if (value != this.field) { + if (!isDirty) { + notifyEndpoints(this) + isDirty = true + } + + this.field = value + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeFloat(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readFloat() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Double] directly + */ + inner class DoubleField(field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField(), IMutableDoubleField { + private val subs = IDoubleSubcripable.Impl() + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + + override val property = object : IMutableDoubleProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Double { + return this@DoubleField.double + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) { + this@DoubleField.double = value + } + } + + private val access = object : DoubleFieldAccess { + override fun readDouble(): Double { + return this@DoubleField.field + } + + override fun write(value: Double) { + if (!isDirty && value != this@DoubleField.field) { + notifyEndpoints(this@DoubleField) + isDirty = true + } + + this@DoubleField.field = value + } + } + + override var double: Double + get() { + return getter?.invoke(access) ?: this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else if (value != this.field) { + if (!isDirty) { + notifyEndpoints(this) + isDirty = true + } + + this.field = value + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeDouble(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readDouble() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + abstract inner class AbstractIntField(field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField(), IMutableIntField { + private val subs = IIntSubcripable.Impl() + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + protected var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + + final override val property = object : IMutableIntProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return this@AbstractIntField.int + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + this@AbstractIntField.int = value + } + } + + protected val access = object : IntFieldAccess { + override fun readInt(): Int { + return this@AbstractIntField.field + } + + override fun write(value: Int) { + if (!isDirty && value != this@AbstractIntField.field) { + notifyEndpoints(this@AbstractIntField) + isDirty = true + } + + this@AbstractIntField.field = value + } + } + + final override var int: Int + get() { + return getter?.invoke(access) ?: this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else if (value != this.field) { + if (!isDirty) { + notifyEndpoints(this) + isDirty = true + } + + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Int] directly, and networking it as variable length integer + */ + inner class IntField(field: Int, getter: IntFieldGetter? = null, setter: IntFieldSetter? = null) : AbstractIntField(field, getter, setter) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeVarIntLE(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readVarIntLE() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Int] directly, and networking it as 4 octets + */ + inner class FixedIntField(field: Int, getter: IntFieldGetter? = null, setter: IntFieldSetter? = null) : AbstractIntField(field, getter, setter) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeInt(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readInt() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Long] directly + */ + abstract inner class AbstractLongField(field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField(), IMutableLongField { + private val subs = ILongSubcripable.Impl() + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + protected var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + + final override val property = object : IMutableLongProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Long { + return this@AbstractLongField.long + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) { + this@AbstractLongField.long = value + } + } + + protected val access = object : LongFieldAccess { + override fun readLong(): Long { + return this@AbstractLongField.field + } + + override fun write(value: Long) { + if (!isDirty && value != this@AbstractLongField.field) { + notifyEndpoints(this@AbstractLongField) + isDirty = true + } + + this@AbstractLongField.field = value + } + } + + final override var long: Long + get() { + return getter?.invoke(access) ?: this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else if (value != this.field) { + if (!isDirty) { + notifyEndpoints(this) + isDirty = true + } + + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Long] directly, and networking it as variable length integer + */ + inner class LongField(field: Long, getter: LongFieldGetter? = null, setter: LongFieldSetter? = null) : AbstractLongField(field, getter, setter) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeVarLongLE(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readVarLongLE() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Long] directly, and networking it as 8 octets + */ + inner class FixedLongField(field: Long, getter: LongFieldGetter? = null, setter: LongFieldSetter? = null) : AbstractLongField(field, getter, setter) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeLong(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readLong() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Type specific field, storing primitive [Boolean] directly + */ + inner class BooleanField(field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField(), IMutableBooleanField { + private val subs = IBooleanSubscriptable.Impl() + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + + override val property = object : IMutableBooleanProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return this@BooleanField.boolean + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + this@BooleanField.boolean = value + } + } + + private val access = object : BooleanFieldAccess { + override fun readBoolean(): Boolean { + return this@BooleanField.field + } + + override fun write(value: Boolean) { + if (!isDirty && value != this@BooleanField.field) { + notifyEndpoints(this@BooleanField) + isDirty = true + } + + this@BooleanField.field = value + } + } + + override var boolean: Boolean + get() { + return getter?.invoke(access) ?: this.field + } + set(value) { + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, false) + } else if (value != this.field) { + if (!isDirty) { + notifyEndpoints(this) + isDirty = true + } + + this.field = value + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeBoolean(this.field) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val value = stream.readBoolean() + val setter = this.setter + + if (setter != null) { + setter.invoke(value, access, true) + } else { + this.field = value + } + } + } + + /** + * Networked value with backing getter which is constantly polled + */ + inner class ComputedField( + private val getter: () -> V, + private val codec: IStreamCodec, + observer: ((new: V) -> Unit)? = null + ) : AbstractField(), IField { + private var remote: Any? = Mark + private var clientValue: Any? = Mark + private val subs = ISubscriptable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } + + init { + observers.add(this) + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = value + + if (!isDirty && (remote === Mark || !codec.compare(remote as V, value))) { + notifyEndpoints(this) + isDirty = true + remote = codec.copy(value) + } + + return isDirty + } + + override val value: V + get() { + val clientValue = clientValue + + if (clientValue === Mark) { + return getter.invoke() + } else { + return clientValue as V + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + codec.write(stream, value) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val newValue = codec.read(stream) + clientValue = newValue + subs.accept(newValue) + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Float] primitive + */ + inner class ComputedFloatField(private val getter: FloatSupplier, observer: FloatConsumer? = null) : AbstractField(), IFloatField { + private var remote: Float = 0f + private var isRemoteSet = false + private var clientValue: Float = 0f + private var isClientValue = false + private val subs = IFloatSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + override val property = object : IFloatProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Float { + return float + } + } + + init { + observers.add(this) + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = getter.getAsFloat() + + if (!isDirty && (!isRemoteSet || remote != value)) { + notifyEndpoints(this) + isDirty = true + isRemoteSet = true + remote = value + } + + return isDirty + } + + override fun markDirty() { + check(!isRemoved) { "Field was removed" } + notifyEndpoints(this) + isDirty = true + } + + override val float: Float + get() { + if (isClientValue) { + return clientValue + } else { + return getter.getAsFloat() + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeFloat(getter.getAsFloat()) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val newValue = stream.readFloat() + clientValue = newValue + isClientValue = true + subs.accept(newValue) + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Float { + return float + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Double] primitive + */ + inner class ComputedDoubleField(private val getter: DoubleSupplier, observer: DoubleConsumer? = null) : AbstractField(), IDoubleField { + private var remote: Double = 0.0 + private var isRemoteSet = false + private var clientValue: Double = 0.0 + private var isClientValue = false + private val subs = IDoubleSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + override val property = object : IDoubleProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Double { + return double + } + } + + init { + observers.add(this) + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = getter.asDouble + + if (!isDirty && (!isRemoteSet || remote != value)) { + notifyEndpoints(this) + isDirty = true + isRemoteSet = true + remote = value + } + + return isDirty + } + + override val double: Double + get() { + if (isClientValue) { + return clientValue + } else { + return getter.asDouble + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeDouble(getter.asDouble) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val newValue = stream.readDouble() + clientValue = newValue + isClientValue = true + subs.accept(newValue) + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Double { + return double + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Int] primitive + */ + abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, observer: IntConsumer? = null) : AbstractField(), IIntField { + private var remote: Int = 0 + private var isRemoteSet = false + protected var clientValue: Int = 0 + set(value) { + isClientValue = true + + if (field != value) { + field = value + subs.accept(value) + } + } + + protected var isClientValue = false + private val subs = IIntSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + final override val property = object : IIntProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return int + } + } + + init { + observers.add(this) + } + + final override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = getter.asInt + + if (!isDirty && (!isRemoteSet || remote != value)) { + notifyEndpoints(this) + isDirty = true + isRemoteSet = true + remote = value + } + + return isDirty + } + + final override val int: Int + get() { + if (isClientValue) { + return clientValue + } else { + return getter.asInt + } + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return int + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Int] primitive, networking it as variable length integer + */ + inner class ComputedIntField(getter: IntSupplier, observer: IntConsumer = IntConsumer {}) : AbstractComputedIntField(getter, observer) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeVarIntLE(getter.asInt) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + clientValue = stream.readVarIntLE() + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Int] primitive, networking it as 4 octets + */ + inner class ComputedFixedIntField(getter: IntSupplier, observer: IntConsumer = IntConsumer {}) : AbstractComputedIntField(getter, observer) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeInt(getter.asInt) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + clientValue = stream.readInt() + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Long] primitive + */ + abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, observer: LongConsumer? = null) : AbstractField(), ILongField { + private var remote: Long = 0L + private var isRemoteSet = false + protected var clientValue: Long = 0L + set(value) { + isClientValue = true + + if (field != value) { + field = value + subs.accept(value) + } + } + + protected var isClientValue = false + private val subs = ILongSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + final override val property = object : ILongProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Long { + return long + } + } + + init { + observers.add(this) + } + + final override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = getter.asLong + + if (!isDirty && (!isRemoteSet || remote != value)) { + notifyEndpoints(this) + isDirty = true + isRemoteSet = true + remote = value + } + + return isDirty + } + + final override val long: Long + get() { + if (isClientValue) { + return clientValue + } else { + return getter.asLong + } + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Long { + return long + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Long] primitive, networking it as variable length integer + */ + inner class ComputedLongField(getter: LongSupplier, observer: LongConsumer = LongConsumer {}) : AbstractComputedLongField(getter, observer) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeVarLongLE(getter.asLong) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + clientValue = stream.readVarLongLE() + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Long] primitive, networking it as 8 octets + */ + inner class ComputedFixedLongField(getter: LongSupplier, observer: LongConsumer = LongConsumer {}) : AbstractComputedLongField(getter, observer) { + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeLong(getter.asLong) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + clientValue = stream.readLong() + } + } + + /** + * Networked value with backing getter which is constantly polled + * + * This class has concrete implementation for [Boolean] primitive + */ + inner class ComputedBooleanField(private val getter: BooleanSupplier, observer: BooleanConsumer? = null) : AbstractField(), IBooleanField { + private var remote: Boolean = false + private var isRemoteSet = false + private var clientValue: Boolean = false + private var isClientValue = false + private val subs = IBooleanSubscriptable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + override val property = object : IBooleanProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return boolean + } + } + + init { + observers.add(this) + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = getter.asBoolean + + if (!isDirty && (!isRemoteSet || remote != value)) { + notifyEndpoints(this) + isDirty = true + isRemoteSet = true + remote = value + } + + return isDirty + } + + override val boolean: Boolean + get() { + if (isClientValue) { + return clientValue + } else { + return getter.asBoolean + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + stream.writeBoolean(getter.asBoolean) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + val newValue = stream.readBoolean() + clientValue = newValue + isClientValue = true + subs.accept(newValue) + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return boolean + } + } + + /** + * Networked variable with backing field holding mutable value, which is constantly observed for changes + */ + inner class ObservedField : AbstractField, IMutableField { + private val codec: IStreamCodec + private val getter: () -> V + private val setter: (V) -> Unit + private var remote: V + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } + + override var value: V + get() = getter.invoke() + set(value) { setter.invoke(value) } + + constructor(field: KMutableProperty0, codec: IStreamCodec) : super() { + this.codec = codec + getter = field::get + setter = field::set + remote = codec.copy(value) + } + + constructor(getter: () -> V, setter: (V) -> Unit, codec: IStreamCodec) : super() { + this.codec = codec + this.getter = getter + this.setter = setter + remote = codec.copy(value) + } + + init { + observers.add(this) + } + + override fun observe(): Boolean { + check(!isRemoved) { "Field was removed" } + + val value = value + + if (!isDirty && !codec.compare(remote, value)) { + notifyEndpoints(this) + isDirty = true + remote = codec.copy(value) + } + + return isDirty + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + codec.write(stream, value) + isDirty = false + } + + override fun read(stream: DataInputStream) { + check(!isRemoved) { "Field was removed" } + this.value = codec.read(stream) + subs.accept(this.value) + } + } + + inner class Set( + private val codec: IStreamCodec, + private val backingSet: MutableSet, + private val callback: ((changes: Collection>) -> Unit)? = null, + ) : AbstractField>(), ISubscriptable> by ISubscriptable.empty() { + private var isRemote = false + + private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) { + check(!isRemote) { "Field marked as remote" } + + val pair = element to action + + forEachEndpoint { + val list = it.getSetBacklog(this) + val iterator = list.listIterator() + + for (value in iterator) { + if (value.first == element) { + iterator.remove() + } + } + + list.addLast(pair) + } + } + + override fun observe(): Boolean { + return false + } + + override fun remove() { + if (!isRemoved) { + forEachEndpoint { it.removeSetBacklog(this) } + } + + super.remove() + } + + override fun markDirty() { + check(!isRemoved) { "Field was removed" } + + if (isRemote || endpoints.isEmpty()) + return + + isDirty = true + + val endpoints = LinkedList Unit>>>() + + forEachEndpoint { + endpoints.add(it.getSetBacklog(this)) + it.addDirtyField(this) + } + + endpoints.forEach { + it.clear() + it.add(null to ClearBacklogEntry) + } + + for (value in backingSet) { + val valueCopy = codec.copy(value) + val pair: Pair Unit> = valueCopy to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, valueCopy) } + + endpoints.forEach { + it.add(pair) + } + } + } + + override fun markDirty(endpoint: Endpoint) { + super.markDirty(endpoint) + + endpoint.getSetBacklog(this).let { + it.clear() + it.add(null to ClearBacklogEntry) + + for (value in backingSet) { + val valueCopy = codec.copy(value) + it.add(valueCopy to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, valueCopy) }) + } + } + } + + override val value: MutableSet = object : MutableSet { + override fun add(element: E): Boolean { + if (backingSet.add(element)) { + if (!isRemote) { + markDirty() + + val copy = codec.copy(element) + + pushBacklog(element) { + it.write(ChangesetAction.ADD.ordinal + 1) + codec.write(it, copy) + } + } + + return true + } + + return false + } + + override fun addAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = add(it) || any } + return any + } + + override fun clear() { + if (backingSet.isNotEmpty()) { + backingSet.clear() + + if (!isRemote) { + markDirty() + + forEachEndpoint { + it.getSetBacklog(this@Set).let { + it.clear() + it.add(null to ClearBacklogEntry) + } + } + } + } + } + + override fun iterator(): MutableIterator { + return object : MutableIterator { + private val parent = backingSet.iterator() + private var lastElement: Any? = Mark + + override fun hasNext(): Boolean { + return parent.hasNext() + } + + override fun next(): E { + return parent.next().also { lastElement = it } + } + + override fun remove() { + parent.remove() + val lastElement = lastElement + + if (lastElement !== Mark) { + this.lastElement = Mark + + if (!isRemote) { + markDirty() + + @Suppress("unchecked_cast") + pushBacklog(lastElement as E) { + it.write(ChangesetAction.REMOVE.ordinal + 1) + codec.write(it, lastElement as E) + } + } + } + } + } + } + + override fun remove(element: E): Boolean { + if (backingSet.remove(element)) { + if (!isRemote) { + markDirty() + + val copy = codec.copy(element) + + pushBacklog(element) { + it.write(ChangesetAction.REMOVE.ordinal + 1) + codec.write(it, copy) + } + } + + return true + } + + return false + } + + override fun removeAll(elements: Collection): Boolean { + var any = false + elements.forEach { any = remove(it) || any } + return any + } + + override fun retainAll(elements: Collection): Boolean { + var any = false + + val iterator = iterator() + + for (value in iterator) { + if (value !in elements) { + any = true + iterator.remove() + } + } + + return any + } + + override val size: Int + get() = backingSet.size + + override fun contains(element: E): Boolean { + return element in backingSet + } + + override fun containsAll(elements: Collection): Boolean { + return backingSet.containsAll(elements) + } + + override fun isEmpty(): Boolean { + return backingSet.isEmpty() + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + val list = endpoint.getSetBacklog(this) + + for (value in list) { + value.second.invoke(stream) + } + + stream.write(0) + list.clear() + isDirty = false + } + + override fun read(stream: DataInputStream) { + if (!isRemote) { + isRemote = true + forEachEndpoint { it.removeSetBacklog(this) } + } + + isDirty = false + + var action = stream.read() + val changeset = LinkedList>() + + while (action != 0) { + if ((action - 1) !in ChangesetActionList.indices) { + throw IllegalArgumentException("Unknown changeset action with index ${action - 1}") + } + + when (ChangesetActionList[action - 1]) { + ChangesetAction.CLEAR -> { + changeset.add(SetChangeset(ChangesetAction.CLEAR, null)) + backingSet.clear() + } + + ChangesetAction.ADD -> { + val read = codec.read(stream) + changeset.add(SetChangeset(ChangesetAction.ADD, read)) + backingSet.add(read) + } + + ChangesetAction.REMOVE -> { + val read = codec.read(stream) + changeset.add(SetChangeset(ChangesetAction.REMOVE, read)) + backingSet.remove(read) + } + } + + action = stream.read() + } + + callback?.invoke(changeset) + } + } + + inner class Map( + private val keyCodec: IStreamCodec, + private val valueCodec: IStreamCodec, + private val backingMap: MutableMap, + private val callback: ((changes: Collection>) -> Unit)? = null, + ) : AbstractField>(), IField>, ISubscriptable> by ISubscriptable.empty() { + private var sentAllValues = false + private var isRemote = false + + private fun pushBacklog(key: Any?, value: (DataOutputStream) -> Unit) { + val pair = key to value + + forEachEndpoint { + val list = it.getMapBacklog(this) + val iterator = list.listIterator() + + for (e in iterator) { + if (e.first == key) { + iterator.remove() + } + } + + list.addLast(pair) + } + } + + override fun observe(): Boolean { + return false + } + + override fun markDirty() { + check(!isRemoved) { "Field was removed" } + + if (isRemote || endpoints.isEmpty()) + return + + isDirty = true + val backlogs = LinkedList Unit>>>() + + forEachEndpoint { + it.addDirtyField(this) + val value = it.getMapBacklog(this) + backlogs.add(value) + value.clear() + value.add(null to ClearBacklogEntry) + } + + for ((key, value) in backingMap) { + val valueCopy = valueCodec.copy(value) + + val action = key to { it: DataOutputStream -> + it.write(ChangesetAction.ADD.ordinal + 1) + keyCodec.write(it, key) + valueCodec.write(it, valueCopy) + } + + for (backlog in backlogs) { + backlog.add(action) + } + } + } + + override fun markDirty(endpoint: Endpoint) { + check(!isRemoved) { "Field was removed" } + + if (isRemote) + return + + val backlog = endpoint.getMapBacklog(this) + + backlog.clear() + backlog.add(null to ClearBacklogEntry) + + for ((key, value) in backingMap) { + val valueCopy = valueCodec.copy(value) + + backlog.add(key to { + it.write(ChangesetAction.ADD.ordinal + 1) + keyCodec.write(it, key) + valueCodec.write(it, valueCopy) + }) + } + } + + private fun lmarkDirty() { + if (!isDirty) { + notifyEndpoints(this@Map) + isDirty = true + } + } + + override val value: MutableMap = object : ProxiedMap(backingMap) { + override fun onClear() { + if (isRemote || endpoints.isEmpty()) + return + + forEachEndpoint { endpoint -> + endpoint.getMapBacklog(this@Map).let { + it.clear() + it.add(null to ClearBacklogEntry) + } + } + + lmarkDirty() + this@FieldSynchronizer.isDirty = true + } + + override fun onValueAdded(key: K, value: V) { + if (isRemote || endpoints.isEmpty()) + return + + val keyCopy = keyCodec.copy(key) + val valueCopy = valueCodec.copy(value) + + pushBacklog(key) { + @Suppress("BlockingMethodInNonBlockingContext") // false positive + it.write(ChangesetAction.ADD.ordinal + 1) + keyCodec.write(it, keyCopy) + valueCodec.write(it, valueCopy) + } + + lmarkDirty() + } + + override fun onValueRemoved(key: K, value: V) { + if (isRemote || endpoints.isEmpty()) + return + + val keyCopy = keyCodec.copy(key) + + pushBacklog(key) { + @Suppress("BlockingMethodInNonBlockingContext") // false positive + it.write(ChangesetAction.REMOVE.ordinal + 1) + keyCodec.write(it, keyCopy) + } + + lmarkDirty() + } + } + + override fun write(stream: DataOutputStream, endpoint: Endpoint) { + sentAllValues = false + isDirty = false + + val iterator = endpoint.getMapBacklog(this).listIterator() + + for (entry in iterator) { + entry.second.invoke(stream) + iterator.remove() + } + + stream.write(0) + } + + override fun read(stream: DataInputStream) { + if (!isRemote) { + isRemote = true + forEachEndpoint { it.removeMapBacklog(this) } + } + + isDirty = false + + val changeset = LinkedList>() + var readAction = stream.read() - 1 + + while (readAction != -1) { + if (readAction >= ChangesetActionList.size) { + throw IndexOutOfBoundsException("Unknown map action with ID $readAction") + } + + when (ChangesetActionList[readAction]) { + ChangesetAction.CLEAR -> { + backingMap.clear() + changeset.add(ClearMapChangeset) + } + + ChangesetAction.ADD -> { + val key = keyCodec.read(stream) + val value = valueCodec.read(stream) + backingMap[key] = value + changeset.add(MapChangeset(ChangesetAction.ADD, key, value)) + } + + ChangesetAction.REMOVE -> { + val key = keyCodec.read(stream) + backingMap.remove(key) + changeset.add(MapChangeset(ChangesetAction.REMOVE, key, null)) + } + } + + readAction = stream.read() - 1 + } + + if (changeset.size != 0) { + callback?.invoke(changeset) + } + } + } + + /** + * marks all fields dirty, invalidates mappings for each endpoint + */ + fun invalidate() { + for (field in fields) { + field?.markDirty() + } + } + + /** + * Observe changes of all fields with backing computation lambda + */ + fun observe(): Boolean { + var changes = false + + if (observers.isNotEmpty()) { + for (field in observers) { + changes = field.observe() || changes + } + } + + return changes + } + + /** + * [defaultEndpoint]#collectNetworkPayload + */ + fun collectNetworkPayload(): FastByteArrayOutputStream? { + check(!defaultEndpoint.unused) { "Default endpoint is not used" } + observe() + val values = defaultEndpoint.collectNetworkPayload() + markClean() + return values + } + + fun read(stream: InputStream): Int { + var fieldId = stream.readVarIntLE() + var i = 0 + val dataStream = DataInputStream(stream) + + while (fieldId != 0) { + val field = fields.getOrNull(fieldId - 1) ?: throw IllegalArgumentException("Unknown field ID ${fieldId - 1}") + field.read(dataStream) + fieldId = stream.readVarIntLE() + i++ + } + + return i + } + + private object Mark + + companion object { + private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(ChangesetAction.CLEAR.ordinal + 1) } + private val ChangesetActionList = ChangesetAction.values() + private val ClearMapChangeset = MapChangeset(ChangesetAction.CLEAR, null, null) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt new file mode 100644 index 000000000..c3276c8cd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt @@ -0,0 +1,173 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +import ru.dbotthepony.mc.otm.core.FloatSupplier +import ru.dbotthepony.mc.otm.core.IBooleanSubscriptable +import ru.dbotthepony.mc.otm.core.IDoubleSubcripable +import ru.dbotthepony.mc.otm.core.IFloatSubcripable +import ru.dbotthepony.mc.otm.core.IIntSubcripable +import ru.dbotthepony.mc.otm.core.ILongSubcripable +import ru.dbotthepony.mc.otm.core.ISubscriptable +import java.io.DataInputStream +import java.io.DataOutputStream +import java.util.function.BooleanSupplier +import java.util.function.DoubleSupplier +import java.util.function.IntSupplier +import java.util.function.LongSupplier +import java.util.function.Supplier +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +sealed interface IField : ReadOnlyProperty, Supplier, () -> V, ISubscriptable { + fun observe(): Boolean + fun markDirty() + fun markDirty(endpoint: FieldSynchronizer.Endpoint) + val value: V + val isRemoved: Boolean + + fun remove() + + fun write(stream: DataOutputStream, endpoint: FieldSynchronizer.Endpoint) + fun read(stream: DataInputStream) + + override fun getValue(thisRef: Any?, property: KProperty<*>): V { + return this.value + } + + override fun get(): V { + return value + } + + override fun invoke(): V { + return value + } +} + +interface IFloatProperty { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Float +} + +sealed interface IFloatField : IField, FloatSupplier, IFloatSubcripable { + val float: Float + val property: IFloatProperty + + override val value: Float + get() = float + + @Deprecated("Use type specific Supplier interface") + override fun get(): Float { + return float + } + + override fun getAsFloat(): Float { + return float + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Float { + return float + } +} + +interface IDoubleProperty { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Double +} + +sealed interface IDoubleField : IField, DoubleSupplier, IDoubleSubcripable { + val double: Double + val property: IDoubleProperty + + @Deprecated("Use type specific Supplier interface") + override fun get(): Double { + return double + } + + override val value: Double + get() = double + + override fun getAsDouble(): Double { + return double + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Double { + return double + } +} + +interface IIntProperty { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Int +} + +sealed interface IIntField : IField, IntSupplier, IIntSubcripable { + val int: Int + val property: IIntProperty + + @Deprecated("Use type specific Supplier interface") + override fun get(): Int { + return int + } + + override val value: Int + get() = int + + override fun getAsInt(): Int { + return int + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return int + } +} + +interface ILongProperty { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Long +} + +sealed interface ILongField : IField, LongSupplier, ILongSubcripable { + val long: Long + val property: ILongProperty + + @Deprecated("Use type specific Supplier interface") + override fun get(): Long { + return long + } + + override val value: Long + get() = long + + override fun getAsLong(): Long { + return long + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Long { + return long + } +} + +interface IBooleanProperty { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean +} + +sealed interface IBooleanField : IField, BooleanSupplier, IBooleanSubscriptable { + val boolean: Boolean + val property: IBooleanProperty + + @Deprecated("Use type specific Supplier interface") + override fun get(): Boolean { + return boolean + } + + override val value: Boolean + get() = boolean + + override fun getAsBoolean(): Boolean { + return boolean + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return boolean + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MapChangeset.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MapChangeset.kt new file mode 100644 index 000000000..47b1f5105 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MapChangeset.kt @@ -0,0 +1,23 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +data class MapChangeset( + val action: ChangesetAction, + val key: K?, + val value: V? +) { + inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit) { + when (action) { + ChangesetAction.ADD -> add.invoke(key!!, value!!) + ChangesetAction.REMOVE -> remove.invoke(key!!) + else -> {} + } + } + + inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit, clear: () -> Unit) { + when (action) { + ChangesetAction.CLEAR -> clear.invoke() + ChangesetAction.ADD -> add.invoke(key!!, value!!) + ChangesetAction.REMOVE -> remove.invoke(key!!) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt new file mode 100644 index 000000000..4f385ad01 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt @@ -0,0 +1,166 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer +import it.unimi.dsi.fastutil.floats.FloatConsumer +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.SentientGetterSetter +import java.util.function.DoubleConsumer +import java.util.function.IntConsumer +import java.util.function.LongConsumer +import kotlin.reflect.KProperty + +sealed interface IMutableField : IField, SentientGetterSetter { + override fun getValue(thisRef: Any?, property: KProperty<*>): V { + return this.value + } + + override var value: V + + override fun accept(t: V) { + value = t + } + + override fun invoke(): V { + return this.value + } +} + +interface IMutableFloatProperty : IFloatProperty { + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Float) +} + +sealed interface IMutableFloatField : IMutableField, IFloatField, FloatConsumer { + override var float: Float + override val property: IMutableFloatProperty + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.float")) + override var value: Float + get() = float + set(value) { float = value } + + override fun accept(t: Float) { + float = t + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Float { + return float + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Float) { + float = value + } +} + +interface IMutableDoubleProperty : IDoubleProperty { + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) +} + +sealed interface IMutableDoubleField : IMutableField, IDoubleField, DoubleConsumer { + override var double: Double + override val property: IMutableDoubleProperty + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.double")) + override var value: Double + get() = double + set(value) { double = value } + + override fun accept(t: Double) { + double = t + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Double { + return double + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) { + double = value + } +} + +interface IMutableIntProperty : IIntProperty { + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) +} + +sealed interface IMutableIntField : IMutableField, IIntField, IntConsumer { + override var int: Int + override val property: IMutableIntProperty + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.int")) + override var value: Int + get() = int + set(value) { int = value } + + override fun accept(t: Int) { + int = t + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return int + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + int = value + } +} + +interface IMutableLongProperty : ILongProperty { + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) +} + +sealed interface IMutableLongField : IMutableField, ILongField, LongConsumer { + override var long: Long + override val property: IMutableLongProperty + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.long")) + override var value: Long + get() = long + set(value) { long = value } + + override fun accept(t: Long) { + long = t + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Long { + return long + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) { + long = value + } +} + +interface IMutableBooleanProperty : IBooleanProperty { + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) +} + +sealed interface IMutableBooleanField : IMutableField, IBooleanField, BooleanConsumer { + override var boolean: Boolean + override val property: IMutableBooleanProperty + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.boolean")) + override var value: Boolean + get() = boolean + set(value) { boolean = value } + + override fun accept(t: Boolean) { + boolean = t + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { + return boolean + } + + @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { + boolean = value + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/SetChangeset.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/SetChangeset.kt new file mode 100644 index 000000000..8c8894616 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/SetChangeset.kt @@ -0,0 +1,22 @@ +package ru.dbotthepony.mc.otm.network.synchronizer + +data class SetChangeset( + val action: ChangesetAction, + val value: V? +) { + inline fun map(add: (V) -> Unit, remove: (V) -> Unit) { + when (action) { + ChangesetAction.ADD -> add.invoke(value!!) + ChangesetAction.REMOVE -> remove.invoke(value!!) + else -> {} + } + } + + inline fun map(add: (V) -> Unit, remove: (V) -> Unit, clear: () -> Unit) { + when (action) { + ChangesetAction.CLEAR -> clear.invoke() + ChangesetAction.ADD -> add.invoke(value!!) + ChangesetAction.REMOVE -> remove.invoke(value!!) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/EnergyContainerRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/EnergyContainerRecipe.kt index cf2ec4a80..8990a7ca2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/EnergyContainerRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/EnergyContainerRecipe.kt @@ -1,49 +1,83 @@ package ru.dbotthepony.mc.otm.recipe import com.google.gson.JsonObject +import com.mojang.serialization.Codec import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.world.inventory.CraftingContainer import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.CraftingBookCategory import net.minecraft.world.item.crafting.CraftingRecipe import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.item.crafting.ShapedRecipe import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.capability.ItemEnergyStorageImpl +import net.minecraftforge.common.crafting.IShapedRecipe import ru.dbotthepony.mc.otm.capability.matteryEnergy -import ru.dbotthepony.mc.otm.container.iterator -import ru.dbotthepony.mc.otm.container.stream -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.container.util.stream +import ru.dbotthepony.mc.otm.core.filterNotNull -class EnergyContainerRecipe( - p_44153_: ResourceLocation, - p_44154_: String, - p_44155_: Int, - p_44156_: Int, - p_44157_: NonNullList, - p_44158_: ItemStack, -) : ShapedRecipe(p_44153_, p_44154_, p_44155_, p_44156_, p_44157_, p_44158_) { - constructor(parent: ShapedRecipe) : this(parent.id, parent.group, parent.width, parent.height, parent.ingredients, parent.resultItem) +class EnergyContainerRecipe(val parent: ShapedRecipe) : CraftingRecipe, IShapedRecipe by parent { + override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean { + return parent.canCraftInDimensions(p_43999_, p_44000_) + } + + override fun getResultItem(): ItemStack { + return parent.getResultItem() + } + + override fun category(): CraftingBookCategory { + return parent.category() + } + + override fun getRemainingItems(p_44004_: CraftingContainer): NonNullList { + return parent.getRemainingItems(p_44004_) + } + + override fun getIngredients(): NonNullList { + return parent.ingredients + } + + override fun isSpecial(): Boolean { + return parent.isSpecial + } + + override fun getGroup(): String { + return parent.group + } + + override fun getToastSymbol(): ItemStack { + return parent.toastSymbol + } + + override fun isIncomplete(): Boolean { + return parent.isIncomplete + } + + override fun getType(): RecipeType<*> { + return parent.type + } override fun assemble(container: CraftingContainer): ItemStack { - val itemStack = super.assemble(container) + val itemStack = parent.assemble(container) val battery = container.stream() .filter { !it.isEmpty } .map { it.matteryEnergy } - .filter { it != null } + .filterNotNull() .findAny().orElse(null) if (battery != null) { - itemStack.tagNotNull[ItemEnergyStorageImpl.ENERGY_KEY] = battery.batteryLevel.serializeNBT() + itemStack.matteryEnergy?.batteryLevel = battery.batteryLevel } if (itemStack.isEnchantable) { for (it in container.iterator()) { - if (!it.isEmpty && it.isEnchanted) { + if (it.isEnchanted) { for ((key, value) in it.allEnchantments) { itemStack.enchant(key, value) } @@ -54,25 +88,25 @@ class EnergyContainerRecipe( return itemStack } - override fun matches(p_44002_: CraftingContainer, p_44003_: Level): Boolean { - return super.matches(p_44002_, p_44003_) && !p_44002_.stream().anyMatch { it.isDamaged } + override fun matches(container: CraftingContainer, level: Level): Boolean { + return parent.matches(container, level) && !container.stream().anyMatch { it.isDamaged } } - override fun getSerializer(): RecipeSerializer<*> { + override fun getSerializer(): RecipeSerializer { return Companion } companion object : RecipeSerializer { + override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): EnergyContainerRecipe? { + return ShapedRecipe.Serializer.SHAPED_RECIPE.fromNetwork(id, data)?.let(::EnergyContainerRecipe) + } + override fun fromJson(p_44103_: ResourceLocation, p_44104_: JsonObject): EnergyContainerRecipe { - return EnergyContainerRecipe(Serializer.SHAPED_RECIPE.fromJson(p_44103_, p_44104_)) + return EnergyContainerRecipe(ShapedRecipe.Serializer.SHAPED_RECIPE.fromJson(p_44103_, p_44104_)) } - override fun fromNetwork(p_44105_: ResourceLocation, p_44106_: FriendlyByteBuf): EnergyContainerRecipe? { - return Serializer.SHAPED_RECIPE.fromNetwork(p_44105_, p_44106_)?.let(::EnergyContainerRecipe) - } - - override fun toNetwork(p_44101_: FriendlyByteBuf, p_44102_: EnergyContainerRecipe) { - Serializer.SHAPED_RECIPE.toNetwork(p_44101_, p_44102_) + override fun toNetwork(buff: FriendlyByteBuf, value: EnergyContainerRecipe) { + ShapedRecipe.Serializer.SHAPED_RECIPE.toNetwork(buff, value.parent) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/ExplosiveHammerPrimingRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/ExplosiveHammerPrimingRecipe.kt new file mode 100644 index 000000000..dbd47c23e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/ExplosiveHammerPrimingRecipe.kt @@ -0,0 +1,83 @@ +package ru.dbotthepony.mc.otm.recipe + +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.CraftingBookCategory +import net.minecraft.world.item.crafting.CraftingRecipe +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.level.Level +import net.minecraftforge.common.Tags +import ru.dbotthepony.mc.otm.container.util.stream +import ru.dbotthepony.mc.otm.core.isActuallyEmpty +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem +import ru.dbotthepony.mc.otm.registry.MItems + +class ExplosiveHammerPrimingRecipe(val payload: Ingredient, private val id: ResourceLocation) : CraftingRecipe { + override fun getId(): ResourceLocation { + return id + } + + override fun isIncomplete(): Boolean { + return payload.isActuallyEmpty + } + + override fun isSpecial(): Boolean { + return true + } + + override fun matches(pContainer: CraftingContainer, pLevel: Level): Boolean { + val result = pContainer.stream().filter { it.isNotEmpty }.toList() + if (result.size != 3) return false + return result.any { it.item is ExplosiveHammerItem && !(it.item as ExplosiveHammerItem).isPrimed(it) } && + result.any { it.`is`(Tags.Items.GUNPOWDER) } && + result.any { payload.test(it) } + } + + override fun assemble(pContainer: CraftingContainer): ItemStack { + val hammer = pContainer.stream().filter { it.isNotEmpty && it.item is ExplosiveHammerItem }.findAny() + if (hammer.isEmpty) return ItemStack.EMPTY + + return hammer.orElseThrow().copyWithCount(1).also { + (it.item as ExplosiveHammerItem).prime(it) + } + } + + override fun canCraftInDimensions(pWidth: Int, pHeight: Int): Boolean { + return pWidth * pHeight >= 3 + } + + override fun getResultItem(): ItemStack { + return ItemStack.EMPTY + } + + override fun getSerializer(): RecipeSerializer<*> { + return CODEC + } + + override fun category(): CraftingBookCategory { + return CraftingBookCategory.EQUIPMENT + } + + override fun getIngredients(): NonNullList { + return NonNullList.of(Ingredient.of(), Ingredient.of(MItems.EXPLOSIVE_HAMMER), Ingredient.of(Tags.Items.GUNPOWDER), payload) + } + + fun toFinished() = CODEC.toFinished(this) + + companion object { + val CODEC = Codec2RecipeSerializer { p -> + RecordCodecBuilder.create { + it.group( + p.ingredients.fieldOf("payload").forGetter(ExplosiveHammerPrimingRecipe::payload) + ).apply(it) { a -> ExplosiveHammerPrimingRecipe(a, p.id) } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/Helpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/Helpers.kt deleted file mode 100644 index d0500fc95..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/Helpers.kt +++ /dev/null @@ -1,58 +0,0 @@ -package ru.dbotthepony.mc.otm.recipe - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.resources.ResourceLocation -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import net.minecraftforge.registries.ForgeRegistries -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.container.set -import ru.dbotthepony.mc.otm.core.set - -fun stackFromJson(obj: JsonElement, field: String = ""): ItemStack { - if (obj is JsonPrimitive) { - check(obj.isString) { "Field $field is supposed to be an Item, but it is malformed (not a string and not an object)" } - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(obj.asString)) ?: return ItemStack.EMPTY - - if (item === Items.AIR) { - return ItemStack.EMPTY - } - - return ItemStack(item, 1) - } - - if (obj is JsonObject) { - val name = obj["name"] as? JsonPrimitive ?: throw IllegalStateException("Invalid name of $field") - check(name.isString) { "Invalid name of $field (supposed to be a string)" } - val item = ForgeRegistries.ITEMS.getValue(ResourceLocation(name.asString)) ?: return ItemStack.EMPTY - - val count = (obj["count"] as? JsonPrimitive)?.let { - try { - return@let it.asInt - } catch(err: Throwable) { - throw IllegalStateException("Invalid format of count of $field (supposed to be a number)", err) - } - } ?: 1 - - check(count > 0) { "Field count of $field does not make any sense" } - - return ItemStack(item, count) - } - - throw IllegalStateException("Invalid or missing $field") -} - -fun stackToJson(stack: ItemStack, field: String = ""): JsonElement { - check(stack.count > 0) { "ItemStack $field is empty" } - - if (stack.count == 1) { - return JsonPrimitive(stack.item.registryName!!.toString()) - } - - return JsonObject().also { - it["name"] = JsonPrimitive(stack.item.registryName!!.toString()) - it["count"] = JsonPrimitive(stack.count) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt new file mode 100644 index 000000000..a8045539c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IMatteryRecipe.kt @@ -0,0 +1,37 @@ +package ru.dbotthepony.mc.otm.recipe + +import net.minecraft.core.NonNullList +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeSerializer +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer + +// passthrough all default methods to fix Kotlin bug related to implementation delegation not properly working on Java interfaces +// https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods +interface IMatteryRecipe : Recipe { + override fun getRemainingItems(p_44004_: C): NonNullList { + return super.getRemainingItems(p_44004_) + } + + override fun getIngredients(): NonNullList { + return super.getIngredients() + } + + override fun isSpecial(): Boolean { + return super.isSpecial() + } + + override fun getGroup(): String { + return super.getGroup() + } + + override fun getToastSymbol(): ItemStack { + return super.getToastSymbol() + } + + override fun isIncomplete(): Boolean { + return super.isIncomplete() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt new file mode 100644 index 000000000..07a50aad2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/IngredientMatrix.kt @@ -0,0 +1,164 @@ +package ru.dbotthepony.mc.otm.recipe + +import net.minecraft.core.NonNullList +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.crafting.Ingredient +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.collect.allEqual +import ru.dbotthepony.mc.otm.core.collect.any +import ru.dbotthepony.mc.otm.core.collect.flatMap +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isActuallyEmpty +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.util.countingLazy +import java.util.function.Predicate + +interface IIngredientMatrix : Predicate, Iterable { + val width: Int + val height: Int + val isEmpty: Boolean + get() = width == 0 || height == 0 + + val isIncomplete: Boolean + get() = iterator().any { it.isActuallyEmpty } + + operator fun get(column: Int, row: Int): Ingredient + + operator fun get(column: Int, row: Int, flop: Boolean): Ingredient { + return if (flop) + get(width - column - 1, row) + else + get(column, row) + } + + override fun iterator(): Iterator { + return (0 until width).iterator().flatMap { x -> + (0 until height).iterator().map { y -> + get(x, y) + } + } + } + + val ingredients: NonNullList get() { + val result = NonNullList.createWithCapacity(width * height) + + for (x in 0 until width) { + for (y in 0 until height) { + result.add(get(x, y)) + } + } + + return result + } + + fun test(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean { + if (t.width - fromColumn < width || t.height - fromRow < height) + return false + + for (column in 0 until width) + for (row in 0 until height) + if (!this[column, row, flop].test(t[fromColumn + column, fromRow + row, flop])) + return false + + return true + } + + fun preemptiveTest(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean { + if (t.width - fromColumn < width || t.height - fromRow < height) + return false + + for (column in 0 until width) { + for (row in 0 until height) { + val item = t[fromColumn + column, fromRow + row, flop] + val ingredient = this[column, row, flop] + + if (!ingredient.test(item) && item.isNotEmpty) { + return false + } + } + } + + return true + } + + override fun test(t: CraftingContainer): Boolean { + if (t.width < width || t.height < height) + return false + + for (column in 0 .. t.width - width) + for (row in 0 .. t.height - height) + if (test(t, column, row, false) || test(t, column, row, true)) + return true + + return false + } + + fun preemptiveTest(t: CraftingContainer): Boolean { + if (t.width < width || t.height < height) + return false + + for (column in 0 .. t.width - width) + for (row in 0 .. t.height - height) + if (preemptiveTest(t, column, row, false) || preemptiveTest(t, column, row, true)) + return true + + return false + } + + companion object : IIngredientMatrix { + override val width: Int + get() = 0 + override val height: Int + get() = 0 + + override fun get(column: Int, row: Int): Ingredient { + return Ingredient.EMPTY + } + } +} + +class IngredientMatrix(override val width: Int, override val height: Int) : IIngredientMatrix { + private val data = Array(width * height) { Ingredient.EMPTY } + + private val lazy = countingLazy(OverdriveThatMatters.INGREDIENT_CACHE_INVALIDATION_COUNTER) { + super.isIncomplete + } + + override val isIncomplete by lazy + + override fun iterator(): Iterator { + return data.iterator() + } + + override fun get(column: Int, row: Int): Ingredient { + require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" } + require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" } + return data[column + row * width] + } + + operator fun set(column: Int, row: Int, value: Ingredient) { + require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" } + require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" } + data[column + row * width] = value + lazy.invalidate() + } + + companion object { + fun of(vararg values: Collection): IngredientMatrix { + if (!values.iterator().map { it.size }.allEqual()) { + throw IllegalArgumentException("One or more rows have different number of columns than the rest") + } + + val result = IngredientMatrix(values.first().size, values.size) + + for ((y, l) in values.withIndex()) { + for ((x, i) in l.withIndex()) { + result[x, y] = i + } + } + + return result + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt new file mode 100644 index 000000000..1bd928749 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt @@ -0,0 +1,167 @@ +package ru.dbotthepony.mc.otm.recipe + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess +import net.minecraft.core.UUIDUtil +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.capability.matter.matter +import ru.dbotthepony.mc.otm.capability.matteryEnergy +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.filterNotNull +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.IngredientMatrixCodec +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MRecipes +import java.util.Optional +import java.util.UUID +import kotlin.jvm.optionals.getOrElse + +interface IMatterEntanglerRecipe : IMatteryRecipe { + val matter: Decimal + val ticks: Double + val ingredients: IIngredientMatrix + val result: ItemStack + val experience: Float + + fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean +} + +open class MatterEntanglerRecipe( + private val id: ResourceLocation, + override val ingredients: IIngredientMatrix, + override val matter: Decimal, + override val ticks: Double, + override val result: ItemStack, + override val experience: Float = 0f, + val uuidKey: String = "uuid", + val fixedUuid: Optional = Optional.empty() +) : IMatterEntanglerRecipe { + override fun getId(): ResourceLocation { + return id + } + + override fun matches(container: CraftingContainer, level: Level): Boolean { + if (isIncomplete) return false + return ingredients.test(container) + } + + override fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean { + if (isIncomplete) return false + return ingredients.preemptiveTest(container) + } + + override fun assemble(container: CraftingContainer): ItemStack { + return result.copy().also { + it.tagNotNull[uuidKey] = fixedUuid.getOrElse { UUID.randomUUID() } + } + } + + override fun canCraftInDimensions(width: Int, height: Int): Boolean { + return width >= ingredients.width && height >= ingredients.height + } + + override fun getResultItem(): ItemStack { + return result + } + + override fun getSerializer(): RecipeSerializer<*> { + return SERIALIZER + } + + override fun getType(): RecipeType<*> { + return MRecipes.MATTER_ENTANGLER + } + + override fun getIngredients(): NonNullList { + return ingredients.ingredients + } + + override fun isIncomplete(): Boolean { + return result.isEmpty || ingredients.isIncomplete + } + + override fun isSpecial(): Boolean { + return true + } + + override fun getToastSymbol(): ItemStack { + return ItemStack(MItems.MATTER_ENTANGLER) + } + + fun toFinished(): FinishedRecipe { + return SERIALIZER.toFinished(this) + } + + fun energetic() = Energy(this) + fun matter() = Matter(this) + + open class Energy(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent { + override fun assemble(container: CraftingContainer): ItemStack { + return parent.assemble(container).also { result -> + container.iterator().map { it.matteryEnergy }.filterNotNull().forEach { + result.matteryEnergy!!.batteryLevel += it.batteryLevel + } + } + } + + fun toFinished(): FinishedRecipe { + return ENERGY_SERIALIZER.toFinished(this) + } + + override fun getSerializer(): RecipeSerializer<*> { + return ENERGY_SERIALIZER + } + } + + open class Matter(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent { + override fun assemble(container: CraftingContainer): ItemStack { + return parent.assemble(container).also { result -> + container.iterator().map { it.matter }.filterNotNull().forEach { + result.matter!!.storedMatter += it.storedMatter + } + } + } + + fun toFinished(): FinishedRecipe { + return MATTER_SERIALIZER.toFinished(this) + } + + override fun getSerializer(): RecipeSerializer<*> { + return MATTER_SERIALIZER + } + } + + companion object { + val SERIALIZER = Codec2RecipeSerializer { context -> + RecordCodecBuilder.create { + it.group( + IngredientMatrixCodec(context.ingredients).fieldOf("ingredients").forGetter(MatterEntanglerRecipe::ingredients), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter), + Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks), + ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result), + Codec.FLOAT.minRange(0f).optionalFieldOf("experience", 0f).forGetter(MatterEntanglerRecipe::experience), + Codec.STRING.optionalFieldOf("uuidKey", "uuid").forGetter(MatterEntanglerRecipe::uuidKey), + UUIDUtil.STRING_CODEC.optionalFieldOf("fixedUuid").forGetter(MatterEntanglerRecipe::fixedUuid) + ).apply(it) { a, b, c, d, e, f, g -> MatterEntanglerRecipe(context.id, a, b, c, d, e, f, g) } + } + } + + val ENERGY_SERIALIZER = SERIALIZER.xmap(::Energy, Energy::parent) + val MATTER_SERIALIZER = SERIALIZER.xmap(::Matter, Matter::parent) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt new file mode 100644 index 000000000..198701d4c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatteryCookingRecipe.kt @@ -0,0 +1,106 @@ +package ru.dbotthepony.mc.otm.recipe + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.valueproviders.ConstantFloat +import net.minecraft.util.valueproviders.FloatProvider +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.* +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.isActuallyEmpty +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MRecipes + +abstract class MatteryCookingRecipe( + private val id: ResourceLocation, + val input: Ingredient, + val output: Ingredient, + val count: Int = 1, + val workTime: Int = 200, + val experience: FloatProvider = ConstantFloat.ZERO +) : Recipe { + override fun getId(): ResourceLocation { + return id + } + + override fun matches(container: Container, level: Level): Boolean { + if (isIncomplete) + return false + + return input.test(container[0]) + } + + fun matches(container: Container, slot: Int): Boolean { + if (isIncomplete) + return false + + return input.test(container[slot]) + } + + private val outputStack: ItemStack by lazy { + if (isIncomplete) { + ItemStack.EMPTY + } else { + val items = output.items + val bestMatch = items.firstOrNull { it.item.registryName?.namespace == OverdriveThatMatters.MOD_ID } ?: items[0] + bestMatch.copy().also { it.count = this.count } + } + } + + override fun getIngredients(): NonNullList { + if (isIncomplete) + return super.getIngredients() + + return NonNullList.of(Ingredient.EMPTY, input) + } + + override fun isIncomplete(): Boolean = input.isActuallyEmpty || output.isActuallyEmpty + + override fun assemble(container: Container): ItemStack = outputStack.copy() + + override fun canCraftInDimensions(width: Int, height: Int): Boolean = true + + override fun getResultItem(): ItemStack = outputStack + + abstract fun toFinished(): FinishedRecipe +} + +class MicrowaveRecipe( + id: ResourceLocation, + input: Ingredient, + output: Ingredient, + count: Int = 1, + workTime: Int = 200, + experience: FloatProvider = ConstantFloat.ZERO +) : MatteryCookingRecipe(id, input, output, count, workTime, experience) { + override fun getType(): RecipeType<*> = MRecipes.MICROWAVE + override fun getToastSymbol(): ItemStack = ItemStack(MItems.POWERED_SMOKER) + override fun getSerializer(): RecipeSerializer<*> = SERIALIZER + override fun toFinished(): FinishedRecipe = SERIALIZER.toFinished(this) + + companion object { + val SERIALIZER = Codec2RecipeSerializer { context -> + RecordCodecBuilder.create { + it.group( + context.ingredients.fieldOf("input").forGetter(MicrowaveRecipe::input), + context.ingredients.fieldOf("output").forGetter(MicrowaveRecipe::output), + Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(MicrowaveRecipe::count), + Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(MicrowaveRecipe::workTime), + FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(MicrowaveRecipe::experience) + ).apply(it) { a, b, c, d, e -> MicrowaveRecipe(context.id, a, b, c, d, e) } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt new file mode 100644 index 000000000..b726497b5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt @@ -0,0 +1,248 @@ +package ru.dbotthepony.mc.otm.recipe + +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.Object2IntMaps +import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess +import net.minecraft.data.recipes.FinishedRecipe +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.StringRepresentable +import net.minecraft.world.Container +import net.minecraft.world.item.* +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.level.Level +import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.isActuallyEmpty +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.PredicatedCodecList +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.registry.MItems +import ru.dbotthepony.mc.otm.registry.MRecipes +import java.util.function.Predicate + +abstract class AbstractPainterRecipe( + private val id: ResourceLocation, + dyes: Map +) : IMatteryRecipe { + override fun getId(): ResourceLocation { + return id + } + + val dyes: Object2IntMap = Object2IntMaps.unmodifiable(Object2IntArrayMap(dyes)) + + abstract fun matches(value: ItemStack): Boolean + + override fun matches(contaier: Container, level: Level): Boolean { + return !isIncomplete && matches(contaier[0]) + } + + override fun getToastSymbol(): ItemStack = ItemStack(MItems.PAINTER) + + fun canCraft(storedDyes: Map): Boolean { + if (isIncomplete) return false + if (dyes.isEmpty() || dyes.values.none { it > 0 }) return true + val copy = Object2IntArrayMap(storedDyes) + + for ((dye, amount) in dyes) { + for (i in 0 until amount) { + if (dye != null) PainterBlockEntity.mixer(dye)?.mix(copy) + if (copy.getInt(dye) <= 0) return false + copy[dye] = copy.getInt(dye) - 1 + } + } + + return true + } + + override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean = true + + override fun isSpecial(): Boolean = true + + open fun getOutput(container: Container): ItemStack { + if (container.isEmpty) return ItemStack.EMPTY + + return container[0].copy() + } + + enum class DyeColorWrapper(private val refName: String, val key: DyeColor?) : StringRepresentable { + WHITE("white", DyeColor.WHITE), + ORANGE("orange", DyeColor.ORANGE), + MAGENTA("magenta", DyeColor.MAGENTA), + LIGHT_BLUE("light_blue", DyeColor.LIGHT_BLUE), + YELLOW("yellow", DyeColor.YELLOW), + LIME("lime", DyeColor.LIME), + PINK("pink", DyeColor.PINK), + GRAY("gray", DyeColor.GRAY), + LIGHT_GRAY("light_gray", DyeColor.LIGHT_GRAY), + CYAN("cyan", DyeColor.CYAN), + PURPLE("purple", DyeColor.PURPLE), + BLUE("blue", DyeColor.BLUE), + BROWN("brown", DyeColor.BROWN), + GREEN("green", DyeColor.GREEN), + RED("red", DyeColor.RED), + BLACK("black", DyeColor.BLACK), + WATER("water", null); + + override fun getSerializedName(): String { + return refName + } + } + + companion object { + private val wrapperCodec: Codec = StringRepresentable.fromEnum(DyeColorWrapper::values) + + @JvmStatic + protected val dyesFieldCodec: MapCodec> = PredicatedCodecList>( + wrapperCodec.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 }, + Codec.list(wrapperCodec).xmap({ it.associateWith { 1 } }, { ArrayList(it.keys) }) to Predicate { it.values.all { it == 1 } }, + Codec.unboundedMap(wrapperCodec, Codec.INT.minRange(1)) to Predicate { true } + ).fieldOf("dyes").xmap({ it.mapKeys { it.key.key } }, { it.mapKeys { k -> DyeColorWrapper.entries.first { k.key == it.key } } }) + } +} + +class PainterRecipe( + id: ResourceLocation, + val input: Ingredient, + val output: ItemStack, + dyes: Map +) : AbstractPainterRecipe(id, dyes) { + constructor( + id: ResourceLocation, + input: Ingredient, + output: ItemStack, + dyes: Set + ) : this(id, input, output, Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) + + override fun matches(value: ItemStack): Boolean { + return !isIncomplete && input.test(value) + } + + override fun isIncomplete(): Boolean { + return input.isActuallyEmpty || output.isEmpty + } + + override fun getIngredients(): NonNullList { + return NonNullList.of(Ingredient.EMPTY, input) + } + + override fun assemble(p_44001_: Container): ItemStack { + return output.copy().also { o -> + p_44001_[0].tag?.let { + if (o.tag == null) { + o.tag = it.copy() + } else { + for (k in it.allKeys) + if (k !in o.tagNotNull) + o.tagNotNull[k] = it[k]!!.copy() + } + } + } + } + + override fun getResultItem(): ItemStack { + return output + } + + override fun getSerializer(): RecipeSerializer<*> { + return SERIALIZER + } + + override fun getType(): RecipeType<*> { + return MRecipes.PAINTER + } + + fun toFinished(): FinishedRecipe { + return SERIALIZER.toFinished(this) + } + + override fun getOutput(container: Container): ItemStack { + return output.copy() + } + + companion object { + val SERIALIZER = Codec2RecipeSerializer { context -> + RecordCodecBuilder.create { + it.group( + context.ingredients.fieldOf("input").forGetter(PainterRecipe::input), + ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output), + dyesFieldCodec.forGetter(AbstractPainterRecipe::dyes), + ).apply(it) { a, b, c -> PainterRecipe(context.id, a, b, c) } + } + } + } +} +class PainterArmorDyeRecipe( + id: ResourceLocation, + dyes: Map +) : AbstractPainterRecipe(id, dyes) { + constructor( + id: ResourceLocation, + dyes: Set + ) : this(id, Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) + + override fun matches(value: ItemStack): Boolean { + return !isIncomplete && value.item is DyeableArmorItem + } + + override fun assemble(container: Container): ItemStack { + var output = container[0].copy() + + dyes.forEach { entry -> + if (entry.key == null) { + (output.item as DyeableLeatherItem).clearColor(output) + } else { + output = DyeableLeatherItem.dyeArmor(output.copy(), listOf(DyeItem.byColor(entry.key!!))) + } + } + + return output + } + + override fun getResultItem(): ItemStack = ItemStack.EMPTY + + override fun getSerializer(): RecipeSerializer<*> = SERIALIZER + + override fun getType(): RecipeType<*> = MRecipes.PAINTER + + override fun isIncomplete(): Boolean = dyes.isEmpty() + + fun toFinished(): FinishedRecipe { + return SERIALIZER.toFinished(this) + } + + override fun getOutput(container: Container): ItemStack { + if (container.isEmpty) return ItemStack.EMPTY + + var output = container[0].copy() + + dyes.forEach { entry -> + if (entry.key == null) { + (output.item as DyeableLeatherItem).clearColor(output) + } else { + output = DyeableLeatherItem.dyeArmor(output, listOf(DyeItem.byColor(entry.key!!))) + } + } + + return output + } + + companion object { + val SERIALIZER = Codec2RecipeSerializer { context -> + RecordCodecBuilder.create { + it.group( + dyesFieldCodec.forGetter(AbstractPainterRecipe::dyes), + ).apply(it) { a -> PainterArmorDyeRecipe(context.id, a) } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt index 784ca124b..a55184efd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PlatePressRecipe.kt @@ -1,43 +1,56 @@ package ru.dbotthepony.mc.otm.recipe -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.NonNullList -import net.minecraft.network.FriendlyByteBuf +import net.minecraft.core.RegistryAccess import net.minecraft.resources.ResourceLocation +import net.minecraft.util.valueproviders.ConstantFloat +import net.minecraft.util.valueproviders.FloatProvider import net.minecraft.world.Container import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeSerializer import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.Level -import net.minecraftforge.common.ForgeHooks -import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.block.entity.PlatePressBlockEntity import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.core.isActuallyEmpty import ru.dbotthepony.mc.otm.registry.MRecipes import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.minRange +import ru.dbotthepony.mc.otm.registry.MItems class PlatePressRecipe( private val id: ResourceLocation, val input: Ingredient, val output: Ingredient, - val count: Int, - val workTime: Int = 200 + val count: Int = 1, + val workTime: Int = 200, + val experience: FloatProvider = ConstantFloat.ZERO ) : Recipe { + override fun getId(): ResourceLocation { + return id + } + override fun matches(container: Container, p_44003_: Level): Boolean { - if (output.isActuallyEmpty || input.isActuallyEmpty) + if (isIncomplete) return false - return input.test(container[PlatePressBlockEntity.SLOT_INPUT]) + return input.test(container[0]) + } + + fun matches(container: Container, slot: Int): Boolean { + if (isIncomplete) + return false + + return input.test(container[slot]) } private val outputStack: ItemStack by lazy { - if (output.isActuallyEmpty || input.isActuallyEmpty) { + if (isIncomplete) { ItemStack.EMPTY } else { val items = output.items @@ -47,7 +60,7 @@ class PlatePressRecipe( } override fun getIngredients(): NonNullList { - if (input.isActuallyEmpty || output.isActuallyEmpty) + if (isIncomplete) return super.getIngredients() return NonNullList.of(Ingredient.EMPTY, input) @@ -61,61 +74,30 @@ class PlatePressRecipe( override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int) = true override fun getResultItem(): ItemStack = outputStack - override fun getId() = id - override fun getSerializer(): RecipeSerializer<*> { - return PlatePressRecipeFactory + return SERIALIZER } override fun getType(): RecipeType = MRecipes.PLATE_PRESS -} -object PlatePressRecipeFactory : RecipeSerializer { - private val EMPTY = PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1) - private val LOGGER = LogManager.getLogger() + override fun getToastSymbol(): ItemStack { + return ItemStack(MItems.PLATE_PRESS) + } - override fun fromJson(loc: ResourceLocation, obj: JsonObject): PlatePressRecipe { - val input = try { - Ingredient.fromJson(obj["input"] ?: throw IllegalStateException("Recipe $loc has no input field defined")) - } catch (err: Throwable) { - if (err.message?.lowercase()?.contains("unknown item tag") == true) { - LOGGER.warn("Ignoring recipe Input of $loc deserialization error, defaulting to empty recipe") - LOGGER.warn(err) - return EMPTY - } else { - throw IllegalStateException("Input of $loc is malformed", err) + fun toFinished() = SERIALIZER.toFinished(this) + + companion object { + val SERIALIZER = Codec2RecipeSerializer { context -> + RecordCodecBuilder.create { + it.group( + context.ingredients.fieldOf("input").forGetter(PlatePressRecipe::input), + context.ingredients.fieldOf("output").forGetter(PlatePressRecipe::output), + Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count), + Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime), + FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience) + ).apply(it) { a, b, c, d, e -> PlatePressRecipe(context.id, a, b, c, d, e) } } } - - val result = try { - Ingredient.fromJson(obj["result"] ?: throw IllegalStateException("Recipe $loc has no result field defined")) - } catch (err: Throwable) { - if (err.message?.lowercase()?.contains("unknown item tag") == true) { - LOGGER.warn("Ignoring recipe Output of $loc deserialization error, defaulting to empty recipe") - LOGGER.warn(err) - return EMPTY - } else { - throw IllegalStateException("Result of $loc is malformed", err) - } - } - - val workTime = (obj["work_time"] as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid work_time")} } ?: 200 - - check(workTime >= 0) { "work_time of $loc does not make any sense" } - - val count = ((obj["result"] as JsonObject)["count"] as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid result.count")} } ?: 1 - - return PlatePressRecipe(loc, input, result, count, workTime) - } - - override fun fromNetwork(loc: ResourceLocation, buff: FriendlyByteBuf): PlatePressRecipe { - return PlatePressRecipe(loc, Ingredient.fromNetwork(buff), Ingredient.fromNetwork(buff), buff.readInt(), buff.readInt()) - } - - override fun toNetwork(buff: FriendlyByteBuf, recipe: PlatePressRecipe) { - recipe.input.toNetwork(buff) - recipe.output.toNetwork(buff) - buff.writeInt(recipe.count) - buff.writeInt(recipe.workTime) } } + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt new file mode 100644 index 000000000..01eb1200a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/UpgradeRecipe.kt @@ -0,0 +1,225 @@ +package ru.dbotthepony.mc.otm.recipe + +import com.google.common.collect.ImmutableList +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.NonNullList +import net.minecraft.core.RegistryAccess +import net.minecraft.nbt.CompoundTag +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.StringRepresentable +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.crafting.CraftingBookCategory +import net.minecraft.world.item.crafting.CraftingRecipe +import net.minecraft.world.item.crafting.Ingredient +import net.minecraft.world.item.crafting.RecipeSerializer +import net.minecraft.world.item.crafting.RecipeType +import net.minecraft.world.item.crafting.ShapedRecipe +import net.minecraft.world.level.Level +import net.minecraftforge.common.crafting.IShapedRecipe +import ru.dbotthepony.mc.otm.container.util.stream +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.tagNotNull +import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer +import ru.dbotthepony.mc.otm.data.RecipePair +import ru.dbotthepony.mc.otm.data.codec +import java.util.stream.Stream + +class UpgradeRecipe( + val parent: RecipePair, + copyPaths: Stream, + val source: ResourceLocation, +) : CraftingRecipe, IShapedRecipe by parent.recipe { + constructor(parent: RecipePair, copyPaths: Collection, source: ResourceLocation) : this(parent, copyPaths.stream(), source) + + override fun matches(p_44002_: CraftingContainer, p_44003_: Level): Boolean { + return parent.recipe.matches(p_44002_, p_44003_) + } + + override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean { + return parent.recipe.canCraftInDimensions(p_43999_, p_44000_) + } + + override fun getResultItem(): ItemStack { + return parent.recipe.resultItem + } + + override fun getRemainingItems(p_44004_: CraftingContainer): NonNullList { + return parent.recipe.getRemainingItems(p_44004_) + } + + override fun getIngredients(): NonNullList { + return parent.recipe.ingredients + } + + override fun isSpecial(): Boolean { + return parent.recipe.isSpecial + } + + override fun getGroup(): String { + return parent.recipe.group + } + + override fun getToastSymbol(): ItemStack { + return parent.recipe.toastSymbol + } + + override fun isIncomplete(): Boolean { + return parent.recipe.isIncomplete + } + + override fun getType(): RecipeType<*> { + return parent.recipe.type + } + + override fun category(): CraftingBookCategory { + return parent.recipe.category() + } + + enum class OpType : StringRepresentable { + DIRECT { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Codec.STRING.fieldOf("path").forGetter(Direct::path) + ).apply(it, ::Direct) + } + }, INDIRECT { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Codec.STRING.fieldOf("source").forGetter(Indirect::pathSource), + Codec.STRING.fieldOf("destination").forGetter(Indirect::pathDestination), + ).apply(it, ::Indirect) + } + }; + + override fun getSerializedName(): String { + return name.lowercase() + } + + abstract val codec: Codec + } + + sealed class Op { + abstract val type: OpType + abstract fun apply(source: CompoundTag, destination: CompoundTag) + } + + data class Direct(val path: String) : Op() { + private val split = path.split('.') + override val type: OpType + get() = OpType.DIRECT + + override fun apply(source: CompoundTag, destination: CompoundTag) { + var a = source + var b = destination + + for (i in 0 until split.size - 1) { + val value = split[i] + + if (value !in a) { + a[value] = CompoundTag() + } + + if (value !in b) { + b[value] = CompoundTag() + } + + a = a[value] as CompoundTag + b = b[value] as CompoundTag + } + + val value = split.last() + + if (value in a) { + b[value] = a[value]!!.copy() + } + } + } + + data class Indirect(val pathSource: String, val pathDestination: String) : Op() { + private val splitSource = pathSource.split('.') + private val splitDestination = pathDestination.split('.') + + override val type: OpType + get() = OpType.INDIRECT + + override fun apply(source: CompoundTag, destination: CompoundTag) { + var a = source + var b = destination + + for (i in 0 until splitSource.size - 1) { + val value = splitSource[i] + + if (value !in a) { + a[value] = CompoundTag() + } + + a = a[value] as CompoundTag + } + + for (i in 0 until splitDestination.size - 1) { + val value = splitDestination[i] + + if (value !in b) { + b[value] = CompoundTag() + } + + b = b[value] as CompoundTag + } + + val valueA = splitSource.last() + val valueB = splitDestination.last() + + if (valueA in a) { + b[valueB] = a[valueA]!!.copy() + } + } + } + + val copyPaths: ImmutableList = copyPaths.collect(ImmutableList.toImmutableList()) + + override fun assemble(pInv: CraftingContainer): ItemStack { + val result = parent.recipe.assemble(pInv) + + if (result.isEmpty) { + return result + } + + val sources = pInv.stream().filter { !it.isEmpty && it.item.registryName == source }.toList() + + if (sources.size != 1) { + return ItemStack.EMPTY + } + + val source = sources.first() + + for (op in copyPaths) { + op.apply(source.tagNotNull, result.tagNotNull) + } + + return result + } + + override fun getSerializer(): RecipeSerializer { + return CODEC + } + + companion object { + val COPY_PATHS_CODEC: Codec> = StringRepresentable + .fromEnum(OpType::values) + .dispatch({ it.type }, { it.codec }) + .listOf() + + val CODEC = Codec2RecipeSerializer { p -> + RecordCodecBuilder.create { + it.group( + ShapedRecipe.Serializer.SHAPED_RECIPE.codec(p).fieldOf("parent").forGetter(UpgradeRecipe::parent), + COPY_PATHS_CODEC.fieldOf("copyPaths").forGetter(UpgradeRecipe::copyPaths), + ResourceLocation.CODEC.fieldOf("source").forGetter(UpgradeRecipe::source) + ).apply(it, ::UpgradeRecipe) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt index d729b079d..eb4b7cb9c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/AndroidFeatures.kt @@ -10,20 +10,20 @@ import ru.dbotthepony.mc.otm.android.feature.* object AndroidFeatures { private val registry = DeferredRegister.create(MRegistry.ANDROID_FEATURES_KEY, OverdriveThatMatters.MOD_ID) - val AIR_BAGS: AndroidFeatureType<*> by registry.register(MNames.AIR_BAGS) { AndroidFeatureType(::DummyAndroidFeature) } - val STEP_ASSIST: AndroidFeatureType<*> by registry.register(MNames.STEP_ASSIST) { AndroidFeatureType(::StepAssistFeature) } - val LIMB_OVERCLOCKING: AndroidFeatureType<*> by registry.register(MNames.LIMB_OVERCLOCKING) { AndroidFeatureType(::LimbOverclockingFeature) } - val ATTACK_BOOST: AndroidFeatureType<*> by registry.register(MNames.ATTACK_BOOST) { AndroidFeatureType(::AttackBoostFeature) } - val NANOBOTS_REGENERATION: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_REGENERATION) { AndroidFeatureType(::NanobotsRegenerationFeature) } - val NANOBOTS_ARMOR: AndroidFeatureType<*> by registry.register(MNames.NANOBOTS_ARMOR) { AndroidFeatureType(::NanobotsArmorFeature) } - val EXTENDED_REACH: AndroidFeatureType<*> by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReachFeature) } - val NIGHT_VISION: AndroidFeatureType<*> by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) } - val SHOCKWAVE: AndroidFeatureType<*> by registry.register(MNames.SHOCKWAVE) { AndroidFeatureType(::ShockwaveFeature) } - val ITEM_MAGNET: AndroidFeatureType<*> by registry.register(MNames.ITEM_MAGNET) { AndroidFeatureType(::ItemMagnetFeature) } - val FALL_DAMPENERS: AndroidFeatureType<*> by registry.register(MNames.FALL_DAMPENERS) { AndroidFeatureType(::FallDampenersFeature) } - val PHANTOM_ATTRACTOR: AndroidFeatureType<*> by registry.register(MNames.PHANTOM_ATTRACTOR) { AndroidFeatureType(::PhantomAttractorFeature) } - val JUMP_BOOST: AndroidFeatureType<*> by registry.register(MNames.JUMP_BOOST) { AndroidFeatureType(::JumpBoostFeature) } - val ENDER_TELEPORTER: AndroidFeatureType<*> by registry.register(MNames.ENDER_TELEPORTER) { AndroidFeatureType(::EnderTeleporterFeature) } + val AIR_BAGS: AndroidFeatureType by registry.register(MNames.AIR_BAGS) { AndroidFeatureType(::DummyAndroidFeature) } + val STEP_ASSIST: AndroidFeatureType by registry.register(MNames.STEP_ASSIST) { AndroidFeatureType(::StepAssistFeature) } + val SWIM_BOOSTERS: AndroidFeatureType by registry.register(MNames.SWIM_BOOSTERS) { AndroidFeatureType(::SwimBoostersFeature) } + val LIMB_OVERCLOCKING: AndroidFeatureType by registry.register(MNames.LIMB_OVERCLOCKING) { AndroidFeatureType(::LimbOverclockingFeature) } + val ATTACK_BOOST: AndroidFeatureType by registry.register(MNames.ATTACK_BOOST) { AndroidFeatureType(::AttackBoostFeature) } + val NANOBOTS_REGENERATION: AndroidFeatureType by registry.register(MNames.NANOBOTS_REGENERATION) { AndroidFeatureType(::NanobotsRegenerationFeature) } + val NANOBOTS_ARMOR: AndroidFeatureType by registry.register(MNames.NANOBOTS_ARMOR) { AndroidFeatureType(::NanobotsArmorFeature) } + val EXTENDED_REACH: AndroidFeatureType by registry.register(MNames.EXTENDED_REACH) { AndroidFeatureType(::ExtendedReachFeature) } + val NIGHT_VISION: AndroidFeatureType by registry.register(MNames.NIGHT_VISION) { AndroidFeatureType(::NightVisionFeature) } + val SHOCKWAVE: AndroidFeatureType by registry.register(MNames.SHOCKWAVE) { AndroidFeatureType(::ShockwaveFeature) } + val ITEM_MAGNET: AndroidFeatureType by registry.register(MNames.ITEM_MAGNET) { AndroidFeatureType(::ItemMagnetFeature) } + val FALL_DAMPENERS: AndroidFeatureType by registry.register(MNames.FALL_DAMPENERS) { AndroidFeatureType(::FallDampenersFeature) } + val JUMP_BOOST: AndroidFeatureType by registry.register(MNames.JUMP_BOOST) { AndroidFeatureType(::JumpBoostFeature) } + val ENDER_TELEPORTER: AndroidFeatureType by registry.register(MNames.ENDER_TELEPORTER) { AndroidFeatureType(::EnderTeleporterFeature) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt index 5d6580085..84fd8fc17 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/DamageSources.kt @@ -138,7 +138,7 @@ class ImmutableDamageSource(private val parent: DamageSource) : DamageSource(par } } -abstract class MatteryDamageSource(name: String, private val entity: Entity? = null, private val inflictor: ItemStack? = null) : DamageSource(name) { +abstract class MatteryDamageSource(name: String, private val entity: Entity? = null, val inflictor: ItemStack? = null) : DamageSource(name) { override fun getLocalizedDeathMessage(victim: LivingEntity): Component { val itemStack = inflictor ?: (entity as LivingEntity?)?.mainHandItem ?: ItemStack.EMPTY @@ -169,6 +169,36 @@ class EMPDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : Ma } } +class ExopackDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_EXOPACK_PROBE_ID, entity, inflictor) { + init { + bypassArmor() + bypassMagic() + bypassEnchantments() + } + + override fun scalesWithDifficulty(): Boolean { + return false + } +} + +class CosmicRaysDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_COSMIC_RAYS_NAME, entity, inflictor) { + init { + bypassArmor() + bypassMagic() + bypassEnchantments() + } + + override fun scalesWithDifficulty(): Boolean { + return false + } +} + +class ExplosiveHammerDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_EXPLOSIVE_HAMMER_NAME, entity, inflictor) { + override fun scalesWithDifficulty(): Boolean { + return false + } +} + class ShockwaveDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_SHOCKWAVE_NAME, entity, inflictor) { init { bypassArmor() @@ -184,3 +214,9 @@ class PlasmaDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : return false } } + +class HammerNailDamageSource(entity: Entity? = null, inflictor: ItemStack? = null) : MatteryDamageSource(MRegistry.DAMAGE_HAMMER_NAIL_NAME, entity, inflictor) { + override fun scalesWithDifficulty(): Boolean { + return false + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Ext.kt index 4d3ef11aa..38286bbda 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Ext.kt @@ -8,8 +8,14 @@ import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.DyeColor import net.minecraftforge.registries.DeferredRegister +import net.minecraftforge.registries.RegistryObject import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.SupplierMap +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import kotlin.reflect.KProperty + +operator fun RegistryObject.getValue(thisRef: Any, property: KProperty<*>): T { + return get() +} private fun DeferredRegister.doColored(prefix: String, factory: (color: DyeColor, name: String) -> T): MutableCollection T>> { return mutableListOf( @@ -32,19 +38,19 @@ private fun DeferredRegister.doColored(prefix: String, factory: (color: D ) } -fun DeferredRegister.colored(prefix: String, factory: (color: DyeColor, name: String) -> T): Map { +internal fun DeferredRegister.colored(prefix: String, factory: (color: DyeColor, name: String) -> T): Map { return SupplierMap(doColored(prefix, factory)) } @Suppress("unchecked_cast") -fun DeferredRegister.allColored(prefix: String, factory: (color: DyeColor?, name: String) -> T): Map { +internal fun DeferredRegister.allColored(prefix: String, factory: (color: DyeColor?, name: String) -> T): Map { return SupplierMap(doColored(prefix, factory).also { (it as MutableCollection T>>).add((null as DyeColor?) to register(prefix) { factory.invoke(null, prefix) }::get) }) } -fun Registry.register(key: String, value: T): Holder { +internal fun Registry.register(key: String, value: T): Holder { return this.register(ResourceLocation(OverdriveThatMatters.MOD_ID, key), value) } -fun Registry.register(key: ResourceLocation, value: T): Holder { +internal fun Registry.register(key: ResourceLocation, value: T): Holder { return (this as WritableRegistry).register(ResourceKey.create(key(), key), value, Lifecycle.stable()) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt new file mode 100644 index 000000000..85900d227 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockColors.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.client.renderer.BiomeColors +import net.minecraft.core.BlockPos +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.BlockAndTintGetter +import net.minecraft.world.level.block.state.BlockState +import net.minecraftforge.client.event.RegisterColorHandlersEvent +import net.minecraftforge.eventbus.api.IEventBus + +object MBlockColors { + private const val DEFAULT_WATER_TINT: Int = 0x3F76E4 + + private fun registerBlockColors(event: RegisterColorHandlersEvent.Block) { + event.register({ state: BlockState, light: BlockAndTintGetter?, pos: BlockPos?, index: Int -> + if (index == 0) { + if (light == null || pos == null) + DEFAULT_WATER_TINT + else + BiomeColors.getAverageWaterColor(light, pos) + } else -1 + }, MBlocks.COBBLESTONE_GENERATOR) + } + + private fun registerItemColors(event: RegisterColorHandlersEvent.Item) { + event.register({ stack: ItemStack, index: Int -> + if (index == 0) DEFAULT_WATER_TINT else -1 + }, MBlocks.COBBLESTONE_GENERATOR) + } + + internal fun register(bus: IEventBus) { + bus.addListener(this::registerBlockColors) + bus.addListener(this::registerItemColors) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt index 047198565..9036fc742 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlockEntities.kt @@ -1,62 +1,102 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.client.renderer.blockentity.BlockEntityRenderers +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.RegistryObject import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.block.entity.* +import ru.dbotthepony.mc.otm.block.entity.tech.* import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntityExplosionDebugger import ru.dbotthepony.mc.otm.block.entity.blackhole.BlockEntitySphereDebugger +import ru.dbotthepony.mc.otm.block.entity.cable.SimpleEnergyCableBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.HoloSignBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.InfiniteWaterSourceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity import ru.dbotthepony.mc.otm.block.entity.matter.* import ru.dbotthepony.mc.otm.block.entity.storage.* -import ru.dbotthepony.mc.otm.client.render.blockentity.BatteryBankRenderer -import ru.dbotthepony.mc.otm.client.render.blockentity.BlackHoleRenderer -import ru.dbotthepony.mc.otm.client.render.blockentity.EnergyCounterRenderer -import ru.dbotthepony.mc.otm.client.render.blockentity.GravitationStabilizerRenderer -import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer -import ru.dbotthepony.mc.otm.client.render.blockentity.MatterReplicatorRenderer +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.EnergyServoBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity +import ru.dbotthepony.mc.otm.client.render.blockentity.* +import ru.dbotthepony.mc.otm.config.CablesConfig +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import ru.dbotthepony.mc.otm.core.getValue +import java.util.function.Supplier @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // Type<*> is unused in BlockEntityType.Builder object MBlockEntities { private val registry = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, OverdriveThatMatters.MOD_ID) - val ANDROID_STATION: BlockEntityType<*> by registry.register(MNames.ANDROID_STATION) { BlockEntityType.Builder.of(::AndroidStationBlockEntity, MBlocks.ANDROID_STATION).build(null) } - val BATTERY_BANK: BlockEntityType<*> by registry.register(MNames.BATTERY_BANK) { BlockEntityType.Builder.of(::BatteryBankBlockEntity, MBlocks.BATTERY_BANK).build(null) } - val MATTER_DECOMPOSER: BlockEntityType<*> by registry.register(MNames.MATTER_DECOMPOSER) { BlockEntityType.Builder.of(::MatterDecomposerBlockEntity, MBlocks.MATTER_DECOMPOSER).build(null) } - val MATTER_CAPACITOR_BANK: BlockEntityType<*> by registry.register(MNames.MATTER_CAPACITOR_BANK) { BlockEntityType.Builder.of(::MatterCapacitorBankBlockEntity, MBlocks.MATTER_CAPACITOR_BANK).build(null) } - val MATTER_CABLE: BlockEntityType<*> by registry.register(MNames.MATTER_CABLE) { BlockEntityType.Builder.of(::MatterCableBlockEntity, MBlocks.MATTER_CABLE).build(null) } - val STORAGE_CABLE: BlockEntityType<*> by registry.register(MNames.STORAGE_CABLE) { BlockEntityType.Builder.of(::StorageCableBlockEntity, MBlocks.STORAGE_CABLE).build(null) } - val PATTERN_STORAGE: BlockEntityType<*> by registry.register(MNames.PATTERN_STORAGE) { BlockEntityType.Builder.of(::PatternStorageBlockEntity, MBlocks.PATTERN_STORAGE).build(null) } - val MATTER_SCANNER: BlockEntityType<*> by registry.register(MNames.MATTER_SCANNER) { BlockEntityType.Builder.of(::MatterScannerBlockEntity, MBlocks.MATTER_SCANNER).build(null) } - val MATTER_PANEL: BlockEntityType<*> by registry.register(MNames.MATTER_PANEL) { BlockEntityType.Builder.of(::MatterPanelBlockEntity, MBlocks.MATTER_PANEL).build(null) } - val MATTER_REPLICATOR: BlockEntityType<*> by registry.register(MNames.MATTER_REPLICATOR) { BlockEntityType.Builder.of(::MatterReplicatorBlockEntity, MBlocks.MATTER_REPLICATOR).build(null) } - val MATTER_BOTTLER: BlockEntityType<*> by registry.register(MNames.MATTER_BOTTLER) { BlockEntityType.Builder.of(::MatterBottlerBlockEntity, MBlocks.MATTER_BOTTLER).build(null) } - val DRIVE_VIEWER: BlockEntityType<*> by registry.register(MNames.DRIVE_VIEWER) { BlockEntityType.Builder.of(::DriveViewerBlockEntity, MBlocks.DRIVE_VIEWER).build(null) } - val BLACK_HOLE: BlockEntityType<*> by registry.register(MNames.BLACK_HOLE) { BlockEntityType.Builder.of(::BlackHoleBlockEntity, MBlocks.BLACK_HOLE).build(null) } - val CARGO_CRATE: BlockEntityType<*> by registry.register(MNames.CARGO_CRATE) { BlockEntityType.Builder.of( - ::CargoCrateBlockEntity, - *MRegistry.CARGO_CRATES.blocks.values.toTypedArray() - ).build(null) } - val DRIVE_RACK: BlockEntityType<*> by registry.register(MNames.DRIVE_RACK) { BlockEntityType.Builder.of(::DriveRackBlockEntity, MBlocks.DRIVE_RACK).build(null) } - val ITEM_MONITOR: BlockEntityType<*> by registry.register(MNames.ITEM_MONITOR) { BlockEntityType.Builder.of(::ItemMonitorBlockEntity, MBlocks.ITEM_MONITOR).build(null) } - val ENERGY_COUNTER: BlockEntityType<*> by registry.register(MNames.ENERGY_COUNTER) { BlockEntityType.Builder.of(::EnergyCounterBlockEntity, MBlocks.ENERGY_COUNTER).build(null) } - val CHEMICAL_GENERATOR: BlockEntityType<*> by registry.register(MNames.CHEMICAL_GENERATOR) { BlockEntityType.Builder.of(::ChemicalGeneratorBlockEntity, MBlocks.CHEMICAL_GENERATOR).build(null) } - val PLATE_PRESS: BlockEntityType<*> by registry.register(MNames.PLATE_PRESS) { BlockEntityType.Builder.of(::PlatePressBlockEntity, MBlocks.PLATE_PRESS).build(null) } - val GRAVITATION_STABILIZER: BlockEntityType<*> by registry.register(MNames.GRAVITATION_STABILIZER) { BlockEntityType.Builder.of(::GravitationStabilizerBlockEntity, MBlocks.GRAVITATION_STABILIZER).build(null) } - val MATTER_RECYCLER: BlockEntityType<*> by registry.register(MNames.MATTER_RECYCLER) { BlockEntityType.Builder.of(::MatterRecyclerBlockEntity, MBlocks.MATTER_RECYCLER).build(null) } - val ENERGY_SERVO: BlockEntityType<*> by registry.register(MNames.ENERGY_SERVO) { BlockEntityType.Builder.of(::EnergyServoBlockEntity, MBlocks.ENERGY_SERVO).build(null) } + private fun register(name: String, factory: BlockEntityType.BlockEntitySupplier, vararg blocks: Supplier): RegistryObject> { + return registry.register(name) { BlockEntityType.Builder.of(factory, *blocks.map { it.get() }.toTypedArray()).build(null) } + } - val STORAGE_BUS: BlockEntityType<*> by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) } - val STORAGE_IMPORTER: BlockEntityType<*> by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) } - val STORAGE_EXPORTER: BlockEntityType<*> by registry.register(MNames.STORAGE_EXPORTER) { BlockEntityType.Builder.of(::StorageExporterBlockEntity, MBlocks.STORAGE_EXPORTER).build(null) } - val STORAGE_POWER_SUPPLIER: BlockEntityType<*> by registry.register(MNames.STORAGE_POWER_SUPPLIER) { BlockEntityType.Builder.of(::StoragePowerSupplierBlockEntity, MBlocks.STORAGE_POWER_SUPPLIER).build(null) } + val ANDROID_STATION by register(MNames.ANDROID_STATION, ::AndroidStationBlockEntity, MBlocks::ANDROID_STATION) + val BATTERY_BANK by register(MNames.BATTERY_BANK, ::BatteryBankBlockEntity, MBlocks::BATTERY_BANK) + val MATTER_DECOMPOSER by register(MNames.MATTER_DECOMPOSER, ::MatterDecomposerBlockEntity, MBlocks::MATTER_DECOMPOSER) + val MATTER_CAPACITOR_BANK by register(MNames.MATTER_CAPACITOR_BANK, ::MatterCapacitorBankBlockEntity, MBlocks::MATTER_CAPACITOR_BANK) + val MATTER_CABLE by register(MNames.MATTER_CABLE, ::MatterCableBlockEntity, MBlocks::MATTER_CABLE) + val STORAGE_CABLE by register(MNames.STORAGE_CABLE, ::StorageCableBlockEntity, MBlocks::STORAGE_CABLE) + val PATTERN_STORAGE by register(MNames.PATTERN_STORAGE, ::PatternStorageBlockEntity, MBlocks::PATTERN_STORAGE) + val MATTER_SCANNER by register(MNames.MATTER_SCANNER, ::MatterScannerBlockEntity, MBlocks::MATTER_SCANNER) + val MATTER_PANEL by register(MNames.MATTER_PANEL, ::MatterPanelBlockEntity, MBlocks::MATTER_PANEL) + val MATTER_REPLICATOR by register(MNames.MATTER_REPLICATOR, ::MatterReplicatorBlockEntity, MBlocks::MATTER_REPLICATOR) + val MATTER_BOTTLER by register(MNames.MATTER_BOTTLER, ::MatterBottlerBlockEntity, MBlocks::MATTER_BOTTLER) + val DRIVE_VIEWER by register(MNames.DRIVE_VIEWER, ::DriveViewerBlockEntity, MBlocks::DRIVE_VIEWER) + val BLACK_HOLE by register(MNames.BLACK_HOLE, ::BlackHoleBlockEntity, MBlocks::BLACK_HOLE) + val CARGO_CRATE by registry.register(MNames.CARGO_CRATE) { BlockEntityType.Builder.of(::CargoCrateBlockEntity, *MRegistry.CARGO_CRATES.blocks.values.toTypedArray()).build(null) } + val DRIVE_RACK by register(MNames.DRIVE_RACK, ::DriveRackBlockEntity, MBlocks::DRIVE_RACK) + val ITEM_MONITOR by register(MNames.ITEM_MONITOR, ::ItemMonitorBlockEntity, MBlocks::ITEM_MONITOR) + val ENERGY_COUNTER by register(MNames.ENERGY_COUNTER, ::EnergyCounterBlockEntity, MBlocks::ENERGY_COUNTER) + val CHEMICAL_GENERATOR by register(MNames.CHEMICAL_GENERATOR, ::ChemicalGeneratorBlockEntity, MBlocks::CHEMICAL_GENERATOR) + val PLATE_PRESS by register(MNames.PLATE_PRESS, ::PlatePressBlockEntity, MBlocks::PLATE_PRESS) + val TWIN_PLATE_PRESS by register(MNames.TWIN_PLATE_PRESS, { a, b -> PlatePressBlockEntity(a, b, true) }, MBlocks::TWIN_PLATE_PRESS) + val GRAVITATION_STABILIZER by register(MNames.GRAVITATION_STABILIZER, ::GravitationStabilizerBlockEntity, MBlocks::GRAVITATION_STABILIZER) + val MATTER_RECYCLER by register(MNames.MATTER_RECYCLER, ::MatterRecyclerBlockEntity, MBlocks::MATTER_RECYCLER) + val ENERGY_SERVO by register(MNames.ENERGY_SERVO, ::EnergyServoBlockEntity, MBlocks::ENERGY_SERVO) + val COBBLESTONE_GENERATOR by register(MNames.COBBLESTONE_GENERATOR, ::CobblerBlockEntity, MBlocks::COBBLESTONE_GENERATOR) + val ESSENCE_STORAGE by register(MNames.ESSENCE_STORAGE, ::EssenceStorageBlockEntity, MBlocks::ESSENCE_STORAGE) + val MATTER_RECONSTRUCTOR by register(MNames.MATTER_RECONSTRUCTOR, ::MatterReconstructorBlockEntity, MBlocks::MATTER_RECONSTRUCTOR) + val FLUID_TANK by register(MNames.FLUID_TANK, ::FluidTankBlockEntity, MBlocks::FLUID_TANK) + val ANDROID_CHARGER by register(MNames.ANDROID_CHARGER, ::AndroidChargerBlockEntity, MBlocks::ANDROID_CHARGER) + val ANDROID_CHARGER_MIDDLE by register(MNames.ANDROID_CHARGER + "_middle", ::AndroidChargerMiddleBlockEntity, MBlocks::ANDROID_CHARGER) + val ANDROID_CHARGER_TOP by register(MNames.ANDROID_CHARGER + "_top", ::AndroidChargerTopBlockEntity, MBlocks::ANDROID_CHARGER) + val INFINITE_WATER_SOURCE by register(MNames.INFINITE_WATER_SOURCE, ::InfiniteWaterSourceBlockEntity, MBlocks::INFINITE_WATER_SOURCE) + val DEV_CHEST by register(MNames.DEV_CHEST, ::DevChestBlockEntity, MBlocks::DEV_CHEST) + val PAINTER by register(MNames.PAINTER, ::PainterBlockEntity, MBlocks::PAINTER) + val MATTER_ENTANGLER by register(MNames.MATTER_ENTANGLER, ::MatterEntanglerBlockEntity, MBlocks::MATTER_ENTANGLER) - val DEBUG_EXPLOSION_SMALL: BlockEntityType<*> by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockEntityType.Builder.of(::BlockEntityExplosionDebugger, MBlocks.DEBUG_EXPLOSION_SMALL).build(null) } - val DEBUG_SPHERE_POINTS: BlockEntityType<*> by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockEntityType.Builder.of(::BlockEntitySphereDebugger, MBlocks.DEBUG_SPHERE_POINTS).build(null) } + val ENERGY_CABLES: Map> = SupplierMap(CablesConfig.E.entries.map { conf -> + var selfFeed: Supplier> = Supplier { TODO() } + selfFeed = register("${conf.name.lowercase()}_energy_cable", { a, b -> SimpleEnergyCableBlockEntity(selfFeed.get(), a, b, conf) }) as Supplier> + conf to selfFeed::get + }) + + val POWERED_FURNACE: BlockEntityType by registry.register(MNames.POWERED_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_FURNACE).build(null) } + val POWERED_BLAST_FURNACE: BlockEntityType by registry.register(MNames.POWERED_BLAST_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_BLAST_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_BLAST_FURNACE).build(null) } + val POWERED_SMOKER: BlockEntityType by registry.register(MNames.POWERED_SMOKER) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_SMOKER.newBlockEntity(a, b) }, MBlocks.POWERED_SMOKER).build(null) } + + val STORAGE_BUS: BlockEntityType by registry.register(MNames.STORAGE_BUS) { BlockEntityType.Builder.of(::StorageBusBlockEntity, MBlocks.STORAGE_BUS).build(null) } + val STORAGE_IMPORTER: BlockEntityType by registry.register(MNames.STORAGE_IMPORTER) { BlockEntityType.Builder.of(::StorageImporterBlockEntity, MBlocks.STORAGE_IMPORTER).build(null) } + val STORAGE_EXPORTER: BlockEntityType by registry.register(MNames.STORAGE_EXPORTER) { BlockEntityType.Builder.of(::StorageExporterBlockEntity, MBlocks.STORAGE_EXPORTER).build(null) } + val STORAGE_POWER_SUPPLIER: BlockEntityType by registry.register(MNames.STORAGE_POWER_SUPPLIER) { BlockEntityType.Builder.of(::StoragePowerSupplierBlockEntity, MBlocks.STORAGE_POWER_SUPPLIER).build(null) } + + val HOLO_SIGN: BlockEntityType by registry.register(MNames.HOLO_SIGN) { BlockEntityType.Builder.of(::HoloSignBlockEntity, MBlocks.HOLO_SIGN).build(null) } + + val DEBUG_EXPLOSION_SMALL: BlockEntityType by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockEntityType.Builder.of(::BlockEntityExplosionDebugger, MBlocks.DEBUG_EXPLOSION_SMALL).build(null) } + val DEBUG_SPHERE_POINTS: BlockEntityType by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockEntityType.Builder.of(::BlockEntitySphereDebugger, MBlocks.DEBUG_SPHERE_POINTS).build(null) } internal fun register(bus: IEventBus) { registry.register(bus) @@ -66,12 +106,16 @@ object MBlockEntities { @Suppress("unchecked_cast") private fun registerClient(event: FMLClientSetupEvent) { event.enqueueWork { - BlockEntityRenderers.register(BLACK_HOLE as BlockEntityType, ::BlackHoleRenderer) - BlockEntityRenderers.register(GRAVITATION_STABILIZER as BlockEntityType, ::GravitationStabilizerRenderer) - BlockEntityRenderers.register(ENERGY_COUNTER as BlockEntityType, ::EnergyCounterRenderer) - BlockEntityRenderers.register(BATTERY_BANK as BlockEntityType, ::BatteryBankRenderer) - BlockEntityRenderers.register(MATTER_CAPACITOR_BANK as BlockEntityType, ::MatterBatteryBankRenderer) - BlockEntityRenderers.register(MATTER_REPLICATOR as BlockEntityType, ::MatterReplicatorRenderer) + BlockEntityRenderers.register(BLACK_HOLE, ::BlackHoleRenderer) + BlockEntityRenderers.register(GRAVITATION_STABILIZER, ::GravitationStabilizerRenderer) + BlockEntityRenderers.register(ENERGY_COUNTER, ::EnergyCounterRenderer) + BlockEntityRenderers.register(BATTERY_BANK, ::BatteryBankRenderer) + BlockEntityRenderers.register(MATTER_CAPACITOR_BANK, ::MatterBatteryBankRenderer) + BlockEntityRenderers.register(MATTER_RECONSTRUCTOR, ::MatterReconstructorRenderer) + BlockEntityRenderers.register(MATTER_REPLICATOR, ::MatterReplicatorRenderer) + BlockEntityRenderers.register(MATTER_SCANNER, ::MatterScannerRenderer) + BlockEntityRenderers.register(HOLO_SIGN, ::HoloSignRenderer) + BlockEntityRenderers.register(FLUID_TANK, ::FluidTankRenderer) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt index 06625839d..ef5561e86 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBlocks.kt @@ -3,16 +3,22 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component +import net.minecraft.sounds.SoundEvents import net.minecraft.util.valueproviders.UniformInt import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.monster.Zombie import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.block.AnvilBlock import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.DoorBlock import net.minecraft.world.level.block.DropExperienceBlock +import net.minecraft.world.level.block.IronBarsBlock +import net.minecraft.world.level.block.LiquidBlock import net.minecraft.world.level.block.SlabBlock import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.block.StairBlock @@ -26,27 +32,26 @@ import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.block.AndroidStationBlock -import ru.dbotthepony.mc.otm.block.BatteryBankBlock import ru.dbotthepony.mc.otm.block.BlackHoleBlock import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger -import ru.dbotthepony.mc.otm.block.BlockGravitationStabilizer -import ru.dbotthepony.mc.otm.block.BlockGravitationStabilizerLens import ru.dbotthepony.mc.otm.block.BlockSphereDebugger -import ru.dbotthepony.mc.otm.block.ChemicalGeneratorBlock -import ru.dbotthepony.mc.otm.block.EnergyCounterBlock -import ru.dbotthepony.mc.otm.block.EnergyServoBlock -import ru.dbotthepony.mc.otm.block.LaboratoryLamp -import ru.dbotthepony.mc.otm.block.LaboratoryLampLight +import ru.dbotthepony.mc.otm.block.EnergyCableBlock import ru.dbotthepony.mc.otm.block.MatterCableBlock -import ru.dbotthepony.mc.otm.block.PhantomAttractorBlock -import ru.dbotthepony.mc.otm.block.PlatePressBlock import ru.dbotthepony.mc.otm.block.StorageCableBlock -import ru.dbotthepony.mc.otm.block.TritaniumPressurePlate +import ru.dbotthepony.mc.otm.block.decorative.DevChestBlock +import ru.dbotthepony.mc.otm.block.decorative.EngineBlock +import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock +import ru.dbotthepony.mc.otm.block.decorative.HoloSignBlock +import ru.dbotthepony.mc.otm.block.decorative.InfiniteWaterSourceBlock +import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLamp +import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLampLight +import ru.dbotthepony.mc.otm.block.decorative.PainterBlock import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock import ru.dbotthepony.mc.otm.block.matter.MatterCapacitorBankBlock import ru.dbotthepony.mc.otm.block.matter.MatterDecomposerBlock +import ru.dbotthepony.mc.otm.block.matter.MatterEntanglerBlock import ru.dbotthepony.mc.otm.block.matter.MatterPanelBlock +import ru.dbotthepony.mc.otm.block.matter.MatterReconstructorBlock import ru.dbotthepony.mc.otm.block.matter.MatterRecyclerBlock import ru.dbotthepony.mc.otm.block.matter.MatterReplicatorBlock import ru.dbotthepony.mc.otm.block.matter.MatterScannerBlock @@ -58,7 +63,25 @@ import ru.dbotthepony.mc.otm.block.storage.StorageBusBlock import ru.dbotthepony.mc.otm.block.storage.StorageExporterBlock import ru.dbotthepony.mc.otm.block.storage.StorageImporterBlock import ru.dbotthepony.mc.otm.block.storage.StoragePowerSupplierBlock +import ru.dbotthepony.mc.otm.block.tech.AndroidChargerBlock +import ru.dbotthepony.mc.otm.block.tech.AndroidStationBlock +import ru.dbotthepony.mc.otm.block.tech.BatteryBankBlock +import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizer +import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizerLens +import ru.dbotthepony.mc.otm.block.tech.ChemicalGeneratorBlock +import ru.dbotthepony.mc.otm.block.tech.CobblerBlock +import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock +import ru.dbotthepony.mc.otm.block.tech.EnergyServoBlock +import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock +import ru.dbotthepony.mc.otm.block.tech.PhantomAttractorBlock +import ru.dbotthepony.mc.otm.block.tech.PlatePressBlock +import ru.dbotthepony.mc.otm.block.tech.PoweredFurnaceBlock +import ru.dbotthepony.mc.otm.config.CablesConfig +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.collect.SupplierList +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import ru.dbotthepony.mc.otm.shapes.BlockShapes object MBlocks { private val registry = DeferredRegister.create(ForgeRegistries.BLOCKS, OverdriveThatMatters.MOD_ID) @@ -68,6 +91,7 @@ object MBlocks { } val ANDROID_STATION: Block by registry.register(MNames.ANDROID_STATION) { AndroidStationBlock() } + val ANDROID_CHARGER: Block by registry.register(MNames.ANDROID_CHARGER) { AndroidChargerBlock() } val BATTERY_BANK: Block by registry.register(MNames.BATTERY_BANK) { BatteryBankBlock() } val MATTER_DECOMPOSER: Block by registry.register(MNames.MATTER_DECOMPOSER) { MatterDecomposerBlock() } val MATTER_CAPACITOR_BANK: Block by registry.register(MNames.MATTER_CAPACITOR_BANK) { MatterCapacitorBankBlock() } @@ -80,8 +104,22 @@ object MBlocks { val ENERGY_COUNTER: Block by registry.register(MNames.ENERGY_COUNTER) { EnergyCounterBlock() } val CHEMICAL_GENERATOR: Block by registry.register(MNames.CHEMICAL_GENERATOR) { ChemicalGeneratorBlock() } val PLATE_PRESS: Block by registry.register(MNames.PLATE_PRESS) { PlatePressBlock() } + val TWIN_PLATE_PRESS: Block by registry.register(MNames.TWIN_PLATE_PRESS) { PlatePressBlock(isTwin = true) } + val POWERED_FURNACE: PoweredFurnaceBlock by registry.register(MNames.POWERED_FURNACE) { PoweredFurnaceBlock(MBlockEntities::POWERED_FURNACE, RecipeType.SMELTING, null, MachinesConfig.POWERED_FURNACE, BlockShapes.POWERED_FURNACE) } + val POWERED_BLAST_FURNACE: PoweredFurnaceBlock by registry.register(MNames.POWERED_BLAST_FURNACE) { PoweredFurnaceBlock(MBlockEntities::POWERED_BLAST_FURNACE, RecipeType.BLASTING, null, MachinesConfig.POWERED_BLAST_FURNACE, BlockShapes.POWERED_BLAST_FURNACE) } + val POWERED_SMOKER: PoweredFurnaceBlock by registry.register(MNames.POWERED_SMOKER) { PoweredFurnaceBlock(MBlockEntities::POWERED_SMOKER, RecipeType.SMOKING, MRecipes::MICROWAVE, MachinesConfig.POWERED_SMOKER, null) } val MATTER_RECYCLER: Block by registry.register(MNames.MATTER_RECYCLER) { MatterRecyclerBlock() } val ENERGY_SERVO: Block by registry.register(MNames.ENERGY_SERVO) { EnergyServoBlock() } + val COBBLESTONE_GENERATOR: Block by registry.register(MNames.COBBLESTONE_GENERATOR) { CobblerBlock() } + val INFINITE_WATER_SOURCE: Block by registry.register(MNames.INFINITE_WATER_SOURCE) { InfiniteWaterSourceBlock() } + val ESSENCE_STORAGE: EssenceStorageBlock by registry.register(MNames.ESSENCE_STORAGE) { EssenceStorageBlock() } + val MATTER_RECONSTRUCTOR: MatterReconstructorBlock by registry.register(MNames.MATTER_RECONSTRUCTOR) { MatterReconstructorBlock() } + val PAINTER: PainterBlock by registry.register(MNames.PAINTER) { PainterBlock() } + val MATTER_ENTANGLER: MatterEntanglerBlock by registry.register(MNames.MATTER_ENTANGLER) { MatterEntanglerBlock() } + + val ENERGY_CABLES: Map = SupplierMap(CablesConfig.E.entries.map { conf -> + conf to registry.register("${conf.name.lowercase()}_energy_cable") { EnergyCableBlock { a, b -> MBlockEntities.ENERGY_CABLES[conf]!!.create(a, b)!! } }::get + }) val STORAGE_BUS: Block by registry.register(MNames.STORAGE_BUS) { StorageBusBlock() } val STORAGE_IMPORTER: Block by registry.register(MNames.STORAGE_IMPORTER) { StorageImporterBlock() } @@ -101,6 +139,10 @@ object MBlocks { val GRAVITATION_STABILIZER_LENS: Block by registry.register(MNames.GRAVITATION_STABILIZER_LENS) { BlockGravitationStabilizerLens() } val PHANTOM_ATTRACTOR: Block by registry.register(MNames.PHANTOM_ATTRACTOR) { PhantomAttractorBlock() } + val FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() } + val DEV_CHEST: DevChestBlock by registry.register(MNames.DEV_CHEST) { DevChestBlock() } + + val LIQUID_XP: LiquidBlock by registry.register("liquid_xp") { LiquidBlock(MFluids::LIQUID_XP, BlockBehaviour.Properties.of(Material.WATER, MaterialColor.EMERALD).noCollission().strength(100.0f).noLootTable().sound(SoundType.POWDER_SNOW)) } val TRITANIUM_ORE: Block by registry.register(MNames.TRITANIUM_ORE) { DropExperienceBlock( BlockBehaviour.Properties.of(Material.STONE) @@ -110,30 +152,83 @@ object MBlocks { ) } val TRITANIUM_INGOT_BLOCK: Block by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { - Block(BlockBehaviour.Properties.of(Material.METAL, MaterialColor.COLOR_BLUE).explosionResistance(400f).destroyTime(3f).requiresCorrectToolForDrops()) + Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.BLUE).sound(SoundType.METAL).explosionResistance(400f).destroyTime(3f).requiresCorrectToolForDrops()) + } + + val METAL_JUNK: Block by registry.register(MNames.METAL_JUNK) { + object : Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.GRAY).sound(SoundType.NETHERITE_BLOCK).explosionResistance(45f).destroyTime(3f).requiresCorrectToolForDrops()) { + override fun appendHoverText( + p_49816_: ItemStack, + p_49817_: BlockGetter?, + p_49818_: MutableList, + p_49819_: TooltipFlag + ) { + super.appendHoverText(p_49816_, p_49817_, p_49818_, p_49819_) + p_49818_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.DARK_GRAY)) + } + } + } + + val METAL_MESH: Block by registry.register(MNames.METAL_MESH) { + Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.GRAY) + .noOcclusion() + .sound(SoundType.COPPER).explosionResistance(30f) + .destroyTime(2f).requiresCorrectToolForDrops()) + } + + val TRITANIUM_BARS: IronBarsBlock by registry.register(MNames.TRITANIUM_BARS) { + IronBarsBlock(BlockBehaviour.Properties.of(Material.METAL, DyeColor.LIGHT_BLUE).sound(SoundType.METAL).explosionResistance(45f).destroyTime(2.5f).requiresCorrectToolForDrops()) } val DEEPSLATE_TRITANIUM_ORE: Block by registry.register(MNames.DEEPSLATE_TRITANIUM_ORE) { DropExperienceBlock( - BlockBehaviour.Properties.of(Material.STONE) + BlockBehaviour.Properties.of(Material.STONE, MaterialColor.DEEPSLATE) + .sound(SoundType.DEEPSLATE) .strength(4.75f, 6.5f) .requiresCorrectToolForDrops().sound(SoundType.DEEPSLATE), UniformInt.of(0, 3) ) } val TRITANIUM_RAW_BLOCK: Block by registry.register(MNames.TRITANIUM_RAW_BLOCK) { Block( - BlockBehaviour.Properties.of(Material.STONE) - .strength(8.0f, 10f) - .requiresCorrectToolForDrops() + BlockBehaviour.Properties.of(Material.STONE, DyeColor.LIGHT_BLUE).strength(8.0f, 10f).requiresCorrectToolForDrops() ) } val LABORATORY_LAMP: Block by registry.register(MNames.LABORATORY_LAMP) { LaboratoryLamp(false) } val LABORATORY_LAMP_INVERTED: Block by registry.register(MNames.LABORATORY_LAMP_INVERTED) { LaboratoryLamp(true) } val LABORATORY_LAMP_LIGHT: Block by registry.register(MNames.LABORATORY_LAMP_LIGHT) { LaboratoryLampLight() } - val DANGER_STRIPE_BLOCK: Block by registry.register(MNames.DANGER_STRIPE_BLOCK) { Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.GRAY).explosionResistance(6f).destroyTime(1.5f).requiresCorrectToolForDrops()) } - val METAL_BEAM: Block by registry.register(MNames.METAL_BEAM) { Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.GRAY).explosionResistance(14f).destroyTime(2.5f).requiresCorrectToolForDrops()) } + val DANGER_STRIPE_BLOCK: Block by registry.register(MNames.DANGER_STRIPE_BLOCK) { Block(BlockBehaviour.Properties.of(Material.STONE, DyeColor.GRAY).explosionResistance(6f).destroyTime(1.5f).requiresCorrectToolForDrops()) } + val METAL_BEAM: Block by registry.register(MNames.METAL_BEAM) { Block(BlockBehaviour.Properties.of(Material.METAL, DyeColor.GRAY).sound(SoundType.METAL).explosionResistance(14f).destroyTime(2.5f).requiresCorrectToolForDrops()) } + val ENGINE: Block by registry.register(MNames.ENGINE) { EngineBlock() } + val HOLO_SIGN: Block by registry.register(MNames.HOLO_SIGN) { HoloSignBlock() } + + const val TRITANIUM_ANVIL_VARIANTS = 7 + + val TRITANIUM_ANVIL: List + + init { + val anvils = ArrayList<() -> Block>() + + for (i in 0 until TRITANIUM_ANVIL_VARIANTS) { + val props = BlockBehaviour.Properties.of(Material.HEAVY_METAL, DyeColor.LIGHT_BLUE) + .sound(SoundType.ANVIL) + .destroyTime(2.5f - i * 0.15f) + .explosionResistance(1200f - i * 80f) + .requiresCorrectToolForDrops() + + anvils.add(registry.register(MNames.TRITANIUM_ANVIL + i) { AnvilBlock(props) }::get) + } + + TRITANIUM_ANVIL = SupplierList(anvils) + } val TRITANIUM_DOOR = registry.allColored(MNames.TRITANIUM_DOOR) { color, _ -> - object : DoorBlock(Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE).explosionResistance(80f).noOcclusion().destroyTime(3f).requiresCorrectToolForDrops()) { + object : DoorBlock( + Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE) + .explosionResistance(80f) + .noOcclusion() + .destroyTime(3f) + .requiresCorrectToolForDrops(), + SoundEvents.IRON_DOOR_CLOSE, SoundEvents.IRON_DOOR_OPEN + ) { override fun appendHoverText( p_49816_: ItemStack, p_49817_: BlockGetter?, @@ -161,7 +256,14 @@ object MBlocks { } val TRITANIUM_TRAPDOOR = registry.allColored(MNames.TRITANIUM_TRAPDOOR) { color, _ -> - object : TrapDoorBlock(Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE).explosionResistance(80f).noOcclusion().destroyTime(3f).requiresCorrectToolForDrops()) { + object : TrapDoorBlock( + Properties.of(Material.METAL, color ?: DyeColor.LIGHT_BLUE) + .explosionResistance(80f) + .noOcclusion().destroyTime(3f) + .requiresCorrectToolForDrops() + .isValidSpawn { _: BlockState, _: BlockGetter, _: BlockPos, _: EntityType<*>? -> false }, + SoundEvents.IRON_DOOR_CLOSE, SoundEvents.IRON_DOOR_OPEN + ) { override fun appendHoverText( p_49816_: ItemStack, p_49817_: BlockGetter?, @@ -198,7 +300,7 @@ object MBlocks { } val TRITANIUM_STRIPED_BLOCK: Block by registry.register(MNames.TRITANIUM_STRIPED_BLOCK) { Block( - BlockBehaviour.Properties.of(Material.METAL, MaterialColor.COLOR_LIGHT_BLUE) + BlockBehaviour.Properties.of(Material.METAL, DyeColor.LIGHT_BLUE) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(80f) @@ -219,7 +321,7 @@ object MBlocks { } val CARBON_FIBRE_BLOCK: Block by registry.register(MNames.CARBON_FIBRE_BLOCK) { Block( - BlockBehaviour.Properties.of(Material.METAL, MaterialColor.COLOR_LIGHT_BLUE) + BlockBehaviour.Properties.of(Material.METAL, DyeColor.BLACK) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(40f) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt new file mode 100644 index 000000000..e73002ea4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt @@ -0,0 +1,297 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.CreativeModeTab +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.material.Fluids +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.event.CreativeModeTabEvent +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler +import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.matter.matter +import ru.dbotthepony.mc.otm.capability.matteryEnergy +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.ifPresentK +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator +import ru.dbotthepony.mc.otm.core.util.WriteOnce +import ru.dbotthepony.mc.otm.registry.MItems.BATTERY_CREATIVE + +private fun CreativeModeTab.Output.accept(values: Collection) { + for (item in values) { + accept(item) + } +} + +private fun CreativeModeTab.Output.base(values: Map) { + accept(values[null]!!) +} + +private val colorOrder = listOf( + null, + DyeColor.WHITE, + DyeColor.ORANGE, + DyeColor.MAGENTA, + DyeColor.LIGHT_BLUE, + DyeColor.YELLOW, + DyeColor.LIME, + DyeColor.PINK, + DyeColor.GRAY, + DyeColor.LIGHT_GRAY, + DyeColor.CYAN, + DyeColor.PURPLE, + DyeColor.BLUE, + DyeColor.BROWN, + DyeColor.GREEN, + DyeColor.RED, + DyeColor.BLACK, +) + +private fun CreativeModeTab.Output.colored(values: Map) { + accept(values[DyeColor.WHITE]!!) + accept(values[DyeColor.ORANGE]!!) + accept(values[DyeColor.MAGENTA]!!) + accept(values[DyeColor.LIGHT_BLUE]!!) + accept(values[DyeColor.YELLOW]!!) + accept(values[DyeColor.LIME]!!) + accept(values[DyeColor.PINK]!!) + accept(values[DyeColor.GRAY]!!) + accept(values[DyeColor.LIGHT_GRAY]!!) + accept(values[DyeColor.CYAN]!!) + accept(values[DyeColor.PURPLE]!!) + accept(values[DyeColor.BLUE]!!) + accept(values[DyeColor.BROWN]!!) + accept(values[DyeColor.GREEN]!!) + accept(values[DyeColor.RED]!!) + accept(values[DyeColor.BLACK]!!) +} + +private fun CreativeModeTab.Output.all(values: Map) { + base(values) + colored(values) +} + +private fun CreativeModeTab.Output.energized(value: Item) { + accept(value) + + val stack = ItemStack(value, 1) + val energy = stack.matteryEnergy ?: throw IllegalArgumentException("${value.registryName} does not implement mattery energy capability") + energy.fillBattery() + + if (ItemStack(value, 1).matteryEnergy!!.batteryLevel != energy.batteryLevel) + accept(stack) +} + +private fun CreativeModeTab.Output.energized(values: Iterable) { + for (value in values) { + energized(value) + } +} + +private fun CreativeModeTab.Output.mattery(value: Item) { + accept(value) + + val stack = ItemStack(value, 1) + val matter = stack.matter ?: throw IllegalArgumentException("${value.registryName} does not implement matter capability") + + matter.fillMatter() + + if (ItemStack(value, 1).matter!!.storedMatter != matter.storedMatter) + accept(stack) +} + +private fun CreativeModeTab.Output.mattery(values: Iterable) { + for (value in values) { + mattery(value) + } +} + +private fun CreativeModeTab.Output.fluids(value: Item) { + accept(value) + + for (fluid in ForgeRegistries.FLUIDS.values) { + if (fluid != Fluids.EMPTY && fluid.isSource(fluid.defaultFluidState())) { + accept(ItemStack(value, 1).also { + it.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + it.fill(FluidStack(fluid, it.getTankCapacity(0)), IFluidHandler.FluidAction.EXECUTE) + } + }) + } + } +} + +private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) { + with(consumer) { + accept(MItems.ENERGY_CABLES.values) + accept(MItems.MACHINES) + accept(MItems.MachineUpgrades.Basic.LIST) + accept(MItems.MachineUpgrades.Normal.LIST) + accept(MItems.MachineUpgrades.Advanced.LIST) + accept(MItems.MachineUpgrades.Creative.LIST) + + accept(MRegistry.CARGO_CRATES.item) + accept(MItems.DEV_CHEST) + accept(MItems.HOLO_SIGN) + + base(MItems.TRITANIUM_DOOR) + base(MItems.TRITANIUM_TRAPDOOR) + accept(MRegistry.TRITANIUM_PRESSURE_PLATE.item) + accept(MItems.TRITANIUM_ANVIL[0]) + + // accept(MItems.MATTER_DUST) + + accept(MItems.TRITANIUM_ORE) + accept(MItems.DEEPSLATE_TRITANIUM_ORE) + accept(MItems.TRITANIUM_ORE_CLUMP) + accept(MItems.TRITANIUM_DUST) + accept(MItems.TRITANIUM_NUGGET) + accept(MItems.TRITANIUM_INGOT) + accept(MItems.TRITANIUM_INGOT_BLOCK) + + accept(MItems.TRITANIUM_TOOLS) + accept(MItems.TRITANIUM_SHIELD) + accept(MItems.SIMPLE_TRITANIUM_ARMOR) + accept(MItems.TRITANIUM_ARMOR) + + energized(MItems.ENERGY_SWORD) + energized(MItems.PLASMA_RIFLE) + + accept(MItems.EXPLOSIVE_HAMMER) + accept(ItemStack(MItems.EXPLOSIVE_HAMMER).also { MItems.EXPLOSIVE_HAMMER.prime(it) }) + + accept(MItems.BLACK_HOLE_SCANNER) + accept(MItems.GRAVITATION_FIELD_LIMITER) + accept(MItems.GRAVITATION_FIELD_SENSOR) + accept(MItems.PORTABLE_GRAVITATION_STABILIZER) + accept(MItems.BLACK_HOLE) + accept(MItems.GRAVITATIONAL_DISRUPTOR) + + accept(MItems.CHEST_UPGRADER) + + accept(MItems.ESSENCE_SERVO) + + energized(MItems.ALL_BATTERIES) + mattery(MItems.MATTER_CAPACITORS) + accept(MItems.PATTERN_DRIVE_NORMAL) + accept(MItems.PATTERN_DRIVE_CREATIVE) + accept(MItems.PATTERN_DRIVE_CREATIVE2) + + accept(MItems.PORTABLE_CONDENSATION_DRIVE) + accept(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE) + + fluids(MItems.FLUID_CAPSULE) + fluids(MItems.FLUID_TANK) + + base(MItems.CARGO_CRATE_MINECARTS) + + accept(MItems.NUTRIENT_PASTE) + + // exo + accept(MItems.EXOPACK_PROBE) + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADE_CREATIVE) + accept(MItems.ExopackUpgrades.CRAFTING_UPGRADE) + accept(MItems.ExopackUpgrades.SMELTING_UPGRADE) + accept(MItems.ExopackUpgrades.ENDER_UPGRADE) + + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADE_BIG) + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADE_HUGE) + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADE_WITHER) + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADE_ENDER_DRAGON) + + accept(MItems.ExopackUpgrades.INVENTORY_UPGRADES) + // /exo + + accept(MItems.PILLS) + + accept(MItems.COMPONENTS) + } +} + +private fun addDecorativeTabItems(consumer: CreativeModeTab.Output) { + with(consumer) { + accept(MItems.LABORATORY_LAMP) + accept(MItems.LABORATORY_LAMP_INVERTED) + accept(MItems.DANGER_STRIPE_BLOCK) + accept(MItems.METAL_BEAM) + accept(MItems.ENGINE) + + accept(MItems.TRITANIUM_STRIPED_BLOCK) + accept(MItems.TRITANIUM_STRIPED_STAIRS) + accept(MItems.TRITANIUM_STRIPED_SLAB) + accept(MItems.TRITANIUM_STRIPED_WALL) + accept(MItems.CARBON_FIBRE_BLOCK) + accept(MItems.METAL_JUNK) + accept(MItems.METAL_MESH) + + accept(MItems.TRITANIUM_BARS) + + colored(MItems.TRITANIUM_DOOR) + colored(MItems.TRITANIUM_TRAPDOOR) + accept(MRegistry.TRITANIUM_PRESSURE_PLATE.items.values) + + for (i in 0 until MItems.TRITANIUM_ANVIL.size) + accept(MItems.TRITANIUM_ANVIL[i]) + + colored(MRegistry.CARGO_CRATES.items) + + colored(MItems.CARGO_CRATE_MINECARTS) + + all(MRegistry.DECORATIVE_CRATE.allItems) + + for (color in colorOrder) { + accept(MRegistry.TRITANIUM_BLOCK.allItems[color]!!) + accept(MRegistry.TRITANIUM_STAIRS.allItems[color]!!) + accept(MRegistry.TRITANIUM_SLAB.allItems[color]!!) + accept(MRegistry.TRITANIUM_WALL.allItems[color]!!) + } + + all(MRegistry.INDUSTRIAL_GLASS.allItems) + all(MRegistry.INDUSTRIAL_GLASS_PANE.allItems) + + colored(MRegistry.UNREFINED_FLOOR_TILES.items) + colored(MRegistry.FLOOR_TILES.items) + colored(MRegistry.FLOOR_TILES_STAIRS.items) + colored(MRegistry.FLOOR_TILES_SLAB.items) + + all(MRegistry.VENT.allItems) + all(MRegistry.VENT_ALTERNATIVE.allItems) + + accept(MRegistry.TRITANIUM_STRIPED_BLOCK.flatItems) + accept(MRegistry.TRITANIUM_STRIPED_STAIRS.flatItems) + accept(MRegistry.TRITANIUM_STRIPED_SLAB.flatItems) + accept(MRegistry.TRITANIUM_STRIPED_WALL.flatItems) + } +} + +object MCreativeTabs { + var MAIN by WriteOnce() + private set + var DECORATIVE by WriteOnce() + private set + + fun register(event: CreativeModeTabEvent.Register) { + CreativeMenuItemComparator.invalidate() + + MAIN = event.registerCreativeModeTab(ResourceLocation(OverdriveThatMatters.MOD_ID, "main")) { + it.icon { ItemStack(BATTERY_CREATIVE, 1) } + it.title(TranslatableComponent("itemGroup.otm")) + + it.displayItems { _, consumer, hasPerms -> + addMainCreativeTabItems(consumer) + } + } + + DECORATIVE = event.registerCreativeModeTab(ResourceLocation(OverdriveThatMatters.MOD_ID, "decorative")) { + it.icon { ItemStack(MRegistry.VENT.item, 1) } + it.title(TranslatableComponent("itemGroup.otm_decorative")) + + it.displayItems { _, consumer, hasPerms -> + addDecorativeTabItems(consumer) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt new file mode 100644 index 000000000..680e58bfa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MDamageTypes.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.world.damagesource.DamageSource + +object MDamageTypes { + val BECOME_ANDROID = ImmutableDamageSource(DamageSource("become_android").bypassArmor().bypassInvul().bypassMagic().bypassEnchantments()) + val BECOME_HUMANE = ImmutableDamageSource(DamageSource("become_humane").bypassArmor().bypassInvul().bypassMagic().bypassEnchantments()) + val EVENT_HORIZON = ImmutableDamageSource(DamageSource("event_horizon").bypassArmor()) + val HAWKING_RADIATION = ImmutableDamageSource(DamageSource("hawking_radiation").bypassArmor()) + val EXOPACK_PROBE = ImmutableDamageSource(DamageSource("exopack_probe").bypassArmor().bypassMagic().bypassEnchantments()) + val EMP = ImmutableDamageSource(DamageSource("emp").bypassArmor()) + val SHOCKWAVE = ImmutableDamageSource(DamageSource("shockwave").bypassArmor()) + val PLASMA = ImmutableDamageSource(DamageSource("plasma")) + val COSMIC_RAYS = ImmutableDamageSource(DamageSource("cosmic_rays").bypassArmor().bypassMagic().bypassEnchantments()) + val EXPLOSIVE_HAMMER = ImmutableDamageSource(DamageSource("explosive_hammer")) + val HAMMER_NAIL = ImmutableDamageSource(DamageSource("hammer_nail")) + val ANDROID_DISCHARGE = ImmutableDamageSource(DamageSource("android_discharge").bypassArmor().bypassInvul().bypassMagic().bypassEnchantments()) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt new file mode 100644 index 000000000..d6e2bfe01 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MFluids.kt @@ -0,0 +1,66 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.resources.ResourceLocation +import net.minecraft.sounds.SoundEvents +import net.minecraft.world.item.Rarity +import net.minecraft.world.level.material.Fluid +import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions +import net.minecraftforge.common.SoundActions +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.fluids.FluidType +import net.minecraftforge.fluids.ForgeFlowingFluid +import net.minecraftforge.registries.DeferredRegister +import net.minecraftforge.registries.ForgeRegistries +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import java.util.function.Consumer + +object MFluids { + private val types: DeferredRegister = DeferredRegister.create(ForgeRegistries.Keys.FLUID_TYPES, OverdriveThatMatters.MOD_ID) + private val fluids: DeferredRegister = DeferredRegister.create(ForgeRegistries.FLUIDS, OverdriveThatMatters.MOD_ID) + + internal fun register(bus: IEventBus) { + types.register(bus) + fluids.register(bus) + } + + private fun makeXpProps(): ForgeFlowingFluid.Properties { + return ForgeFlowingFluid.Properties(::LIQUID_XP_TYPE, ::LIQUID_XP, ::LIQUID_XP_FLOWING).bucket(MItems::LIQUID_XP_BUCKET).block(MBlocks::LIQUID_XP) + } + + private val xpProps = makeXpProps() + + val LIQUID_XP_TYPE: FluidType by types.register("liquid_xp") { + object : FluidType( + Properties.create() + .rarity(Rarity.UNCOMMON) + .canDrown(true) + .canExtinguish(false) + .canSwim(true) + .canHydrate(false) + .canPushEntity(true) + .canConvertToSource(false) + .fallDistanceModifier(0f) + .sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL) + .sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY) + .sound(SoundActions.FLUID_VAPORIZE, SoundEvents.FIRE_EXTINGUISH) + .descriptionId("block.overdrive_that_matters.liquid_xp") + ) { + override fun initializeClient(consumer: Consumer) { + val path = ResourceLocation(OverdriveThatMatters.MOD_ID, "block/ph_kitty") + + consumer.accept(object : IClientFluidTypeExtensions { + override fun getStillTexture(): ResourceLocation { + return path + } + + override fun getFlowingTexture(): ResourceLocation { + return path + } + }) + } + } + } + + val LIQUID_XP: ForgeFlowingFluid.Source by fluids.register("liquid_xp") { ForgeFlowingFluid.Source(xpProps) } + val LIQUID_XP_FLOWING: ForgeFlowingFluid.Flowing by fluids.register("liquid_xp_flowing") { ForgeFlowingFluid.Flowing(xpProps) } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt index ef7ac0a2b..16a3b4d89 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItemFunctionTypes.kt @@ -1,16 +1,20 @@ package ru.dbotthepony.mc.otm.registry -import net.minecraft.core.Registry +import net.minecraft.core.registries.Registries import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.registries.DeferredRegister import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.data.loot.RandomizerFunction +import ru.dbotthepony.mc.otm.data.loot.CopyTileNbtFunction +import ru.dbotthepony.mc.otm.item.ProceduralBatteryItem +import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem object MItemFunctionTypes { - private val registry = DeferredRegister.create(Registry.LOOT_FUNCTION_REGISTRY, OverdriveThatMatters.MOD_ID) + private val registry = DeferredRegister.create(Registries.LOOT_FUNCTION_TYPE, OverdriveThatMatters.MOD_ID) - val RANDOMIZER: LootItemFunctionType by registry.register("randomizer") { LootItemFunctionType(RandomizerFunction.Companion) } + val COPY_TILE_NBT: LootItemFunctionType by registry.register("copy_tile_nbt") { LootItemFunctionType(CopyTileNbtFunction.CODEC) } + val PROCEDURAL_BATTERY: LootItemFunctionType by registry.register(MNames.PROCEDURAL_BATTERY) { LootItemFunctionType(ProceduralBatteryItem.Randomizer.CODEC) } + val PROCEDURAL_EXOPACK_UPGRADE: LootItemFunctionType by registry.register("exopack_upgrade") { LootItemFunctionType(ProceduralExopackSlotUpgradeItem.Randomizer.CODEC) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 6ecd83923..bc94ff668 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -3,102 +3,117 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component -import net.minecraft.tags.BlockTags +import net.minecraft.resources.ResourceLocation import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.food.FoodProperties import net.minecraft.world.item.* import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.level.Level import net.minecraftforge.common.ForgeTier +import net.minecraftforge.common.TierSortingRegistry import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.ServerConfig -import ru.dbotthepony.mc.otm.core.SupplierList +import ru.dbotthepony.mc.otm.capability.ITieredUpgradeSet +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.config.CablesConfig +import ru.dbotthepony.mc.otm.config.ItemsConfig +import ru.dbotthepony.mc.otm.core.collect.SupplierList import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.item.* +import ru.dbotthepony.mc.otm.item.exopack.ExopackProbeItem +import ru.dbotthepony.mc.otm.item.exopack.ExopackSlotUpgradeItem +import ru.dbotthepony.mc.otm.item.matter.CreativePatternItem +import ru.dbotthepony.mc.otm.item.matter.MatterCapacitorItem +import ru.dbotthepony.mc.otm.item.matter.MatterDustItem +import ru.dbotthepony.mc.otm.item.matter.PatternStorageItem +import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem +import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem +import ru.dbotthepony.mc.otm.item.tool.MatteryAxeItem +import ru.dbotthepony.mc.otm.item.armor.PortableGravitationStabilizerItem +import ru.dbotthepony.mc.otm.item.armor.SimpleTritaniumArmorItem +import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem +import ru.dbotthepony.mc.otm.item.exopack.ExopackUpgradeItem +import ru.dbotthepony.mc.otm.item.exopack.ProceduralExopackSlotUpgradeItem import ru.dbotthepony.mc.otm.item.weapon.PlasmaRifleItem object MItems { - private val DEFAULT_PROPERTIES = Item.Properties().stacksTo(64).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB) - private val DEFAULT_PROPERTIES_DECORATIVE = Item.Properties().stacksTo(64).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE) + private val DEFAULT_PROPERTIES = Item.Properties() private val registry: DeferredRegister = DeferredRegister.create(ForgeRegistries.ITEMS, OverdriveThatMatters.MOD_ID) internal fun register(bus: IEventBus) { registry.register(bus) } - val ANDROID_STATION: Item by registry.register(MNames.ANDROID_STATION) { BlockItem(MBlocks.ANDROID_STATION, DEFAULT_PROPERTIES) } - val BATTERY_BANK: Item by registry.register(MNames.BATTERY_BANK) { BlockItem(MBlocks.BATTERY_BANK, DEFAULT_PROPERTIES) } - val MATTER_DECOMPOSER: Item by registry.register(MNames.MATTER_DECOMPOSER) { BlockItem(MBlocks.MATTER_DECOMPOSER, DEFAULT_PROPERTIES) } - val MATTER_CAPACITOR_BANK: Item by registry.register(MNames.MATTER_CAPACITOR_BANK) { BlockItem(MBlocks.MATTER_CAPACITOR_BANK, DEFAULT_PROPERTIES) } - val MATTER_CABLE: Item by registry.register(MNames.MATTER_CABLE) { BlockItem(MBlocks.MATTER_CABLE, DEFAULT_PROPERTIES) } - val PATTERN_STORAGE: Item by registry.register(MNames.PATTERN_STORAGE) { BlockItem(MBlocks.PATTERN_STORAGE, DEFAULT_PROPERTIES) } - val MATTER_SCANNER: Item by registry.register(MNames.MATTER_SCANNER) { BlockItem(MBlocks.MATTER_SCANNER, DEFAULT_PROPERTIES) } - val MATTER_PANEL: Item by registry.register(MNames.MATTER_PANEL) { BlockItem(MBlocks.MATTER_PANEL, DEFAULT_PROPERTIES) } - val MATTER_REPLICATOR: Item by registry.register(MNames.MATTER_REPLICATOR) { BlockItem(MBlocks.MATTER_REPLICATOR, DEFAULT_PROPERTIES) } - val MATTER_BOTTLER: Item by registry.register(MNames.MATTER_BOTTLER) { BlockItem(MBlocks.MATTER_BOTTLER, DEFAULT_PROPERTIES) } + val ENERGY_CABLES: Map = SupplierMap(CablesConfig.E.entries.map { conf -> + conf to registry.register("${conf.name.lowercase()}_energy_cable") { BlockItem(MBlocks.ENERGY_CABLES[conf]!!, DEFAULT_PROPERTIES) }::get + }) - val TRITANIUM_ORE: Item by registry.register(MNames.TRITANIUM_ORE) { BlockItem(MBlocks.TRITANIUM_ORE, DEFAULT_PROPERTIES) } - val DEEPSLATE_TRITANIUM_ORE: Item by registry.register(MNames.DEEPSLATE_TRITANIUM_ORE) { BlockItem(MBlocks.DEEPSLATE_TRITANIUM_ORE, DEFAULT_PROPERTIES) } - val TRITANIUM_RAW_BLOCK: Item by registry.register(MNames.TRITANIUM_RAW_BLOCK) { BlockItem(MBlocks.TRITANIUM_RAW_BLOCK, DEFAULT_PROPERTIES) } - val ENERGY_COUNTER: Item by registry.register(MNames.ENERGY_COUNTER) { BlockItem(MBlocks.ENERGY_COUNTER, DEFAULT_PROPERTIES) } - val CHEMICAL_GENERATOR: Item by registry.register(MNames.CHEMICAL_GENERATOR) { BlockItem(MBlocks.CHEMICAL_GENERATOR, DEFAULT_PROPERTIES) } - val PLATE_PRESS: Item by registry.register(MNames.PLATE_PRESS) { BlockItem(MBlocks.PLATE_PRESS, DEFAULT_PROPERTIES) } - val MATTER_RECYCLER: Item by registry.register(MNames.MATTER_RECYCLER) { BlockItem(MBlocks.MATTER_RECYCLER, DEFAULT_PROPERTIES) } + val ANDROID_STATION: BlockItem by registry.register(MNames.ANDROID_STATION) { BlockItem(MBlocks.ANDROID_STATION, DEFAULT_PROPERTIES) } + val ANDROID_CHARGER: BlockItem by registry.register(MNames.ANDROID_CHARGER) { BlockItem(MBlocks.ANDROID_CHARGER, DEFAULT_PROPERTIES) } + val BATTERY_BANK: BlockItem by registry.register(MNames.BATTERY_BANK) { BlockItem(MBlocks.BATTERY_BANK, DEFAULT_PROPERTIES) } + val MATTER_DECOMPOSER: BlockItem by registry.register(MNames.MATTER_DECOMPOSER) { BlockItem(MBlocks.MATTER_DECOMPOSER, DEFAULT_PROPERTIES) } + val MATTER_CAPACITOR_BANK: BlockItem by registry.register(MNames.MATTER_CAPACITOR_BANK) { BlockItem(MBlocks.MATTER_CAPACITOR_BANK, DEFAULT_PROPERTIES) } + val MATTER_CABLE: BlockItem by registry.register(MNames.MATTER_CABLE) { BlockItem(MBlocks.MATTER_CABLE, DEFAULT_PROPERTIES) } + val PATTERN_STORAGE: BlockItem by registry.register(MNames.PATTERN_STORAGE) { BlockItem(MBlocks.PATTERN_STORAGE, DEFAULT_PROPERTIES) } + val MATTER_SCANNER: BlockItem by registry.register(MNames.MATTER_SCANNER) { BlockItem(MBlocks.MATTER_SCANNER, DEFAULT_PROPERTIES) } + val MATTER_PANEL: BlockItem by registry.register(MNames.MATTER_PANEL) { BlockItem(MBlocks.MATTER_PANEL, DEFAULT_PROPERTIES) } + val MATTER_REPLICATOR: BlockItem by registry.register(MNames.MATTER_REPLICATOR) { BlockItem(MBlocks.MATTER_REPLICATOR, DEFAULT_PROPERTIES) } + val MATTER_BOTTLER: BlockItem by registry.register(MNames.MATTER_BOTTLER) { BlockItem(MBlocks.MATTER_BOTTLER, DEFAULT_PROPERTIES) } - val STORAGE_BUS: Item by registry.register(MNames.STORAGE_BUS) { BlockItem(MBlocks.STORAGE_BUS, DEFAULT_PROPERTIES) } - val STORAGE_IMPORTER: Item by registry.register(MNames.STORAGE_IMPORTER) { BlockItem(MBlocks.STORAGE_IMPORTER, DEFAULT_PROPERTIES) } - val STORAGE_EXPORTER: Item by registry.register(MNames.STORAGE_EXPORTER) { BlockItem(MBlocks.STORAGE_EXPORTER, DEFAULT_PROPERTIES) } - val DRIVE_VIEWER: Item by registry.register(MNames.DRIVE_VIEWER) { BlockItem(MBlocks.DRIVE_VIEWER, DEFAULT_PROPERTIES) } - val DRIVE_RACK: Item by registry.register(MNames.DRIVE_RACK) { BlockItem(MBlocks.DRIVE_RACK, DEFAULT_PROPERTIES) } - val ITEM_MONITOR: Item by registry.register(MNames.ITEM_MONITOR) { BlockItem(MBlocks.ITEM_MONITOR, DEFAULT_PROPERTIES) } - val STORAGE_CABLE: Item by registry.register(MNames.STORAGE_CABLE) { BlockItem(MBlocks.STORAGE_CABLE, DEFAULT_PROPERTIES) } - val STORAGE_POWER_SUPPLIER: Item by registry.register(MNames.STORAGE_POWER_SUPPLIER) { BlockItem(MBlocks.STORAGE_POWER_SUPPLIER, DEFAULT_PROPERTIES) } + val TRITANIUM_ORE: BlockItem by registry.register(MNames.TRITANIUM_ORE) { BlockItem(MBlocks.TRITANIUM_ORE, DEFAULT_PROPERTIES) } + val DEEPSLATE_TRITANIUM_ORE: BlockItem by registry.register(MNames.DEEPSLATE_TRITANIUM_ORE) { BlockItem(MBlocks.DEEPSLATE_TRITANIUM_ORE, DEFAULT_PROPERTIES) } + val TRITANIUM_RAW_BLOCK: BlockItem by registry.register(MNames.TRITANIUM_RAW_BLOCK) { BlockItem(MBlocks.TRITANIUM_RAW_BLOCK, DEFAULT_PROPERTIES) } + val ENERGY_COUNTER: BlockItem by registry.register(MNames.ENERGY_COUNTER) { BlockItem(MBlocks.ENERGY_COUNTER, DEFAULT_PROPERTIES) } + val CHEMICAL_GENERATOR: BlockItem by registry.register(MNames.CHEMICAL_GENERATOR) { BlockItem(MBlocks.CHEMICAL_GENERATOR, DEFAULT_PROPERTIES) } + val PLATE_PRESS: BlockItem by registry.register(MNames.PLATE_PRESS) { BlockItem(MBlocks.PLATE_PRESS, DEFAULT_PROPERTIES) } + val TWIN_PLATE_PRESS: BlockItem by registry.register(MNames.TWIN_PLATE_PRESS) { BlockItem(MBlocks.TWIN_PLATE_PRESS, DEFAULT_PROPERTIES) } + val MATTER_RECYCLER: BlockItem by registry.register(MNames.MATTER_RECYCLER) { BlockItem(MBlocks.MATTER_RECYCLER, DEFAULT_PROPERTIES) } - val GRAVITATION_STABILIZER: Item by registry.register(MNames.GRAVITATION_STABILIZER) { + val POWERED_FURNACE: BlockItem by registry.register(MNames.POWERED_FURNACE) { BlockItem(MBlocks.POWERED_FURNACE, DEFAULT_PROPERTIES) } + val POWERED_BLAST_FURNACE: BlockItem by registry.register(MNames.POWERED_BLAST_FURNACE) { BlockItem(MBlocks.POWERED_BLAST_FURNACE, DEFAULT_PROPERTIES) } + val POWERED_SMOKER: BlockItem by registry.register(MNames.POWERED_SMOKER) { BlockItem(MBlocks.POWERED_SMOKER, DEFAULT_PROPERTIES) } + + val STORAGE_BUS: BlockItem by registry.register(MNames.STORAGE_BUS) { BlockItem(MBlocks.STORAGE_BUS, DEFAULT_PROPERTIES) } + val STORAGE_IMPORTER: BlockItem by registry.register(MNames.STORAGE_IMPORTER) { BlockItem(MBlocks.STORAGE_IMPORTER, DEFAULT_PROPERTIES) } + val STORAGE_EXPORTER: BlockItem by registry.register(MNames.STORAGE_EXPORTER) { BlockItem(MBlocks.STORAGE_EXPORTER, DEFAULT_PROPERTIES) } + val DRIVE_VIEWER: BlockItem by registry.register(MNames.DRIVE_VIEWER) { BlockItem(MBlocks.DRIVE_VIEWER, DEFAULT_PROPERTIES) } + val DRIVE_RACK: BlockItem by registry.register(MNames.DRIVE_RACK) { BlockItem(MBlocks.DRIVE_RACK, DEFAULT_PROPERTIES) } + val ITEM_MONITOR: BlockItem by registry.register(MNames.ITEM_MONITOR) { BlockItem(MBlocks.ITEM_MONITOR, DEFAULT_PROPERTIES) } + val STORAGE_CABLE: BlockItem by registry.register(MNames.STORAGE_CABLE) { BlockItem(MBlocks.STORAGE_CABLE, DEFAULT_PROPERTIES) } + val STORAGE_POWER_SUPPLIER: BlockItem by registry.register(MNames.STORAGE_POWER_SUPPLIER) { BlockItem(MBlocks.STORAGE_POWER_SUPPLIER, DEFAULT_PROPERTIES) } + + val GRAVITATION_STABILIZER: BlockItem by registry.register(MNames.GRAVITATION_STABILIZER) { object : BlockItem(MBlocks.GRAVITATION_STABILIZER, DEFAULT_PROPERTIES) { - override fun appendHoverText( - p_40572_: ItemStack, - p_40573_: Level?, - p_40574_: MutableList, - p_40575_: TooltipFlag - ) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) p_40574_.add(TranslatableComponent("${MBlocks.GRAVITATION_STABILIZER.descriptionId}.desc").withStyle(ChatFormatting.GRAY)) + p_40574_.add(TranslatableComponent("otm.needs_no_power").withStyle(ChatFormatting.DARK_GRAY)) p_40574_.add(TranslatableComponent("${MBlocks.GRAVITATION_STABILIZER.descriptionId}.desc2").withStyle(ChatFormatting.DARK_GRAY)) p_40574_.add(TranslatableComponent("${MBlocks.GRAVITATION_STABILIZER.descriptionId}.desc3").withStyle(ChatFormatting.DARK_GRAY)) - p_40574_.add(TranslatableComponent("${MBlocks.GRAVITATION_STABILIZER.descriptionId}.desc4").withStyle(ChatFormatting.DARK_GRAY)) } } } - val PHANTOM_ATTRACTOR: Item by registry.register(MNames.PHANTOM_ATTRACTOR) { + val PHANTOM_ATTRACTOR: DoubleHighBlockItem by registry.register(MNames.PHANTOM_ATTRACTOR) { object : DoubleHighBlockItem(MBlocks.PHANTOM_ATTRACTOR, DEFAULT_PROPERTIES) { - override fun appendHoverText( - p_40572_: ItemStack, - p_40573_: Level?, - p_40574_: MutableList, - p_40575_: TooltipFlag - ) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) p_40574_.add(TranslatableComponent("${MBlocks.PHANTOM_ATTRACTOR.descriptionId}.desc").withStyle(ChatFormatting.GRAY)) - p_40574_.add(TranslatableComponent("${MBlocks.PHANTOM_ATTRACTOR.descriptionId}.desc2").withStyle(ChatFormatting.DARK_GRAY)) + p_40574_.add(TranslatableComponent("otm.needs_no_power").withStyle(ChatFormatting.DARK_GRAY)) } } } - val ENERGY_SERVO: Item by registry.register(MNames.ENERGY_SERVO) { - object : BlockItem(MBlocks.ENERGY_SERVO, DEFAULT_PROPERTIES){ - override fun appendHoverText( - p_40572_: ItemStack, - p_40573_: Level?, - p_40574_: MutableList, - p_40575_: TooltipFlag - ) { + val ENERGY_SERVO: BlockItem by registry.register(MNames.ENERGY_SERVO) { + object : BlockItem(MBlocks.ENERGY_SERVO, DEFAULT_PROPERTIES) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) p_40574_.add(TranslatableComponent("${MBlocks.ENERGY_SERVO.descriptionId}.desc").withStyle(ChatFormatting.GRAY)) @@ -106,24 +121,183 @@ object MItems { } } + val COBBLESTONE_GENERATOR: BlockItem by registry.register(MNames.COBBLESTONE_GENERATOR) { + object : BlockItem(MBlocks.COBBLESTONE_GENERATOR, DEFAULT_PROPERTIES) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { + super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) + p_40574_.add(TranslatableComponent("otm.needs_no_power").withStyle(ChatFormatting.GRAY)) + } + } + } + + val INFINITE_WATER_SOURCE: BlockItem by registry.register(MNames.INFINITE_WATER_SOURCE) { BlockItem(MBlocks.INFINITE_WATER_SOURCE, DEFAULT_PROPERTIES) } + + val ESSENCE_STORAGE: BlockItem by registry.register(MNames.ESSENCE_STORAGE) { + object : BlockItem(MBlocks.ESSENCE_STORAGE, DEFAULT_PROPERTIES) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { + super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) + p_40574_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY)) + } + } + } + + val MATTER_RECONSTRUCTOR: BlockItem by registry.register(MNames.MATTER_RECONSTRUCTOR) { + object : BlockItem(MBlocks.MATTER_RECONSTRUCTOR, DEFAULT_PROPERTIES) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { + super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) + p_40574_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY)) + } + } + } + + val DEV_CHEST: BlockItem by registry.register(MNames.DEV_CHEST) { + object : BlockItem(MBlocks.DEV_CHEST, DEFAULT_PROPERTIES) { + override fun appendHoverText(p_40572_: ItemStack, p_40573_: Level?, p_40574_: MutableList, p_40575_: TooltipFlag) { + super.appendHoverText(p_40572_, p_40573_, p_40574_, p_40575_) + p_40574_.add(TranslatableComponent("$descriptionId.desc").withStyle(ChatFormatting.GRAY)) + } + } + } + + val PAINTER: BlockItem by registry.register(MNames.PAINTER) { BlockItem(MBlocks.PAINTER, DEFAULT_PROPERTIES) } + val MATTER_ENTANGLER: BlockItem by registry.register(MNames.MATTER_ENTANGLER) { BlockItem(MBlocks.MATTER_ENTANGLER, DEFAULT_PROPERTIES) } + val MACHINES = SupplierList( - ::ANDROID_STATION, ::BATTERY_BANK, ::MATTER_DECOMPOSER, ::MATTER_CAPACITOR_BANK, ::MATTER_CABLE, ::PATTERN_STORAGE, - ::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR, - ::PLATE_PRESS, ::MATTER_RECYCLER, ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER, - ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, ::ENERGY_SERVO, - ::PHANTOM_ATTRACTOR, - ::GRAVITATION_STABILIZER, + ::ANDROID_STATION, ::ANDROID_CHARGER, ::BATTERY_BANK, ::MATTER_DECOMPOSER, ::MATTER_CAPACITOR_BANK, ::MATTER_CABLE, ::PATTERN_STORAGE, + ::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::MATTER_ENTANGLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR, + ::MATTER_RECYCLER, ::PLATE_PRESS, ::TWIN_PLATE_PRESS, ::POWERED_FURNACE, ::POWERED_BLAST_FURNACE, + ::POWERED_SMOKER, + ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER, + ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, + ::ENERGY_SERVO, ::PAINTER, + ::PHANTOM_ATTRACTOR, ::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR, ::INFINITE_WATER_SOURCE, + ::ESSENCE_STORAGE, ::MATTER_RECONSTRUCTOR ) val DEBUG_EXPLOSION_SMALL: Item by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockItem(MBlocks.DEBUG_EXPLOSION_SMALL, Item.Properties().stacksTo(64)) } val DEBUG_SPHERE_POINTS: Item by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockItem(MBlocks.DEBUG_SPHERE_POINTS, Item.Properties().stacksTo(64)) } - val MATTER_DUST: Item by registry.register(MNames.MATTER_DUST) { MatterDustItem() } + val TRITANIUM_ANVIL: List + + init { + val props = ArrayList<() -> BlockItem>() + + for (i in MBlocks.TRITANIUM_ANVIL.indices) { + props.add(registry.register(MNames.TRITANIUM_ANVIL + i) { BlockItem(MBlocks.TRITANIUM_ANVIL[i], DEFAULT_PROPERTIES) }::get) + } + + TRITANIUM_ANVIL = SupplierList(props) + } + + object MachineUpgrades { + object Basic : ITieredUpgradeSet { + override val BLANK: Item by registry.register("blank_machine_upgrade_basic") { Item(DEFAULT_PROPERTIES) } + + override val SPEED: SimpleUpgrade by registry.register("speed_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.SPEED.set(), speedBonus = 0.125, failureMultiplier = 1.125, energyConsumed = Decimal.ONE_EIGHTH) } + override val ENERGY_CONSUMPTION: SimpleUpgrade by registry.register("energy_consumption_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.ENERGY_CONSUMPTION.set(), speedBonus = -0.25, failureMultiplier = 1.125, energyConsumed = Decimal.MINUS_ONE_EIGHTH) } + override val FAILSAFE: SimpleUpgrade by registry.register("failsafe_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.FAILSAFE.set(), speedBonus = -0.125, failureMultiplier = 0.75, energyConsumed = Decimal.ONE_EIGHTH) } + + override val ENERGY_STORAGE: SimpleUpgrade by registry.register("energy_storage_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.ENERGY_STORAGE.set(), energyStorage = Decimal.ONE_HALF) } + override val MATTER_STORAGE: SimpleUpgrade by registry.register("matter_storage_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.MATTER_STORAGE.set(), matterStorage = Decimal.ONE_HALF) } + + override val PROCESSING_ITEMS: SimpleUpgrade by registry.register("processing_upgrade_basic") { SimpleUpgrade(Item.Properties(), UpgradeType.PROCESSING.set(), processingItems = 1, speedBonus = -0.25, failureMultiplier = 1.25, energyConsumed = Decimal.ONE_QUARTER) } + } + + object Normal : ITieredUpgradeSet { + override val BLANK: Item by registry.register("blank_machine_upgrade_normal") { Item(DEFAULT_PROPERTIES) } + + override val SPEED: SimpleUpgrade by registry.register("speed_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.SPEED.set(), speedBonus = 0.25, failureMultiplier = 1.25, energyConsumed = Decimal.ONE_QUARTER) } + override val ENERGY_CONSUMPTION: SimpleUpgrade by registry.register("energy_consumption_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.ENERGY_CONSUMPTION.set(), speedBonus = -0.5, failureMultiplier = 1.25, energyConsumed = Decimal.MINUS_ONE_QUARTER) } + override val FAILSAFE: SimpleUpgrade by registry.register("failsafe_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.FAILSAFE.set(), speedBonus = -0.25, failureMultiplier = 0.5, energyConsumed = Decimal.ONE_QUARTER) } + + override val ENERGY_STORAGE: SimpleUpgrade by registry.register("energy_storage_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.ENERGY_STORAGE.set(), energyStorage = Decimal.ONE) } + override val MATTER_STORAGE: SimpleUpgrade by registry.register("matter_storage_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.MATTER_STORAGE.set(), matterStorage = Decimal.ONE) } + + override val PROCESSING_ITEMS: SimpleUpgrade by registry.register("processing_upgrade_normal") { SimpleUpgrade(Item.Properties(), UpgradeType.PROCESSING.set(), processingItems = 1, speedBonus = -0.125, failureMultiplier = 1.125, energyConsumed = Decimal.ONE_EIGHTH) } + } + + object Advanced : ITieredUpgradeSet { + override val BLANK: Item by registry.register("blank_machine_upgrade_advanced") { Item(DEFAULT_PROPERTIES) } + + override val SPEED: SimpleUpgrade by registry.register("speed_upgrade_advanced") { SimpleUpgrade(Item.Properties().rarity(Rarity.UNCOMMON), UpgradeType.SPEED.set(), speedBonus = 0.25, failureMultiplier = 1.125, energyConsumed = Decimal.ONE_EIGHTH) } + override val ENERGY_CONSUMPTION: SimpleUpgrade by registry.register("energy_consumption_upgrade_advanced") { SimpleUpgrade(Item.Properties().rarity(Rarity.UNCOMMON), UpgradeType.ENERGY_CONSUMPTION.set(), speedBonus = -0.25, failureMultiplier = 1.125, energyConsumed = Decimal.MINUS_ONE_QUARTER) } + override val FAILSAFE: SimpleUpgrade by registry.register("failsafe_upgrade_advanced") { SimpleUpgrade(Item.Properties().rarity(Rarity.UNCOMMON), UpgradeType.FAILSAFE.set(), speedBonus = -0.125, failureMultiplier = 0.5, energyConsumed = Decimal.ONE_EIGHTH) } + + override val ENERGY_STORAGE: SimpleUpgrade by registry.register("energy_storage_upgrade_advanced") { SimpleUpgrade(Item.Properties(), UpgradeType.ENERGY_STORAGE.set(), energyStorage = Decimal.TWO) } + override val MATTER_STORAGE: SimpleUpgrade by registry.register("matter_storage_upgrade_advanced") { SimpleUpgrade(Item.Properties(), UpgradeType.MATTER_STORAGE.set(), matterStorage = Decimal.TWO) } + + override val PROCESSING_ITEMS: SimpleUpgrade by registry.register("processing_upgrade_advanced") { SimpleUpgrade(Item.Properties().rarity(Rarity.UNCOMMON), UpgradeType.PROCESSING.set(), processingItems = 2, speedBonus = -0.125, failureMultiplier = 1.125, energyConsumed = Decimal.ONE_EIGHTH) } + } + + object Creative { + val SPEED: SimpleUpgrade by registry.register("creative_speed_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.SPEED.set(), speedBonus = 1.0) } + val ENERGY_CONSUMPTION: SimpleUpgrade by registry.register("creative_energy_consumption_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_CONSUMPTION.set(), energyConsumed = Decimal.MINUS_ONE) } + val ENERGY_STORAGE: SimpleUpgrade by registry.register("creative_energy_storage_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorage = Decimal.ONE) } + val ENERGY_STORAGE_FLAT: SimpleUpgrade by registry.register("creative_energy_storage_flat_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorageFlat = Decimal.LONG_MAX_VALUE) } + val ENERGY_STORAGE_FLAT_SMALL: SimpleUpgrade by registry.register("creative_energy_storage_flat2_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorageFlat = Decimal.INT_MAX_VALUE) } + val ENERGY_THROUGHPUT: SimpleUpgrade by registry.register("creative_energy_throughput_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughput = Decimal.ONE) } + val ENERGY_THROUGHPUT_FLAT: SimpleUpgrade by registry.register("creative_energy_throughput_flat_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughputFlat = Decimal.LONG_MAX_VALUE) } + val ENERGY_THROUGHPUT_FLAT_SMALL: SimpleUpgrade by registry.register("creative_energy_throughput_flat2_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughputFlat = Decimal.INT_MAX_VALUE) } + val FAILSAFE: SimpleUpgrade by registry.register("creative_failsafe_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.FAILSAFE.set(), failureMultiplier = 0.0) } + val FAILURE: SimpleUpgrade by registry.register("creative_failure_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.FAILSAFE.set(), failureMultiplier = 2.0) } + val PROCESSING_ITEMS: SimpleUpgrade by registry.register("creative_processing_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.PROCESSING.set(), processingItems = 1) } + + val MATTER_STORAGE: SimpleUpgrade by registry.register("creative_matter_storage_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.MATTER_STORAGE.set(), matterStorage = Decimal.ONE) } + val MATTER_STORAGE_FLAT: SimpleUpgrade by registry.register("creative_matter_storage_flat_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.MATTER_STORAGE.set(), matterStorageFlat = Decimal.LONG_MAX_VALUE) } + val MATTER_STORAGE_FLAT_SMALL: SimpleUpgrade by registry.register("creative_matter_storage_flat2_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.MATTER_STORAGE.set(), matterStorageFlat = Decimal.INT_MAX_VALUE) } + + val LIST = SupplierList( + ::SPEED, + ::ENERGY_CONSUMPTION, + ::ENERGY_STORAGE, + ::ENERGY_STORAGE_FLAT, + ::ENERGY_STORAGE_FLAT_SMALL, + ::ENERGY_THROUGHPUT, + ::ENERGY_THROUGHPUT_FLAT, + ::ENERGY_THROUGHPUT_FLAT_SMALL, + ::FAILSAFE, + ::FAILURE, + ::PROCESSING_ITEMS, + ::MATTER_STORAGE, + ::MATTER_STORAGE_FLAT, + ::MATTER_STORAGE_FLAT_SMALL, + ) + } + + init { + Basic + Normal + Advanced + Creative + } + + val CRAFTABLE_TIERS = listOf( + Basic, + Normal, + Advanced, + ) + } + + init { + MachineUpgrades + } + + val MATTER_DUST: MatterDustItem by registry.register(MNames.MATTER_DUST) { MatterDustItem() } val TRITANIUM_ORE_CLUMP: Item by registry.register(MNames.TRITANIUM_ORE_CLUMP) { Item(DEFAULT_PROPERTIES) } val TRITANIUM_DUST: Item by registry.register(MNames.TRITANIUM_DUST) { Item(DEFAULT_PROPERTIES) } + val TRITANIUM_NUGGET: Item by registry.register(MNames.TRITANIUM_NUGGET) { Item(DEFAULT_PROPERTIES) } val TRITANIUM_INGOT: Item by registry.register(MNames.TRITANIUM_INGOT) { Item(DEFAULT_PROPERTIES) } - val TRITANIUM_INGOT_BLOCK: Item by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { BlockItem(MBlocks.TRITANIUM_INGOT_BLOCK, DEFAULT_PROPERTIES) } + val TRITANIUM_INGOT_BLOCK: BlockItem by registry.register(MNames.TRITANIUM_INGOT_BLOCK) { BlockItem(MBlocks.TRITANIUM_INGOT_BLOCK, DEFAULT_PROPERTIES) } + val TRITANIUM_BARS: BlockItem by registry.register(MNames.TRITANIUM_BARS) { BlockItem(MBlocks.TRITANIUM_BARS, DEFAULT_PROPERTIES) } + + val ESSENCE_SERVO: EssenceServoItem by registry.register("essence_servo") { EssenceServoItem() } + val ESSENCE_CAPSULE: EssenceCapsuleItem by registry.register("essence_capsule") { EssenceCapsuleItem(false) } + val ESSENCE_DRIVE: EssenceCapsuleItem by registry.register("essence_drive") { EssenceCapsuleItem(true) } + + val FLUID_CAPSULE: FluidCapsuleItem by registry.register("fluid_capsule") { FluidCapsuleItem(ItemsConfig::FLUID_CAPSULE_CAPACITY) } + val FLUID_TANK: FluidTankItem by registry.register(MNames.FLUID_TANK) { FluidTankItem(MBlocks.FLUID_TANK, Item.Properties().stacksTo(1), ItemsConfig::FLUID_TANK_CAPACITY) } + val LIQUID_XP_BUCKET: BucketItem by registry.register("liquid_xp_bucket") { BucketItem(MFluids::LIQUID_XP, Item.Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) } val TRITANIUM_COMPONENT: ForgeTier = ForgeTier( Tiers.IRON.level, @@ -131,37 +305,69 @@ object MItems { Tiers.IRON.speed * 1.1f, 3.5f, 16, - BlockTags.NEEDS_IRON_TOOL + MBlockTags.REQUIRES_TRITANIUM_TOOL ) { Ingredient.of(TRITANIUM_INGOT) } - private val TOOLS_PROPRTIES = Item.Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB) + val TRITANIUM_COMPONENT_NAME = ResourceLocation(OverdriveThatMatters.MOD_ID, "tritanium") - val TRITANIUM_SWORD: Item by registry.register(MNames.TRITANIUM_SWORD) { SwordItem(TRITANIUM_COMPONENT, 4, -2.7f, TOOLS_PROPRTIES) } - val TRITANIUM_SHOVEL: Item by registry.register(MNames.TRITANIUM_SHOVEL) { ShovelItem(TRITANIUM_COMPONENT, 1.5f, -2.4f, TOOLS_PROPRTIES) } - val TRITANIUM_AXE: Item by registry.register(MNames.TRITANIUM_AXE) { AxeItem(TRITANIUM_COMPONENT, 8.5f, -3.4f, TOOLS_PROPRTIES) } - val TRITANIUM_PICKAXE: Item by registry.register(MNames.TRITANIUM_PICKAXE) { PickaxeItem(TRITANIUM_COMPONENT, 2, -2.8f, TOOLS_PROPRTIES) } - val TRITANIUM_HOE: Item by registry.register(MNames.TRITANIUM_HOE) { HoeItem(TRITANIUM_COMPONENT, 0, -3.4f, TOOLS_PROPRTIES) } + init { + TierSortingRegistry.registerTier(TRITANIUM_COMPONENT, TRITANIUM_COMPONENT_NAME, listOf(Tiers.IRON), listOf(Tiers.DIAMOND)) + } + + private val TOOLS_PROPRTIES = Item.Properties() + + val TRITANIUM_SWORD: SwordItem by registry.register(MNames.TRITANIUM_SWORD) { SwordItem(TRITANIUM_COMPONENT, 4, -2.7f, TOOLS_PROPRTIES) } + val TRITANIUM_SHOVEL: ShovelItem by registry.register(MNames.TRITANIUM_SHOVEL) { ShovelItem(TRITANIUM_COMPONENT, 1.5f, -2.4f, TOOLS_PROPRTIES) } + val TRITANIUM_AXE: MatteryAxeItem by registry.register(MNames.TRITANIUM_AXE) { MatteryAxeItem(TRITANIUM_COMPONENT, 8.5f, -3.4f, TOOLS_PROPRTIES) } + val TRITANIUM_PICKAXE: PickaxeItem by registry.register(MNames.TRITANIUM_PICKAXE) { PickaxeItem(TRITANIUM_COMPONENT, 2, -2.8f, TOOLS_PROPRTIES) } + val TRITANIUM_HOE: HoeItem by registry.register(MNames.TRITANIUM_HOE) { HoeItem(TRITANIUM_COMPONENT, 0, -3.4f, TOOLS_PROPRTIES) } + val TRITANIUM_SHEARS: ShearsItem by registry.register(MNames.TRITANIUM_SHEARS) { object : ShearsItem(Properties().durability(3072)) { + override fun isValidRepairItem(pToRepair: ItemStack, pRepair: ItemStack): Boolean { + return pRepair.`is`(MItemTags.TRITANIUM_INGOTS) + } + } } + + val TRITANIUM_SHIELD: ShieldItem by registry.register(MNames.TRITANIUM_SHIELD) { object : ShieldItem(Properties().durability(2048)) { + override fun isValidRepairItem(pToRepair: ItemStack, pRepair: ItemStack): Boolean { + return pRepair.`is`(MItemTags.REINFORCED_TRITANIUM_PLATES) + } + } } val TRITANIUM_TOOLS = SupplierList( - { TRITANIUM_SWORD }, - { TRITANIUM_SHOVEL }, - { TRITANIUM_AXE }, - { TRITANIUM_PICKAXE }, - { TRITANIUM_HOE } + ::TRITANIUM_SWORD, + ::TRITANIUM_SHOVEL, + ::TRITANIUM_AXE, + ::TRITANIUM_PICKAXE, + ::TRITANIUM_HOE, + ::TRITANIUM_SHEARS, ) - val TRITANIUM_HELMET: Item by registry.register(MNames.TRITANIUM_HELMET) { ItemTritaniumArmor(EquipmentSlot.HEAD) } - val TRITANIUM_CHESTPLATE: Item by registry.register(MNames.TRITANIUM_CHESTPLATE) { ItemTritaniumArmor(EquipmentSlot.CHEST) } - val TRITANIUM_PANTS: Item by registry.register(MNames.TRITANIUM_PANTS) { ItemTritaniumArmor(EquipmentSlot.LEGS) } - val TRITANIUM_BOOTS: Item by registry.register(MNames.TRITANIUM_BOOTS) { ItemTritaniumArmor(EquipmentSlot.FEET) } + val TRITANIUM_HELMET: TritaniumArmorItem by registry.register(MNames.TRITANIUM_HELMET) { TritaniumArmorItem(EquipmentSlot.HEAD) } + val TRITANIUM_CHESTPLATE: TritaniumArmorItem by registry.register(MNames.TRITANIUM_CHESTPLATE) { TritaniumArmorItem(EquipmentSlot.CHEST) } + val TRITANIUM_PANTS: TritaniumArmorItem by registry.register(MNames.TRITANIUM_PANTS) { TritaniumArmorItem(EquipmentSlot.LEGS) } + val TRITANIUM_BOOTS: TritaniumArmorItem by registry.register(MNames.TRITANIUM_BOOTS) { TritaniumArmorItem(EquipmentSlot.FEET) } + + val SIMPLE_TRITANIUM_HELMET: SimpleTritaniumArmorItem by registry.register(MNames.SIMPLE_TRITANIUM_HELMET) { SimpleTritaniumArmorItem(EquipmentSlot.HEAD) } + val SIMPLE_TRITANIUM_CHESTPLATE: SimpleTritaniumArmorItem by registry.register(MNames.SIMPLE_TRITANIUM_CHESTPLATE) { SimpleTritaniumArmorItem(EquipmentSlot.CHEST) } + val SIMPLE_TRITANIUM_PANTS: SimpleTritaniumArmorItem by registry.register(MNames.SIMPLE_TRITANIUM_PANTS) { SimpleTritaniumArmorItem(EquipmentSlot.LEGS) } + val SIMPLE_TRITANIUM_BOOTS: SimpleTritaniumArmorItem by registry.register(MNames.SIMPLE_TRITANIUM_BOOTS) { SimpleTritaniumArmorItem(EquipmentSlot.FEET) } val TRITANIUM_ARMOR = SupplierList( - { TRITANIUM_HELMET }, - { TRITANIUM_CHESTPLATE }, - { TRITANIUM_PANTS }, - { TRITANIUM_BOOTS } + ::TRITANIUM_HELMET, + ::TRITANIUM_CHESTPLATE, + ::TRITANIUM_PANTS, + ::TRITANIUM_BOOTS ) + val SIMPLE_TRITANIUM_ARMOR = SupplierList( + ::SIMPLE_TRITANIUM_HELMET, + ::SIMPLE_TRITANIUM_CHESTPLATE, + ::SIMPLE_TRITANIUM_PANTS, + ::SIMPLE_TRITANIUM_BOOTS + ) + + val EXPLOSIVE_HAMMER: ExplosiveHammerItem by registry.register("explosive_hammer") { ExplosiveHammerItem() } + val ENERGY_SWORD: Item by registry.register(MNames.ENERGY_SWORD) { EnergySwordItem() } val PLASMA_RIFLE: Item by registry.register(MNames.PLASMA_RIFLE) { PlasmaRifleItem() } @@ -182,7 +388,7 @@ object MItems { val GRAVITATION_FIELD_LIMITER: Item by registry.register(MNames.GRAVITATION_FIELD_LIMITER) { Item(DEFAULT_PROPERTIES) } val GRAVITATION_FIELD_SENSOR: Item by registry.register(MNames.GRAVITATION_FIELD_SENSOR) { Item(DEFAULT_PROPERTIES) } - val PORTABLE_GRAVITATION_STABILIZER: Item by registry.register(MNames.PORTABLE_GRAVITATION_STABILIZER) { ItemPortableGravitationStabilizer() } + val PORTABLE_GRAVITATION_STABILIZER: Item by registry.register(MNames.PORTABLE_GRAVITATION_STABILIZER) { PortableGravitationStabilizerItem() } val BLACK_HOLE: Item by registry.register(MNames.BLACK_HOLE) { BlockItem(MBlocks.BLACK_HOLE, DEFAULT_PROPERTIES) } @@ -193,48 +399,85 @@ object MItems { val PILL_OBLIVION: Item by registry.register(MNames.PILL_OBLIVION) { PillItem(PillType.OBLIVION) } val PILL_HEAL: Item by registry.register(MNames.PILL_HEAL) { HealPillItem() } - val BATTERY_CRUDE: Item by registry.register(MNames.BATTERY_CRUDE) { CrudeBatteryItem() } - val BATTERY_BASIC: Item by registry.register(MNames.BATTERY_BASIC) { BatteryItem(ServerConfig.BATTERY_BASIC) } - val BATTERY_NORMAL: Item by registry.register(MNames.BATTERY_NORMAL) { BatteryItem(ServerConfig.BATTERY_NORMAL) } - val BATTERY_DENSE: Item by registry.register(MNames.BATTERY_DENSE) { BatteryItem(ServerConfig.BATTERY_DENSE) } - val BATTERY_CAPACITOR: Item by registry.register(MNames.BATTERY_CAPACITOR) { BatteryItem(ServerConfig.BATTERY_CAPACITOR) } - val BATTERY_CREATIVE: Item by registry.register(MNames.BATTERY_CREATIVE) { BatteryItem() } - - val QUANTUM_BATTERY: Item by registry.register(MNames.QUANTUM_BATTERY) { QuantumBatteryItem(MNames.QUANTUM_BATTERY, ServerConfig.QUANTUM_BATTERY) } - val QUANTUM_CAPACITOR: Item by registry.register(MNames.QUANTUM_CAPACITOR) { QuantumBatteryItem(MNames.QUANTUM_CAPACITOR, ServerConfig.QUANTUM_CAPACITOR) } - val QUANTUM_BATTERY_CREATIVE: Item by registry.register(MNames.QUANTUM_BATTERY_CREATIVE) { QuantumBatteryItem(MNames.QUANTUM_BATTERY_CREATIVE) } - val ZPM_BATTERY: Item by registry.register(MNames.ZPM_BATTERY) { ZPMItem() } - - val BATTERIES = SupplierList( - { BATTERY_CRUDE }, - { BATTERY_BASIC }, - { BATTERY_NORMAL }, - { BATTERY_DENSE }, - { BATTERY_CAPACITOR }, + val PILLS = SupplierList( + ::PILL_ANDROID, + ::PILL_HUMANE, + ::PILL_OBLIVION, + ::PILL_HEAL, ) - val MATTER_CAPACITOR_BASIC: Item by registry.register(MNames.MATTER_CAPACITOR_BASIC) { MatterCapacitorItem(ServerConfig::MATTER_CAPACITOR_BASIC) } - val MATTER_CAPACITOR_NORMAL: Item by registry.register(MNames.MATTER_CAPACITOR_NORMAL) { MatterCapacitorItem(ServerConfig::MATTER_CAPACITOR_NORMAL) } - val MATTER_CAPACITOR_DENSE: Item by registry.register(MNames.MATTER_CAPACITOR_DENSE) { MatterCapacitorItem(ServerConfig::MATTER_CAPACITOR_DENSE) } + val BATTERY_CRUDE: Item by registry.register(MNames.BATTERY_CRUDE) { CrudeBatteryItem() } + val BATTERY_BASIC: Item by registry.register(MNames.BATTERY_BASIC) { BatteryItem(ItemsConfig.Batteries.BASIC) } + val BATTERY_NORMAL: Item by registry.register(MNames.BATTERY_NORMAL) { BatteryItem(ItemsConfig.Batteries.NORMAL) } + val BATTERY_DENSE: Item by registry.register(MNames.BATTERY_DENSE) { BatteryItem(ItemsConfig.Batteries.DENSE) } + val BATTERY_CAPACITOR: Item by registry.register(MNames.BATTERY_CAPACITOR) { BatteryItem(ItemsConfig.Batteries.CAPACITOR) } + val BATTERY_CREATIVE: Item by registry.register(MNames.BATTERY_CREATIVE) { BatteryItem() } + + val QUANTUM_BATTERY: QuantumBatteryItem by registry.register(MNames.QUANTUM_BATTERY) { QuantumBatteryItem(MNames.QUANTUM_BATTERY, ItemsConfig.Batteries.QUANTUM_BATTERY) } + val QUANTUM_CAPACITOR: QuantumBatteryItem by registry.register(MNames.QUANTUM_CAPACITOR) { QuantumBatteryItem(MNames.QUANTUM_CAPACITOR, ItemsConfig.Batteries.QUANTUM_CAPACITOR) } + val QUANTUM_BATTERY_CREATIVE: QuantumBatteryItem by registry.register(MNames.QUANTUM_BATTERY_CREATIVE) { QuantumBatteryItem(MNames.QUANTUM_BATTERY_CREATIVE, null) } + val ZPM_BATTERY: ZPMItem by registry.register(MNames.ZPM_BATTERY) { ZPMItem() } + val PROCEDURAL_BATTERY: ProceduralBatteryItem by registry.register(MNames.PROCEDURAL_BATTERY) { ProceduralBatteryItem() } + + val BATTERIES = SupplierList( + ::BATTERY_CRUDE, + ::BATTERY_BASIC, + ::BATTERY_NORMAL, + ::BATTERY_DENSE, + ::BATTERY_CAPACITOR, + ) + + val UNIQUE_BATTERIES = SupplierList( + ::QUANTUM_BATTERY, + ::QUANTUM_CAPACITOR, + ::QUANTUM_BATTERY_CREATIVE, + ::ZPM_BATTERY, + ) + + val ALL_BATTERIES = SupplierList( + ::BATTERY_CRUDE, + ::BATTERY_BASIC, + ::BATTERY_NORMAL, + ::BATTERY_DENSE, + ::BATTERY_CAPACITOR, + ::BATTERY_CREATIVE, + + ::QUANTUM_BATTERY, + ::QUANTUM_CAPACITOR, + ::QUANTUM_BATTERY_CREATIVE, + ::ZPM_BATTERY, + ) + + val MATTER_CAPACITOR_BASIC: Item by registry.register(MNames.MATTER_CAPACITOR_BASIC) { MatterCapacitorItem(ItemsConfig.Capacitors::BASIC) } + val MATTER_CAPACITOR_NORMAL: Item by registry.register(MNames.MATTER_CAPACITOR_NORMAL) { MatterCapacitorItem(ItemsConfig.Capacitors::NORMAL) } + val MATTER_CAPACITOR_DENSE: Item by registry.register(MNames.MATTER_CAPACITOR_DENSE) { MatterCapacitorItem(ItemsConfig.Capacitors::DENSE) } val MATTER_CAPACITOR_CREATIVE: Item by registry.register(MNames.MATTER_CAPACITOR_CREATIVE) { MatterCapacitorItem() } - val PATTERN_DRIVE_NORMAL: Item by registry.register(MNames.PATTERN_DRIVE_NORMAL) { PatternStorageItem(ServerConfig::PATTERN_DRIVE_NORMAL) } + val MATTER_CAPACITORS = SupplierList( + ::MATTER_CAPACITOR_BASIC, + ::MATTER_CAPACITOR_NORMAL, + ::MATTER_CAPACITOR_DENSE, + ::MATTER_CAPACITOR_CREATIVE, + ) + + val PATTERN_DRIVE_NORMAL: Item by registry.register(MNames.PATTERN_DRIVE_NORMAL) { PatternStorageItem(ItemsConfig.PatternDrives::NORMAL) } val PATTERN_DRIVE_CREATIVE: Item by registry.register(MNames.PATTERN_DRIVE_CREATIVE) { PatternStorageItem() } val PATTERN_DRIVE_CREATIVE2: Item by registry.register(MNames.PATTERN_DRIVE_CREATIVE2) { CreativePatternItem() } val PORTABLE_CONDENSATION_DRIVE: Item by registry.register(MNames.PORTABLE_CONDENSATION_DRIVE) { PortableCondensationDriveItem(4000) } val PORTABLE_DENSE_CONDENSATION_DRIVE: Item by registry.register(MNames.PORTABLE_DENSE_CONDENSATION_DRIVE) { PortableCondensationDriveItem(25000) } - val NUTRIENT_PASTE: Item by registry.register(MNames.NUTRIENT_PASTE) { Item(Item.Properties().stacksTo(64).food( - FoodProperties.Builder().meat().nutrition(8).saturationMod(0.9F).build()).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) } + val NUTRIENT_PASTE: Item by registry.register(MNames.NUTRIENT_PASTE) { Item(Item.Properties().stacksTo(64).food(FoodProperties.Builder().meat().nutrition(8).saturationMod(0.9F).build())) } - val LABORATORY_LAMP: Item by registry.register(MNames.LABORATORY_LAMP) { BlockItem(MBlocks.LABORATORY_LAMP, DEFAULT_PROPERTIES_DECORATIVE) } - val LABORATORY_LAMP_INVERTED: Item by registry.register(MNames.LABORATORY_LAMP_INVERTED) { BlockItem(MBlocks.LABORATORY_LAMP_INVERTED, DEFAULT_PROPERTIES_DECORATIVE) } - val DANGER_STRIPE_BLOCK: Item by registry.register(MNames.DANGER_STRIPE_BLOCK) { BlockItem(MBlocks.DANGER_STRIPE_BLOCK, DEFAULT_PROPERTIES_DECORATIVE) } - val METAL_BEAM: Item by registry.register(MNames.METAL_BEAM) { BlockItem(MBlocks.METAL_BEAM, DEFAULT_PROPERTIES_DECORATIVE) } + val LABORATORY_LAMP: Item by registry.register(MNames.LABORATORY_LAMP) { BlockItem(MBlocks.LABORATORY_LAMP, DEFAULT_PROPERTIES) } + val LABORATORY_LAMP_INVERTED: Item by registry.register(MNames.LABORATORY_LAMP_INVERTED) { BlockItem(MBlocks.LABORATORY_LAMP_INVERTED, DEFAULT_PROPERTIES) } + val DANGER_STRIPE_BLOCK: Item by registry.register(MNames.DANGER_STRIPE_BLOCK) { BlockItem(MBlocks.DANGER_STRIPE_BLOCK, DEFAULT_PROPERTIES) } + val METAL_BEAM: Item by registry.register(MNames.METAL_BEAM) { BlockItem(MBlocks.METAL_BEAM, DEFAULT_PROPERTIES) } + val ENGINE: Item by registry.register(MNames.ENGINE) { BlockItem(MBlocks.ENGINE, DEFAULT_PROPERTIES) } + val HOLO_SIGN: Item by registry.register(MNames.HOLO_SIGN) { BlockItem(MBlocks.HOLO_SIGN, DEFAULT_PROPERTIES) } - val TRITANIUM_DOOR = registry.allColored(MNames.TRITANIUM_DOOR) { color, _ -> DoubleHighBlockItem(MBlocks.TRITANIUM_DOOR[color]!!, if (color == null) DEFAULT_PROPERTIES else DEFAULT_PROPERTIES_DECORATIVE) } - val TRITANIUM_TRAPDOOR = registry.allColored(MNames.TRITANIUM_TRAPDOOR) { color, _ -> BlockItem(MBlocks.TRITANIUM_TRAPDOOR[color]!!, if (color == null) DEFAULT_PROPERTIES else DEFAULT_PROPERTIES_DECORATIVE) } + val TRITANIUM_DOOR = registry.allColored(MNames.TRITANIUM_DOOR) { color, _ -> DoubleHighBlockItem(MBlocks.TRITANIUM_DOOR[color]!!, DEFAULT_PROPERTIES) } + val TRITANIUM_TRAPDOOR = registry.allColored(MNames.TRITANIUM_TRAPDOOR) { color, _ -> BlockItem(MBlocks.TRITANIUM_TRAPDOOR[color]!!, DEFAULT_PROPERTIES) } init { MRegistry.TRITANIUM_PRESSURE_PLATE.registerItems(registry) @@ -257,11 +500,8 @@ object MItems { val BASIC_CONTROL_CIRCUIT: Item by registry.register(MNames.BASIC_CONTROL_CIRCUIT) { Item(DEFAULT_PROPERTIES) } val ADVANCED_CONTROL_CIRCUIT: Item by registry.register(MNames.ADVANCED_CONTROL_CIRCUIT) { Item(DEFAULT_PROPERTIES) } val MATTER_CAPACITOR_PARTS: Item by registry.register(MNames.MATTER_CAPACITOR_PARTS) { Item(DEFAULT_PROPERTIES) } - - val QUANTUM_TRANSCEIVER: Item by registry.register("quantum_transceiver") { Item(DEFAULT_PROPERTIES) } - val ELECTROMAGNET: Item by registry.register("electromagnet") { Item(DEFAULT_PROPERTIES) } - val MIRROR_COMPOUND: Item by registry.register("mirror_compound") { Item(DEFAULT_PROPERTIES) } - val MIRROR: Item by registry.register("mirror") { object : Item(DEFAULT_PROPERTIES) { + val CARBON_MESH: Item by registry.register(MNames.CARBON_MESH) { Item(DEFAULT_PROPERTIES) } + val REINFORCED_TRITANIUM_PLATE: Item by registry.register(MNames.REINFORCED_TRITANIUM_PLATE) { object : Item(DEFAULT_PROPERTIES) { override fun appendHoverText( p_41421_: ItemStack, p_41422_: Level?, @@ -273,45 +513,105 @@ object MItems { } } } + val QUANTUM_TRANSCEIVER: Item by registry.register(MNames.QUANTUM_TRANSCEIVER) { Item(DEFAULT_PROPERTIES) } + val ELECTROMAGNET: Item by registry.register(MNames.ELECTROMAGNET) { Item(DEFAULT_PROPERTIES) } + val ELECTROMOTOR: Item by registry.register(MNames.ELECTROMOTOR) { Item(DEFAULT_PROPERTIES) } + val MIRROR_COMPOUND: Item by registry.register(MNames.MIRROR_COMPOUND) { Item(DEFAULT_PROPERTIES) } + val MIRROR: Item by registry.register(MNames.MIRROR) { object : Item(DEFAULT_PROPERTIES) { + override fun appendHoverText( + p_41421_: ItemStack, + p_41422_: Level?, + p_41423_: MutableList, + p_41424_: TooltipFlag + ) { + super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) + p_41423_.add(TranslatableComponent("$descriptionId.description").withStyle(ChatFormatting.DARK_GRAY)) + } + } } + + /** + * List of components for everything else + */ + val COMPONENTS = SupplierList( + ::MATTER_IO_PORT, + ::MATTER_TRANSFORM_MATRIX, + ::ENERGY_BUS, + ::ELECTRIC_PARTS, + ::MACHINE_FRAME, + ::TRITANIUM_PLATE, + ::IRON_PLATE, + ::GOLD_PLATE, + ::COPPER_WIRING, + ::GOLD_WIRING, + ::PORTABLE_CONDENSATION_DRIVE_CASING, + ::PORTABLE_DENSE_CONDENSATION_DRIVE_CASING, + ::CIRCUIT_PLATING, + ::BASIC_CONTROL_CIRCUIT, + ::ADVANCED_CONTROL_CIRCUIT, + ::MATTER_CAPACITOR_PARTS, + + ::QUANTUM_TRANSCEIVER, + ::ELECTROMAGNET, + ::ELECTROMOTOR, + ::MIRROR_COMPOUND, + ::MIRROR, + + ::CARBON_MESH, + ::REINFORCED_TRITANIUM_PLATE, + + MachineUpgrades.Basic::BLANK, + MachineUpgrades.Normal::BLANK, + MachineUpgrades.Advanced::BLANK, + ) + + /** + * List of components for datagen code + */ val DATAGEN_COMPONENTS = SupplierList( - { ENERGY_BUS }, - { ELECTRIC_PARTS }, - { TRITANIUM_PLATE }, - { IRON_PLATE }, - { GOLD_PLATE }, - { COPPER_WIRING }, - { GOLD_WIRING }, - { CIRCUIT_PLATING }, - { BASIC_CONTROL_CIRCUIT }, - { ADVANCED_CONTROL_CIRCUIT }, - { MATTER_CAPACITOR_PARTS }, - { MATTER_IO_PORT }, - { MATTER_TRANSFORM_MATRIX }, - { QUANTUM_TRANSCEIVER }, - { ELECTROMAGNET }, - { MIRROR_COMPOUND }, - { MIRROR }, + ::ENERGY_BUS, + ::ELECTRIC_PARTS, + ::TRITANIUM_PLATE, + ::IRON_PLATE, + ::GOLD_PLATE, + ::COPPER_WIRING, + ::GOLD_WIRING, + ::CIRCUIT_PLATING, + ::BASIC_CONTROL_CIRCUIT, + ::ADVANCED_CONTROL_CIRCUIT, + ::MATTER_CAPACITOR_PARTS, + ::MATTER_IO_PORT, + ::MATTER_TRANSFORM_MATRIX, + ::QUANTUM_TRANSCEIVER, + ::ELECTROMAGNET, + ::ELECTROMOTOR, + ::MIRROR_COMPOUND, + ::MIRROR, + + ::CARBON_MESH, + ::REINFORCED_TRITANIUM_PLATE, ) val CARGO_CRATE_MINECARTS = registry.allColored(MNames.MINECART_CARGO_CRATE) { color, _ -> MinecartCargoCrateItem(color) } - val EXOPACK_PROBE: Item by registry.register(MNames.EXOPACK_PROBE, ::ExoPackProbeItem) + val EXOPACK_PROBE: Item by registry.register(MNames.EXOPACK_PROBE, ::ExopackProbeItem) object ExopackUpgrades { - val INVENTORY_UPGRADE_CREATIVE: Item by registry.register("exosuit_inventory_upgrade_creative") { ExoPackSlotUpgradeItem(null, 27, Rarity.EPIC) } - val CRAFTING_UPGRADE: Item by registry.register("exosuit_crafting_upgrade", ::ExoPackCraftingUpgradeItem) + val INVENTORY_UPGRADE_CREATIVE: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_creative") { ExopackSlotUpgradeItem(null, 27, Rarity.EPIC) } + val CRAFTING_UPGRADE: ExopackUpgradeItem by registry.register("exosuit_crafting_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.CRAFTING, "crafting_upgrade", "crafting_upgraded") } + val SMELTING_UPGRADE: ExopackUpgradeItem by registry.register("exopack_smelting_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.SMELTING, "smelting_upgrade", "smelting_installed") } + val ENDER_UPGRADE: ExopackUpgradeItem by registry.register("exopack_ender_upgrade") { ExopackUpgradeItem(MatteryPlayerCapability.UpgradeType.ENDER_ACCESS, "ender_access_upgrade", "ender_access_installed") } val INVENTORY_UPGRADES = SupplierList(8) { - registry.register("exosuit_inventory_upgrade_$it") { ExoPackSlotUpgradeItem(18, Rarity.COMMON) }::get + registry.register("exosuit_inventory_upgrade_$it") { ExopackSlotUpgradeItem(18, Rarity.COMMON) }::get } - val INVENTORY_UPGRADE_PROCEDURAL: Item by registry.register("exosuit_inventory_upgrade_procedural") { ProceduralExoPackSlotUpgradeItem() } + val INVENTORY_UPGRADE_PROCEDURAL: ProceduralExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_procedural") { ProceduralExopackSlotUpgradeItem() } - val INVENTORY_UPGRADE_BIG: Item by registry.register("exosuit_inventory_upgrade_big") { ExoPackSlotUpgradeItem(60, Rarity.RARE) } - val INVENTORY_UPGRADE_HUGE: Item by registry.register("exosuit_inventory_upgrade_huge") { ExoPackSlotUpgradeItem(135, Rarity.RARE) } + val INVENTORY_UPGRADE_BIG: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_big") { ExopackSlotUpgradeItem(60, Rarity.RARE) } + val INVENTORY_UPGRADE_HUGE: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_huge") { ExopackSlotUpgradeItem(135, Rarity.RARE) } - val INVENTORY_UPGRADE_WITHER: Item by registry.register("exosuit_inventory_upgrade_wither") { ExoPackSlotUpgradeItem(180, Rarity.RARE, hasDescription = true) } - val INVENTORY_UPGRADE_ENDER_DRAGON: Item by registry.register("exosuit_inventory_upgrade_ender_dragon") { ExoPackSlotUpgradeItem(180, Rarity.EPIC, hasDescription = true) } + val INVENTORY_UPGRADE_WITHER: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_wither") { ExopackSlotUpgradeItem(180, Rarity.RARE, hasDescription = true) } + val INVENTORY_UPGRADE_ENDER_DRAGON: ExopackSlotUpgradeItem by registry.register("exosuit_inventory_upgrade_ender_dragon") { ExopackSlotUpgradeItem(180, Rarity.EPIC, hasDescription = true) } } init { @@ -325,11 +625,15 @@ object MItems { MRegistry.TRITANIUM_WALL.registerItems(registry) } - val TRITANIUM_STRIPED_BLOCK: Item by registry.register(MNames.TRITANIUM_STRIPED_BLOCK) { BlockItem(MBlocks.TRITANIUM_STRIPED_BLOCK, DEFAULT_PROPERTIES_DECORATIVE) } - val TRITANIUM_STRIPED_STAIRS: Item by registry.register(MNames.TRITANIUM_STRIPED_STAIRS) { BlockItem(MBlocks.TRITANIUM_STRIPED_STAIRS, DEFAULT_PROPERTIES_DECORATIVE) } - val TRITANIUM_STRIPED_SLAB: Item by registry.register(MNames.TRITANIUM_STRIPED_SLAB) { BlockItem(MBlocks.TRITANIUM_STRIPED_SLAB, DEFAULT_PROPERTIES_DECORATIVE) } - val TRITANIUM_STRIPED_WALL: Item by registry.register(MNames.TRITANIUM_STRIPED_WALL) { BlockItem(MBlocks.TRITANIUM_STRIPED_WALL, DEFAULT_PROPERTIES_DECORATIVE) } - val CARBON_FIBRE_BLOCK: Item by registry.register(MNames.CARBON_FIBRE_BLOCK) { BlockItem(MBlocks.CARBON_FIBRE_BLOCK, DEFAULT_PROPERTIES_DECORATIVE) } + val TRITANIUM_STRIPED_BLOCK: Item by registry.register(MNames.TRITANIUM_STRIPED_BLOCK) { BlockItem(MBlocks.TRITANIUM_STRIPED_BLOCK, DEFAULT_PROPERTIES) } + val TRITANIUM_STRIPED_STAIRS: Item by registry.register(MNames.TRITANIUM_STRIPED_STAIRS) { BlockItem(MBlocks.TRITANIUM_STRIPED_STAIRS, DEFAULT_PROPERTIES) } + val TRITANIUM_STRIPED_SLAB: Item by registry.register(MNames.TRITANIUM_STRIPED_SLAB) { BlockItem(MBlocks.TRITANIUM_STRIPED_SLAB, DEFAULT_PROPERTIES) } + val TRITANIUM_STRIPED_WALL: Item by registry.register(MNames.TRITANIUM_STRIPED_WALL) { BlockItem(MBlocks.TRITANIUM_STRIPED_WALL, DEFAULT_PROPERTIES) } + val CARBON_FIBRE_BLOCK: Item by registry.register(MNames.CARBON_FIBRE_BLOCK) { BlockItem(MBlocks.CARBON_FIBRE_BLOCK, DEFAULT_PROPERTIES) } + val METAL_JUNK: Item by registry.register(MNames.METAL_JUNK) { BlockItem(MBlocks.METAL_JUNK, DEFAULT_PROPERTIES) } + val METAL_MESH: Item by registry.register(MNames.METAL_MESH) { BlockItem(MBlocks.METAL_MESH, DEFAULT_PROPERTIES) } + + val CHEST_UPGRADER: Item by registry.register(MNames.CHEST_UPGRADER) { ChestUpgraderItem() } init { MRegistry.INDUSTRIAL_GLASS.registerItems(registry) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt index 20fa79776..ad46b96a6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MLootItemConditions.kt @@ -1,10 +1,13 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.core.Registry +import net.minecraft.core.registries.Registries import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.registries.DeferredRegister import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.data.Codec2Serializer +import ru.dbotthepony.mc.otm.data.SingletonCodec import ru.dbotthepony.mc.otm.data.condition.ChanceWithPlaytimeCondition import ru.dbotthepony.mc.otm.data.condition.HasExoPackCondition import ru.dbotthepony.mc.otm.data.condition.ItemInInventoryCondition @@ -13,14 +16,14 @@ import ru.dbotthepony.mc.otm.data.condition.KilledByRealPlayerOrIndirectly import ru.dbotthepony.mc.otm.data.condition.ChanceCondition object MLootItemConditions { - private val registry = DeferredRegister.create(Registry.LOOT_ITEM_REGISTRY, OverdriveThatMatters.MOD_ID) + private val registry = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, OverdriveThatMatters.MOD_ID) - val HAS_EXOPACK: LootItemConditionType by registry.register("has_exopack") { LootItemConditionType(HasExoPackCondition) } - val CHANCE_WITH_PLAYTIME: LootItemConditionType by registry.register("chance_with_playtime") { LootItemConditionType(ChanceWithPlaytimeCondition) } - val ITEM_IN_INVENTORY: LootItemConditionType by registry.register("item_in_inventory") { LootItemConditionType(ItemInInventoryCondition) } - val KILLED_BY_REAL_PLAYER: LootItemConditionType by registry.register("killed_by_real_player") { LootItemConditionType(KilledByRealPlayer) } - val KILLED_BY_REAL_PLAYER_OR_INDIRECTLY: LootItemConditionType by registry.register("killed_by_real_player_or_indirectly") { LootItemConditionType(KilledByRealPlayerOrIndirectly) } - val CHANCE: LootItemConditionType by registry.register("chance") { LootItemConditionType(ChanceCondition.Companion) } + val HAS_EXOPACK: LootItemConditionType by registry.register("has_exopack") { LootItemConditionType(Codec2Serializer(SingletonCodec(HasExoPackCondition))) } + val CHANCE_WITH_PLAYTIME: LootItemConditionType by registry.register("chance_with_playtime") { LootItemConditionType(Codec2Serializer(ChanceWithPlaytimeCondition.CODEC)) } + val ITEM_IN_INVENTORY: LootItemConditionType by registry.register("item_in_inventory") { LootItemConditionType(Codec2Serializer(ItemInInventoryCondition.CODEC)) } + val KILLED_BY_REAL_PLAYER: LootItemConditionType by registry.register("killed_by_real_player") { LootItemConditionType(Codec2Serializer(SingletonCodec(KilledByRealPlayer))) } + val KILLED_BY_REAL_PLAYER_OR_INDIRECTLY: LootItemConditionType by registry.register("killed_by_real_player_or_indirectly") { LootItemConditionType(Codec2Serializer(SingletonCodec(KilledByRealPlayerOrIndirectly))) } + val CHANCE: LootItemConditionType by registry.register("chance") { LootItemConditionType(Codec2Serializer(ChanceCondition.CODEC)) } internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt index ec03ab814..e308ef926 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MMenus.kt @@ -1,75 +1,159 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.client.gui.screens.MenuScreens +import net.minecraft.world.flag.FeatureFlags import net.minecraft.world.inventory.MenuType import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.client.screen.* -import ru.dbotthepony.mc.otm.menu.* +import ru.dbotthepony.mc.otm.block.entity.tech.AndroidChargerBlockEntity +import ru.dbotthepony.mc.otm.client.screen.decorative.CargoCrateScreen +import ru.dbotthepony.mc.otm.client.screen.decorative.FluidTankScreen +import ru.dbotthepony.mc.otm.client.screen.decorative.HoloSignScreen +import ru.dbotthepony.mc.otm.client.screen.decorative.MinecartCargoCrateScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterReconstructorScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterBottlerScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterCapacitorBankScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterDecomposerScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterEntanglerScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterPanelScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterRecyclerScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterReplicatorScreen +import ru.dbotthepony.mc.otm.client.screen.matter.MatterScannerScreen +import ru.dbotthepony.mc.otm.client.screen.matter.PatternStorageScreen +import ru.dbotthepony.mc.otm.client.screen.storage.DriveRackScreen +import ru.dbotthepony.mc.otm.client.screen.storage.DriveViewerScreen +import ru.dbotthepony.mc.otm.client.screen.storage.ItemMonitorScreen +import ru.dbotthepony.mc.otm.client.screen.storage.StorageBusScreen +import ru.dbotthepony.mc.otm.client.screen.storage.StorageImporterExporterScreen +import ru.dbotthepony.mc.otm.client.screen.storage.StoragePowerSupplierScreen +import ru.dbotthepony.mc.otm.client.screen.tech.AndroidChargerScreen +import ru.dbotthepony.mc.otm.client.screen.tech.AndroidStationScreen +import ru.dbotthepony.mc.otm.client.screen.tech.BatteryBankScreen +import ru.dbotthepony.mc.otm.client.screen.tech.ChemicalGeneratorScreen +import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen +import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen +import ru.dbotthepony.mc.otm.client.screen.tech.EnergyServoScreen +import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen +import ru.dbotthepony.mc.otm.client.screen.decorative.PainterScreen +import ru.dbotthepony.mc.otm.client.screen.tech.PlatePressScreen +import ru.dbotthepony.mc.otm.client.screen.tech.PoweredFurnaceScreen +import ru.dbotthepony.mc.otm.client.screen.tech.TwinPlatePressScreen +import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu +import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu +import ru.dbotthepony.mc.otm.menu.decorative.HoloSignMenu +import ru.dbotthepony.mc.otm.menu.decorative.MinecartCargoCrateMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu +import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu +import ru.dbotthepony.mc.otm.menu.matter.PatternStorageMenu +import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu +import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu +import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu +import ru.dbotthepony.mc.otm.menu.storage.StorageBusMenu +import ru.dbotthepony.mc.otm.menu.storage.StorageImporterExporterMenu +import ru.dbotthepony.mc.otm.menu.storage.StoragePowerSupplierMenu +import ru.dbotthepony.mc.otm.menu.tech.AndroidChargerMenu +import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu +import ru.dbotthepony.mc.otm.menu.tech.BatteryBankMenu +import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu +import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu +import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu +import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu +import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu +import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu +import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu +import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu +import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu object MMenus { private val registry = DeferredRegister.create(ForgeRegistries.MENU_TYPES, OverdriveThatMatters.MOD_ID) - val ANDROID_STATION: MenuType<*> by registry.register(MNames.ANDROID_STATION) { MenuType(::AndroidStationMenu) } - val BATTERY_BANK: MenuType<*> by registry.register(MNames.BATTERY_BANK) { MenuType(::BatteryBankMenu) } - val MATTER_DECOMPOSER: MenuType<*> by registry.register(MNames.MATTER_DECOMPOSER) { MenuType(::MatterDecomposerMenu) } - val MATTER_CAPACITOR_BANK: MenuType<*> by registry.register(MNames.MATTER_CAPACITOR_BANK) { MenuType(::MatterCapacitorBankMenu) } - val PATTERN_STORAGE: MenuType<*> by registry.register(MNames.PATTERN_STORAGE) { MenuType(::PatternStorageMenu) } - val MATTER_SCANNER: MenuType<*> by registry.register(MNames.MATTER_SCANNER) { MenuType(::MatterScannerMenu) } - val MATTER_PANEL: MenuType<*> by registry.register(MNames.MATTER_PANEL) { MenuType(::MatterPanelMenu) } - val MATTER_REPLICATOR: MenuType<*> by registry.register(MNames.MATTER_REPLICATOR) { MenuType(::MatterReplicatorMenu) } - val MATTER_BOTTLER: MenuType<*> by registry.register(MNames.MATTER_BOTTLER) { MenuType(::MatterBottlerMenu) } - val DRIVE_VIEWER: MenuType<*> by registry.register(MNames.DRIVE_VIEWER) { MenuType(::DriveViewerMenu) } - val CARGO_CRATE: MenuType<*> by registry.register(MNames.CARGO_CRATE) { MenuType(::CargoCrateMenu) } - val MINECART_CARGO_CRATE: MenuType<*> by registry.register(MNames.MINECART_CARGO_CRATE) { MenuType(::MinecartCargoCrateMenu) } - val DRIVE_RACK: MenuType<*> by registry.register(MNames.DRIVE_RACK) { MenuType(::DriveRackMenu) } - val ITEM_MONITOR: MenuType<*> by registry.register(MNames.ITEM_MONITOR) { MenuType(::ItemMonitorMenu) } - val ENERGY_COUNTER: MenuType<*> by registry.register(MNames.ENERGY_COUNTER) { MenuType(::EnergyCounterMenu) } - val CHEMICAL_GENERATOR: MenuType<*> by registry.register(MNames.CHEMICAL_GENERATOR) { MenuType(::ChemicalGeneratorMenu) } - val PLATE_PRESS: MenuType<*> by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) } - val MATTER_RECYCLER: MenuType<*> by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) } - val ENERGY_SERVO: MenuType<*> by registry.register(MNames.ENERGY_SERVO) { MenuType(::EnergyServoMenu) } + val ANDROID_STATION: MenuType by registry.register(MNames.ANDROID_STATION) { MenuType(::AndroidStationMenu) } + val ANDROID_CHARGER: MenuType by registry.register(MNames.ANDROID_CHARGER) { MenuType({ a, b -> AndroidChargerMenu(a, b, null as AndroidChargerBlockEntity?) }) } + val BATTERY_BANK: MenuType by registry.register(MNames.BATTERY_BANK) { MenuType(::BatteryBankMenu) } + val MATTER_DECOMPOSER: MenuType by registry.register(MNames.MATTER_DECOMPOSER) { MenuType(::MatterDecomposerMenu) } + val MATTER_CAPACITOR_BANK: MenuType by registry.register(MNames.MATTER_CAPACITOR_BANK) { MenuType(::MatterCapacitorBankMenu) } + val PATTERN_STORAGE: MenuType by registry.register(MNames.PATTERN_STORAGE) { MenuType(::PatternStorageMenu) } + val MATTER_SCANNER: MenuType by registry.register(MNames.MATTER_SCANNER) { MenuType(::MatterScannerMenu) } + val MATTER_PANEL: MenuType by registry.register(MNames.MATTER_PANEL) { MenuType(::MatterPanelMenu) } + val MATTER_REPLICATOR: MenuType by registry.register(MNames.MATTER_REPLICATOR) { MenuType(::MatterReplicatorMenu) } + val MATTER_BOTTLER: MenuType by registry.register(MNames.MATTER_BOTTLER) { MenuType(::MatterBottlerMenu) } + val DRIVE_VIEWER: MenuType by registry.register(MNames.DRIVE_VIEWER) { MenuType(::DriveViewerMenu) } + val CARGO_CRATE: MenuType by registry.register(MNames.CARGO_CRATE) { MenuType(::CargoCrateMenu) } + val MINECART_CARGO_CRATE: MenuType by registry.register(MNames.MINECART_CARGO_CRATE) { MenuType(::MinecartCargoCrateMenu) } + val DRIVE_RACK: MenuType by registry.register(MNames.DRIVE_RACK) { MenuType(::DriveRackMenu) } + val ITEM_MONITOR: MenuType by registry.register(MNames.ITEM_MONITOR) { MenuType(::ItemMonitorMenu) } + val ENERGY_COUNTER: MenuType by registry.register(MNames.ENERGY_COUNTER) { MenuType(::EnergyCounterMenu) } + val CHEMICAL_GENERATOR: MenuType by registry.register(MNames.CHEMICAL_GENERATOR) { MenuType(::ChemicalGeneratorMenu) } + val PLATE_PRESS: MenuType by registry.register(MNames.PLATE_PRESS) { MenuType(::PlatePressMenu) } + val POWERED_FURNACE: MenuType by registry.register(MNames.POWERED_FURNACE) { MenuType(PoweredFurnaceMenu::furnace) } + val POWERED_BLAST_FURNACE: MenuType by registry.register(MNames.POWERED_BLAST_FURNACE) { MenuType(PoweredFurnaceMenu::blasting) } + val POWERED_SMOKER: MenuType by registry.register(MNames.POWERED_SMOKER) { MenuType(PoweredFurnaceMenu::smoking) } + val TWIN_PLATE_PRESS: MenuType by registry.register(MNames.TWIN_PLATE_PRESS) { MenuType(::TwinPlatePressMenu) } + val MATTER_RECYCLER: MenuType by registry.register(MNames.MATTER_RECYCLER) { MenuType(::MatterRecyclerMenu) } + val ENERGY_SERVO: MenuType by registry.register(MNames.ENERGY_SERVO) { MenuType(::EnergyServoMenu) } + val HOLO_SIGN: MenuType by registry.register(MNames.HOLO_SIGN) { MenuType(::HoloSignMenu) } + val COBBLESTONE_GENERATOR: MenuType by registry.register(MNames.COBBLESTONE_GENERATOR) { MenuType(::CobblerMenu) } + val ESSENCE_STORAGE: MenuType by registry.register(MNames.ESSENCE_STORAGE) { MenuType(::EssenceStorageMenu) } + val ITEM_REPAIER: MenuType by registry.register(MNames.MATTER_RECONSTRUCTOR) { MenuType(::MatterReconstructorMenu) } + val FLUID_TANK: MenuType by registry.register(MNames.FLUID_TANK) { MenuType(::FluidTankMenu) } + val PAINTER: MenuType by registry.register(MNames.PAINTER) { MenuType(::PainterMenu) } + val MATTER_ENTANGLER: MenuType by registry.register(MNames.MATTER_ENTANGLER) { MenuType(::MatterEntanglerMenu) } - val STORAGE_BUS: MenuType<*> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) } - val STORAGE_EXPORTER: MenuType<*> by registry.register(MNames.STORAGE_EXPORTER) { MenuType(::StorageExporterMenu) } - val STORAGE_IMPORTER: MenuType<*> by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterMenu) } - val STORAGE_POWER_SUPPLIER: MenuType<*> by registry.register(MNames.STORAGE_POWER_SUPPLIER) { MenuType(::StoragePowerSupplierMenu) } + val STORAGE_BUS: MenuType by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu) } + val STORAGE_IMPORTER_EXPORTER: MenuType by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterExporterMenu) } + val STORAGE_POWER_SUPPLIER: MenuType by registry.register(MNames.STORAGE_POWER_SUPPLIER) { MenuType(::StoragePowerSupplierMenu) } internal fun register(bus: IEventBus) { registry.register(bus) bus.addListener(this::registerClient) } - @Suppress("unchecked_cast") private fun registerClient(event: FMLClientSetupEvent) { event.enqueueWork { - MenuScreens.register(ANDROID_STATION as MenuType, ::AndroidStationScreen) - MenuScreens.register(BATTERY_BANK as MenuType, ::BatteryBankScreen) - MenuScreens.register(MATTER_DECOMPOSER as MenuType, ::MatterDecomposerScreen) - MenuScreens.register(MATTER_CAPACITOR_BANK as MenuType, ::MatterCapacitorBankScreen) - MenuScreens.register(PATTERN_STORAGE as MenuType, ::PatternStorageScreen) - MenuScreens.register(MATTER_SCANNER as MenuType, ::MatterScannerScreen) - MenuScreens.register(MATTER_PANEL as MenuType, ::MatterPanelScreen) - MenuScreens.register(MATTER_REPLICATOR as MenuType, ::MatterReplicatorScreen) - MenuScreens.register(MATTER_BOTTLER as MenuType, ::MatterBottlerScreen) - MenuScreens.register(DRIVE_VIEWER as MenuType, ::DriveViewerScreen) - MenuScreens.register(CARGO_CRATE as MenuType, ::CargoCrateScreen) - MenuScreens.register(MINECART_CARGO_CRATE as MenuType, ::MinecartCargoCrateScreen) - MenuScreens.register(DRIVE_RACK as MenuType, ::DriveRackScreen) - MenuScreens.register(ITEM_MONITOR as MenuType, ::ItemMonitorScreen) - MenuScreens.register(ENERGY_COUNTER as MenuType, ::EnergyCounterScreen) - MenuScreens.register(CHEMICAL_GENERATOR as MenuType, ::ChemicalGeneratorScreen) - MenuScreens.register(PLATE_PRESS as MenuType, ::PlatePressScreen) - MenuScreens.register(MATTER_RECYCLER as MenuType, ::MatterRecyclerScreen) - MenuScreens.register(STORAGE_BUS as MenuType, ::StorageBusScreen) - MenuScreens.register(STORAGE_EXPORTER as MenuType, ::StorageExporterScreen) - MenuScreens.register(STORAGE_IMPORTER as MenuType, ::StorageImporterScreen) - MenuScreens.register(STORAGE_POWER_SUPPLIER as MenuType, ::StoragePowerSupplierScreen) - MenuScreens.register(ENERGY_SERVO as MenuType, ::EnergyServoScreen) + MenuScreens.register(ANDROID_STATION, ::AndroidStationScreen) + MenuScreens.register(ANDROID_CHARGER, ::AndroidChargerScreen) + MenuScreens.register(BATTERY_BANK, ::BatteryBankScreen) + MenuScreens.register(MATTER_DECOMPOSER, ::MatterDecomposerScreen) + MenuScreens.register(MATTER_CAPACITOR_BANK, ::MatterCapacitorBankScreen) + MenuScreens.register(PATTERN_STORAGE, ::PatternStorageScreen) + MenuScreens.register(MATTER_SCANNER, ::MatterScannerScreen) + MenuScreens.register(MATTER_PANEL, ::MatterPanelScreen) + MenuScreens.register(MATTER_REPLICATOR, ::MatterReplicatorScreen) + MenuScreens.register(MATTER_BOTTLER, ::MatterBottlerScreen) + MenuScreens.register(DRIVE_VIEWER, ::DriveViewerScreen) + MenuScreens.register(CARGO_CRATE, ::CargoCrateScreen) + MenuScreens.register(MINECART_CARGO_CRATE, ::MinecartCargoCrateScreen) + MenuScreens.register(DRIVE_RACK, ::DriveRackScreen) + MenuScreens.register(ITEM_MONITOR, ::ItemMonitorScreen) + MenuScreens.register(ENERGY_COUNTER, ::EnergyCounterScreen) + MenuScreens.register(CHEMICAL_GENERATOR, ::ChemicalGeneratorScreen) + MenuScreens.register(PLATE_PRESS, ::PlatePressScreen) + MenuScreens.register(TWIN_PLATE_PRESS, ::TwinPlatePressScreen) + MenuScreens.register(MATTER_RECYCLER, ::MatterRecyclerScreen) + MenuScreens.register(STORAGE_BUS, ::StorageBusScreen) + MenuScreens.register(STORAGE_IMPORTER_EXPORTER, ::StorageImporterExporterScreen) + MenuScreens.register(STORAGE_POWER_SUPPLIER, ::StoragePowerSupplierScreen) + MenuScreens.register(ENERGY_SERVO, ::EnergyServoScreen) + MenuScreens.register(HOLO_SIGN, ::HoloSignScreen) + MenuScreens.register(COBBLESTONE_GENERATOR, ::CobblerScreen) + MenuScreens.register(ESSENCE_STORAGE, ::EssenceStorageScreen) + MenuScreens.register(ITEM_REPAIER, ::MatterReconstructorScreen) + MenuScreens.register(FLUID_TANK, ::FluidTankScreen) + MenuScreens.register(POWERED_FURNACE, ::PoweredFurnaceScreen) + MenuScreens.register(POWERED_BLAST_FURNACE, ::PoweredFurnaceScreen) + MenuScreens.register(POWERED_SMOKER, ::PoweredFurnaceScreen) + MenuScreens.register(PAINTER, ::PainterScreen) + MenuScreens.register(MATTER_ENTANGLER, ::MatterEntanglerScreen) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt index 4a2ef2694..258cc69c5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MNames.kt @@ -9,6 +9,14 @@ object MNames { const val LABORATORY_LAMP_LIGHT = "laboratory_lamp_light" const val DANGER_STRIPE_BLOCK = "danger_stripe_block" const val METAL_BEAM = "metal_beam" + const val ENGINE = "engine" + const val HOLO_SIGN = "holo_sign" + const val FLUID_TANK = "fluid_tank" + const val ANDROID_CHARGER = "android_charger" + const val INFINITE_WATER_SOURCE = "infinite_water_source" + const val DEV_CHEST = "dev_chest" + const val PAINTER = "painter" + const val MATTER_ENTANGLER = "matter_entangler" // blocks const val ANDROID_STATION = "android_station" @@ -21,14 +29,22 @@ object MNames { const val MATTER_PANEL = "matter_panel" const val MATTER_REPLICATOR = "matter_replicator" const val MATTER_BOTTLER = "matter_bottler" + const val MATTER_RECONSTRUCTOR = "matter_reconstructor" const val DRIVE_VIEWER = "drive_viewer" const val DRIVE_RACK = "drive_rack" const val ITEM_MONITOR = "item_monitor" const val ENERGY_COUNTER = "energy_counter" const val CHEMICAL_GENERATOR = "chemical_generator" const val PLATE_PRESS = "plate_press" + const val POWERED_FURNACE = "powered_furnace" + const val POWERED_BLAST_FURNACE = "powered_blast_furnace" + const val POWERED_SMOKER = "powered_smoker" + const val TWIN_PLATE_PRESS = "twin_plate_press" const val MATTER_RECYCLER = "matter_recycler" const val ENERGY_SERVO = "energy_servo" + const val COBBLESTONE_GENERATOR = "cobblestone_generator" + const val ESSENCE_STORAGE = "essence_storage" + const val TRITANIUM_ANVIL = "tritanium_anvil" const val STORAGE_CABLE = "storage_cable" // нужен рецепт const val STORAGE_POWER_SUPPLIER = "storage_power_supplier" // нужен рецепт @@ -62,6 +78,8 @@ object MNames { const val INDUSTRIAL_GLASS = "industrial_glass" const val INDUSTRIAL_GLASS_PANE = "industrial_glass_pane" + const val METAL_MESH = "metal_mesh" + const val METAL_JUNK = "metal_junk" const val CARBON_FIBRE_BLOCK = "carbon_fibre_block" const val FLOOR_TILES = "floor_tiles" @@ -102,6 +120,7 @@ object MNames { const val PATTERN_DRIVE_CREATIVE2 = "pattern_drive_creative2" const val ZPM_BATTERY = "zpm_battery" + const val PROCEDURAL_BATTERY = "procedural_battery" const val NUTRIENT_PASTE = "nutrient_paste" @@ -121,25 +140,46 @@ object MNames { const val TRITANIUM_PANTS = "tritanium_pants" const val TRITANIUM_BOOTS = "tritanium_boots" + const val SIMPLE_TRITANIUM_HELMET = "simple_tritanium_helmet" + const val SIMPLE_TRITANIUM_CHESTPLATE = "simple_tritanium_chestplate" + const val SIMPLE_TRITANIUM_PANTS = "simple_tritanium_pants" + const val SIMPLE_TRITANIUM_BOOTS = "simple_tritanium_boots" + // tools const val TRITANIUM_SWORD = "tritanium_sword" const val TRITANIUM_AXE = "tritanium_axe" const val TRITANIUM_PICKAXE = "tritanium_pickaxe" const val TRITANIUM_SHOVEL = "tritanium_shovel" const val TRITANIUM_HOE = "tritanium_hoe" + const val TRITANIUM_SHEARS = "tritanium_shears" + const val TRITANIUM_SHIELD = "tritanium_shield" const val ENERGY_SWORD = "energy_sword" const val PLASMA_RIFLE = "plasma_rifle" + const val CHEST_UPGRADER = "chest_upgrader" + // items: crafting components const val TRITANIUM_DUST = "tritanium_dust" + const val TRITANIUM_NUGGET = "tritanium_nugget" const val TRITANIUM_INGOT = "tritanium_ingot" const val TRITANIUM_INGOT_BLOCK = "tritanium_ingot_block" + const val TRITANIUM_BARS = "tritanium_bars" const val MATTER_IO_PORT = "matter_io_port" const val MATTER_TRANSFORM_MATRIX = "matter_transform_matrix" + const val CARBON_MESH = "carbon_mesh" + const val REINFORCED_TRITANIUM_PLATE = "reinforced_tritanium_plate" + + const val QUANTUM_TRANSCEIVER = "quantum_transceiver" + const val ELECTROMAGNET = "electromagnet" + const val ELECTROMOTOR = "electromotor" + const val MIRROR_COMPOUND = "mirror_compound" + const val MIRROR = "mirror" + const val BLANK_MACHINE_UPGRADE = "blank_machine_upgrade" + const val ENERGY_BUS = "energy_bus" const val ELECTRIC_PARTS = "electric_parts" @@ -165,6 +205,7 @@ object MNames { // android features and research const val AIR_BAGS = "air_bags" const val STEP_ASSIST = "step_assist" + const val SWIM_BOOSTERS = "swim_boosters" const val LIMB_OVERCLOCKING = "limb_overclocking" const val LIMB_OVERCLOCKING_1 = "limb_overclocking_1" diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt new file mode 100644 index 000000000..db9109e31 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.registry + +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.RecipeType +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import net.minecraftforge.registries.ForgeRegistries +import net.minecraftforge.registries.RegistryObject +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.recipe.* + +@Suppress("SameParameterValue") +object MRecipes { + class Type>(id: String) : RecipeType { + val id = ResourceLocation(OverdriveThatMatters.MOD_ID, id) + + override fun toString(): String { + return "RecipeType[$id]" + } + } + + private val types = DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, OverdriveThatMatters.MOD_ID) + private val serializers = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, OverdriveThatMatters.MOD_ID) + + internal fun register(bus: IEventBus) { + types.register(bus) + serializers.register(bus) + } + + private fun > register(name: String): RegistryObject> { + return types.register(name) { Type(name) } + } + + val PLATE_PRESS by register("plate_press") + val PAINTER by register("painter") + val MATTER_ENTANGLER by register("matter_entangler") + val MICROWAVE by register("microwave") + + init { + serializers.register("plate_press") { PlatePressRecipe.SERIALIZER } + serializers.register("energy_container") { EnergyContainerRecipe.Companion } + serializers.register("upgrade") { UpgradeRecipe.CODEC } + serializers.register("hammer_priming") { ExplosiveHammerPrimingRecipe.CODEC } + serializers.register("painter") { PainterRecipe.SERIALIZER } + serializers.register("painter_armor_dye") { PainterArmorDyeRecipe.SERIALIZER } + serializers.register("matter_entangler") { MatterEntanglerRecipe.SERIALIZER } + serializers.register("matter_entangler_energetic") { MatterEntanglerRecipe.ENERGY_SERIALIZER } + serializers.register("matter_entangler_matter") { MatterEntanglerRecipe.MATTER_SERIALIZER } + serializers.register("microwave") { MicrowaveRecipe.SERIALIZER } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt index da076c070..211e305d3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistry.kt @@ -1,51 +1,85 @@ package ru.dbotthepony.mc.otm.registry +import com.google.common.collect.ImmutableSet +import com.google.common.collect.Streams +import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.advancements.CriteriaTriggers +import net.minecraft.client.gui.Font import net.minecraft.client.renderer.item.ItemProperties import net.minecraft.core.BlockPos +import net.minecraft.core.cauldron.CauldronInteraction +import net.minecraft.core.registries.Registries import net.minecraft.resources.ResourceLocation -import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.entity.EntityType +import net.minecraft.world.entity.ai.village.poi.PoiType +import net.minecraft.world.entity.ai.village.poi.PoiTypes +import net.minecraft.world.item.DyeColor +import net.minecraft.world.item.DyeableArmorItem +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.block.* import net.minecraft.world.level.block.state.BlockBehaviour import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument import net.minecraft.world.level.material.Material import net.minecraft.world.level.material.MaterialColor +import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.client.IItemDecorator +import net.minecraftforge.client.event.RegisterColorHandlersEvent +import net.minecraftforge.client.event.RegisterItemDecorationsEvent +import net.minecraftforge.client.model.DynamicFluidContainerModel import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent +import net.minecraftforge.fml.loading.FMLEnvironment import net.minecraftforge.registries.NewRegistryEvent +import net.minecraftforge.registries.RegisterEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.android.AndroidResearchResult +import ru.dbotthepony.mc.otm.android.AndroidResearchResults import ru.dbotthepony.mc.otm.android.AndroidFeatureType +import ru.dbotthepony.mc.otm.android.AndroidResearchDescription +import ru.dbotthepony.mc.otm.android.AndroidResearchDescriptions import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature -import ru.dbotthepony.mc.otm.android.feature.FallDampenersFeature -import ru.dbotthepony.mc.otm.android.feature.ItemMagnetFeature -import ru.dbotthepony.mc.otm.android.feature.JumpBoostFeature import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature -import ru.dbotthepony.mc.otm.android.feature.ShockwaveFeature -import ru.dbotthepony.mc.otm.block.CargoCrateBlock -import ru.dbotthepony.mc.otm.block.TritaniumPressurePlate +import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock +import ru.dbotthepony.mc.otm.block.decorative.TritaniumPressurePlate import ru.dbotthepony.mc.otm.capability.matteryEnergy -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.item.EnergySwordItem +import ru.dbotthepony.mc.otm.client.MatteryGUI +import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import ru.dbotthepony.mc.otm.data.DecimalProvider +import ru.dbotthepony.mc.otm.item.weapon.EnergySwordItem +import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction +import ru.dbotthepony.mc.otm.matter.IMatterFunction import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock -import ru.dbotthepony.mc.otm.registry.objects.CrateProperties import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock import ru.dbotthepony.mc.otm.registry.objects.StripedColoredDecorativeBlock +import ru.dbotthepony.mc.otm.storage.StorageStack import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger +import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger import ru.dbotthepony.mc.otm.triggers.AndroidResearchTrigger +import ru.dbotthepony.mc.otm.triggers.AndroidTravelUnderwater import ru.dbotthepony.mc.otm.triggers.BecomeAndroidDeathTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidSleepTrigger import ru.dbotthepony.mc.otm.triggers.BecomeAndroidTrigger import ru.dbotthepony.mc.otm.triggers.BecomeHumaneTrigger import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger import ru.dbotthepony.mc.otm.triggers.EnderTeleporterFallDeathTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedCraftingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedEnderAccessTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger +import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger import ru.dbotthepony.mc.otm.triggers.FallDampenersSaveTrigger +import ru.dbotthepony.mc.otm.triggers.NailedEntityTrigger import ru.dbotthepony.mc.otm.triggers.NanobotsArmorTrigger -import ru.dbotthepony.mc.otm.triggers.PhantomSpawnDeniedTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveDamageMobTrigger import ru.dbotthepony.mc.otm.triggers.ShockwaveTrigger +import ru.dbotthepony.mc.otm.triggers.TakeItemOutOfReplicatorTrigger object MRegistry { private val features = RegistryDelegate>("android_features") @@ -57,151 +91,118 @@ object MRegistry { features.build(event) } - val CARGO_CRATES = DecorativeBlock(MNames.CARGO_CRATE, ::CargoCrateBlock, baseItemGroup = OverdriveThatMatters.INSTANCE.CREATIVE_TAB) + val CARGO_CRATES = DecorativeBlock(MNames.CARGO_CRATE, ::CargoCrateBlock) - val DECORATIVE_CRATE = DecorativeBlock.simple(MNames.DECORATIVE_CRATE, { - BlockBehaviour.Properties.of(Material.METAL, it?.materialColor ?: MaterialColor.SNOW) + val DECORATIVE_CRATE = DecorativeBlock.simple(MNames.DECORATIVE_CRATE) { + BlockBehaviour.Properties.of(Material.METAL, MaterialColor.SNOW) .sound(SoundType.METAL) .requiresCorrectToolForDrops() .explosionResistance(10f) .destroyTime(1f) - }) + } - val TRITANIUM_BLOCK = DecorativeBlock.simple(MNames.TRITANIUM_BLOCK, { - BlockBehaviour.Properties.of(Material.METAL, it?.materialColor ?: MaterialColor.COLOR_LIGHT_BLUE) + val TRITANIUM_BLOCK = DecorativeBlock.simple(MNames.TRITANIUM_BLOCK) { + BlockBehaviour.Properties.of(Material.METAL, DyeColor.LIGHT_BLUE) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(80f) .destroyTime(2.5f) - }) + } - val TRITANIUM_STAIRS = DecorativeBlock(MNames.TRITANIUM_STAIRS, { + val TRITANIUM_STAIRS = DecorativeBlock(MNames.TRITANIUM_STAIRS) { StairBlock( { TRITANIUM_BLOCK.allBlocks[it]!!.defaultBlockState() }, BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!) ) - }) + } - val TRITANIUM_SLAB = DecorativeBlock(MNames.TRITANIUM_SLAB, { + val TRITANIUM_SLAB = DecorativeBlock(MNames.TRITANIUM_SLAB) { SlabBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!)) - }) + } - val TRITANIUM_WALL = DecorativeBlock(MNames.TRITANIUM_WALL, { + val TRITANIUM_WALL = DecorativeBlock(MNames.TRITANIUM_WALL) { WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_BLOCK.allBlocks[it]!!)) - }) + } val TRITANIUM_PRESSURE_PLATE = DecorativeBlock(MNames.TRITANIUM_PRESSURE_PLATE, ::TritaniumPressurePlate) - val VENT = DecorativeBlock.simple(MNames.VENT, { - BlockBehaviour.Properties.of(Material.METAL, it?.materialColor ?: MaterialColor.COLOR_LIGHT_BLUE) + val VENT = DecorativeBlock.simple(MNames.VENT) { + BlockBehaviour.Properties.of(Material.METAL, DyeColor.LIGHT_BLUE) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(20f) .destroyTime(1.5f) - }) + } - val VENT_ALTERNATIVE = DecorativeBlock.simple(MNames.VENT_ALTERNATIVE, { - BlockBehaviour.Properties.of(Material.METAL, it?.materialColor ?: MaterialColor.COLOR_LIGHT_BLUE) + val VENT_ALTERNATIVE = DecorativeBlock.simple(MNames.VENT_ALTERNATIVE) { + BlockBehaviour.Properties.of(Material.METAL, DyeColor.LIGHT_BLUE) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(20f) .destroyTime(1.5f) - }) + } - val FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.FLOOR_TILES, { - BlockBehaviour.Properties.of(Material.STONE, it.materialColor) + val FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.FLOOR_TILES) { + BlockBehaviour.Properties.of(Material.STONE, it) .sound(SoundType.STONE) .requiresCorrectToolForDrops() .strength(1.5f, 6f) - }) + } - val FLOOR_TILES_STAIRS = ColoredDecorativeBlock(MNames.FLOOR_TILES_STAIRS, { + val FLOOR_TILES_STAIRS = ColoredDecorativeBlock(MNames.FLOOR_TILES_STAIRS) { StairBlock( { FLOOR_TILES.blocks[it]!!.defaultBlockState() }, BlockBehaviour.Properties.copy(FLOOR_TILES.blocks[it]!!) ) - }) + } - val FLOOR_TILES_SLAB = ColoredDecorativeBlock(MNames.FLOOR_TILES_SLAB, { + val FLOOR_TILES_SLAB = ColoredDecorativeBlock(MNames.FLOOR_TILES_SLAB) { SlabBlock(BlockBehaviour.Properties.copy(FLOOR_TILES.blocks[it]!!)) - }) + } - val UNREFINED_FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.UNREFINED_FLOOR_TILES, { - BlockBehaviour.Properties.of(Material.CLAY, it.materialColor) + val UNREFINED_FLOOR_TILES = ColoredDecorativeBlock.simple(MNames.UNREFINED_FLOOR_TILES) { + BlockBehaviour.Properties.of(Material.CLAY, it) .sound(SoundType.GRAVEL) .strength(1f, 2f) - }) + } - val INDUSTRIAL_GLASS = DecorativeBlock(MNames.INDUSTRIAL_GLASS, { color -> - val properties = BlockBehaviour.Properties.of(Material.GLASS, if (color != null) color.materialColor else MaterialColor.NONE) - .destroyTime(1.5f) - .explosionResistance(40f) - .requiresCorrectToolForDrops() - .sound(SoundType.GLASS) - .noOcclusion() - .isValidSpawn { _: BlockState, _: BlockGetter, _: BlockPos, _: EntityType<*>? -> false } - .isRedstoneConductor { _: BlockState, _: BlockGetter, _: BlockPos -> false } - .isSuffocating { _: BlockState, _: BlockGetter, _: BlockPos -> false } - .isViewBlocking { _: BlockState, _: BlockGetter, _: BlockPos -> false } + val INDUSTRIAL_GLASS = DecorativeBlock(MNames.INDUSTRIAL_GLASS) { color -> + val properties = + BlockBehaviour.Properties.of(Material.GLASS, color?.materialColor ?: MaterialColor.NONE) + .destroyTime(1.5f) + .explosionResistance(40f) + .requiresCorrectToolForDrops() + .sound(SoundType.GLASS) + .noOcclusion() + .isValidSpawn { _: BlockState, _: BlockGetter, _: BlockPos, _: EntityType<*>? -> false } + .isRedstoneConductor { _: BlockState, _: BlockGetter, _: BlockPos -> false } + .isSuffocating { _: BlockState, _: BlockGetter, _: BlockPos -> false } + .isViewBlocking { _: BlockState, _: BlockGetter, _: BlockPos -> false } if (color != null) { return@DecorativeBlock StainedGlassBlock(color, properties) } return@DecorativeBlock GlassBlock(properties) - }) + } - val INDUSTRIAL_GLASS_PANE = DecorativeBlock(MNames.INDUSTRIAL_GLASS_PANE, { color -> - val properties = BlockBehaviour.Properties.of(Material.GLASS, if (color != null) color.materialColor else MaterialColor.NONE) - .strength(1.25f, 5.0f) - .requiresCorrectToolForDrops() - .sound(SoundType.GLASS) - .noOcclusion() + val INDUSTRIAL_GLASS_PANE = DecorativeBlock(MNames.INDUSTRIAL_GLASS_PANE) { color -> + val properties = + BlockBehaviour.Properties.of(Material.GLASS, color?.materialColor ?: MaterialColor.NONE) + .strength(1.25f, 5.0f) + .requiresCorrectToolForDrops() + .sound(SoundType.GLASS) + .noOcclusion() if (color != null) { return@DecorativeBlock StainedGlassPaneBlock(color, properties) } return@DecorativeBlock IronBarsBlock(properties) - }) - - val CRATE_RED = CrateProperties(MaterialColor.COLOR_RED, "crate_red") - val CRATE_BLUE = CrateProperties(MaterialColor.COLOR_BLUE, "crate_blue") - val CRATE_YELLOW = CrateProperties(MaterialColor.COLOR_YELLOW, "crate_yellow") - val CRATE_GREEN = CrateProperties(MaterialColor.COLOR_GREEN, "crate_green") - val CRATE_BLACK = CrateProperties(MaterialColor.COLOR_BLACK, "crate_black") - val CRATE_PINK = CrateProperties(MaterialColor.COLOR_PINK, "crate_pink") - val CRATE_PURPLE = CrateProperties(MaterialColor.COLOR_PURPLE, "crate_purple") - - val CRATE_LIST = listOf( - CRATE_RED, - CRATE_BLUE, - CRATE_YELLOW, - CRATE_GREEN, - CRATE_BLACK, - CRATE_PINK, - CRATE_PURPLE, - ) - - const val DAMAGE_BECOME_ANDROID_ID = "otm_become_android" - const val DAMAGE_BECOME_HUMANE_ID = "otm_become_humane" - const val DAMAGE_EVENT_HORIZON_ID = "otm_event_horizon" - const val DAMAGE_HAWKING_RADIATION_ID = "otm_hawking_radiation" - const val DAMAGE_EXOPACK_PROBE_ID = "otm_exopack_probe" - - val DAMAGE_EXOPACK_PROBE = ImmutableDamageSource(DamageSource(DAMAGE_EXOPACK_PROBE_ID).bypassArmor().bypassMagic()) - - val DAMAGE_BECOME_ANDROID = ImmutableDamageSource(DamageSource(DAMAGE_BECOME_ANDROID_ID).bypassArmor().bypassInvul().bypassMagic()) - val DAMAGE_BECOME_HUMANE = ImmutableDamageSource(DamageSource(DAMAGE_BECOME_HUMANE_ID).bypassArmor().bypassInvul().bypassMagic()) - val DAMAGE_EVENT_HORIZON = ImmutableDamageSource(DamageSource(DAMAGE_EVENT_HORIZON_ID).bypassMagic().bypassArmor()) - val DAMAGE_HAWKING_RADIATION = ImmutableDamageSource(DamageSource(DAMAGE_HAWKING_RADIATION_ID)) - const val DAMAGE_EMP_NAME = "otm_emp" - const val DAMAGE_SHOCKWAVE_NAME = "otm_shockwave" - const val DAMAGE_PLASMA_NAME = "otm_plasma" - val DAMAGE_EMP: DamageSource = ImmutableDamageSource(EMPDamageSource()) + } val TRITANIUM_STRIPED_BLOCK = StripedColoredDecorativeBlock(MNames.TRITANIUM_STRIPED_BLOCK, { colorA, _ -> - Block(BlockBehaviour.Properties.of(Material.METAL, colorA.materialColor) + Block(BlockBehaviour.Properties.of(Material.METAL, colorA) .sound(SoundType.BASALT) .requiresCorrectToolForDrops() .explosionResistance(80f) @@ -220,13 +221,66 @@ object MRegistry { WallBlock(BlockBehaviour.Properties.copy(TRITANIUM_STRIPED_BLOCK.getBlock(colorA, colorB))) }) + const val DAMAGE_BECOME_ANDROID_ID = "otm_become_android" + const val DAMAGE_BECOME_HUMANE_ID = "otm_become_humane" + const val DAMAGE_EVENT_HORIZON_ID = "otm_event_horizon" + const val DAMAGE_HAWKING_RADIATION_ID = "otm_hawking_radiation" + const val DAMAGE_EXOPACK_PROBE_ID = "otm_exopack_probe" + const val DAMAGE_EMP_NAME = "otm_emp" + const val DAMAGE_SHOCKWAVE_NAME = "otm_shockwave" + const val DAMAGE_PLASMA_NAME = "otm_plasma" + const val DAMAGE_COSMIC_RAYS_NAME = "otm_cosmic_rays" + const val DAMAGE_EXPLOSIVE_HAMMER_NAME = "otm_explosive_hammer" + const val DAMAGE_HAMMER_NAIL_NAME = "otm_hammer_nail" + + private fun registerEvent(event: RegisterEvent) { + // mojang moment + + if (event.registryKey == Registries.POINT_OF_INTEREST_TYPE) { + val reg = event.getVanillaRegistry() ?: throw IllegalStateException("POI registry is not a vanilla registry") + + event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.BUTCHER.location()) { + val old = reg[PoiTypes.BUTCHER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist") + + if (old.`is`(MBlocks.POWERED_SMOKER.defaultBlockState())) { + old + } else { + PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_SMOKER.stateDefinition.possibleStates.stream()).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange) + } + } + + event.register(Registries.POINT_OF_INTEREST_TYPE, PoiTypes.ARMORER.location()) { + val old = reg[PoiTypes.ARMORER] ?: throw IllegalStateException("POI with type ${PoiTypes.ARMORER} does not exist") + + if (old.`is`(MBlocks.POWERED_BLAST_FURNACE.defaultBlockState())) { + old + } else { + PoiType(Streams.concat(old.matchingStates.stream(), MBlocks.POWERED_BLAST_FURNACE.stateDefinition.possibleStates.stream()).collect(ImmutableSet.toImmutableSet()), old.maxTickets, old.validRange) + } + } + } + } + fun initialize(bus: IEventBus) { bus.addListener(this::register) bus.addListener(this::initializeClient) bus.addListener(this::initializeCommon) bus.addListener(MStats::registerVanilla) + bus.addListener(this::registerEvent) + bus.addListener(this::registerItemColorHandlers) + bus.addListener(this::registerItemDecorators) + + DecimalProvider.register(bus) + AndroidResearchDescription.register(bus) + AndroidResearchDescriptions.register(bus) + AndroidResearchResult.register(bus) + AndroidResearchResults.register(bus) + + AbstractRegistryAction.register(bus) + IMatterFunction.register(bus) MBlocks.register(bus) + MFluids.register(bus) MBlockEntities.register(bus) MEntityTypes.register(bus) MMenus.register(bus) @@ -236,43 +290,94 @@ object MRegistry { LootModifiers.register(bus) MItemFunctionTypes.register(bus) MLootItemConditions.register(bus) - MRecipes.register(bus) + StorageStack.register(bus) + MatteryChestMenu.register(bus) + + if (FMLEnvironment.dist == Dist.CLIENT) { + MBlockColors.register(bus) + } // call static constructors NanobotsArmorFeature.Companion - ShockwaveFeature.Companion - ItemMagnetFeature.Companion - FallDampenersFeature.Companion - JumpBoostFeature.Companion EnderTeleporterFeature.Companion + + CriteriaTriggers.register(BlackHoleTrigger) + CriteriaTriggers.register(BecomeAndroidTrigger) + CriteriaTriggers.register(BecomeAndroidDeathTrigger) + CriteriaTriggers.register(BecomeAndroidSleepTrigger) + CriteriaTriggers.register(BecomeHumaneTrigger) + CriteriaTriggers.register(AndroidResearchTrigger) + CriteriaTriggers.register(ShockwaveDamageMobTrigger) + CriteriaTriggers.register(ShockwaveTrigger) + CriteriaTriggers.register(AndroidBatteryTrigger) + CriteriaTriggers.register(NanobotsArmorTrigger) + CriteriaTriggers.register(FallDampenersSaveTrigger) + CriteriaTriggers.register(EnderTeleporterFallDeathTrigger) + CriteriaTriggers.register(KillAsAndroidTrigger) + CriteriaTriggers.register(AndroidTravelUnderwater) + CriteriaTriggers.register(NailedEntityTrigger) + CriteriaTriggers.register(ExopackObtainedTrigger) + CriteriaTriggers.register(ExopackGainedSmeltingTrigger) + CriteriaTriggers.register(ExopackGainedCraftingTrigger) + CriteriaTriggers.register(ExopackGainedEnderAccessTrigger) + CriteriaTriggers.register(ExopackSlotsExpandedTrigger) + CriteriaTriggers.register(ExopackBatterySlotTrigger) + CriteriaTriggers.register(TakeItemOutOfReplicatorTrigger) } private fun initializeCommon(event: FMLCommonSetupEvent) { - event.enqueueWork { - CriteriaTriggers.register(BlackHoleTrigger) - CriteriaTriggers.register(BecomeAndroidTrigger) - CriteriaTriggers.register(BecomeAndroidDeathTrigger) - CriteriaTriggers.register(BecomeAndroidSleepTrigger) - CriteriaTriggers.register(BecomeHumaneTrigger) - CriteriaTriggers.register(AndroidResearchTrigger) - CriteriaTriggers.register(ShockwaveDamageMobTrigger) - CriteriaTriggers.register(ShockwaveTrigger) - CriteriaTriggers.register(PhantomSpawnDeniedTrigger) - CriteriaTriggers.register(AndroidBatteryTrigger) - CriteriaTriggers.register(NanobotsArmorTrigger) - CriteriaTriggers.register(FallDampenersSaveTrigger) - CriteriaTriggers.register(EnderTeleporterFallDeathTrigger) - } + registerCauldronHandlers() } private fun initializeClient(event: FMLClientSetupEvent) { - ItemProperties.register(MItems.ENERGY_SWORD, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_powered")) { stack, _, _, _ -> - if ((stack.matteryEnergy?.batteryLevel ?: Decimal.ZERO) >= EnergySwordItem.ENERGY_PER_SWING) { - 1f - } else { - 0f + event.enqueueWork { + ItemProperties.register(MItems.TRITANIUM_SHIELD, ResourceLocation(OverdriveThatMatters.MOD_ID, "blocking")) { stack, level, entity, _ -> + if (entity?.isUsingItem == true && entity.useItem == stack) { + 1f + } else { + 0f + } + } + + ItemProperties.register(MItems.ENERGY_SWORD, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_powered")) { stack, _, _, _ -> + if ((stack.matteryEnergy?.batteryLevel ?: Decimal.ZERO) >= EnergySwordItem.ENERGY_PER_SWING) { + 1f + } else { + 0f + } + } + + ItemProperties.register(MItems.EXPLOSIVE_HAMMER, ResourceLocation(OverdriveThatMatters.MOD_ID, "is_primed")) { stack, level, entity, _ -> + if (MItems.EXPLOSIVE_HAMMER.isPrimed(stack)) { + 1f + } else { + if ((entity?.useItemRemainingTicks ?: 0) <= 0) { + 0f + } else { + (stack.useDuration - (entity?.useItemRemainingTicks ?: stack.useDuration)).toFloat() / stack.useDuration + } + } } } } + + private fun registerCauldronHandlers() { + MItems.TRITANIUM_ARMOR.forEach { CauldronInteraction.WATER[it] = CauldronInteraction.DYED_ITEM } + } + + private fun registerItemColorHandlers(event: RegisterColorHandlersEvent.Item) { + event.register(DynamicFluidContainerModel.Colors(), MItems.FLUID_CAPSULE) + event.register({ stack, layer -> if (layer == 0) (stack.item as DyeableArmorItem).getColor(stack) else -1 }, + *MItems.TRITANIUM_ARMOR.toTypedArray() + ) + event.register({ _, layer -> if (layer == 1) RGBAColor.AQUA.toARGB() else -1 }, + *MItems.MachineUpgrades.Creative.LIST.toTypedArray()) + } + + private fun registerItemDecorators(event: RegisterItemDecorationsEvent) { + event.register(Items.SHIELD) { font, stack, xOffset, yOffset, blitOffset -> MatteryGUI.renderShieldCooldownOverlay(PoseStack(), font, stack, xOffset, yOffset) } + + event.register(MItems.TRITANIUM_SHIELD) { font, stack, xOffset, yOffset, blitOffset -> MatteryGUI.renderShieldCooldownOverlay(PoseStack(), font, stack, xOffset, yOffset) } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt index 35ad59aa6..d1c3d73b7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MSoundEvents.kt @@ -13,7 +13,8 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters object MSoundEvents { private val registry: DeferredRegister = DeferredRegister.create(ForgeRegistries.SOUND_EVENTS, OverdriveThatMatters.MOD_ID) - private fun make(name: String) = registry.register(name) { SoundEvent(ResourceLocation(OverdriveThatMatters.MOD_ID, name)) } + // TODO: 1.19.3 + private fun make(name: String) = registry.register(name) { SoundEvent.createVariableRangeEvent(ResourceLocation(OverdriveThatMatters.MOD_ID, name)) } val RIFLE_SHOT: SoundEvent by make("item.rifle_shot") val PLASMA_WEAPON_OVERHEAT: SoundEvent by make("item.plasma_weapon_overheat") @@ -22,6 +23,7 @@ object MSoundEvents { val ANDROID_JUMP_BOOST: SoundEvent by make("android.jump_boost") val ANDROID_SHOCKWAVE: SoundEvent by make("android.shockwave") + val ANDROID_PROJ_PARRY: SoundEvent by make("android.projectile_parry") internal fun register(bus: IEventBus) { registry.register(bus) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MStats.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MStats.kt index dffa75140..efc37a888 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MStats.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MStats.kt @@ -1,9 +1,9 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.core.Registry +import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.stats.StatFormatter import net.minecraft.stats.Stats -import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent import ru.dbotthepony.mc.otm.registry.StatNames.DAMAGE_ABSORBED import ru.dbotthepony.mc.otm.registry.StatNames.HEALTH_REGENERATED @@ -12,9 +12,9 @@ import ru.dbotthepony.mc.otm.registry.StatNames.POWER_CONSUMED object MStats { fun registerVanilla(event: FMLCommonSetupEvent) { event.enqueueWork { - Registry.register(Registry.CUSTOM_STAT, DAMAGE_ABSORBED, DAMAGE_ABSORBED) - Registry.register(Registry.CUSTOM_STAT, HEALTH_REGENERATED, HEALTH_REGENERATED) - Registry.register(Registry.CUSTOM_STAT, POWER_CONSUMED, POWER_CONSUMED) + Registry.register(BuiltInRegistries.CUSTOM_STAT, DAMAGE_ABSORBED, DAMAGE_ABSORBED) + Registry.register(BuiltInRegistries.CUSTOM_STAT, HEALTH_REGENERATED, HEALTH_REGENERATED) + Registry.register(BuiltInRegistries.CUSTOM_STAT, POWER_CONSUMED, POWER_CONSUMED) Stats.CUSTOM[DAMAGE_ABSORBED, StatFormatter.DIVIDE_BY_TEN] Stats.CUSTOM[HEALTH_REGENERATED, StatFormatter.DIVIDE_BY_TEN] Stats.CUSTOM[POWER_CONSUMED, StatFormatter.DIVIDE_BY_TEN] diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryEntryExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryEntryExt.kt deleted file mode 100644 index 85b9e3f8a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/RegistryEntryExt.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dbotthepony.mc.otm.registry - -import net.minecraftforge.registries.RegistryObject -import kotlin.reflect.KProperty - -operator fun RegistryObject.getValue(thisRef: Any, property: KProperty<*>): T { - return get() -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt index 9bb794004..86a49364c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt @@ -13,16 +13,23 @@ object MItemTags { val TRITANIUM_ORES: TagKey = ItemTags.create(ResourceLocation("forge", "ores/tritanium")) val TRITANIUM_ORE_CLUMPS: TagKey = ItemTags.create(ResourceLocation("forge", "raw_materials/tritanium")) val TRITANIUM_INGOTS: TagKey = ItemTags.create(ResourceLocation("forge", "ingots/tritanium")) + val TRITANIUM_NUGGETS: TagKey = ItemTags.create(ResourceLocation("forge", "nuggets/tritanium")) + val NUGGETS: TagKey = ItemTags.create(ResourceLocation("forge", "nuggets")) val TRITANIUM_INGOTS_STORAGE: TagKey = ItemTags.create(ResourceLocation("forge", "storage_blocks/tritanium")) val TRITANIUM_PLATES: TagKey = ItemTags.create(ResourceLocation("forge", "plates/tritanium")) + val CARBON_PLATES: TagKey = ItemTags.create(ResourceLocation("forge", "plates/carbon")) + val REINFORCED_TRITANIUM_PLATES: TagKey = ItemTags.create(ResourceLocation("forge", "reinforced_tritanium")) val TRITANIUM_DUSTS: TagKey = ItemTags.create(ResourceLocation("forge", "dusts/tritanium")) val IRON_PLATES: TagKey = ItemTags.create(ResourceLocation("forge", "plates/iron")) val GOLD_PLATES: TagKey = ItemTags.create(ResourceLocation("forge", "plates/gold")) val CARGO_CRATES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "cargo_crates")) val MINECART_CARGO_CRATES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "minecart_cargo_crates")) val INDUSTRIAL_GLASS: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "industrial_glass")) + val UPGRADES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "upgrades")) val CRAFTING_TABLES: TagKey = ItemTags.create(ResourceLocation("forge", "crafting_tables")) + val WORKBENCHES: TagKey = ItemTags.create(ResourceLocation("forge", "workbenches")) + val WORKBENCH: TagKey = ItemTags.create(ResourceLocation("forge", "workbench")) val BASIC_CIRCUIT: TagKey = ItemTags.create(ResourceLocation("forge", "circuits/basic")) val ADVANCED_CIRCUIT: TagKey = ItemTags.create(ResourceLocation("forge", "circuits/advanced")) @@ -32,6 +39,8 @@ object MItemTags { val PISTONS: TagKey = ItemTags.create(ResourceLocation("forge", "pistons")) + val TOOLS_HAMMERS: TagKey = ItemTags.create(ResourceLocation("forge", "tools/hammers")) + val HARDENED_GLASS_PANES: TagKey = ItemTags.create(ResourceLocation("forge", "hardened_glass_panes")) val HARDENED_GLASS_PANES_BLACK: TagKey = ItemTags.create(ResourceLocation("forge", "hardened_glass_panes/black")) val HARDENED_GLASS_PANES_BLUE: TagKey = ItemTags.create(ResourceLocation("forge", "hardened_glass_panes/blue")) @@ -79,6 +88,10 @@ object MBlockTags { val INDUSTRIAL_GLASS: TagKey = BlockTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "industrial_glass")) val CRAFTING_TABLES: TagKey = BlockTags.create(ResourceLocation("forge", "crafting_tables")) + val WORKBENCHES: TagKey = BlockTags.create(ResourceLocation("forge", "workbenches")) + val WORKBENCH: TagKey = BlockTags.create(ResourceLocation("forge", "workbench")) + + val REQUIRES_TRITANIUM_TOOL: TagKey = BlockTags.create(ResourceLocation("minecraft", "requires_tritanium_tool")) val HARDENED_GLASS_PANES: TagKey = BlockTags.create(ResourceLocation("forge", "hardened_glass_panes")) val HARDENED_GLASS_PANES_BLACK: TagKey = BlockTags.create(ResourceLocation("forge", "hardened_glass_panes/black")) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt index 81416befa..7b5e10a4d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/ColoredDecorativeBlock.kt @@ -1,15 +1,13 @@ package ru.dbotthepony.mc.otm.registry.objects import net.minecraft.world.item.BlockItem -import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.DyeColor import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockBehaviour import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.RegistryObject -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.SupplierMap +import ru.dbotthepony.mc.otm.core.collect.SupplierMap import java.util.EnumMap /** @@ -19,7 +17,6 @@ import java.util.EnumMap open class ColoredDecorativeBlock( val baseName: String, private val provider: (DyeColor) -> Block, - itemGroup: CreativeModeTab = OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE ) { var registeredItems = false private set @@ -111,7 +108,7 @@ open class ColoredDecorativeBlock( registeredBlocks = true } - private val properties = Item.Properties().tab(itemGroup).stacksTo(64) + private val properties = Item.Properties().stacksTo(64) open fun registerItems(registry: DeferredRegister) { check(itemMap.isEmpty()) { "( ͡° ͜ʖ ͡°) No. \\(• ε •)/ ( ͠° ل͜ ͡°) ( ͠° ͟ ͟ʖ ͡°) (ง ͠° ͟ل͜ ͡°)ง ( ͡°︺͡°) ('ω')" } @@ -138,10 +135,10 @@ open class ColoredDecorativeBlock( } companion object { - fun simple(baseName: String, provider: (DyeColor) -> BlockBehaviour.Properties, itemGroup: CreativeModeTab = OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE): ColoredDecorativeBlock { - return ColoredDecorativeBlock(baseName, { + fun simple(baseName: String, provider: (DyeColor) -> BlockBehaviour.Properties): ColoredDecorativeBlock { + return ColoredDecorativeBlock(baseName) { Block(provider.invoke(it)) - }, itemGroup) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/CrateProperties.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/CrateProperties.kt deleted file mode 100644 index f909fb76e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/CrateProperties.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ru.dbotthepony.mc.otm.registry.objects - -import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.SoundType -import net.minecraft.world.level.block.state.BlockBehaviour -import net.minecraft.world.level.material.Material -import net.minecraft.world.level.material.MaterialColor - -class CrateProperties(val color: MaterialColor, val name: String) { - fun makeBlock(): Block { - return Block( - BlockBehaviour.Properties.of(Material.METAL, color) - .sound(SoundType.METAL) - .requiresCorrectToolForDrops() - .strength(5.0F, 6.0F) - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/DecorativeBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/DecorativeBlock.kt index 2ba5ac727..d12237294 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/DecorativeBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/DecorativeBlock.kt @@ -1,16 +1,14 @@ package ru.dbotthepony.mc.otm.registry.objects import net.minecraft.world.item.BlockItem -import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.DyeColor import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockBehaviour import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.RegistryObject -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.SupplierMap -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.core.collect.SupplierMap +import ru.dbotthepony.mc.otm.core.util.WriteOnce /** * Base + Colored @@ -18,9 +16,7 @@ import ru.dbotthepony.mc.otm.core.WriteOnce class DecorativeBlock( baseName: String, private val provider: (DyeColor?) -> Block, - itemGroup: CreativeModeTab = OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE, - val baseItemGroup: CreativeModeTab = OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE, -) : ColoredDecorativeBlock(baseName, provider, itemGroup) { +) : ColoredDecorativeBlock(baseName, provider) { private var _block: RegistryObject by WriteOnce() private var _item: RegistryObject by WriteOnce() @@ -43,15 +39,15 @@ class DecorativeBlock( } override fun registerItems(registry: DeferredRegister) { - _item = registry.register(baseName) { BlockItem(_block.get(), Item.Properties().tab(baseItemGroup).stacksTo(64)) } + _item = registry.register(baseName) { BlockItem(_block.get(), Item.Properties().stacksTo(64)) } super.registerItems(registry) } companion object { - fun simple(baseName: String, provider: (DyeColor?) -> BlockBehaviour.Properties, itemGroup: CreativeModeTab = OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE): DecorativeBlock { - return DecorativeBlock(baseName, { + fun simple(baseName: String, provider: (DyeColor?) -> BlockBehaviour.Properties): DecorativeBlock { + return DecorativeBlock(baseName) { Block(provider.invoke(it)) - }, itemGroup) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt index ba916e079..6e357144d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/objects/StripedColoredDecorativeBlock.kt @@ -8,9 +8,8 @@ import net.minecraft.world.item.Item import net.minecraft.world.level.block.Block import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.RegistryObject -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.SupplierList -import ru.dbotthepony.mc.otm.core.SupplierMap +import ru.dbotthepony.mc.otm.core.collect.SupplierList +import ru.dbotthepony.mc.otm.core.collect.SupplierMap import java.util.EnumMap @Suppress("PropertyName", "unused", "ReplaceGetOrSet", "ReplacePutWithAssignment") @@ -18,7 +17,7 @@ class StripedColoredDecorativeBlock( val basename: String, val blockFactory: (colorA: DyeColor, colorB: DyeColor) -> Block, val itemFactory: ((colorA: DyeColor, colorB: DyeColor, block: Block) -> Item) = { _, _, block -> - BlockItem(block, Item.Properties().tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB_DECORATIVE).stacksTo(64)) + BlockItem(block, Item.Properties().stacksTo(64)) } ) { private val mapBlocks = EnumMap>>(DyeColor::class.java) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt index a1ef2d947..42263a437 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/saveddata/SavedCountingMap.kt @@ -8,9 +8,9 @@ import net.minecraft.nbt.ListTag import net.minecraft.nbt.Tag import net.minecraft.world.level.saveddata.SavedData import org.apache.logging.log4j.LogManager -import ru.dbotthepony.mc.otm.core.ProxiedMap -import ru.dbotthepony.mc.otm.core.set -import ru.dbotthepony.mc.otm.core.WriteOnce +import ru.dbotthepony.mc.otm.core.collect.ProxiedMap +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.WriteOnce class SavedMapDelegate(val parent: SavedCountingMap>?, val index: Int, value: V) { constructor(value: V) : this(null, -1, value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt index 6081246ed..4c4d9a099 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt @@ -3,70 +3,22 @@ package ru.dbotthepony.mc.otm.storage import java.math.BigInteger import java.util.* import java.util.stream.Stream - -/** - * Represents a stack in storage system. - * - * In general there are next rules: - * 1. Once created, stack is immutable, except for it's [count], therefore [copy] is expected to do - * "shallow" copies (not performance taxing). - * 2. Due to condition above, and due to sublying storage most of time being - * mutable, it is expected you do defensive copies. Examples of when you should do - * them are described on related interfaces. - * 3. For storing stacks as [Map] keys, a stack with size of 1 is utilized (requiring [equals] and [hashCode] to return meaningful results, see [IStorageStack.key]). - * 4. For equality (INCLUDING count), regular [equals] is utilized. - */ -interface IStorageStack { - fun copy(): IStorageStack - - /** - * Size of this storage stack - * - * This is overriden in subclasses - */ - var count: BigInteger - val isEmpty: Boolean - - /** - * @return max stack size for this stack object, - * null if unlimited (default) - */ - val maxStackSize: BigInteger? get() = null - - /** - * Increase [count] by [amount] - */ - fun grow(amount: BigInteger) { - count += amount - } - - /** - * Decrease [count] by [amount] - */ - fun shrink(amount: BigInteger) { - count -= amount - } -} - -@Suppress("UNCHECKED_CAST") -fun T.key(): T { - return copy().also { it.count = BigInteger.ONE } as T -} +import kotlin.Comparator /** * Storage system root, along IStorageStack interface */ -interface IStorage { +interface IStorage> { /** * @return Identity of this virtual component */ - val storageType: StorageStackType + val storageType: StorageStack.Type } /** * Generates events for [IStorageEventConsumer] */ -interface IStorageEventProducer : IStorage { +interface IStorageEventProducer> : IStorage { /** * [listener] is [IStorageEventConsumer] which want to subscribe to our events */ @@ -81,41 +33,58 @@ interface IStorageEventProducer : IStorage { /** * Consumes events produced by [IStorageEventConsumer] */ -interface IStorageEventConsumer : IStorage { +interface IStorageEventConsumer> : IStorage { /** * Fired on whenever an object is added (to listener) we subscribed to */ - fun addStack(stack: T, id: UUID, provider: IStorageProvider) + fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) /** * Fired on whenever an object is changes on listener we subscribed to */ - fun changeStack(stack: T, id: UUID, oldCount: BigInteger) + fun onStackChanged(stack: T, id: UUID) /** * Fired on whenever an object is removed from listener we subscribed to */ - fun removeStack(stack: T, id: UUID) + fun onStackRemoved(id: UUID) + + fun onStackChanged(tuple: IStorageTuple) { + onStackChanged(tuple.stack, tuple.id) + } + + fun onStackAdded(tuple: IStorageTuple, provider: IStorageProvider) { + onStackAdded(tuple.stack, tuple.id, provider) + } + + fun onStackRemoved(tuple: IStorageTuple) { + onStackRemoved(tuple.id) + } } /** * Storage acceptor, accepts (insert only) stacks provided via [insertStack] method. */ -interface IStorageAcceptor : IStorage { +interface IStorageAcceptor> : IStorage { /** * Inserts an item into system. * @return leftover, might equal to [stack] if no items were inserted */ fun insertStack(stack: T, simulate: Boolean): T + + val insertPriority: Int get() = 0 + + companion object : Comparator> { + override fun compare(o1: IStorageAcceptor<*>, o2: IStorageAcceptor<*>): Int { + return o2.insertPriority.compareTo(o1.insertPriority) + } + } } /** * Storage provider, provides (extract only) stacks to whoever needs them (the "warehouse"). * - * [get] methods work as in bidirectional map. - * - * It is **required** for storage having **exactly one or none** of mappings of one stack [T] - * to one [UUID] (exactly one *UUID -> stack* and exactly one *stack -> UUID*). + * [get] methods work as in bidirectional map, with each stack mapping to unique [UUID] * * What this means is that [get] with [T] as an argument shall never experience a situation where * two stacks match provided key. @@ -126,15 +95,14 @@ interface IStorageAcceptor : IStorage { * which may or may not produce performance hit; [UUID]s are lightweight, semantically not bound to anything and are * very good for distributed ID generation (so nothing in game has to be bound to one sequential number generator). */ -interface IStorageProvider : IStorageEventProducer { +interface IStorageProvider> : IStorageEventProducer { /** * Attempts to retrieve stored stack by its [id]. * - * Returns stored stack as-is. If no stack with [id] exists, then - * "empty stack" is returned. + * If no stack with [id] exists, then "empty stack" is returned. * * @param id identifier of stack - * @return stored object (not a copy). Do not edit it. + * @return stored object */ operator fun get(id: UUID): T @@ -148,21 +116,13 @@ interface IStorageProvider : IStorageEventProducer { * @return UUID of stack if present */ operator fun get(key: T): UUID? { - val key1 = key.let { if (it.count == BigInteger.ONE) it else it.key() } - - return stacks.filter { - if (it.stack.count == BigInteger.ONE) { - return@filter it.stack == key1 - } else { - return@filter it.stack.key() == key1 - } - }.findFirst().orElse(null)?.id + return stacks.filter { it.stack.equalsWithoutCount(key) }.findFirst().orElse(null)?.id } val stacks: Stream> /** - * If tuple does not exist, returns empty stack + * If storage has no such stack, returns empty stack * * @param id identifier of stack to extract * @param amount amount of units to extract @@ -170,60 +130,72 @@ interface IStorageProvider : IStorageEventProducer { * @return copy of object, with amount of units actually extracted */ fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T -} -fun IStorageProvider.removeListenerAuto(listener: IStorageEventConsumer): Boolean { - if (removeListener(listener)) { - for (stack in stacks) { - listener.removeStack(stack.stack, stack.id) - } - - return true + fun extractStack(id: T, amount: BigInteger, simulate: Boolean): T { + return extractStack(get(id) ?: return storageType.empty, amount, simulate) } - return false -} + val extractPriority: Int get() = 0 -fun IStorageProvider.addListenerAuto(listener: IStorageEventConsumer): Boolean { - if (addListener(listener)) { - for (stack in stacks) { - listener.addStack(stack.stack, stack.id, this) + companion object : Comparator> { + override fun compare(o1: IStorageProvider<*>, o2: IStorageProvider<*>): Int { + return o2.extractPriority.compareTo(o1.extractPriority) } - - return true } - - return false } /** * Storage component, which basically implement Input and Output */ -interface IStorageComponent : IStorageProvider, IStorageAcceptor +interface IStorageComponent> : IStorageProvider, IStorageAcceptor { + class C>(private val provider: IStorageProvider, private val acceptor: IStorageAcceptor) : IStorageComponent, IStorageProvider by provider, IStorageAcceptor by acceptor { + constructor(acceptor: IStorageAcceptor, provider: IStorageProvider) : this(provider, acceptor) -fun IStorageEventConsumer.changeStack(tuple: IStorageTuple, oldCount: BigInteger) { - changeStack(tuple.stack, tuple.id, oldCount) + init { + require(provider.storageType == acceptor.storageType) { "Provider and Accept have different storage types: ${provider.storageType} != ${acceptor.storageType}" } + } + + override val storageType: StorageStack.Type + get() = provider.storageType + + override fun equals(other: Any?): Boolean { + return other is C<*> && provider == other.provider && acceptor == other.acceptor + } + + override fun hashCode(): Int { + return (provider.hashCode() * 31) xor acceptor.hashCode() + } + + override fun toString(): String { + return "IStorageComponent[$provider; $acceptor]" + } + } } -fun IStorageEventConsumer.addStack(tuple: IStorageTuple, provider: IStorageProvider) { - addStack(tuple.stack, tuple.id, provider) -} - -fun IStorageEventConsumer.removeStack(tuple: IStorageTuple) { - removeStack(tuple.stack, tuple.id) -} - -interface IStorageTuple { +interface IStorageTuple> { val id: UUID val stack: T -} -class StorageTuple(override val id: UUID, override val stack: T) : IStorageTuple + interface IMutable> : IStorageTuple { + override var stack: T + + fun grow(amount: BigInteger) { + stack = stack.grow(amount) + } + + fun shrink(amount: BigInteger) { + stack = stack.shrink(amount) + } + } + + data class I>(override val id: UUID, override val stack: T) : IStorageTuple + data class M>(override val id: UUID, override var stack: T) : IMutable +} /** * Component which (most time) proxy other components (combine their contents into single view) */ -interface IVirtualStorageComponent : IStorageComponent, IStorageEventConsumer { +interface IVirtualStorageComponent> : IStorageComponent, IStorageEventConsumer { fun add(identity: IStorage) fun remove(identity: IStorage) fun contains(identity: IStorage): Boolean diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Helpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Helpers.kt new file mode 100644 index 000000000..a19e4bb61 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Helpers.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.mc.otm.storage + +fun > IStorageProvider.removeListenerAndNotify(listener: IStorageEventConsumer): Boolean { + if (removeListener(listener)) { + for (stack in stacks) { + listener.onStackRemoved(stack.id) + } + + return true + } + + return false +} + +fun > IStorageProvider.addListenerAndNotify(listener: IStorageEventConsumer): Boolean { + if (addListener(listener)) { + for (stack in stacks) { + listener.onStackAdded(stack.stack, stack.id, this) + } + + return true + } + + return false +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt deleted file mode 100644 index 4ba6e3e5d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStackWrapper.kt +++ /dev/null @@ -1,105 +0,0 @@ -package ru.dbotthepony.mc.otm.storage - -import net.minecraft.network.FriendlyByteBuf -import net.minecraft.world.item.ItemStack -import org.jetbrains.annotations.ApiStatus -import ru.dbotthepony.mc.otm.core.* -import ru.dbotthepony.mc.otm.core.registryName -import java.math.BigInteger - -/** - * constructors always copy its input. - */ -class ItemStackWrapper : IStorageStack { - /** - * [ItemStack] representing what item is this. - * - * In most cases you want to use [stack] instead. - */ - @ApiStatus.Internal - val item: ItemStack - val registryName get() = item.item.registryName!! - private val hash: Int - - override var count: BigInteger - - /** - * [copy] as false is used internally for fast index construction, do not specify - * it unless you know what you are doing! - */ - @JvmOverloads - constructor(item: ItemStack, copy: Boolean = true) { - if (copy) { - this.item = item.copy() - } else { - this.item = item - } - - this.count = BigInteger.valueOf(item.count.toLong()) - - if (copy) { - this.item.count = 1 - } - - this.hash = item.tag.hashCode() xor item.item.hashCode() - } - - constructor(item: ItemStackWrapper) { - this.item = item.item - this.count = item.count - this.item.count = 1 - this.hash = item.hash - } - - override fun copy(): ItemStackWrapper { - return ItemStackWrapper(this) - } - - fun sameItem(other: ItemStack) = ItemStack.isSameItemSameTags(item, other) - - override fun equals(other: Any?): Boolean { - if (other === this) - return true - - if (other is ItemStackWrapper) - return other.hash == hash && count == other.count && ItemStack.isSameItemSameTags(item, other.item) - - return false - } - - override fun hashCode(): Int { - return hash * 31 + count.hashCode() - } - - override val maxStackSize: BigInteger get() = BigInteger.valueOf(item.maxStackSize.toLong()) - - override val isEmpty: Boolean get() = item.isEmpty || !count.isPositive - - /** - * [ItemStack] with valid amount and which can be modified after - */ - val stack: ItemStack get() = item.copy().also { it.count = count.toIntSafe() } - - override fun toString(): String { - return "ItemStackWrapper[$count $registryName]" - } - - fun write(buff: FriendlyByteBuf) { - buff.writeItem(item) - buff.writeBigInteger(count) - } - - companion object { - @JvmField - val EMPTY = ItemStackWrapper(ItemStack.EMPTY) - - fun read(buff: FriendlyByteBuf): ItemStackWrapper { - val item = buff.readItem() - val count = buff.readBigInteger() - return ItemStackWrapper(item, copy = false).also { it.count = count } - } - } -} - -fun FriendlyByteBuf.writeBigItem(value: ItemStackWrapper) = value.write(this) -fun FriendlyByteBuf.readBigItem() = ItemStackWrapper.read(this) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt new file mode 100644 index 000000000..260ca07bc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt @@ -0,0 +1,65 @@ +package ru.dbotthepony.mc.otm.storage + +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.lazy2 +import ru.dbotthepony.mc.otm.core.math.toIntSafe +import java.math.BigInteger + +class ItemStorageStack private constructor(private val stack: ItemStack, count: BigInteger, mark: Nothing?) : StorageStack(count) { + constructor(stack: ItemStack) : this(stack.copy(), BigInteger.valueOf(stack.count.toLong()), null) + constructor(stack: ItemStack, count: Int) : this(stack.copy(), BigInteger.valueOf(count.toLong()), null) + constructor(stack: ItemStack, count: Long) : this(stack.copy(), BigInteger.valueOf(count), null) + constructor(stack: ItemStack, count: BigInteger) : this(stack.copy(), count, null) + + override val type: Type + get() = ITEMS + + override fun copy(newCount: BigInteger): ItemStorageStack { + if (isEmpty) + return ITEMS.empty + + if (newCount == count) + return this + + return ItemStorageStack(stack, newCount, null) + } + + override fun equals(other: Any?): Boolean { + return other is ItemStorageStack && other.count == count && ItemStack.isSameItemSameTags(stack, other.stack) + } + + override fun hashCode(): Int { + return Integer.rotateLeft(count.hashCode(), 12) xor (stack.item.hashCode() * 31 + stack.tag.hashCode()) + } + + override fun equalsWithoutCount(other: StorageStack<*>): Boolean { + return other is ItemStorageStack && ItemStack.isSameItemSameTags(stack, other.stack) + } + + override fun hashCodeWithoutCount(): Int { + return stack.item.hashCode() * 31 + stack.tag.hashCode() + } + + override val isEmpty: Boolean = stack.isEmpty || super.isEmpty + override val maxStackSize: BigInteger = BigInteger.valueOf(stack.maxStackSize.toLong()) + + val hoverName: Component by lazy2({ toItemStack().hoverName }, { copy() }) + + fun toItemStack(count: Int = this.count.toIntSafe()): ItemStack { + return stack.copyWithCount(count) + } + + val item: Item get() = stack.item + + companion object { + val EMPTY get() = ITEMS.empty + + fun unsafe(stack: ItemStack) = ItemStorageStack(stack, BigInteger.valueOf(stack.count.toLong()), null) + fun unsafe(stack: ItemStack, count: Int) = ItemStorageStack(stack, BigInteger.valueOf(count.toLong()), null) + fun unsafe(stack: ItemStack, count: Long) = ItemStorageStack(stack, BigInteger.valueOf(count), null) + fun unsafe(stack: ItemStack, count: BigInteger) = ItemStorageStack(stack, count, null) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt deleted file mode 100644 index 56ad6c23f..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/Registry.kt +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dbotthepony.mc.otm.storage - -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.Decimal -import java.util.IdentityHashMap - -open class StorageStackType( - val identity: Class, - open val empty: T, - - /** - * Speculated energy required per operation on stack with size of 1 - */ - open val energyPerOperation: Decimal, -) { - open fun energyPerOperation(stack: T): Decimal { - return energyPerOperation * stack.count - } - - open fun energyPerStorage(stack: T) = energyPerOperation(stack) - open fun energyPerExtraction(stack: T) = energyPerOperation(stack) - open fun energyForUpkeep(stack: T) = Decimal.ZERO -} - -@Suppress("unchecked_cast") -object StorageRegistry { - private val REGISTRY = IdentityHashMap, StorageStackType<*>>() - - @JvmStatic - fun register(type: StorageStackType<*>): StorageStackType<*> { - check(!REGISTRY.containsKey(type.identity)) { "Already have storage stack type for ${type.identity}" } - REGISTRY[type.identity] = type - return type - } - - /** - * Refer to [StorageStackType] for explanation of flags. - */ - @JvmStatic - fun register( - identity: Class, - empty: T, - energyPerOperation: Decimal - ): StorageStackType { - return register(StorageStackType(identity, empty, energyPerOperation)) as StorageStackType - } - - @JvmStatic - fun get(identity: Class): StorageStackType { - val get = REGISTRY[identity] ?: throw NoSuchElementException("Registry does not contain $identity") - return get as StorageStackType - } - - @JvmStatic - fun getOrNull(identity: Class): StorageStackType? { - return REGISTRY[identity] as StorageStackType? - } -} - -inline val ITEM_STORAGE: StorageStackType get() = OverdriveThatMatters.INSTANCE.ITEM_STORAGE() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt new file mode 100644 index 000000000..75081b50e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt @@ -0,0 +1,137 @@ +package ru.dbotthepony.mc.otm.storage + +import it.unimi.dsi.fastutil.Hash +import net.minecraft.network.FriendlyByteBuf +import net.minecraft.world.item.ItemStack +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.registries.DeferredRegister +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.readBigInteger +import ru.dbotthepony.mc.otm.core.writeBigInteger +import ru.dbotthepony.mc.otm.core.writeItemType +import ru.dbotthepony.mc.otm.registry.RegistryDelegate +import java.math.BigInteger + +abstract class StorageStack>(val count: BigInteger) { + init { + require(count >= BigInteger.ZERO) { "Negative amount of things in stack: $count" } + } + + abstract val type: Type + abstract fun copy(newCount: BigInteger): S + + abstract fun equalsWithoutCount(other: StorageStack<*>): Boolean + abstract fun hashCodeWithoutCount(): Int + + open val isEmpty: Boolean get() = count == BigInteger.ZERO + inline val isNotEmpty get() = !isEmpty + + open val maxStackSize: BigInteger? get() = null + + abstract override fun equals(other: Any?): Boolean + abstract override fun hashCode(): Int + + fun grow(amount: BigInteger): S { + if (amount == BigInteger.ZERO) return this as S + val newCount = count + amount + + if (newCount <= BigInteger.ZERO) + return type.empty + + return copy(newCount) + } + + fun shrink(amount: BigInteger): S { + if (amount == BigInteger.ZERO) return this as S + val newCount = count - amount + + if (newCount <= BigInteger.ZERO) + return type.empty + + return copy(newCount) + } + + fun write(buff: FriendlyByteBuf) { + type.write(buff, this as S) + } + + interface Type> { + val empty: T + + fun read(buff: FriendlyByteBuf): T + fun write(buff: FriendlyByteBuf, value: T) + + /** + * If there is not enough energy for operation, it is completely cancelled + */ + fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack) + + /** + * If there is not enough energy for operation, it is completely cancelled + */ + fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack) + + /** + * If there is not enough energy for operation, it is completely cancelled + */ + fun energyPerOperation(stack: T): Decimal + } + + class SimpleType>( + val energyPerOperation: Decimal, + override val empty: T, + private val read: (FriendlyByteBuf) -> T, + private val write: (FriendlyByteBuf, T) -> Unit + ) : Type { + override fun read(buff: FriendlyByteBuf): T { + return read.invoke(buff) + } + + override fun write(buff: FriendlyByteBuf, value: T) { + write.invoke(buff, value) + } + + override fun energyPerOperation(stack: T): Decimal { + return energyPerOperation * stack.count + } + } + + companion object : Hash.Strategy?> { + override fun equals(a: StorageStack<*>?, b: StorageStack<*>?): Boolean { + if (a == null && b == null) return true + if (a != null && b != null) return a.equalsWithoutCount(b) + return false + } + + override fun hashCode(o: StorageStack<*>?): Int { + return o?.hashCodeWithoutCount() ?: 0 + } + + private val delegate = RegistryDelegate>("stack_type") { disableSaving() } + val REGISTRY by delegate + val REGISTRY_KEY by delegate::key + + private val registrar = DeferredRegister.create(REGISTRY_KEY, OverdriveThatMatters.MOD_ID) + + val ITEMS: Type by registrar.register("items") { + SimpleType( + Decimal("4"), + ItemStorageStack(ItemStack.EMPTY), + { + ItemStorageStack.unsafe(it.readItem(), it.readBigInteger()) + }, + { buff, v -> + buff.writeItem(v.toItemStack(1)) + buff.writeBigInteger(v.count) + } + ) + } + + internal fun register(bus: IEventBus) { + bus.addListener(delegate::build) + registrar.register(bus) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt index 201cdb8be..a363a609f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt @@ -1,24 +1,31 @@ package ru.dbotthepony.mc.otm.storage -import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet +import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArraySet -import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.core.isPositive -import ru.dbotthepony.mc.otm.core.isZero +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet +import ru.dbotthepony.mc.otm.core.math.isPositive +import ru.dbotthepony.mc.otm.core.math.isZero import java.math.BigInteger import java.util.* import java.util.stream.Stream -import kotlin.collections.HashMap -import kotlin.reflect.full.isSubclassOf +import kotlin.collections.ArrayList -class RemoteTuple( - override val stack: T, +class RemoteTuple>( + override var stack: T, override val id: UUID, - val provider: IStorageProvider, + val parent: IStorageProvider, val local: LocalTuple -) : IStorageTuple { +) : IStorageTuple, Comparable> { + override fun compareTo(other: RemoteTuple): Int { + return parent.extractPriority.compareTo(other.parent.extractPriority) + } + fun extract(amount: BigInteger, simulate: Boolean): T { - return provider.extractStack(id, amount, simulate) + return parent.extractStack(id, amount, simulate) } override fun equals(other: Any?): Boolean { @@ -30,177 +37,159 @@ class RemoteTuple( } } -class LocalTuple(override val stack: T, override val id: UUID, val tuples: ArrayList>) : IStorageTuple +class LocalTuple>(override var stack: T, override val id: UUID) : IStorageTuple { + val tuples = ArrayList>(1) -open class VirtualComponent(type: StorageStackType) : IVirtualStorageComponent { - constructor(type: Class) : this(StorageRegistry.get(type)) + fun grow(amount: BigInteger) { + stack = stack.grow(amount) + } - override val storageType: StorageStackType = type + fun shrink(amount: BigInteger) { + stack = stack.shrink(amount) + } +} +class VirtualComponent>(override val storageType: StorageStack.Type) : IVirtualStorageComponent { // удаленный UUID -> Кортеж - protected val remoteTuples = HashMap>() + private val remoteTuples = Object2ObjectOpenHashMap>() // локальный UUID -> Локальный кортеж - protected val localTuples = HashMap>() + private val localTuples = Object2ObjectOpenHashMap>() // Стак -> Локальный кортеж стака - protected val hashedTuples = HashMap>() + private val item2tuple = Object2ObjectOpenCustomHashMap>(StorageStack.Companion) - // ArrayList для скорости работы - protected val listeners: MutableSet> = ObjectArraySet() - protected val children: MutableSet> = ObjectArraySet() - protected val consumers: MutableSet> = ObjectArraySet() - - protected open fun onAdd(identity: IStorage) {} - protected open fun onRemove(identity: IStorage) {} + private val listeners = ObjectLinkedOpenHashSet>() + private val children = ObjectLinkedOpenHashSet>() + private val consumers = ObjectLinkedOpenHashSet>() override fun add(identity: IStorage) { - if (!(identity.storageType::class.isSubclassOf(storageType::class))) - throw ClassCastException("Reified Generics: ${identity.storageType::class.qualifiedName} can not be cast into ${storageType::class.qualifiedName}") + require(identity.storageType == storageType) { "Attempt to add component of type ${identity.storageType} to virtual component of type $storageType" } if (children.add(identity)) { if (identity is IStorageProvider) { - identity.addListenerAuto(this) + identity.addListenerAndNotify(this) } else if (identity is IStorageEventProducer) { identity.addListener(this) } if (identity is IStorageEventConsumer) { - addListenerAuto(identity) + addListenerAndNotify(identity) } if (identity is IStorageAcceptor) { consumers.add(identity) } - - onAdd(identity) } } override fun remove(identity: IStorage) { - if (!(identity.storageType::class.isSubclassOf(storageType::class))) - throw ClassCastException("Reified Generics: ${identity.storageType::class.qualifiedName} can not be cast into ${storageType::class.qualifiedName}") + require(identity.storageType == storageType) { "Attempt to remove component of type ${identity.storageType} from virtual component of type $storageType" } if (children.remove(identity)) { if (identity is IStorageProvider) { - identity.removeListenerAuto(this) + identity.removeListenerAndNotify(this) } else if (identity is IStorageEventProducer) { identity.removeListener(this) } if (identity is IStorageEventConsumer) { - removeListenerAuto(identity) + removeListenerAndNotify(identity) } if (identity is IStorageAcceptor) { consumers.remove(identity) } - - onRemove(identity) } } - override fun contains(identity: IStorage): Boolean { - return children.contains(identity) - } - - override fun addListener(listener: IStorageEventConsumer): Boolean { - if (listeners.add(listener)) { - return true - } - - return false - } - - override fun removeListener(listener: IStorageEventConsumer): Boolean { - return listeners.remove(listener) - } + override fun contains(identity: IStorage) = children.contains(identity) + override fun addListener(listener: IStorageEventConsumer) = listeners.add(listener) + override fun removeListener(listener: IStorageEventConsumer) = listeners.remove(listener) override fun get(id: UUID): T { return localTuples[id]?.stack ?: this.storageType.empty } override val stacks: Stream> get() { - return ArrayList>(hashedTuples.size).also { it.addAll(hashedTuples.values) }.stream() + return ArrayList>(item2tuple.size).also { it.addAll(item2tuple.values) }.stream() } override fun get(key: T): UUID? { - return hashedTuples[key.key()]?.id + return item2tuple[key]?.id } - @Suppress("unchecked_cast") - override fun addStack(stack: T, id: UUID, provider: IStorageProvider) { + override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) { check(!remoteTuples.containsKey(id)) { "Already tracking tuple with id $id" } - val key = stack.key() - var local: LocalTuple? = hashedTuples[key] - var oldCount = BigInteger.ZERO + var local = item2tuple[stack] val added = local == null if (local == null) { - local = LocalTuple(stack.copy() as T, UUID.randomUUID(), ArrayList>(1)) + local = LocalTuple(stack, UUID.randomUUID()) localTuples[local.id] = local - hashedTuples[key] = local + item2tuple[stack] = local } else { - oldCount = local.stack.count - local.stack.grow(stack.count) + local.grow(stack.count) } - val remote = RemoteTuple(stack.copy() as T, id, provider, local) + val remote = RemoteTuple(stack, id, provider, local) local.tuples.add(remote) remoteTuples[id] = remote if (added) { for (listener in listeners) { - listener.addStack(local.stack, local.id, this) + listener.onStackAdded(local.stack, local.id, this) } } else { for (listener in listeners) { - listener.changeStack(local.stack, local.id, oldCount) + listener.onStackChanged(local.stack, local.id) } } } - override fun changeStack(stack: T, id: UUID, oldCount: BigInteger) { + override fun onStackChanged(stack: T, id: UUID) { require(stack.count.isPositive) + val tuple = remoteTuples[id] ?: throw IllegalStateException("No such tuple with id $id") - val diff = stack.count - tuple.stack.count - tuple.stack.count = stack.count - - @Suppress("NAME_SHADOWING") val oldCount = tuple.local.stack.count - tuple.local.stack.grow(diff) + tuple.stack = stack + tuple.local.grow(diff) for (listener in listeners) { - listener.changeStack(tuple.local.stack, tuple.local.id, oldCount) + listener.onStackChanged(tuple.local.stack, tuple.local.id) } } - override fun removeStack(stack: T, id: UUID) { - val tuple = remoteTuples[id] ?: throw IllegalStateException("No such tuple with id $id") + override fun onStackRemoved(id: UUID) { + val remote = remoteTuples.remove(id) ?: throw IllegalStateException("No such tuple with id $id") + val local = remote.local - tuple.local.stack.shrink(tuple.stack.count) - tuple.local.tuples.remove(tuple) + local.shrink(remote.stack.count) + check(local.tuples.remove(remote)) - remoteTuples.remove(id) - - val a = tuple.local.stack.count <= BigInteger.ZERO - val b = tuple.local.tuples.size == 0 + val a = local.stack.isEmpty + val b = local.tuples.isEmpty() if (a || b) { - check(a && b) { "View object is empty, but tuple list is not!" } - localTuples.remove(tuple.local.id) - val key = stack.key() - checkNotNull(hashedTuples.remove(key)) { "No such stack $key" } + check(a && b) { "View stack is empty, but remote tuple list is not!" } + localTuples.remove(local.id) + checkNotNull(item2tuple.remove(remote.stack)) { "No such stack ${remote.stack}" } for (listener in listeners) { - listener.removeStack(tuple.local.stack, tuple.local.id) + listener.onStackRemoved(local.id) + } + } else { + for (listener in listeners) { + listener.onStackChanged(local.stack, local.id) } } } override fun insertStack(stack: T, simulate: Boolean): T { var leftover = stack + val consumers = ObjectArrayList(consumers) + consumers.sortWith(IStorageAcceptor.Companion) for (consumer in consumers) { leftover = consumer.insertStack(leftover, simulate) @@ -213,14 +202,13 @@ open class VirtualComponent(type: StorageStackType) : IVir return leftover } - @Suppress("unchecked_cast") override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { if (!amount.isPositive) return this.storageType.empty @Suppress("name_shadowing") var amount = amount - val tuple: LocalTuple? = localTuples[id] + val tuple = localTuples[id] if (tuple == null || amount.isZero) return this.storageType.empty @@ -230,180 +218,25 @@ open class VirtualComponent(type: StorageStackType) : IVir val toExtract = tuple.stack.count.coerceAtMost(amount) var extracted = BigInteger.ZERO - val copy = tuple.stack.copy() as T + val copy = tuple.stack + val tuples = ArrayList(tuple.tuples) + tuples.sort() - for (remote_tuple in tuple.tuples.let { Array(it.size) { i -> it[i] } }) { - val extractedStack = remote_tuple.extract(toExtract - extracted, simulate) - extracted += extractedStack.count + for (remote in tuples) { + extracted += remote.extract(toExtract - extracted, simulate).count if (extracted >= toExtract) break } if (extracted.isPositive) { - copy.count = extracted - return copy + return copy.copy(extracted) } return this.storageType.empty } -} - -/** - * Adds energy demand to operations over [parent] - */ -open class PoweredComponent(open val parent: IStorageComponent, val energyProvider: () -> IMatteryEnergyStorage) : IStorageComponent { - override val storageType: StorageStackType - get() = parent.storageType - - private val subscribedThrough = ObjectArraySet>() - - final override fun addListener(listener: IStorageEventConsumer): Boolean { - if (parent.addListener(listener)) { - subscribedThrough.add(listener) - return true - } - - return false - } - - final override fun removeListener(listener: IStorageEventConsumer): Boolean { - subscribedThrough.remove(listener) - return parent.removeListener(listener) - } - - fun removeListeners() { - for (child in subscribedThrough) { - parent.removeListener(child) - } - - subscribedThrough.clear() - } - - override fun get(key: T) = parent[key] - - @Suppress("unchecked_cast") - override fun insertStack(stack: T, simulate: Boolean): T { - val required = storageType.energyPerOperation * stack.count - val energy = energyProvider.invoke() - val extracted = energy.extractEnergyInner(required, true) - - if (extracted.isZero) { - return stack.copy() as T - } - - if (extracted == required) { - val leftover = parent.insertStack(stack, simulate) - - if (leftover.isEmpty) { - if (!simulate) { - energy.extractEnergyInner(required, false) - } - - return leftover - } - - if (!simulate) { - val requiredNew = storageType.energyPerOperation * (stack.count - leftover.count) - energy.extractEnergyInner(requiredNew, false) - } - - return leftover - } - - @Suppress("name_shadowing") - val stack = stack.copy() as T - val oldCount = stack.count - stack.count = (extracted / storageType.energyPerOperation).whole - val diff = oldCount - stack.count - val newRequired = storageType.energyPerOperation * stack.count - val newExtracted = energy.extractEnergyInner(newRequired, true) - - if (newExtracted == newRequired) { - val leftover = parent.insertStack(stack, simulate) - - if (leftover.isEmpty) { - if (!simulate) { - energy.extractEnergyInner(newRequired, false) - } - - leftover.count = diff - return leftover - } - - if (!simulate) { - val requiredNew = storageType.energyPerOperation * (stack.count - leftover.count) - energy.extractEnergyInner(requiredNew, false) - } - - leftover.count += diff - return leftover - } - - return stack - } - - override fun get(id: UUID) = parent[id] - - override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { - val required = storageType.energyPerOperation * amount - val energy = energyProvider.invoke() - val extracted = energy.extractEnergyInner(required, true) - - if (extracted.isZero) { - return storageType.empty - } - - if (extracted == required) { - val extractedStack = parent.extractStack(id, amount, simulate) - - if (extractedStack.isEmpty) { - return extractedStack - } - - if (!simulate) { - if (extractedStack.count == amount) { - energy.extractEnergyInner(required, false) - } else { - energy.extractEnergyInner(storageType.energyPerOperation * extractedStack.count, false) - } - } - - return extractedStack - } - - @Suppress("name_shadowing") - val amount = (required / storageType.energyPerOperation).whole - val extractedStack = parent.extractStack(id, amount, simulate) - - if (extractedStack.isEmpty) { - return extractedStack - } - - if (!simulate) { - energy.extractEnergyInner(storageType.energyPerOperation * extractedStack.count, false) - } - - return extractedStack - } - - override val stacks get() = parent.stacks -} - -/** - * Adds energy demand to virtual component [parent] - */ -open class PoweredVirtualComponent(override val parent: IVirtualStorageComponent, energyProvider: () -> IMatteryEnergyStorage) - : PoweredComponent(parent, energyProvider), IVirtualStorageComponent { - constructor(parent: IVirtualStorageComponent, energy: IMatteryEnergyStorage) : this(parent, { energy }) - constructor(parent: Class, energy: IMatteryEnergyStorage) : this(VirtualComponent(parent), { energy }) - constructor(parent: StorageStackType, energy: IMatteryEnergyStorage) : this(VirtualComponent(parent), { energy }) - - override fun addStack(stack: T, id: UUID, provider: IStorageProvider) = parent.addStack(stack, id, provider) - override fun changeStack(stack: T, id: UUID, oldCount: BigInteger) = parent.changeStack(stack, id, oldCount) - override fun removeStack(stack: T, id: UUID) = parent.removeStack(stack, id) - - override fun add(identity: IStorage) = parent.add(identity) - override fun remove(identity: IStorage) = parent.remove(identity) - override fun contains(identity: IStorage) = parent.contains(identity) + + override fun toString(): String { + return "VirtualComponent[$storageType; listeners: ${listeners.size}; size: ${localTuples.size}]" + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ExtractPriority.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ExtractPriority.kt new file mode 100644 index 000000000..f92b8030b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ExtractPriority.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.function.IntSupplier + +class ExtractPriority>(private val parent: IStorageProvider, private val priority: IntSupplier) : IStorageProvider by parent { + override val extractPriority: Int + get() = priority.asInt + + override fun addListener(listener: IStorageEventConsumer): Boolean { + return parent.addListener(ListenerProxy(listener, this)) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return parent.removeListener(ListenerProxy(listener, this)) + } + + override fun equals(other: Any?): Boolean { + return other is ExtractPriority<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "ExtractPriority[$parent with $priority]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/FlowControlComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/FlowControlComponent.kt new file mode 100644 index 000000000..e5105985b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/FlowControlComponent.kt @@ -0,0 +1,47 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.storage.IStorageComponent +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.math.BigInteger +import java.util.* +import java.util.function.Supplier + +class FlowControlComponent>(private val parent: IStorageComponent, private val flow: Supplier) : IStorageComponent by parent { + override fun addListener(listener: IStorageEventConsumer): Boolean { + return parent.addListener(ListenerProxy(listener, this)) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return parent.removeListener(ListenerProxy(listener, this)) + } + + override fun insertStack(stack: S, simulate: Boolean): S { + if (!flow.get().input) { + return storageType.empty + } + + return parent.insertStack(stack, simulate) + } + + override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): S { + if (!flow.get().output) { + return storageType.empty + } + + return parent.extractStack(id, amount, simulate) + } + + override fun equals(other: Any?): Boolean { + return other is FlowControlComponent<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "FlowControlComponent[flow=${flow.get()}, $parent]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/InsertPriority.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/InsertPriority.kt new file mode 100644 index 000000000..e98b35f2f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/InsertPriority.kt @@ -0,0 +1,22 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import ru.dbotthepony.mc.otm.storage.IStorageAcceptor +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.function.IntSupplier + +class InsertPriority>(private val parent: IStorageAcceptor, private val priority: IntSupplier) : IStorageAcceptor by parent { + override val insertPriority: Int + get() = priority.asInt + + override fun equals(other: Any?): Boolean { + return other is InsertPriority<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "InsertPriority[$parent with $priority]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ListenerProxy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ListenerProxy.kt new file mode 100644 index 000000000..ffca4ea33 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ListenerProxy.kt @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import it.unimi.dsi.fastutil.HashCommon +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.* + +class ListenerProxy>(private val consumer: IStorageEventConsumer, private val provider: IStorageProvider) : IStorageEventConsumer by consumer { + init { + require(consumer.storageType == provider.storageType) { "Consumer storage type does not match provider's: ${consumer.storageType} != ${provider.storageType}" } + } + + override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) { + consumer.onStackAdded(stack, id, this.provider) + } + + override fun toString(): String { + return "ListenerProxy[$consumer from $provider]" + } + + override fun equals(other: Any?): Boolean { + return other is ListenerProxy<*> && consumer == other.consumer && provider == other.provider + } + + override fun hashCode(): Int { + return HashCommon.mix(consumer.hashCode() * 31 + provider.hashCode()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ModifyPriority.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ModifyPriority.kt new file mode 100644 index 000000000..97a1f6a5a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/ModifyPriority.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import ru.dbotthepony.mc.otm.storage.IStorageAcceptor +import ru.dbotthepony.mc.otm.storage.IStorageComponent +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.function.IntSupplier + +class ModifyPriority>( + private val parent: IStorageComponent, + insertPriority: IntSupplier, + extractPriority: IntSupplier +) : IStorageComponent, IStorageAcceptor by InsertPriority(parent, insertPriority), IStorageProvider by ExtractPriority(parent, extractPriority) { + override fun equals(other: Any?): Boolean { + return other is ModifyPriority<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "ModifyPriority[$parent with $insertPriority and $extractPriority]" + } + + override val storageType: StorageStack.Type + get() = parent.storageType +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/Optics.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/Optics.kt new file mode 100644 index 000000000..ad5811e35 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/optics/Optics.kt @@ -0,0 +1,87 @@ +package ru.dbotthepony.mc.otm.storage.optics + +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorageAcceptor +import ru.dbotthepony.mc.otm.storage.IStorageComponent +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.IVirtualStorageComponent +import ru.dbotthepony.mc.otm.storage.StorageStack +import ru.dbotthepony.mc.otm.storage.powered.PoweredComponent +import ru.dbotthepony.mc.otm.storage.powered.PoweredStorageAcceptor +import ru.dbotthepony.mc.otm.storage.powered.PoweredStorageProvider +import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent +import java.util.function.IntSupplier +import java.util.function.Supplier + +fun > IStorageAcceptor.insertPriority(priority: IntSupplier): IStorageAcceptor { + return InsertPriority(this, priority) +} + +fun > IStorageAcceptor.insertPriority(priority: Int): IStorageAcceptor { + return InsertPriority(this) { priority } +} + +fun > IStorageProvider.extractPriority(priority: IntSupplier): IStorageProvider { + return ExtractPriority(this, priority) +} + +fun > IStorageProvider.extractPriority(priority: Int): IStorageProvider { + return ExtractPriority(this) { priority } +} + +fun > IStorageComponent.priority(priority: IntSupplier): IStorageComponent { + return ModifyPriority(this, priority, priority) +} + +fun > IStorageComponent.priority(priority: Int): IStorageComponent { + return ModifyPriority(this, { priority }, { priority }) +} + +fun > IStorageComponent.priority(insertPriority: IntSupplier, extractPriority: IntSupplier): IStorageComponent { + return ModifyPriority(this, insertPriority, extractPriority) +} + +fun > IStorageComponent.priority(insertPriority: Int, extractPriority: Int): IStorageComponent { + return ModifyPriority(this, { insertPriority }, { extractPriority }) +} + +fun > IStorageAcceptor.powered(energy: Supplier): IStorageAcceptor { + return PoweredStorageAcceptor(this, energy) +} + +fun > IStorageAcceptor.powered(energy: IMatteryEnergyStorage): IStorageAcceptor { + return PoweredStorageAcceptor(this) { energy } +} + +fun > IStorageProvider.powered(energy: Supplier): IStorageProvider { + return PoweredStorageProvider(this, energy) +} + +fun > IStorageProvider.powered(energy: IMatteryEnergyStorage): IStorageProvider { + return PoweredStorageProvider(this) { energy } +} + +fun > IStorageComponent.powered(energy: Supplier): IStorageComponent { + return PoweredComponent(this, energy) +} + +fun > IStorageComponent.powered(energy: IMatteryEnergyStorage): IStorageComponent { + return PoweredComponent(this) { energy } +} + +fun > IVirtualStorageComponent.powered(energy: Supplier): IVirtualStorageComponent { + return PoweredVirtualComponent(this, energy) +} + +fun > IVirtualStorageComponent.powered(energy: IMatteryEnergyStorage): IVirtualStorageComponent { + return PoweredVirtualComponent(this) { energy } +} + +fun > IStorageComponent.flow(flow: Supplier): IStorageComponent { + return FlowControlComponent(this, flow) +} + +fun > IStorageComponent.flow(flow: FlowDirection): IStorageComponent { + return FlowControlComponent(this) { flow } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt new file mode 100644 index 000000000..a692bea46 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.storage.powered + +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorageAcceptor +import ru.dbotthepony.mc.otm.storage.IStorageComponent +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.function.Supplier + +class PoweredComponent>( + val parent: IStorageComponent, + energy: Supplier +) : IStorageComponent, IStorageProvider by PoweredStorageProvider(parent, energy), IStorageAcceptor by PoweredStorageAcceptor(parent, energy) { + constructor(provider: IStorageProvider, acceptor: IStorageAcceptor, energy: Supplier) : this(IStorageComponent.C(provider, acceptor), energy) + constructor(acceptor: IStorageAcceptor, provider: IStorageProvider, energy: Supplier) : this(IStorageComponent.C(provider, acceptor), energy) + + override val storageType: StorageStack.Type + get() = parent.storageType + + override fun equals(other: Any?): Boolean { + return other is PoweredComponent<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredComponent[$parent]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt new file mode 100644 index 000000000..bc856176a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.mc.otm.storage.powered + +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorageAcceptor +import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.function.Supplier + +class PoweredStorageAcceptor>(val parent: IStorageAcceptor, val energy: Supplier) : IStorageAcceptor by parent { + override fun equals(other: Any?): Boolean { + return other is PoweredStorageAcceptor<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredStorageAcceptor[$parent]" + } + + override fun insertStack(stack: T, simulate: Boolean): T { + val leftover = parent.insertStack(stack, true) + if (leftover == stack) return stack + + val energy = energy.get() + val required = storageType.energyPerInsert(stack.shrink(leftover.count)) + + if (energy.extractEnergy(required, true) == required) { + if (!simulate) { + val leftover2 = parent.insertStack(stack, false) + + if (leftover2.count != leftover.count) { + energy.extractEnergy(storageType.energyPerInsert(stack.shrink(leftover2.count)), false) + } else { + energy.extractEnergy(required, false) + } + + return leftover2 + } + + return leftover + } + + return stack + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt new file mode 100644 index 000000000..19b3c8850 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.storage.powered + +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.StorageStack +import ru.dbotthepony.mc.otm.storage.optics.ListenerProxy +import java.math.BigInteger +import java.util.* +import java.util.function.Supplier + +class PoweredStorageProvider>(val parent: IStorageProvider, val energy: Supplier) : IStorageProvider by parent { + override fun equals(other: Any?): Boolean { + return other is PoweredStorageProvider<*> && other.parent == parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredStorageProvider[$parent]" + } + + override fun addListener(listener: IStorageEventConsumer): Boolean { + return parent.addListener(ListenerProxy(listener, this)) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return parent.removeListener(ListenerProxy(listener, this)) + } + + override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { + val extracted = parent.extractStack(id, amount, true) + if (extracted.isEmpty) return extracted + + val energy = energy.get() + val required = storageType.energyPerExtract(extracted) + + if (energy.extractEnergy(required, true) == required) { + if (!simulate) { + val extracted2 = parent.extractStack(id, amount, false) + + if (extracted.count != extracted2.count) { + energy.extractEnergy(storageType.energyPerExtract(extracted2), false) + } else { + energy.extractEnergy(required, false) + } + + return extracted2 + } + + return extracted + } + + return storageType.empty + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt new file mode 100644 index 000000000..9975847cf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt @@ -0,0 +1,37 @@ +package ru.dbotthepony.mc.otm.storage.powered + +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorage +import ru.dbotthepony.mc.otm.storage.IStorageComponent +import ru.dbotthepony.mc.otm.storage.IStorageProvider +import ru.dbotthepony.mc.otm.storage.IVirtualStorageComponent +import ru.dbotthepony.mc.otm.storage.StorageStack +import ru.dbotthepony.mc.otm.storage.VirtualComponent +import java.util.* +import java.util.function.Supplier + +class PoweredVirtualComponent>( + val parent: IVirtualStorageComponent, + energy: Supplier +) : IVirtualStorageComponent, IStorageComponent by PoweredComponent(parent, energy) { + constructor(type: StorageStack.Type, energy: Supplier) : this(VirtualComponent(type), energy) + + override fun equals(other: Any?): Boolean { + return other is PoweredVirtualComponent<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredVirtualComponent[$parent]" + } + + override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) = parent.onStackAdded(stack, id, provider) + override fun onStackChanged(stack: T, id: UUID) = parent.onStackChanged(stack, id) + override fun onStackRemoved(id: UUID) = parent.onStackRemoved(id) + override fun add(identity: IStorage) = parent.add(identity) + override fun remove(identity: IStorage) = parent.remove(identity) + override fun contains(identity: IStorage) = parent.contains(identity) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidBatteryTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidBatteryTrigger.kt deleted file mode 100644 index 513d10a9f..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidBatteryTrigger.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.ItemPredicate -import net.minecraft.advancements.critereon.SerializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.item.ItemStack -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.set - -object AndroidBatteryTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_battery") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance( - ItemPredicate.fromJson(p_66248_["predicate"]) - ) - } - - fun trigger(player: ServerPlayer, newItem: ItemStack) { - trigger(player) { it.predicate.matches(newItem) } - } - - class Instance(val predicate: ItemPredicate) : AbstractCriterionTriggerInstance(ID, Composite.ANY) { - override fun serializeToJson(p_16979_: SerializationContext): JsonObject { - return super.serializeToJson(p_16979_).also { - it["predicate"] = predicate.serializeToJson() - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt index 2eb8c4634..45f48abee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidResearchTrigger.kt @@ -1,54 +1,32 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate -import net.minecraft.advancements.critereon.SerializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.android.AndroidResearchType -import ru.dbotthepony.mc.otm.core.set +import java.util.* import java.util.function.Predicate -object AndroidResearchTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance( - p_66248_["research"]?.asString?.let(::ResourceLocation) - ) - } - +object AndroidResearchTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_research")) { fun trigger(player: ServerPlayer, research: AndroidResearchType) { - trigger(player) { - it.test(research) - } + trigger(player) { it.test(research) } } - class Instance(val research: ResourceLocation?) : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY), Predicate { - constructor(research: AndroidResearchType) : this(research.id) + override val codec: Codec = RecordCodecBuilder.create { + it.group( + ResourceLocation.CODEC.optionalFieldOf("research").forGetter(Instance::research), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + class Instance(val research: Optional, player: Optional = Optional.empty()) : AbstractInstance(player), Predicate { + constructor(research: ResourceLocation) : this(Optional.of(research)) + constructor(research: AndroidResearchType) : this(Optional.of(research.id)) override fun test(t: AndroidResearchType): Boolean { - return research == null || t.id == research - } - - override fun serializeToJson(p_16979_: SerializationContext): JsonObject { - return super.serializeToJson(p_16979_).also { - if (research != null) - it["research"] = JsonPrimitive(research.toString()) - } + return research.map { it == t.id }.orElse(true) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidStatusTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidStatusTriggers.kt deleted file mode 100644 index 28f186619..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidStatusTriggers.kt +++ /dev/null @@ -1,98 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object BecomeAndroidTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) -} - -object BecomeAndroidSleepTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android_sleep") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) -} - -object BecomeAndroidDeathTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android_death") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) -} - -object BecomeHumaneTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "become_humane") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt new file mode 100644 index 000000000..07fc6cdc8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/AndroidTravelUnderwater.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.data.minRange +import java.util.* + +object AndroidTravelUnderwater : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_walk_underwater")) { + fun trigger(player: ServerPlayer, travelled: Double) { + trigger(player) { it.distanceToTravel <= travelled } + } + + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.minRange(0.0).fieldOf("distanceToTravel").forGetter(Instance::distanceToTravel), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + class Instance(val distanceToTravel: Double, playerPredicate: Optional = Optional.empty()) : AbstractInstance(playerPredicate) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/BlackHoleTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/BlackHoleTrigger.kt deleted file mode 100644 index fd693b86a..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/BlackHoleTrigger.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object BlackHoleTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "black_hole_pull") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/EnderTeleporterFallDeathTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/EnderTeleporterFallDeathTrigger.kt deleted file mode 100644 index 8d85a8313..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/EnderTeleporterFallDeathTrigger.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object EnderTeleporterFallDeathTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "ender_teleporter_fall_death") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt new file mode 100644 index 000000000..31b52ddd7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ExopackTriggers.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import java.util.* + +val ExopackObtainedTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_obtained")) +val ExopackGainedCraftingTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_gained_crafting")) +val ExopackGainedSmeltingTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_gained_smelting")) +val ExopackGainedEnderAccessTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_gained_ender_access")) + +val ExopackBatterySlotTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_battery_slot")) + +object ExopackSlotsExpandedTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "exopack_expanded")) { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minGained", 0).forGetter(Instance::minGained), + Codec.intRange(0, Int.MAX_VALUE).optionalFieldOf("minTotal", 0).forGetter(Instance::minTotal), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + fun trigger(player: ServerPlayer, gained: Int, total: Int) { + trigger(player) { it.minGained <= gained && it.minTotal <= total } + } + + class Instance(val minGained: Int = 0, val minTotal: Int = 0, player: Optional = Optional.empty()) : AbstractInstance(player) { + init { + require(minGained >= 0) { "Invalid minGained $minGained" } + require(minTotal >= 0) { "Invalid minTotal $minTotal" } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/FallDampenersSaveTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/FallDampenersSaveTrigger.kt deleted file mode 100644 index 633f6426b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/FallDampenersSaveTrigger.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object FallDampenersSaveTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "fall_dampeners_save") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt new file mode 100644 index 000000000..899cce78a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/HurtTrigger.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.critereon.DamagePredicate +import net.minecraft.advancements.critereon.DamageSourcePredicate +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.MinMaxBounds +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.damagesource.DamageSource +import net.minecraft.world.entity.LivingEntity +import ru.dbotthepony.mc.otm.data.DamagePredicateCodec +import java.util.* + +class HurtTrigger(id: ResourceLocation) : MCriterionTrigger(id) { + fun trigger(player: ServerPlayer, entity: LivingEntity, damage: Float, damageSource: DamageSource) { + val context = EntityPredicate.createContext(player, entity) + + trigger(player) { + it.predicate.map { it.matches(context) }.orElse(true) && it.damagePredicate.map { it.matches(player, damageSource, damage, damage, false) }.orElse(true) + } + } + + override val codec: Codec = RecordCodecBuilder.create { + it.group( + predicateCodec.optionalFieldOf("predicate").forGetter(Instance::predicate), + DamagePredicateCodec.optionalFieldOf("damagePredicate").forGetter(Instance::damagePredicate), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + inner class Instance( + val predicate: Optional = Optional.empty(), + val damagePredicate: Optional = Optional.of(DamagePredicate( + MinMaxBounds.Doubles.atLeast(1.0), + MinMaxBounds.Doubles.atLeast(1.0), + EntityPredicate.ANY, + null, + DamageSourcePredicate.ANY + )), + player: Optional = Optional.empty() + ) : AbstractInstance(player) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt new file mode 100644 index 000000000..d14c49011 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ItemTrigger.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.critereon.ItemPredicate +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.data.ItemPredicateCodec +import java.util.* + +class ItemTrigger(id: ResourceLocation) : MCriterionTrigger(id) { + fun trigger(player: ServerPlayer, item: ItemStack) { + trigger(player) { if (it.invert) !it.predicate.matches(item) else it.predicate.matches(item) } + } + + override val codec: Codec = RecordCodecBuilder.create { + it.group( + ItemPredicateCodec.fieldOf("predicate").forGetter(Instance::predicate), + Codec.BOOL.optionalFieldOf("invert", false).forGetter(Instance::invert), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + inner class Instance(val predicate: ItemPredicate, val invert: Boolean = false, player: Optional = Optional.empty()) : AbstractInstance(player) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt new file mode 100644 index 000000000..432303360 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/KillAsAndroidTrigger.kt @@ -0,0 +1,142 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.google.common.collect.ImmutableList +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.critereon.* +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.util.StringRepresentable +import net.minecraft.world.entity.monster.ElderGuardian +import net.minecraftforge.event.entity.living.LivingDeathEvent +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.data.SingletonCodec +import ru.dbotthepony.mc.otm.registry.MRegistry +import java.util.Optional +import java.util.function.Predicate + +object KillAsAndroidTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "kill_as_android")) { + val FEATURE_CODEC: Codec = StringRepresentable.fromEnum(PredicateType::values).dispatch({ it.type }, { it.codec }) + + override val codec: Codec = RecordCodecBuilder.create { + it.group( + predicateCodec.optionalFieldOf("entityPredicate").forGetter(Instance::predicate), + FEATURE_CODEC.fieldOf("featurePredicate").forGetter(Instance::featurePredicate), + playerPredicateCodec.forGetter(Instance::playerPredicate) + ).apply(it, ::Instance) + } + + enum class PredicateType(codec: Lazy>) : StringRepresentable { + ALWAYS(lazy { + SingletonCodec(Always) + }), + HAS(lazy { + RecordCodecBuilder.create { + it.group(ResourceLocation.CODEC.fieldOf("name").forGetter(Has::name)).apply(it, ::Has) + } + }), + NOT(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.fieldOf("parent").forGetter(Not::parent)).apply(it, ::Not) + } + }), + AND(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(And::children)).apply(it, ::And) + } + }), + OR(lazy { + RecordCodecBuilder.create { + it.group(FEATURE_CODEC.listOf().fieldOf("children").forGetter(OrPredicate::children)).apply(it, ::OrPredicate) + } + }); + + val codec by codec + + override fun getSerializedName(): String { + return name.lowercase() + } + } + + abstract class FeaturePredicate : Predicate { + abstract val type: PredicateType + } + + object Always : FeaturePredicate() { + override val type: PredicateType + get() = PredicateType.ALWAYS + + override fun test(t: MatteryPlayerCapability): Boolean { + return true + } + } + + class Has(val name: ResourceLocation) : FeaturePredicate() { + private val resolved by lazy { MRegistry.ANDROID_FEATURES.getValue(name) } + + override val type: PredicateType + get() = PredicateType.HAS + + override fun test(t: MatteryPlayerCapability): Boolean { + return t.hasFeature(resolved ?: return false) + } + } + + class Not(val parent: FeaturePredicate) : FeaturePredicate() { + override val type: PredicateType + get() = PredicateType.NOT + + override fun test(t: MatteryPlayerCapability): Boolean { + return !parent.test(t) + } + } + + class And(children: Iterable) : FeaturePredicate() { + val children: ImmutableList = ImmutableList.copyOf(children) + + override val type: PredicateType + get() = PredicateType.AND + + override fun test(t: MatteryPlayerCapability): Boolean { + return children.all { it.test(t) } + } + } + + class OrPredicate(children: Iterable) : FeaturePredicate() { + val children: ImmutableList = ImmutableList.copyOf(children) + + override val type: PredicateType + get() = PredicateType.OR + + override fun test(t: MatteryPlayerCapability): Boolean { + return children.any { it.test(t) } + } + } + + class Instance( + val predicate: Optional = Optional.empty(), + val featurePredicate: FeaturePredicate = Always, + playerPredicate: Optional = Optional.empty(), + ) : AbstractInstance(playerPredicate) + + fun onKill(event: LivingDeathEvent) { + if (event.entity is ElderGuardian) { + val killer = event.source.entity + + if (killer is ServerPlayer) { + val data = killer.matteryPlayer ?: return + + if (data.isAndroid) { + val context = EntityPredicate.createContext(killer, event.entity) + + trigger(killer) { + it.predicate.map { it.matches(context) }.orElse(true) && + it.featurePredicate.test(data) + } + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt new file mode 100644 index 000000000..2d5bdee45 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MCriterionTrigger.kt @@ -0,0 +1,126 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.google.gson.JsonObject +import com.google.gson.JsonParseException +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import com.mojang.serialization.JsonOps +import com.mojang.serialization.MapCodec +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.advancements.Criterion +import net.minecraft.advancements.CriterionTrigger +import net.minecraft.advancements.CriterionTriggerInstance +import net.minecraft.advancements.critereon.DeserializationContext +import net.minecraft.advancements.critereon.EntityPredicate +import net.minecraft.advancements.critereon.SerializationContext +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.PlayerAdvancements +import net.minecraft.server.level.ServerPlayer +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.toImmutableList +import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.core.toJsonStrict +import java.util.Optional +import java.util.function.Predicate + +typealias ContextAwarePredicate = EntityPredicate.Composite + +// allows to support both 1.20.1 and 1.20.2 with ease +// and has slightly less memory footprint than vanilla SimpleCriterionTrigger +abstract class MCriterionTrigger.AbstractInstance>(private val id: ResourceLocation) : CriterionTrigger { + override fun getId(): ResourceLocation { + return id + } + + private val listeners = Reference2ObjectOpenHashMap>>() + + override fun addPlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener) { + listeners.computeIfAbsent(advancements, Reference2ObjectFunction { ObjectOpenHashSet() }).add(listener) + } + + override fun removePlayerListener(advancements: PlayerAdvancements, listener: CriterionTrigger.Listener) { + listeners[advancements]?.remove(listener) + } + + override fun removePlayerListeners(advancements: PlayerAdvancements) { + listeners.remove(advancements) + } + + protected abstract val codec: Codec + + override fun createInstance(data: JsonObject, context: DeserializationContext): T { + return try { + deserializationContext.get().addLast(context) + codec.decode(JsonOps.INSTANCE, data).getOrThrow(false) { throw JsonParseException("Failed to decode data: $it") }.first + } finally { + deserializationContext.get().removeLast() + } + } + + fun trigger(player: ServerPlayer, predicate: Predicate = Predicate { true }) { + val advancements = player.advancements + val listeners = listeners[advancements] ?: return + + val context = EntityPredicate.createContext(player, player) + + listeners.iterator() + .filter { predicate.test(it.triggerInstance) && it.triggerInstance.playerPredicate.map { it.matches(context) }.orElse(true) } + .toImmutableList() + .forEach { it.run(advancements) } + } + + abstract inner class AbstractInstance(val playerPredicate: Optional) : CriterionTriggerInstance { + fun criterion() = Criterion(this as T) + + override fun getCriterion(): ResourceLocation { + return id + } + + final override fun serializeToJson(context: SerializationContext): JsonObject { + return try { + serializationContext.get().addLast(context) + codec.toJsonStrict(this as T) as JsonObject + } finally { + serializationContext.get().removeLast() + } + } + } + + companion object { + protected val deserializationContext = object : ThreadLocal>() { + override fun initialValue(): ArrayDeque { + return ArrayDeque() + } + } + + protected val serializationContext = object : ThreadLocal>() { + override fun initialValue(): ArrayDeque { + return ArrayDeque() + } + } + + @JvmStatic + protected val predicateCodec: Codec = object : Codec { + override fun encode(input: ContextAwarePredicate, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(JsonOps.INSTANCE.convertTo(ops, input.toJson(serializationContext.get().lastOrNull() ?: return DataResult.error("Not serializing trigger instance")))) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val context = deserializationContext.get().lastOrNull() ?: return DataResult.error("Not current deserializing trigger instance") + + return try { + DataResult.success(Pair.of(EntityPredicate.Composite.fromJson(JsonObject().also { it["a"] = ops.convertTo(JsonOps.INSTANCE, input) }, "a", context), ops.empty())) + } catch (err: Exception) { + DataResult.error("Failed to deserialize ContextAwarePredicate: " + err.message) + } + } + } + + @JvmStatic + protected val playerPredicateCodec: MapCodec> = predicateCodec.optionalFieldOf("player") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt new file mode 100644 index 000000000..4b7f4564e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/MatteryInventoryChangeTrigger.kt @@ -0,0 +1,267 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.google.gson.JsonObject +import it.unimi.dsi.fastutil.Hash.Strategy +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap +import net.minecraft.advancements.CriteriaTriggers +import net.minecraft.advancements.CriterionTrigger +import net.minecraft.advancements.critereon.DeserializationContext +import net.minecraft.advancements.critereon.InventoryChangeTrigger +import net.minecraft.advancements.critereon.MinMaxBounds +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.PlayerAdvancements +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.util.iterator +import ru.dbotthepony.mc.otm.core.collect.flatMap +import ru.dbotthepony.mc.otm.core.collect.toList +import ru.dbotthepony.mc.otm.core.isNotEmpty +import java.util.stream.Collectors + +/** + * This object detours all necessary InventoryChangeTrigger methods + * + * Reason behind this is to support arbitrary containers (not just [Inventory], done for Exopack) and to improve performance by using search tree + */ +object MatteryInventoryChangeTrigger : CriterionTrigger { + private object BoundsStrategy : Strategy { + override fun equals(a: MinMaxBounds.Ints?, b: MinMaxBounds.Ints?): Boolean { + return a?.min == b?.min && a?.max == b?.max + } + + override fun hashCode(o: MinMaxBounds.Ints?): Int { + return o?.let { Integer.rotateLeft(it.min.hashCode(), 16) xor it.max.hashCode() } ?: 0 + } + } + + private object DefaultStrategy : Strategy { + override fun equals(a: Any?, b: Any?): Boolean { + return a == b + } + + override fun hashCode(o: Any?): Int { + return o.hashCode() + } + } + + private fun interface Tester { + fun test(value: T, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int): Boolean + } + + private fun interface Hint { + fun getHints(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int): Collection? + } + + private class Node( + val strategy: Strategy, + private val getter: InventoryChangeTrigger.TriggerInstance.() -> Collection, + val test: Tester, // if hint is present, tester is always going to be called with `null` value once + val hint: Hint? = null + ) { + fun getValues(instance: InventoryChangeTrigger.TriggerInstance): Set { + val result = ObjectArraySet() + result.addAll(getter.invoke(instance)) + return result + } + } + + private val nodes = ArrayList>() + + init { + nodes.add(Node(BoundsStrategy, { listOf(slotsOccupied) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsOccupied) })) + nodes.add(Node(BoundsStrategy, { listOf(slotsFull) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsFull) })) + nodes.add(Node(BoundsStrategy, { listOf(slotsEmpty) }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(slotsEmpty) })) + + nodes.add(Node( + DefaultStrategy, + { predicates.iterator().flatMap { (it.items ?: listOf(null)).iterator() }.toList() }, + { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.item == v }, + { inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> mutableListOf(item.item) })) + + nodes.add(Node( + DefaultStrategy, + { predicates.map { it.tag } }, + { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v == null || item.`is`(v) }, + { inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> item.tags.collect(Collectors.toCollection(::ArrayList)) })) + + nodes.add(Node(BoundsStrategy, { predicates.map { it.count } }, { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.matches(item.count) })) + + nodes.add(Node( + BoundsStrategy, + { predicates.map { it.durability } }, + { v, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int -> v!!.isAny || item.isDamageableItem && v.matches(item.maxDamage - item.damageValue) })) + } + + private class ListenerTree(private val advancements: PlayerAdvancements) { + private val set = ObjectOpenHashSet>() + private val tree = Object2ObjectOpenCustomHashMap(nodes.first().strategy as Strategy) + + private fun search(instance: InventoryChangeTrigger.TriggerInstance, tree: MutableMap, nodeId: Int): Collection>> { + val node = nodes[nodeId] + + if (nodeId + 1 != nodes.size) { + val result = ArrayList>>() + + for (v in node.getValues(instance)) { + result.addAll( + search( + instance, + tree.computeIfAbsent(v, Object2ObjectFunction { Object2ObjectOpenCustomHashMap(4, nodes[nodeId + 1].strategy as Strategy) }) as MutableMap, + nodeId + 1)) + } + + return result + } else { + val result = ArrayList>>() + + for (v in node.getValues(instance)) { + result.add(tree.computeIfAbsent(v, Object2ObjectFunction { ObjectOpenHashSet>(4) }) as MutableSet>) + } + + return result + } + } + + fun add(listener: CriterionTrigger.Listener) { + if (set.add(listener)) { + search(listener.triggerInstance, tree, 0).forEach { it.add(listener) } + } + } + + fun remove(listener: CriterionTrigger.Listener) { + if (set.remove(listener)) { + search(listener.triggerInstance, tree, 0).forEach { it.remove(listener) } + } + } + + private fun addNull(input: Collection): Collection { + return if (null in input) + input + else if (input is ArrayList) { + input.add(null) + input + } else { + ArrayList(input).also { it.add(null) } + } + } + + private fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int, tree: MutableMap, nodeId: Int, set: MutableSet>) { + val node = nodes[nodeId] as Node + val keys = node.hint?.getHints(inventory, item, slotsFull, slotsEmpty, slotsOccupied)?.let(::addNull) ?: tree.keys + + for (k in keys) { + val v = tree[k] ?: continue + + if (node.test.test(k, inventory, item, slotsFull, slotsEmpty, slotsOccupied)) { + if (nodeId + 1 == nodes.size) { + for (l in v as Set>) { + // переделываем matches у InventoryTriggerInstance + with (l.triggerInstance) { + if ( + this.slotsFull.matches(slotsFull) && + this.slotsEmpty.matches(slotsEmpty) && + this.slotsOccupied.matches(slotsOccupied) + ) { + if (this.predicates.isEmpty() || this.predicates.size == 1 && item.isNotEmpty && this.predicates[0].matches(item)) { + set.add(l) + } else if (this.predicates.size > 1) { + val unsatisfied = ObjectArrayList(this.predicates) + unsatisfied.removeIf { it.matches(ItemStack.EMPTY) } + + for (inventoryItem in inventory) { + unsatisfied.removeIf { it.matches(inventoryItem) } + if (unsatisfied.isEmpty) break + } + + if (unsatisfied.isEmpty) { + set.add(l) + } + } + } + } + } + } else { + trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, v as MutableMap, nodeId + 1, set) + } + } + } + } + + fun trigger(inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int) { + val matches = ObjectOpenHashSet>() + + trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied, tree, 0, matches) + + for (l in matches) { + l.run(advancements) + } + } + } + + private val listeners = Reference2ObjectOpenHashMap() + + override fun addPlayerListener(advancements: PlayerAdvancements, instance: CriterionTrigger.Listener) { + listeners.computeIfAbsent(advancements, Reference2ObjectFunction { ListenerTree(it as PlayerAdvancements) }).add(instance) + } + + override fun removePlayerListener(advancements: PlayerAdvancements, instance: CriterionTrigger.Listener) { + listeners[advancements]?.remove(instance) + } + + override fun getId(): ResourceLocation { + return CriteriaTriggers.INVENTORY_CHANGED.id + } + + override fun removePlayerListeners(advancements: PlayerAdvancements) { + listeners.remove(advancements) + } + + override fun createInstance(data: JsonObject, context: DeserializationContext): InventoryChangeTrigger.TriggerInstance { + return CriteriaTriggers.INVENTORY_CHANGED.createInstance(data, context) + } + + // реплицирует ванильный метод + fun trigger(player: ServerPlayer, inventory: Container, item: ItemStack) { + if (inventory === player.inventory) { + val mattery = player.matteryPlayer + + if (mattery != null) { + return trigger(player, mattery.combinedInventory, item) + } + } + + var slotsFull = 0 + var slotsEmpty = 0 + var slotsOccupied = 0 + + for (slot in 0 until inventory.containerSize) { + val slotItem = inventory[slot] + + if (slotItem.isEmpty) + slotsEmpty++ + else { + slotsOccupied++ + + if (slotItem.count >= inventory.maxStackSize) + slotsFull++ + } + } + + trigger(player, inventory, item, slotsFull, slotsEmpty, slotsOccupied) + } + + // реплицирует ванильный метод + fun trigger(player: ServerPlayer, inventory: Container, item: ItemStack, slotsFull: Int, slotsEmpty: Int, slotsOccupied: Int) { + listeners[player.advancements]?.trigger(inventory, item, slotsFull, slotsEmpty, slotsOccupied) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt index fa026e177..e6266921f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/NanobotsArmorTrigger.kt @@ -1,43 +1,25 @@ package ru.dbotthepony.mc.otm.triggers -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.advancements.critereon.MinMaxBounds.Doubles -import net.minecraft.advancements.critereon.SerializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.set +import ru.dbotthepony.mc.otm.data.DoublesCodec +import java.util.* -object NanobotsArmorTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance( - Doubles.fromJson(p_66248_["predicate"]) - ) +object NanobotsArmorTrigger : MCriterionTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor")) { + override val codec: Codec = RecordCodecBuilder.create { + it.group( + DoublesCodec.fieldOf("predicate").forGetter(Instance::predicate), + playerPredicateCodec.forGetter(Instance::playerPredicate), + ).apply(it, ::Instance) } fun trigger(player: ServerPlayer, damageAbsorbed: Double) { trigger(player) { it.predicate.matches(damageAbsorbed) } } - class Instance(val predicate: Doubles) : AbstractCriterionTriggerInstance(ID, Composite.ANY) { - override fun serializeToJson(p_16979_: SerializationContext): JsonObject { - return super.serializeToJson(p_16979_).also { - it["predicate"] = predicate.serializeToJson() - } - } - } + class Instance(val predicate: Doubles, player: Optional = Optional.empty()) : AbstractInstance(player) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/PhantomSpawnDeniedTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/PhantomSpawnDeniedTrigger.kt deleted file mode 100644 index 1be863f36..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/PhantomSpawnDeniedTrigger.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object PhantomSpawnDeniedTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "phantom_spawn_denied") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt deleted file mode 100644 index a8fe3d1f1..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveDamageMobTrigger.kt +++ /dev/null @@ -1,63 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DamagePredicate -import net.minecraft.advancements.critereon.DamageSourcePredicate -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate -import net.minecraft.advancements.critereon.MinMaxBounds -import net.minecraft.advancements.critereon.SerializationContext -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.damagesource.DamageSource -import net.minecraft.world.entity.Entity -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.core.set - -object ShockwaveDamageMobTrigger: SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: EntityPredicate.Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance( - EntityPredicate.Composite.fromJson(p_66248_, "entity_predicate", p_66250_), - (p_66248_["damage"] as? JsonObject)?.let(DamagePredicate::fromJson) ?: DamagePredicate.ANY - ) - } - - fun trigger(player: ServerPlayer, entity: Entity, damageSource: DamageSource, damage: Float) { - val context = EntityPredicate.createContext(player, entity) - - trigger(player) { - it.predicate.matches(context) && it.damagePredicate.matches(player, damageSource, damage, damage, false) - } - } - - class Instance( - val predicate: EntityPredicate.Composite = EntityPredicate.Composite.ANY, - val damagePredicate: DamagePredicate = DamagePredicate( - MinMaxBounds.Doubles.atLeast(1.0), - MinMaxBounds.Doubles.atLeast(1.0), - EntityPredicate.ANY, - null, - DamageSourcePredicate.ANY - ) - ) : AbstractCriterionTriggerInstance(ID, EntityPredicate.Composite.ANY) { - override fun serializeToJson(p_16979_: SerializationContext): JsonObject { - return super.serializeToJson(p_16979_).also { - it["entity_predicate"] = predicate.toJson(p_16979_) - it["damage"] = damagePredicate.serializeToJson() - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveTrigger.kt deleted file mode 100644 index 207f27d5b..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/ShockwaveTrigger.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.dbotthepony.mc.otm.triggers - -import com.google.gson.JsonObject -import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance -import net.minecraft.advancements.critereon.DeserializationContext -import net.minecraft.advancements.critereon.EntityPredicate.Composite -import net.minecraft.advancements.critereon.SimpleCriterionTrigger -import net.minecraft.resources.ResourceLocation -import net.minecraft.server.level.ServerPlayer -import ru.dbotthepony.mc.otm.OverdriveThatMatters - -object ShockwaveTrigger : SimpleCriterionTrigger() { - val ID = ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave") - - override fun getId(): ResourceLocation { - return ID - } - - override fun createInstance( - p_66248_: JsonObject, - p_66249_: Composite, - p_66250_: DeserializationContext - ): Instance { - return Instance - } - - fun trigger(player: ServerPlayer) { - trigger(player) { true } - } - - object Instance : AbstractCriterionTriggerInstance(ID, Composite.ANY) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt new file mode 100644 index 000000000..18adae924 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SimpleTriggers.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.mc.otm.triggers + +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters + +val BlackHoleTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "black_hole_pull")) +val FallDampenersSaveTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "fall_dampeners_save")) +val ShockwaveTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave")) +val EnderTeleporterFallDeathTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "ender_teleporter_fall_death")) + +val BecomeAndroidTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android")) +val BecomeAndroidSleepTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android_sleep")) +val BecomeAndroidDeathTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "become_android_death")) +val BecomeHumaneTrigger = SingletonTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "become_humane")) + +val AndroidBatteryTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "android_battery")) +val TakeItemOutOfReplicatorTrigger = ItemTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "take_item_out_of_replicator")) + +val NailedEntityTrigger = HurtTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "hammer_nail_damage")) +val ShockwaveDamageMobTrigger = HurtTrigger(ResourceLocation(OverdriveThatMatters.MOD_ID, "shockwave_damage_mob")) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt new file mode 100644 index 000000000..c21893896 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/triggers/SingletonTrigger.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.mc.otm.triggers + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.advancements.Criterion +import net.minecraft.resources.ResourceLocation +import java.util.* + +class SingletonTrigger(id: ResourceLocation) : MCriterionTrigger(id) { + override val codec: Codec = RecordCodecBuilder.create { + it.group(playerPredicateCodec.forGetter(Instance::playerPredicate)).apply(it, ::Instance) + } + + val empty = Instance() + val criterion = Criterion(empty) + + inner class Instance(player: Optional = Optional.empty()) : AbstractInstance(player) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/OreGen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/OreGen.kt deleted file mode 100644 index 37429e909..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/OreGen.kt +++ /dev/null @@ -1,51 +0,0 @@ -package ru.dbotthepony.mc.otm.worldgen - -import net.minecraft.core.Holder -import net.minecraft.data.BuiltinRegistries.CONFIGURED_FEATURE -import net.minecraft.data.BuiltinRegistries.PLACED_FEATURE -import net.minecraft.data.worldgen.features.OreFeatures -import net.minecraft.world.level.levelgen.VerticalAnchor -import net.minecraft.world.level.levelgen.feature.ConfiguredFeature -import net.minecraft.world.level.levelgen.feature.Feature -import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration -import net.minecraft.world.level.levelgen.placement.CountPlacement -import net.minecraft.world.level.levelgen.placement.HeightRangePlacement -import net.minecraft.world.level.levelgen.placement.InSquarePlacement -import net.minecraft.world.level.levelgen.placement.PlacedFeature -import ru.dbotthepony.mc.otm.registry.MBlocks -import ru.dbotthepony.mc.otm.core.WriteOnce -import ru.dbotthepony.mc.otm.registry.register - -@Suppress("UNNECESSARY_NOT_NULL_ASSERTION") -object OreGen { - val ORE_TRITANIUM_TARGET_LIST = listOf( - OreConfiguration.target(OreFeatures.STONE_ORE_REPLACEABLES, MBlocks.TRITANIUM_ORE.defaultBlockState())!!, - OreConfiguration.target(OreFeatures.DEEPSLATE_ORE_REPLACEABLES, MBlocks.DEEPSLATE_TRITANIUM_ORE.defaultBlockState())!!, - ) - - var ORE_FEATURE_TRITANIUM_NORMAL by WriteOnce>>() - var ORE_TRITANIUM_NORMAL by WriteOnce>() - var ORE_TRITANIUM_DEEP by WriteOnce>() - - fun register() { - ORE_FEATURE_TRITANIUM_NORMAL = CONFIGURED_FEATURE.register("ore_tritanium", ConfiguredFeature(Feature.ORE, OreConfiguration(ORE_TRITANIUM_TARGET_LIST, 9))) - - ORE_TRITANIUM_NORMAL = PLACED_FEATURE.register("ore_tritanium_normal", PlacedFeature( - ORE_FEATURE_TRITANIUM_NORMAL, - listOf( - CountPlacement.of(8), - InSquarePlacement.spread(), - HeightRangePlacement.triangle(VerticalAnchor.absolute(0), VerticalAnchor.absolute(50)) - ) - )) - - ORE_TRITANIUM_DEEP = PLACED_FEATURE.register("ore_tritanium_deep", PlacedFeature( - ORE_FEATURE_TRITANIUM_NORMAL, - listOf( - CountPlacement.of(10), - InSquarePlacement.spread(), - HeightRangePlacement.uniform(VerticalAnchor.aboveBottom(8), VerticalAnchor.absolute(0)) - ) - )) - } -} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 87431a1a8..cdac62ced 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -2,6 +2,8 @@ public-f net.minecraft.client.gui.screens.Screen f_96539_ # title public net.minecraft.server.MinecraftServer f_129744_ # storageSource +public net.minecraft.world.entity.player.Inventory f_150070_ # SELECTION_SIZE + # for accessing and setting from MatteryScreen class public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169600_ public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169605_ @@ -10,29 +12,34 @@ public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_1696 public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97732_ # menu public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_169604_ # playerInventoryTitle -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97706_ # clickedSlot -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97707_ # snapbackEnd -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97708_ # quickdropSlot -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97709_ # lastClickSlot -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97710_ # isSplittingStack -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97711_ # draggingItem -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97712_ # snapbackStartX -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97713_ # snapbackStartY -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97714_ # snapbackTime -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97715_ # snapbackItem -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97716_ # quickdropTime -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97717_ # quickCraftingType -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97718_ # quickCraftingButton -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97719_ # skipNextRelease -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97720_ # quickCraftingRemainder -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97721_ # lastClickTime -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97722_ # lastClickButton -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97723_ # doubleclick -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97724_ # lastQuickMoved +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97706_ # clickedSlot +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97707_ # snapbackEnd +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97708_ # quickdropSlot +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97709_ # lastClickSlot +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97710_ # isSplittingStack +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97711_ # draggingItem +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97712_ # snapbackStartX +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97713_ # snapbackStartY +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97714_ # snapbackTime +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97715_ # snapbackItem +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97716_ # quickdropTime +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97717_ # quickCraftingType +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97718_ # quickCraftingButton +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97719_ # skipNextRelease +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97720_ # quickCraftingRemainder +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97721_ # lastClickTime +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97722_ # lastClickButton +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97723_ # doubleclick +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen f_97724_ # lastQuickMoved +public net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97782_(Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V # renderFloatingItem + +public net.minecraft.client.gui.GuiComponent m_168740_(Lcom/mojang/blaze3d/vertex/PoseStack;IIIIIII)V # fillGradient +public net.minecraft.client.gui.GuiComponent m_93123_(Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V # fillGradient +public net.minecraft.client.gui.GuiComponent m_93179_(Lcom/mojang/blaze3d/vertex/PoseStack;IIIIII)V # fillGradient public net.minecraft.world.item.BlockItem f_150696_ # BLOCK_ENTITY_TAG -public net.minecraft.client.gui.screens.InBedChatScreen f_242488_ # leaveBedButton +public net.minecraft.client.gui.screens.InBedChatScreen f_263129_ # leaveBedButton public net.minecraft.world.inventory.AbstractContainerMenu f_182405_ # stateId @@ -44,39 +51,12 @@ public net.minecraft.server.level.ServerPlayer f_143380_ # containerListener protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97762_(I)V # checkHotbarMouseClicked protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97818_()V # recalculateQuickCraftRemaining protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97744_(DD)Lnet/minecraft/world/inventory/Slot; # findSlot -protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_97782_(Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V # renderFloatingItem +protected net.minecraft.client.gui.screens.inventory.AbstractContainerScreen m_280211_(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/world/item/ItemStack;IILjava/lang/String;)V # renderFloatingItem protected net.minecraft.client.resources.TextureAtlasHolder f_118884_ # textureAtlas public net.minecraft.client.renderer.RenderStateShard f_110133_ # name -public com.mojang.math.Matrix4f f_27603_ # m00 -public com.mojang.math.Matrix4f f_27604_ # m01 -public com.mojang.math.Matrix4f f_27605_ # m02 -public com.mojang.math.Matrix4f f_27606_ # m03 -public com.mojang.math.Matrix4f f_27607_ # m10 -public com.mojang.math.Matrix4f f_27608_ # m11 -public com.mojang.math.Matrix4f f_27609_ # m12 -public com.mojang.math.Matrix4f f_27610_ # m13 -public com.mojang.math.Matrix4f f_27611_ # m20 -public com.mojang.math.Matrix4f f_27612_ # m21 -public com.mojang.math.Matrix4f f_27613_ # m22 -public com.mojang.math.Matrix4f f_27614_ # m23 -public com.mojang.math.Matrix4f f_27615_ # m30 -public com.mojang.math.Matrix4f f_27616_ # m31 -public com.mojang.math.Matrix4f f_27617_ # m32 -public com.mojang.math.Matrix4f f_27618_ # m33 - -public com.mojang.math.Matrix3f f_8134_ # m00 -public com.mojang.math.Matrix3f f_8135_ # m01 -public com.mojang.math.Matrix3f f_8136_ # m02 -public com.mojang.math.Matrix3f f_8137_ # m10 -public com.mojang.math.Matrix3f f_8138_ # m11 -public com.mojang.math.Matrix3f f_8139_ # m12 -public com.mojang.math.Matrix3f f_8140_ # m20 -public com.mojang.math.Matrix3f f_8141_ # m21 -public com.mojang.math.Matrix3f f_8142_ # m22 - public-f net.minecraft.world.inventory.Slot f_40220_ # x public-f net.minecraft.world.inventory.Slot f_40221_ # y @@ -181,6 +161,39 @@ public net.minecraft.client.renderer.FogRenderer f_109010_ # fogRed public net.minecraft.world.item.crafting.RecipeManager m_44054_(Lnet/minecraft/world/item/crafting/RecipeType;)Ljava/util/Map; # byType +public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265888_ # base +public net.minecraft.world.item.crafting.SmithingTransformRecipe f_265907_ # addition + public net.minecraft.world.entity.boss.wither.WitherBoss f_31432_ # TARGETING_CONDITIONS public-f net.minecraft.world.entity.boss.wither.WitherBoss f_31431_ # LIVING_ENTITY_SELECTOR public net.minecraft.world.entity.ai.targeting.TargetingConditions f_26879_ # selector + +public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43179_ +public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43178_ +public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43177_ +public net.minecraft.advancements.critereon.InventoryChangeTrigger$TriggerInstance f_43176_ +#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_6467_(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V # addPlayerListener +#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_6468_(Lnet/minecraft/server/PlayerAdvancements;Lnet/minecraft/advancements/CriterionTrigger$Listener;)V # removePlayerListener +#public-f net.minecraft.advancements.critereon.SimpleCriterionTrigger m_5656_(Lnet/minecraft/server/PlayerAdvancements;)V # removePlayerListeners + +public net.minecraft.advancements.critereon.ItemPredicate f_45031_ +public net.minecraft.advancements.critereon.ItemPredicate f_45032_ +public net.minecraft.advancements.critereon.ItemPredicate f_45033_ +public net.minecraft.advancements.critereon.ItemPredicate f_151427_ +public net.minecraft.advancements.critereon.ItemPredicate f_45036_ +public net.minecraft.advancements.critereon.ItemPredicate f_45035_ +public net.minecraft.advancements.critereon.ItemPredicate f_45034_ +public net.minecraft.advancements.critereon.ItemPredicate f_45029_ + +public net.minecraft.advancements.critereon.DamagePredicate f_24906_ # blocked +public net.minecraft.advancements.critereon.DamagePredicate f_24903_ # dealtDamage +public net.minecraft.advancements.critereon.DamagePredicate f_24905_ # sourceEntity +public net.minecraft.advancements.critereon.DamagePredicate f_24904_ # takenDamage +public net.minecraft.advancements.critereon.DamagePredicate f_24907_ # type + +public net.minecraft.world.item.crafting.UpgradeRecipe f_44519_ # addition +public net.minecraft.world.item.crafting.UpgradeRecipe f_44518_ # base +public net.minecraft.world.item.crafting.UpgradeRecipe f_44521_ # id +public net.minecraft.world.item.crafting.UpgradeRecipe f_44520_ # result + +public net.minecraft.client.gui.components.AbstractWidget m_93696_()Z # isFocused diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json index 0f2a0791b..677dde2cf 100644 --- a/src/main/resources/META-INF/coremods.json +++ b/src/main/resources/META-INF/coremods.json @@ -1,3 +1,5 @@ { - "code_injector": "coremods/code_injector.js" + "code_injector": "coremods/code_injector.js", + "chest_menus": "coremods/chest_menus.js", + "limb_brush_overclock": "coremods/limb_brush_overclock.js" } \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index bcedd782e..eb74bb55e 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -6,7 +6,7 @@ # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +loaderVersion="[44,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="2 Clause BSD" @@ -15,11 +15,9 @@ license="2 Clause BSD" # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod -modId="overdrive_that_matters" #mandatory +modId="${mod_id}" #mandatory # The version number of the mod - there's a few well known ${} variables useable here or just hardcode it -# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata -# see the associated build.gradle script for how to populate this completely automatically during a build -version="${file.jarVersion}" #mandatory +version="${mod_version}" #mandatory # A display name for the mod displayName="Overdrive That Matters" #mandatory # A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ @@ -37,22 +35,22 @@ description=''' Matter. Energy. Combined. ''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.overdrive_that_matters]] #optional +[[dependencies.${mod_id}]] #optional # the modid of the dependency modId="forge" #mandatory # Does this dependency have to exist - if not, ordering below must be specified mandatory=true #mandatory # The version range of the dependency - versionRange="[43.1.32,)" #mandatory + versionRange="[44.0.0,)" #mandatory # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER side="BOTH" # Here's another dependency -[[dependencies.overdrive_that_matters]] +[[dependencies.${mod_id}]] modId="minecraft" mandatory=true # This version range declares a minimum of the current minecraft version up to but not including the next major version - versionRange="[1.19.2,1.20)" + versionRange="[1.19.3,1.19.4)" ordering="NONE" side="BOTH" diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/advanced_energy_cable.json b/src/main/resources/assets/overdrive_that_matters/blockstates/advanced_energy_cable.json new file mode 100644 index 000000000..ae8a815be --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/advanced_energy_cable.json @@ -0,0 +1,74 @@ +{ + "multipart": [ + { + "apply": { + "model": "overdrive_that_matters:block/storage_cable_core" + } + }, + + { + "when": { + "connect_south": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection" + } + }, + + { + "when": { + "connect_west": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 90 + } + }, + + { + "when": { + "connect_north": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 180 + } + }, + + { + "when": { + "connect_east": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 270 + } + }, + + { + "when": { + "connect_up": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 90 + } + }, + + { + "when": { + "connect_down": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 270 + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/crude_energy_cable.json b/src/main/resources/assets/overdrive_that_matters/blockstates/crude_energy_cable.json new file mode 100644 index 000000000..ae8a815be --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/crude_energy_cable.json @@ -0,0 +1,74 @@ +{ + "multipart": [ + { + "apply": { + "model": "overdrive_that_matters:block/storage_cable_core" + } + }, + + { + "when": { + "connect_south": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection" + } + }, + + { + "when": { + "connect_west": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 90 + } + }, + + { + "when": { + "connect_north": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 180 + } + }, + + { + "when": { + "connect_east": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 270 + } + }, + + { + "when": { + "connect_up": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 90 + } + }, + + { + "when": { + "connect_down": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 270 + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/metal_junk.json b/src/main/resources/assets/overdrive_that_matters/blockstates/metal_junk.json new file mode 100644 index 000000000..0452e9c4c --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/metal_junk.json @@ -0,0 +1,30 @@ +{ + "variants": { + "":[ + { + "model": "overdrive_that_matters:block/metal_junk", "weight": 2 + }, + { + "model": "overdrive_that_matters:block/metal_junk", "y": 180, "weight": 2 + }, + { + "model": "overdrive_that_matters:block/metal_junk_b", "weight": 4 + }, + { + "model": "overdrive_that_matters:block/metal_junk_b", "y": 180, "weight": 4 + }, + { + "model": "overdrive_that_matters:block/metal_junk_c", "weight": 2 + }, + { + "model": "overdrive_that_matters:block/metal_junk_c", "y": 180, "weight": 2 + }, + { + "model": "overdrive_that_matters:block/metal_junk_d", "weight": 2 + }, + { + "model": "overdrive_that_matters:block/metal_junk_d", "y": 180, "weight": 2 + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/regular_energy_cable.json b/src/main/resources/assets/overdrive_that_matters/blockstates/regular_energy_cable.json new file mode 100644 index 000000000..ae8a815be --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/regular_energy_cable.json @@ -0,0 +1,74 @@ +{ + "multipart": [ + { + "apply": { + "model": "overdrive_that_matters:block/storage_cable_core" + } + }, + + { + "when": { + "connect_south": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection" + } + }, + + { + "when": { + "connect_west": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 90 + } + }, + + { + "when": { + "connect_north": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 180 + } + }, + + { + "when": { + "connect_east": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 270 + } + }, + + { + "when": { + "connect_up": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 90 + } + }, + + { + "when": { + "connect_down": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 270 + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/superconductor_energy_cable.json b/src/main/resources/assets/overdrive_that_matters/blockstates/superconductor_energy_cable.json new file mode 100644 index 000000000..ae8a815be --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/superconductor_energy_cable.json @@ -0,0 +1,74 @@ +{ + "multipart": [ + { + "apply": { + "model": "overdrive_that_matters:block/storage_cable_core" + } + }, + + { + "when": { + "connect_south": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection" + } + }, + + { + "when": { + "connect_west": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 90 + } + }, + + { + "when": { + "connect_north": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 180 + } + }, + + { + "when": { + "connect_east": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "y": 270 + } + }, + + { + "when": { + "connect_up": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 90 + } + }, + + { + "when": { + "connect_down": true + }, + + "apply": { + "model": "overdrive_that_matters:block/storage_cable_connection", + "x": 270 + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_base.json b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_base.json new file mode 100644 index 000000000..e38c6c3ac --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_base.json @@ -0,0 +1,154 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/android_charger", + "particle": "overdrive_that_matters:block/android_charger" + }, + "elements": [ + { + "name": "body", + "from": [0, 0, 0], + "to": [16, 8, 16], + "faces": { + "north": {"uv": [0, 5, 8, 7], "texture": "#0"}, + "east": {"uv": [0, 8, 8, 10], "texture": "#0"}, + "south": {"uv": [0, 10, 8, 12], "texture": "#0"}, + "west": {"uv": [8, 8, 0, 10], "texture": "#0"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#0"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 8, 1], + "to": [15, 12, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "east": {"uv": [9, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [5, 8, 1], + "to": [1, 12, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "east": {"uv": [9, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "texture": "#0"} + } + }, + { + "name": "body", + "from": [2, 8, 2], + "to": [14, 11, 14], + "faces": { + "north": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "east": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "south": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 13, 14, 16], "texture": "#0"}, + "down": {"uv": [0, 0, 12, 12], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [1, 8, 1], + "to": [5, 12, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [15, 8, 1], + "to": [11, 12, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 11, 3], + "to": [5, 16, 5], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"} + } + }, + { + "name": "core", + "from": [4, 11, 4], + "to": [12, 16, 12], + "faces": { + "north": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "east": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "south": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "west": {"uv": [8, 9, 12, 10.25], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [11, 11, 3], + "to": [13, 16, 5], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 11, 11], + "to": [13, 16, 13], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 11, 11], + "to": [5, 16, 13], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"} + } + }, + { + "name": "spine", + "from": [5, 8, 12], + "to": [11, 16, 16], + "faces": { + "east": {"uv": [16, 5, 14, 7], "texture": "#0"}, + "south": {"uv": [12, 9, 15, 11], "texture": "#0"}, + "west": {"uv": [14, 5, 16, 7], "texture": "#0"} + } + } + ], + "display": {}, + "groups": [ + { + "name": "base", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_middle.json b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_middle.json new file mode 100644 index 000000000..4faec05de --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_middle.json @@ -0,0 +1,132 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/android_charger", + "particle": "overdrive_that_matters:block/android_charger" + }, + "elements": [ + { + "name": "core", + "from": [4, 0, 4], + "to": [12, 16, 12], + "faces": { + "north": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "east": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "south": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "west": {"uv": [8, 9, 12, 13], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [3, 0, 11], + "to": [5, 16, 13], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 3], + "to": [13, 16, 5], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 0, 3], + "to": [5, 16, 5], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 11], + "to": [13, 16, 13], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "spine", + "from": [5, 0, 12], + "to": [11, 16, 16], + "faces": { + "east": {"uv": [16, 5, 14, 9], "texture": "#0"}, + "south": {"uv": [12, 9, 15, 13], "texture": "#0"}, + "west": {"uv": [14, 5, 16, 9], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, -1, 2], + "to": [14, 5, 14], + "faces": { + "north": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "east": {"uv": [0, 1.5, 6, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "west": {"uv": [0, 1.5, 6, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, -1, 14], + "to": [14, 5, 2], + "faces": { + "north": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "east": {"uv": [6, 1.5, 0, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "west": {"uv": [6, 1.5, 0, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, 11, 14], + "to": [14, 17, 2], + "faces": { + "north": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "east": {"uv": [6, 1.5, 0, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "west": {"uv": [6, 1.5, 0, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, 11, 2], + "to": [14, 17, 14], + "faces": { + "north": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "east": {"uv": [0, 1.5, 6, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "west": {"uv": [0, 1.5, 6, 3], "texture": "#0"} + } + } + ], + "display": {}, + "groups": [ + { + "name": "middle", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_top.json b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_top.json new file mode 100644 index 000000000..c93c5609f --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/android_charger_top.json @@ -0,0 +1,164 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/android_charger", + "particle": "overdrive_that_matters:block/android_charger" + }, + "elements": [ + { + "name": "screen", + "from": [3, 11, 0], + "to": [13, 14, 0], + "faces": { + "north": {"uv": [11, 2.25, 12.5, 4.75], "rotation": 90, "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [3, 0, 11], + "to": [5, 5, 13], + "faces": { + "north": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 3], + "to": [13, 5, 5], + "faces": { + "north": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 0, 3], + "to": [5, 5, 5], + "faces": { + "north": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 11], + "to": [13, 5, 13], + "faces": { + "north": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "core", + "from": [4, 0, 4], + "to": [12, 5, 12], + "faces": { + "north": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "east": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "south": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "west": {"uv": [8, 11.75, 12, 13], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "spine", + "from": [5, 0, 12], + "to": [11, 8, 16], + "faces": { + "east": {"uv": [16, 7, 14, 9], "texture": "#0"}, + "south": {"uv": [12, 11, 15, 13], "texture": "#0"}, + "west": {"uv": [14, 7, 16, 9], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 4, 1], + "to": [5, 8, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [15, 4, 1], + "to": [11, 8, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 4, 1], + "to": [15, 8, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "east": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [5, 4, 1], + "to": [1, 8, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "east": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [2, 5, 2], + "to": [14, 8, 14], + "faces": { + "north": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "east": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "west": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "down": {"uv": [8, 13, 14, 16], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 8, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [10, 5, 14, 9], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 8, 0, 10], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 10, 8, 12], "rotation": 180, "texture": "#0"}, + "west": {"uv": [0, 8, 8, 10], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#0"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#0"} + } + } + ], + "display": {}, + "groups": [ + { + "name": "top", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/android_station_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/android_station_idle.json index ff09df1fa..233ecf9eb 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/android_station_idle.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/android_station_idle.json @@ -1,8 +1,11 @@ -{ +{ + "credit": "Made with Blockbench", "parent": "block/cube_all", - "render_type": "translucent", + "render_type": "translucent", + "texture_size": [32, 32], "textures": { "0": "overdrive_that_matters:block/android_station_offline", + "1": "overdrive_that_matters:block/android_station_base", "particle": "overdrive_that_matters:block/android_station_offline" }, "elements": [ @@ -11,12 +14,12 @@ "from": [0, 7, 0], "to": [16, 9, 16], "faces": { - "north": {"uv": [8, 4, 16, 4.5], "texture": "#0"}, - "east": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, - "south": {"uv": [8, 4, 16, 4.5], "texture": "#0"}, - "west": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, - "up": {"uv": [8, 0, 16, 4], "texture": "#0"}, - "down": {"uv": [8, 4.5, 16, 8.5], "texture": "#0"} + "north": {"uv": [8, 0, 16, 1], "texture": "#1"}, + "east": {"uv": [0, 0, 8, 1], "texture": "#1"}, + "south": {"uv": [8, 0, 16, 1], "texture": "#1"}, + "west": {"uv": [0, 0, 8, 1], "texture": "#1"}, + "up": {"uv": [8, 0, 16, 16], "texture": "#0"}, + "down": {"uv": [8, 8, 16, 16], "texture": "#1"} } }, { @@ -24,12 +27,12 @@ "from": [0, 0, 0], "to": [16, 4, 16], "faces": { - "north": {"uv": [0, 8.5, 8, 9.5], "texture": "#0"}, - "east": {"uv": [0, 8.5, 8, 9.5], "texture": "#0"}, - "south": {"uv": [8, 8.5, 16, 9.5], "texture": "#0"}, - "west": {"uv": [8, 8.5, 16, 9.5], "texture": "#0"}, - "up": {"uv": [8, 4.5, 16, 8.5], "texture": "#0"}, - "down": {"uv": [8, 9.5, 16, 13.5], "texture": "#0"} + "north": {"uv": [8, 1, 16, 3], "texture": "#1"}, + "east": {"uv": [0, 1, 8, 3], "texture": "#1"}, + "south": {"uv": [8, 1, 16, 3], "texture": "#1"}, + "west": {"uv": [0, 1, 8, 3], "texture": "#1"}, + "up": {"uv": [8, 8, 16, 16], "texture": "#1"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#1"} } }, { @@ -37,12 +40,10 @@ "from": [2, 4, 2], "to": [14, 7, 14], "faces": { - "north": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "east": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "south": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "west": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "up": {"uv": [9, 5, 15, 8], "texture": "#0"}, - "down": {"uv": [9, 5, 15, 8], "texture": "#0"} + "north": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "east": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "south": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "west": {"uv": [1, 0, 7, 3], "texture": "#0"} } }, { @@ -50,25 +51,10 @@ "from": [1, 4, 13], "to": [3, 7, 15], "faces": { - "north": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "south": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} - } - }, - { - "name": "frame", - "from": [13, 4, 13], - "to": [15, 7, 15], - "faces": { - "north": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "east": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "south": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} + "north": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "south": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} } }, { @@ -76,12 +62,21 @@ "from": [13, 4, 1], "to": [15, 7, 3], "faces": { - "north": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "south": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} + "north": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "south": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [13, 4, 13], + "to": [15, 7, 15], + "faces": { + "north": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "east": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} } }, { @@ -89,12 +84,10 @@ "from": [1, 4, 1], "to": [3, 7, 3], "faces": { - "north": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, + "north": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "east": {"uv": [7, 0, 8, 3], "texture": "#0"}, "south": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "west": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} } } ], diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/android_station_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/android_station_working.json index 97b5b2064..a261046ba 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/android_station_working.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/android_station_working.json @@ -1,9 +1,11 @@ -{ +{ "parent": "block/cube_all", - "render_type": "translucent", + "render_type": "translucent", + "texture_size": [16, 32], "textures": { "0": "overdrive_that_matters:block/android_station", "1": "overdrive_that_matters:block/android_station_em", + "2": "overdrive_that_matters:block/android_station_base", "particle": "overdrive_that_matters:block/android_station" }, "elements": [ @@ -12,12 +14,12 @@ "from": [0, 7, 0], "to": [16, 9, 16], "faces": { - "north": {"uv": [8, 4, 16, 4.5], "texture": "#0"}, - "east": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, - "south": {"uv": [8, 4, 16, 4.5], "texture": "#0"}, - "west": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, - "up": {"uv": [8, 0, 16, 4], "texture": "#0"}, - "down": {"uv": [8, 4.5, 16, 8.5], "texture": "#0"} + "north": {"uv": [8, 0, 16, 1], "texture": "#2"}, + "east": {"uv": [0, 0, 8, 1], "texture": "#2"}, + "south": {"uv": [8, 0, 16, 1], "texture": "#2"}, + "west": {"uv": [0, 0, 8, 1], "texture": "#2"}, + "up": {"uv": [8, 0, 16, 16], "texture": "#0"}, + "down": {"uv": [8, 8, 16, 16], "texture": "#2"} } }, { @@ -25,25 +27,26 @@ "from": [0, 7, 0], "to": [16, 9, 16], "faces": { - "north": {"uv": [8, 4, 16, 4.5], "texture": "#1", "emissivity": 15}, - "east": {"uv": [0, 4, 8, 4.5], "texture": "#1", "emissivity": 15}, - "south": {"uv": [8, 4, 16, 4.5], "texture": "#1", "emissivity": 15}, - "west": {"uv": [0, 4, 8, 4.5], "texture": "#1", "emissivity": 15}, - "up": {"uv": [8, 0, 16, 4], "texture": "#1", "emissivity": 15}, - "down": {"uv": [8, 4.5, 16, 8.5], "texture": "#1", "emissivity": 15} - } + "north": {"uv": [8, 4, 16, 4.5], "texture": "#1"}, + "east": {"uv": [0, 4, 8, 4.5], "texture": "#1"}, + "south": {"uv": [8, 4, 16, 4.5], "texture": "#1"}, + "west": {"uv": [0, 4, 8, 4.5], "texture": "#1"}, + "up": {"uv": [8, 0, 16, 4], "texture": "#1"}, + "down": {"uv": [8, 4.5, 16, 8.5], "texture": "#1"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } }, { "name": "base", "from": [0, 0, 0], "to": [16, 4, 16], "faces": { - "north": {"uv": [0, 8.5, 8, 9.5], "texture": "#0"}, - "east": {"uv": [0, 8.5, 8, 9.5], "texture": "#0"}, - "south": {"uv": [8, 8.5, 16, 9.5], "texture": "#0"}, - "west": {"uv": [8, 8.5, 16, 9.5], "texture": "#0"}, - "up": {"uv": [8, 4.5, 16, 8.5], "texture": "#0"}, - "down": {"uv": [8, 9.5, 16, 13.5], "texture": "#0"} + "north": {"uv": [8, 1, 16, 3], "texture": "#2"}, + "east": {"uv": [0, 1, 8, 3], "texture": "#2"}, + "south": {"uv": [8, 1, 16, 3], "texture": "#2"}, + "west": {"uv": [0, 1, 8, 3], "texture": "#2"}, + "up": {"uv": [8, 8, 16, 16], "texture": "#2"}, + "down": {"uv": [0, 8, 8, 16], "texture": "#2"} } }, { @@ -51,12 +54,10 @@ "from": [2, 4, 2], "to": [14, 7, 14], "faces": { - "north": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "east": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "south": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "west": {"uv": [1, 0, 7, 0.75], "texture": "#0"}, - "up": {"uv": [9, 5, 15, 8], "texture": "#0"}, - "down": {"uv": [9, 5, 15, 8], "texture": "#0"} + "north": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "east": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "south": {"uv": [1, 0, 7, 3], "texture": "#0"}, + "west": {"uv": [1, 0, 7, 3], "texture": "#0"} } }, { @@ -64,51 +65,24 @@ "from": [2, 4, 2], "to": [14, 7, 14], "faces": { - "north": {"uv": [1, 0, 7, 0.75], "texture": "#1", "emissivity": 15}, - "east": {"uv": [1, 0, 7, 0.75], "texture": "#1", "emissivity": 15}, - "south": {"uv": [1, 0, 7, 0.75], "texture": "#1", "emissivity": 15}, - "west": {"uv": [1, 0, 7, 0.75], "texture": "#1", "emissivity": 15}, - "up": {"uv": [9, 5, 15, 8], "texture": "#1", "emissivity": 15}, - "down": {"uv": [9, 5, 15, 8], "texture": "#1", "emissivity": 15} - } - }, - { - "name": "frame", - "from": [1, 4, 13], - "to": [3, 7, 15], - "faces": { - "north": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "south": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} - } + "north": {"uv": [1, 0, 7, 0.75], "texture": "#1"}, + "east": {"uv": [1, 0, 7, 0.75], "texture": "#1"}, + "south": {"uv": [1, 0, 7, 0.75], "texture": "#1"}, + "west": {"uv": [1, 0, 7, 0.75], "texture": "#1"}, + "up": {"uv": [9, 5, 15, 8], "texture": "#1"}, + "down": {"uv": [9, 5, 15, 8], "texture": "#1"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } }, { "name": "frame", "from": [13, 4, 13], "to": [15, 7, 15], "faces": { - "north": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "east": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "south": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} - } - }, - { - "name": "frame", - "from": [13, 4, 1], - "to": [15, 7, 3], - "faces": { - "north": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "south": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "west": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} + "north": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "east": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} } }, { @@ -116,24 +90,73 @@ "from": [1, 4, 1], "to": [3, 7, 3], "faces": { - "north": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "south": {"uv": [0, 0, 1, 0.75], "texture": "#0"}, - "west": {"uv": [7, 0, 8, 0.75], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.58182], "texture": "#0"}, - "down": {"uv": [0, 0, 1, 0.58182], "texture": "#0"} + "north": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "east": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} } }, { + "name": "frame", + "from": [1, 4, 13], + "to": [3, 7, 15], + "faces": { + "north": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "south": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [13, 4, 1], + "to": [15, 7, 3], + "faces": { + "north": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 3], "texture": "#0"}, + "south": {"uv": [7, 0, 8, 3], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 3], "texture": "#0"} + } + }, + { + "name": "hologram", "from": [-1, 10, -1], "to": [17, 10, 17], "faces": { - "north": {"uv": [0, 0, 8, 0], "texture": "#missing"}, - "east": {"uv": [0, 0, 8, 0], "texture": "#missing"}, - "south": {"uv": [0, 0, 8, 0], "texture": "#missing"}, - "west": {"uv": [0, 0, 8, 0], "texture": "#missing"}, - "up": {"uv": [0, 9.5, 8, 13.5], "texture": "#0", "emissivity": 15}, - "down": {"uv": [0, 9.5, 8, 13.5], "texture": "#0", "emissivity": 15} + "up": {"uv": [0, 9.5, 8, 13.5], "texture": "#1"}, + "down": {"uv": [0, 9.5, 8, 13.5], "texture": "#1"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "hologram", + "from": [6, 9.5, 6], + "to": [10, 9.5, 10], + "faces": { + "up": {"uv": [11, 11, 13, 12], "texture": "#1"}, + "down": {"uv": [11, 11, 13, 12], "texture": "#1"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "holotext", + "from": [1, 10.5, 3], + "to": [8, 14.5, 3], + "rotation": {"angle": 22.5, "axis": "x", "origin": [5.5, 13.5, 1]}, + "faces": { + "north": {"uv": [2.5, 7.25, 5.5, 8.25], "texture": "#1"}, + "south": {"uv": [5.5, 7.25, 2.5, 8.25], "texture": "#1"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "holotext", + "from": [8, 5.1, 12], + "to": [15, 9.1, 12], + "rotation": {"angle": -22.5, "axis": "x", "origin": [5.5, 13.5, 1]}, + "faces": { + "north": {"uv": [5.5, 7.25, 2.5, 8.25], "texture": "#1"}, + "south": {"uv": [2.5, 7.25, 5.5, 8.25], "texture": "#1"} } } ], diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery0.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery0.json index 194766728..c605a9108 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery0.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery0.json @@ -23,7 +23,7 @@ "from": [11, 2, 1], "to": [13, 7, 1], "faces": { - "north": {"uv": [1.5, 3, 2.5, 8], "texture": "#1", "emissivity": 15}, + "north": {"uv": [1.5, 3, 2.5, 8], "texture": "#1", "forge_data": {"block_light": 15}}, "east": {"uv": [0, 0, 0, 5], "texture": "#missing"}, "south": {"uv": [0, 0, 1, 5], "texture": "#missing"}, "west": {"uv": [0, 0, 0, 5], "texture": "#missing"}, diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery1.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery1.json new file mode 100644 index 000000000..cf73f222b --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery1.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + } + }, + "from": [ + 7, + 2, + 1 + ], + "to": [ + 9, + 7, + 7 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 1.5, + 3.0, + 2.5, + 8.0 + ] + }, + "south": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 7, + 2, + 1 + ], + "to": [ + 9, + 7, + 1 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery10.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery10.json new file mode 100644 index 000000000..61036c0e1 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery10.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "up": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + } + }, + "from": [ + 7, + 8, + 9 + ], + "to": [ + 9, + 13, + 15 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "south": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 2.0, + 3.0, + 3.0, + 8.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 7, + 8, + 15 + ], + "to": [ + 9, + 13, + 15 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery11.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery11.json new file mode 100644 index 000000000..8ef46e816 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery11.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "up": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + } + }, + "from": [ + 11, + 8, + 9 + ], + "to": [ + 13, + 13, + 15 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "south": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 2.0, + 3.0, + 3.0, + 8.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 11, + 8, + 15 + ], + "to": [ + 13, + 13, + 15 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery2.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery2.json new file mode 100644 index 000000000..bd0876bd3 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery2.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + } + }, + "from": [ + 3, + 2, + 1 + ], + "to": [ + 5, + 7, + 7 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 1.5, + 3.0, + 2.5, + 8.0 + ] + }, + "south": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 3, + 2, + 1 + ], + "to": [ + 5, + 7, + 1 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery3.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery3.json new file mode 100644 index 000000000..e194b47fa --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery3.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + } + }, + "from": [ + 11, + 8, + 1 + ], + "to": [ + 13, + 13, + 7 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 1.5, + 3.0, + 2.5, + 8.0 + ] + }, + "south": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 11, + 8, + 1 + ], + "to": [ + 13, + 13, + 1 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery4.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery4.json new file mode 100644 index 000000000..6e2a355e3 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery4.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + } + }, + "from": [ + 7, + 8, + 1 + ], + "to": [ + 9, + 13, + 7 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 1.5, + 3.0, + 2.5, + 8.0 + ] + }, + "south": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 7, + 8, + 1 + ], + "to": [ + 9, + 13, + 1 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery5.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery5.json new file mode 100644 index 000000000..55c80c12c --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery5.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + } + }, + "from": [ + 3, + 8, + 1 + ], + "to": [ + 5, + 13, + 7 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 1.5, + 3.0, + 2.5, + 8.0 + ] + }, + "south": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 3, + 8, + 1 + ], + "to": [ + 5, + 13, + 1 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery6.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery6.json index ca6135657..dbadc1c35 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery6.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery6.json @@ -26,7 +26,7 @@ "faces": { "north": {"uv": [0, 0, 1, 5], "texture": "#missing"}, "east": {"uv": [0, 0, 0, 5], "texture": "#missing"}, - "south": {"uv": [2, 3, 3, 8], "texture": "#1", "emissivity": 15}, + "south": {"uv": [2, 3, 3, 8], "texture": "#1", "forge_data": {"block_light": 15}}, "west": {"uv": [0, 0, 0, 5], "texture": "#missing"}, "up": {"uv": [0, 0, 1, 0], "texture": "#missing"}, "down": {"uv": [0, 0, 1, 0], "texture": "#missing"} diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery7.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery7.json new file mode 100644 index 000000000..f39d18793 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery7.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "up": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + } + }, + "from": [ + 7, + 2, + 9 + ], + "to": [ + 9, + 7, + 15 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "south": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 2.0, + 3.0, + 3.0, + 8.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 7, + 2, + 15 + ], + "to": [ + 9, + 7, + 15 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery8.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery8.json new file mode 100644 index 000000000..44587c83e --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery8.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "up": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + } + }, + "from": [ + 11, + 2, + 9 + ], + "to": [ + 13, + 7, + 15 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "south": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 2.0, + 3.0, + 3.0, + 8.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 11, + 2, + 15 + ], + "to": [ + 13, + 7, + 15 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery9.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery9.json new file mode 100644 index 000000000..6749cd43a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/battery9.json @@ -0,0 +1,153 @@ +{ + "elements": [ + { + "faces": { + "down": { + "rotation": 90, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 9.0 + ] + }, + "east": { + "texture": "#1", + "uv": [ + 8.0, + 2.0, + 11.0, + 7.0 + ] + }, + "north": { + "texture": "#1", + "uv": [ + 11.0, + 2.0, + 12.0, + 7.0 + ] + }, + "south": { + "texture": "#1", + "uv": [ + 12.0, + 2.0, + 13.0, + 7.0 + ] + }, + "up": { + "rotation": 270, + "texture": "#1", + "uv": [ + 8.0, + 0.0, + 11.0, + 2.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "uv": [ + 8.0, + 7.0, + 11.0, + 2.0 + ] + } + }, + "from": [ + 3, + 8, + 9 + ], + "to": [ + 5, + 13, + 15 + ] + }, + { + "faces": { + "down": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "east": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + }, + "north": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 5.0 + ] + }, + "south": { + "forge_data": { + "block_light": 15 + }, + "texture": "#1", + "uv": [ + 2.0, + 3.0, + 3.0, + 8.0 + ] + }, + "up": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0 + ] + }, + "west": { + "texture": "#missing", + "uv": [ + 0.0, + 0.0, + 0.0, + 5.0 + ] + } + }, + "from": [ + 3, + 8, + 15 + ], + "to": [ + 5, + 13, + 15 + ] + } + ], + "texture_size": [ + 32.0, + 16.0 + ], + "textures": { + "1": "overdrive_that_matters:block/batterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor0.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor0.json new file mode 100644 index 000000000..623fc86f9 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor0.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery0", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor1.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor1.json new file mode 100644 index 000000000..ba7a0f00b --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor1.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery1", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor10.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor10.json new file mode 100644 index 000000000..e2d1aebbd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor10.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery10", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor11.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor11.json new file mode 100644 index 000000000..3f585c50a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor11.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery11", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor2.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor2.json new file mode 100644 index 000000000..aa5a377a4 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor2.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery2", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor3.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor3.json new file mode 100644 index 000000000..d56db1fe8 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor3.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery3", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor4.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor4.json new file mode 100644 index 000000000..102188f76 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor4.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery4", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor5.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor5.json new file mode 100644 index 000000000..ffcfb8898 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor5.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery5", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor6.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor6.json new file mode 100644 index 000000000..9e50b467a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor6.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery6", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor7.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor7.json new file mode 100644 index 000000000..176468b41 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor7.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery7", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor8.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor8.json new file mode 100644 index 000000000..58e866b6b --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor8.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery8", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor9.json b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor9.json new file mode 100644 index 000000000..6819e719f --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/battery/matter_capacitor9.json @@ -0,0 +1,6 @@ +{ + "parent": "overdrive_that_matters:block/battery/battery9", + "textures": { + "1": "overdrive_that_matters:block/matterybank_core" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_closed.json b/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_closed.json index d055be750..826915b14 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_closed.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_closed.json @@ -1,8 +1,11 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "texture_size": [16, 32], "textures": { - "texture": "overdrive_that_matters:block/cargo_crates/cargo_crate", - "particle": "overdrive_that_matters:block/cargo_crates/cargo_crate" + "particle": "overdrive_that_matters:block/cargo_crates/cargo_crate", + "body": "overdrive_that_matters:block/cargo_crates/cargo_crate", + "core": "overdrive_that_matters:block/cargo_crates/cargo_crate_core" }, "elements": [ { @@ -10,25 +13,11 @@ "from": [0, 0, 0], "to": [16, 13, 16], "faces": { - "north": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "east": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "south": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "west": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "up": {"uv": [0, 0, 8, 4], "texture": "#texture"}, - "down": {"uv": [0, 8, 8, 12], "texture": "#texture"} - } - }, - { - "name": "inside", - "from": [2, 13, 2], - "to": [14, 15, 14], - "faces": { - "north": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "east": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "south": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "west": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "up": {"uv": [0, 0, 6, 3], "texture": "#missing"}, - "down": {"uv": [0, 0, 6, 3], "texture": "#missing"} + "north": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "east": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "south": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "west": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "down": {"uv": [0, 0, 16, 8], "texture": "#core"} } }, { @@ -36,65 +25,13 @@ "from": [0, 13, 0], "to": [16, 16, 16], "faces": { - "north": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "east": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "up": {"uv": [0, 0, 8, 4], "texture": "#texture"}, - "down": {"uv": [0, 0, 8, 4], "texture": "#texture"} - } - }, - { - "from": [0, 13, 7], - "to": [0, 15, 9], - "rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 8]}, - "faces": { - "north": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "east": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "west": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "up": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "texture": "#missing"} - } - }, - { - "from": [16, 13, 7], - "to": [16, 15, 9], - "rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 8]}, - "faces": { - "north": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "east": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "west": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "up": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "texture": "#missing"} - } - }, - { - "from": [7, 13, 16], - "to": [9, 15, 16], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 14, 16]}, - "faces": { - "north": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "east": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "south": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "up": {"uv": [0, 0, 0, 0.5], "rotation": 90, "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "rotation": 270, "texture": "#missing"} - } - }, - { - "from": [7, 13, 0], - "to": [9, 15, 0], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 14, 16]}, - "faces": { - "north": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "east": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "south": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "up": {"uv": [0, 0, 0, 0.5], "rotation": 90, "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "rotation": 270, "texture": "#missing"} + "north": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "east": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "south": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "west": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "up": {"uv": [0, 0, 16, 8], "texture": "#body"} } } - ] + ], + "display": {} } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_open.json b/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_open.json index 1cf744a25..89531631f 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_open.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/cargo_crate_open.json @@ -1,8 +1,11 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "texture_size": [16, 32], "textures": { - "texture": "overdrive_that_matters:block/cargo_crates/cargo_crate", - "particle": "overdrive_that_matters:block/cargo_crates/cargo_crate" + "particle": "overdrive_that_matters:block/cargo_crates/cargo_crate", + "body": "overdrive_that_matters:block/cargo_crates/cargo_crate", + "core": "overdrive_that_matters:block/cargo_crates/cargo_crate_core" }, "elements": [ { @@ -10,12 +13,12 @@ "from": [0, 0, 0], "to": [16, 13, 16], "faces": { - "north": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "east": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "south": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "west": {"uv": [0, 4.75, 8, 8], "texture": "#texture"}, - "up": {"uv": [0, 0, 8, 4], "texture": "#texture"}, - "down": {"uv": [0, 8, 8, 12], "texture": "#texture"} + "north": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "east": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "south": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "west": {"uv": [0, 9.5, 16, 16], "texture": "#body"}, + "up": {"uv": [0, 0, 16, 8], "texture": "#body"}, + "down": {"uv": [0, 0, 16, 8], "texture": "#core"} } }, { @@ -23,12 +26,10 @@ "from": [2, 13, 2], "to": [14, 15, 14], "faces": { - "north": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "east": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "south": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "west": {"uv": [9, 0, 15, 0.5], "texture": "#texture"}, - "up": {"uv": [0, 0, 6, 3], "texture": "#missing"}, - "down": {"uv": [0, 0, 6, 3], "texture": "#missing"} + "north": {"uv": [0, 8, 16, 9], "texture": "#core"}, + "east": {"uv": [0, 8, 16, 9], "texture": "#core"}, + "south": {"uv": [0, 8, 16, 9], "texture": "#core"}, + "west": {"uv": [0, 8, 16, 9], "texture": "#core"} } }, { @@ -36,12 +37,12 @@ "from": [0, 15, 0], "to": [16, 18, 16], "faces": { - "north": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "east": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 4, 8, 4.75], "texture": "#texture"}, - "up": {"uv": [0, 0, 8, 4], "texture": "#texture"}, - "down": {"uv": [0, 0, 8, 4], "texture": "#texture"} + "north": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "east": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "south": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "west": {"uv": [0, 8, 16, 9.5], "texture": "#body"}, + "up": {"uv": [0, 0, 16, 8], "texture": "#body"}, + "down": {"uv": [0, 0, 16, 8], "texture": "#body"} } }, { @@ -49,12 +50,8 @@ "to": [0, 15, 9], "rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 8]}, "faces": { - "north": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "east": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "west": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "up": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "texture": "#missing"} + "east": {"uv": [7, 9.5, 9, 10.5], "texture": "#body"}, + "west": {"uv": [7, 9.5, 9, 10.5], "texture": "#body"} } }, { @@ -62,12 +59,8 @@ "to": [16, 15, 9], "rotation": {"angle": 0, "axis": "y", "origin": [0, 14, 8]}, "faces": { - "north": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "east": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "west": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "up": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "texture": "#missing"} + "east": {"uv": [7, 9.5, 9, 10.5], "texture": "#body"}, + "west": {"uv": [7, 9.5, 9, 10.5], "texture": "#body"} } }, { @@ -75,12 +68,8 @@ "to": [9, 15, 16], "rotation": {"angle": 0, "axis": "y", "origin": [8, 14, 16]}, "faces": { - "north": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "east": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "south": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "up": {"uv": [0, 0, 0, 0.5], "rotation": 90, "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "rotation": 270, "texture": "#missing"} + "north": {"uv": [7, 8.5, 9, 9.5], "texture": "#body"}, + "south": {"uv": [7, 8.5, 9, 9.5], "texture": "#body"} } }, { @@ -88,13 +77,10 @@ "to": [9, 15, 0], "rotation": {"angle": 0, "axis": "y", "origin": [8, 14, 16]}, "faces": { - "north": {"uv": [3.5, 4.75, 4.5, 5.25], "texture": "#texture"}, - "east": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "south": {"uv": [3.5, 4.25, 4.5, 4.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0, 0.5], "texture": "#missing"}, - "up": {"uv": [0, 0, 0, 0.5], "rotation": 90, "texture": "#missing"}, - "down": {"uv": [0, 0, 0, 0.5], "rotation": 270, "texture": "#missing"} + "north": {"uv": [7, 8.5, 9, 9.5], "texture": "#body"}, + "south": {"uv": [7, 8.5, 9, 9.5], "texture": "#body"} } } - ] + ], + "display": {} } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_idle.json index 1864f2bb0..d29ddb107 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_idle.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_idle.json @@ -1,5 +1,7 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "texture_size": [64, 32], "textures": { "0": "overdrive_that_matters:block/chemical_generator", "particle": "overdrive_that_matters:block/chemical_generator" @@ -21,9 +23,7 @@ "from": [3, 1, 2], "to": [5, 2, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "up": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"} @@ -33,9 +33,7 @@ "from": [11, 1, 2], "to": [13, 2, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "up": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"} @@ -61,17 +59,14 @@ "east": {"uv": [0.5, 0, 1, 7], "texture": "#0"}, "south": {"uv": [0.5, 0, 0, 7], "texture": "#0"}, "west": {"uv": [0.25, 0, 0.5, 7], "texture": "#0"}, - "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"} } }, { "from": [14, 14, 2], "to": [16, 16, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "east": {"uv": [0.5, 8, 3.5, 7], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "west": {"uv": [0.5, 7, 3.5, 7.5], "texture": "#0"}, "up": {"uv": [4.5, 1, 4, 7], "texture": "#0"}, "down": {"uv": [1, 1, 0.75, 7], "texture": "#0"} @@ -85,8 +80,7 @@ "east": {"uv": [1, 0, 0.5, 7], "texture": "#0"}, "south": {"uv": [0.75, 0, 1, 7], "texture": "#0"}, "west": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, - "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"} } }, { @@ -97,17 +91,14 @@ "east": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, "south": {"uv": [0, 0, 0.5, 7], "texture": "#0"}, "west": {"uv": [1, 0, 0.5, 7], "texture": "#0"}, - "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"} } }, { "from": [0, 14, 2], "to": [2, 16, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "east": {"uv": [0.5, 7, 3.5, 7.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "west": {"uv": [0.5, 7, 3.5, 8], "rotation": 180, "texture": "#0"}, "up": {"uv": [0, 1, 0.5, 7], "texture": "#0"}, "down": {"uv": [0.75, 1, 1, 7], "texture": "#0"} @@ -118,9 +109,7 @@ "to": [14, 7, 2], "faces": { "north": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "east": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "south": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "west": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "up": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "texture": "#0"} } @@ -138,25 +127,19 @@ } }, { - "from": [1, 8, 2], - "to": [2, 10, 14], + "from": [1, 10, 2], + "to": [2, 12, 14], "faces": { - "north": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, - "east": {"uv": [0, 0, 3, 0.5], "texture": "#missing"}, - "south": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 7], "texture": "#0"}, "up": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"} } }, { - "from": [14, 8, 2], - "to": [15, 10, 14], + "from": [14, 10, 2], + "to": [15, 12, 14], "faces": { - "north": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "south": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, - "west": {"uv": [0, 0, 3, 0.5], "texture": "#missing"}, "up": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"} } @@ -169,8 +152,7 @@ "east": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, "south": {"uv": [0.75, 0, 1, 7], "texture": "#0"}, "west": {"uv": [0.5, 0, 1, 7], "texture": "#0"}, - "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"} } }, { @@ -194,7 +176,7 @@ "south": {"uv": [12.5, 3.5, 13, 6.5], "texture": "#0"}, "west": {"uv": [11.75, 3.5, 12.5, 6.5], "texture": "#0"}, "up": {"uv": [11.75, 2.5, 12.5, 3.5], "rotation": 90, "texture": "#0"}, - "down": {"uv": [11.75, 2.5, 12.5, 3.5], "rotation": 90, "texture": "#0"} + "down": {"uv": [12.5, 2.5, 13.25, 3.5], "rotation": 90, "texture": "#0"} } }, { @@ -213,11 +195,9 @@ "from": [3, 4, 11], "to": [7, 8, 13], "faces": { - "north": {"uv": [0, 0, 1, 1], "texture": "#missing"}, "east": {"uv": [2, 4, 2.5, 6], "texture": "#0"}, "south": {"uv": [1, 4, 2, 6], "texture": "#0"}, "west": {"uv": [2, 4, 2.5, 6], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"}, "down": {"uv": [2, 4, 2.5, 6], "rotation": 90, "texture": "#0"} } }, @@ -237,12 +217,10 @@ "from": [2, 3, 9], "to": [14, 8, 11], "faces": { - "north": {"uv": [8.75, 0.5, 11, 6.25], "texture": "#0"}, - "east": {"uv": [8.75, 0.5, 11.25, 6.25], "texture": "#0"}, - "south": {"uv": [9, 1, 11, 5.75], "texture": "#0"}, - "west": {"uv": [9, 0.5, 11, 5.75], "texture": "#0"}, - "up": {"uv": [9, 6, 11, 0.5], "texture": "#0"}, - "down": {"uv": [8.75, 0.5, 11.25, 6.5], "texture": "#0"} + "east": {"uv": [14.25, 11.75, 16, 16], "texture": "#0"}, + "south": {"uv": [14, 11.25, 16, 16], "texture": "#0"}, + "west": {"uv": [14.5, 11.75, 16, 16], "texture": "#0"}, + "down": {"uv": [14.25, 11.5, 16, 16], "texture": "#0"} } }, { diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_working.json index 12bc4e40d..c7d8ecce1 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_working.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/chemical_generator_working.json @@ -1,5 +1,7 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "texture_size": [64, 32], "textures": { "0": "overdrive_that_matters:block/chemical_generator", "particle": "overdrive_that_matters:block/chemical_generator" @@ -21,9 +23,7 @@ "from": [3, 1, 2], "to": [5, 2, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "up": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"} @@ -33,9 +33,7 @@ "from": [11, 1, 2], "to": [13, 2, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "up": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 7], "rotation": 90, "texture": "#0"} @@ -61,17 +59,14 @@ "east": {"uv": [0.5, 0, 1, 7], "texture": "#0"}, "south": {"uv": [0.5, 0, 0, 7], "texture": "#0"}, "west": {"uv": [0.25, 0, 0.5, 7], "texture": "#0"}, - "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"} } }, { "from": [14, 14, 2], "to": [16, 16, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "east": {"uv": [0.5, 8, 3.5, 7], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "west": {"uv": [0.5, 7, 3.5, 7.5], "texture": "#0"}, "up": {"uv": [4.5, 1, 4, 7], "texture": "#0"}, "down": {"uv": [1, 1, 0.75, 7], "texture": "#0"} @@ -85,8 +80,7 @@ "east": {"uv": [1, 0, 0.5, 7], "texture": "#0"}, "south": {"uv": [0.75, 0, 1, 7], "texture": "#0"}, "west": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, - "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0.5, 0, 0, 1], "texture": "#0"} } }, { @@ -97,17 +91,14 @@ "east": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, "south": {"uv": [0, 0, 0.5, 7], "texture": "#0"}, "west": {"uv": [1, 0, 0.5, 7], "texture": "#0"}, - "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"} } }, { "from": [0, 14, 2], "to": [2, 16, 14], "faces": { - "north": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "east": {"uv": [0.5, 7, 3.5, 7.5], "texture": "#0"}, - "south": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, "west": {"uv": [0.5, 7, 3.5, 8], "rotation": 180, "texture": "#0"}, "up": {"uv": [0, 1, 0.5, 7], "texture": "#0"}, "down": {"uv": [0.75, 1, 1, 7], "texture": "#0"} @@ -118,9 +109,7 @@ "to": [14, 7, 2], "faces": { "north": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "east": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "south": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "west": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "up": {"uv": [1, 6, 4, 6.5], "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "texture": "#0"} } @@ -138,25 +127,19 @@ } }, { - "from": [1, 8, 2], - "to": [2, 10, 14], + "from": [1, 10, 2], + "to": [2, 12, 14], "faces": { - "north": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, - "east": {"uv": [0, 0, 3, 0.5], "texture": "#missing"}, - "south": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "west": {"uv": [1, 6, 4, 7], "texture": "#0"}, "up": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"} } }, { - "from": [14, 8, 2], - "to": [15, 10, 14], + "from": [14, 10, 2], + "to": [15, 12, 14], "faces": { - "north": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, "east": {"uv": [1, 6, 4, 7], "texture": "#0"}, - "south": {"uv": [0, 0, 0.25, 0.5], "texture": "#missing"}, - "west": {"uv": [0, 0, 3, 0.5], "texture": "#missing"}, "up": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"}, "down": {"uv": [1, 6, 4, 6.5], "rotation": 90, "texture": "#0"} } @@ -169,8 +152,7 @@ "east": {"uv": [4.25, 0, 4.5, 7], "texture": "#0"}, "south": {"uv": [0.75, 0, 1, 7], "texture": "#0"}, "west": {"uv": [0.5, 0, 1, 7], "texture": "#0"}, - "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"}, - "down": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"} + "up": {"uv": [0, 0, 0.5, 1], "texture": "#0"} } }, { @@ -194,7 +176,7 @@ "south": {"uv": [12.5, 3.5, 13, 6.5], "texture": "#0"}, "west": {"uv": [11.75, 3.5, 12.5, 6.5], "texture": "#0"}, "up": {"uv": [11.75, 2.5, 12.5, 3.5], "rotation": 90, "texture": "#0"}, - "down": {"uv": [11.75, 2.5, 12.5, 3.5], "rotation": 90, "texture": "#0"} + "down": {"uv": [12.5, 2.5, 13.25, 3.5], "rotation": 90, "texture": "#0"} } }, { @@ -213,11 +195,9 @@ "from": [3, 4, 11], "to": [7, 8, 13], "faces": { - "north": {"uv": [0, 0, 1, 1], "texture": "#missing"}, "east": {"uv": [2, 4, 2.5, 6], "texture": "#0"}, "south": {"uv": [1, 4, 2, 6], "texture": "#0"}, "west": {"uv": [2, 4, 2.5, 6], "texture": "#0"}, - "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"}, "down": {"uv": [2, 4, 2.5, 6], "rotation": 90, "texture": "#0"} } }, @@ -237,12 +217,10 @@ "from": [2, 3, 9], "to": [14, 8, 11], "faces": { - "north": {"uv": [8.75, 0.5, 11, 6.25], "texture": "#0"}, - "east": {"uv": [8.75, 0.5, 11.25, 6.25], "texture": "#0"}, - "south": {"uv": [9, 1, 11, 5.75], "texture": "#0"}, - "west": {"uv": [9, 0.5, 11, 5.75], "texture": "#0"}, - "up": {"uv": [9, 6, 11, 0.5], "texture": "#0"}, - "down": {"uv": [8.75, 0.5, 11.25, 6.5], "texture": "#0"} + "east": {"uv": [14.25, 11.75, 16, 16], "texture": "#0"}, + "south": {"uv": [14, 11.25, 16, 16], "texture": "#0"}, + "west": {"uv": [14.5, 11.75, 16, 16], "texture": "#0"}, + "down": {"uv": [14.25, 11.5, 16, 16], "texture": "#0"} } }, { diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/cobblestone_generator.json b/src/main/resources/assets/overdrive_that_matters/models/block/cobblestone_generator.json new file mode 100644 index 000000000..1e9f62a3c --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/cobblestone_generator.json @@ -0,0 +1,129 @@ +{ + "credit": "Made with Blockbench", + "render_type": "translucent", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/cobblestone_generator", + "particle": "overdrive_that_matters:block/cobblestone_generator", + "lava": "block/lava_still", + "water": "block/water_still" + }, + "elements": [ + { + "from": [0, 0, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [8, 0, 16, 1.5], "texture": "#0"}, + "east": {"uv": [8, 0, 16, 1.5], "texture": "#0"}, + "south": {"uv": [8, 0, 16, 1.5], "texture": "#0"}, + "west": {"uv": [8, 0, 16, 1.5], "texture": "#0"}, + "up": {"uv": [0, 10, 8, 14], "texture": "#0"}, + "down": {"uv": [0, 0, 8, 4], "texture": "#0"} + } + }, + { + "from": [4, 6, 0], + "to": [12, 16, 16], + "faces": { + "north": {"uv": [12, 4, 16, 6.5], "texture": "#0"}, + "east": {"uv": [8, 1.5, 16, 4], "texture": "#0"}, + "south": {"uv": [12, 8.5, 16, 11], "texture": "#0"}, + "west": {"uv": [8, 1.5, 16, 4], "texture": "#0"}, + "up": {"uv": [8.5, 6.5, 16, 8.5], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [12, 6, 1], + "to": [15, 15, 15], + "faces": { + "north": {"uv": [7, 4, 8.5, 6.25], "texture": "#0"}, + "east": {"uv": [0, 4, 7, 6.25], "texture": "#0"}, + "south": {"uv": [8.5, 4, 7, 6.25], "texture": "#0"}, + "up": {"uv": [0, 6.25, 7, 7], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [15, 6, 1], + "to": [12, 15, 15], + "faces": { + "north": {"uv": [8.5, 4, 7, 6.25], "texture": "#0"}, + "south": {"uv": [7, 4, 8.5, 6.25], "texture": "#0"}, + "west": {"uv": [0, 4, 7, 6.25], "texture": "#0"}, + "up": {"uv": [0, 6.25, 7, 7], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [1, 6, 1], + "to": [4, 15, 15], + "faces": { + "north": {"uv": [8.5, 4, 7, 6.25], "texture": "#0"}, + "south": {"uv": [7, 4, 8.5, 6.25], "texture": "#0"}, + "west": {"uv": [0, 4, 7, 6.25], "texture": "#0"}, + "up": {"uv": [0, 6.25, 7, 7], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [4, 6, 1], + "to": [1, 15, 15], + "faces": { + "north": {"uv": [7, 4, 8.5, 6.25], "texture": "#0"}, + "east": {"uv": [0, 4, 7, 6.25], "texture": "#0"}, + "south": {"uv": [8.5, 4, 7, 6.25], "texture": "#0"}, + "up": {"uv": [0, 6.25, 7, 7], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "lava", + "from": [1.1, 6, 1.1], + "to": [4, 14.9, 14.9], + "faces": { + "north": {"uv": [12, 1, 15, 10], "texture": "#lava"}, + "south": {"uv": [1, 1, 4, 10], "texture": "#lava"}, + "west": {"uv": [1, 1, 15, 10], "texture": "#lava"}, + "up": {"uv": [4, 15, 1, 1], "texture": "#lava"} + } + }, + { + "name": "water", + "from": [12, 6, 1.1], + "to": [14.9, 14.9, 14.9], + "faces": { + "north": {"uv": [1, 1, 4, 10], "texture": "#water", "tintindex": 0}, + "east": {"uv": [1, 1, 15, 10], "texture": "#water", "tintindex": 0}, + "south": {"uv": [12, 1, 15, 10], "texture": "#water", "tintindex": 0}, + "up": {"uv": [15, 15, 12, 1], "texture": "#water", "tintindex": 0} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/engine.json b/src/main/resources/assets/overdrive_that_matters/models/block/engine.json new file mode 100644 index 000000000..89cb5ee31 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/engine.json @@ -0,0 +1,303 @@ +{ + "credit": "Made with Blockbench", + "parent": "minecraft:block/cube_all", + "render_type": "cutout", + "texture_size": [16, 32], + "textures": { + "0": "overdrive_that_matters:block/ship_engine", + "particle": "overdrive_that_matters:block/ship_engine" + }, + "elements": [ + { + "name": "frame", + "from": [0, 0, 14], + "to": [16, 16, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 8, 4], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 4, 8, 4.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 8, 4], "texture": "#0"}, + "west": {"uv": [0, 4, 8, 4.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 4, 8, 4.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 4, 8, 4.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 1, 10], + "to": [15, 15, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 5.5, 7, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 4.5, 7, 5.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 7, 7], "texture": "#missing"}, + "west": {"uv": [0, 4.5, 7, 5.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 4.5, 7, 5.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 4.5, 7, 5.5], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [4, 4, 7], + "to": [12, 12, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 4, 4], "rotation": 180, "texture": "#missing"}, + "east": {"uv": [2, 1.75, 6, 3.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 4, 4], "texture": "#missing"}, + "west": {"uv": [2, 1.75, 6, 3.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [2, 1.75, 6, 3.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [2, 1.75, 6, 3.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [3, 3, 4], + "to": [13, 13, 7], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 5, 5], "rotation": 180, "texture": "#missing"}, + "east": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1.5, 0.75, 6.5, 3.25], "texture": "#0"}, + "west": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [1.5, 2.25, 6.5, 3.75], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [2, 2, 0], + "to": [14, 14, 4], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 9, 6, 12], "rotation": 180, "texture": "#0"}, + "east": {"uv": [1, 2, 7, 4], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 0.5, 7, 3.5], "texture": "#0"}, + "west": {"uv": [1, 2, 7, 4], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 2, 7, 4], "rotation": 180, "texture": "#0"}, + "down": {"uv": [1, 2, 7, 4], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [10, 7, 1], + "to": [13, 9, 10], + "rotation": {"angle": -22.5, "axis": "y", "origin": [12, 8, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 270, "texture": "#0"}, + "east": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1.5, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 4.5], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [13.5, 0, 15, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [11, 0, 12.5, 2.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [7, 10, 1], + "to": [9, 13, 10], + "rotation": {"angle": 22.5, "axis": "x", "origin": [8, 12, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 180, "texture": "#0"}, + "east": {"uv": [11, 0, 12.5, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1.5], "texture": "#missing"}, + "west": {"uv": [13.5, 0, 15, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 0, 1, 4.5], "texture": "#missing"} + } + }, + { + "name": "funnycone", + "from": [7, 3, 1], + "to": [9, 6, 10], + "rotation": {"angle": -22.5, "axis": "x", "origin": [8, 4, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 180, "texture": "#0"}, + "east": {"uv": [13.5, 0, 15, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1.5], "texture": "#missing"}, + "west": {"uv": [11, 0, 12.5, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 1, 4.5], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [12.5, 0, 13.5, 2.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [3, 7, 1], + "to": [6, 9, 10], + "rotation": {"angle": 22.5, "axis": "y", "origin": [4, 8, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 1, 4.5], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [0, 0, 1.5, 1], "texture": "#missing"}, + "west": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [11, 0, 12.5, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [13.5, 0, 15, 2.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [0, 14, 9], + "to": [2, 16, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "texture": "#0"}, + "east": {"uv": [9, 0.5, 8, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [9, 0.5, 8, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 0.5, 9, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8, 0.5, 9, 1.75], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 14, 4], + "to": [2, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 1, 4], + "to": [2, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 1, 4], + "to": [15, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 14, 4], + "to": [15, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "from": [14, 2, 7], + "to": [15, 14, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9.5, 0.25, 10.5, 3.25], "texture": "#0"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "texture": "#0"}, + "west": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 0, 0.5, 1], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [0, 0, 0.5, 1], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [1, 2, 7], + "to": [2, 14, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9.5, 0.25, 10.5, 3.25], "texture": "#0"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "texture": "#0"}, + "west": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 0, 0.5, 1], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [0, 0, 0.5, 1], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [2, 14, 7], + "to": [14, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 0.5, 1], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.5, 1], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 270, "texture": "#0"}, + "down": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [2, 1, 7], + "to": [14, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 0.5, 1], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.5, 1], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 270, "texture": "#0"}, + "down": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 14, 9], + "to": [16, 16, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [8, 0.5, 9, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [8, 0.5, 9, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.5, 8, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.5, 8, 1.75], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 0, 9], + "to": [16, 2, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.5, 8, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [9, 0.5, 8, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 0.5, 9, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8, 0.5, 9, 1.75], "texture": "#0"} + } + }, + { + "from": [0, 0, 9], + "to": [2, 2, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 0.5, 9, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [8, 0.5, 9, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.5, 8, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.5, 8, 1.75], "texture": "#0"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/engine_active.json b/src/main/resources/assets/overdrive_that_matters/models/block/engine_active.json new file mode 100644 index 000000000..23a785b34 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/engine_active.json @@ -0,0 +1,303 @@ +{ + "credit": "Made with Blockbench", + "parent": "minecraft:block/cube_all", + "render_type": "cutout", + "texture_size": [16, 32], + "textures": { + "0": "overdrive_that_matters:block/ship_engine", + "particle": "overdrive_that_matters:block/ship_engine" + }, + "elements": [ + { + "name": "frame", + "from": [0, 0, 14], + "to": [16, 16, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 8, 4], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 4, 8, 4.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 8, 4], "texture": "#0"}, + "west": {"uv": [0, 4, 8, 4.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 4, 8, 4.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 4, 8, 4.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 1, 10], + "to": [15, 15, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 5.5, 7, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 4.5, 7, 5.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 7, 7], "texture": "#missing"}, + "west": {"uv": [0, 4.5, 7, 5.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 4.5, 7, 5.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 4.5, 7, 5.5], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [4, 4, 7], + "to": [12, 12, 10], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 4, 4], "rotation": 180, "texture": "#missing"}, + "east": {"uv": [2, 1.75, 6, 3.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 4, 4], "texture": "#missing"}, + "west": {"uv": [2, 1.75, 6, 3.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [2, 1.75, 6, 3.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [2, 1.75, 6, 3.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [3, 3, 4], + "to": [13, 13, 7], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [0, 0, 5, 5], "rotation": 180, "texture": "#missing"}, + "east": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1.5, 0.75, 6.5, 3.25], "texture": "#0"}, + "west": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1.5, 2.25, 6.5, 3.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [1.5, 2.25, 6.5, 3.75], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [2, 2, 0], + "to": [14, 14, 4], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [6, 9, 12, 12], "rotation": 180, "texture": "#0"}, + "east": {"uv": [1, 2, 7, 4], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 0.5, 7, 3.5], "texture": "#0"}, + "west": {"uv": [1, 2, 7, 4], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 2, 7, 4], "rotation": 180, "texture": "#0"}, + "down": {"uv": [1, 2, 7, 4], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [10, 7, 1], + "to": [13, 9, 10], + "rotation": {"angle": -22.5, "axis": "y", "origin": [12, 8, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 270, "texture": "#0"}, + "east": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1.5, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 4.5], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [13.5, 0, 15, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [11, 0, 12.5, 2.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [7, 10, 1], + "to": [9, 13, 10], + "rotation": {"angle": 22.5, "axis": "x", "origin": [8, 12, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 180, "texture": "#0"}, + "east": {"uv": [11, 0, 12.5, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1.5], "texture": "#missing"}, + "west": {"uv": [13.5, 0, 15, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 0, 1, 4.5], "texture": "#missing"} + } + }, + { + "name": "funnycone", + "from": [7, 3, 1], + "to": [9, 6, 10], + "rotation": {"angle": -22.5, "axis": "x", "origin": [8, 4, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 180, "texture": "#0"}, + "east": {"uv": [13.5, 0, 15, 2.25], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1.5], "texture": "#missing"}, + "west": {"uv": [11, 0, 12.5, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 1, 4.5], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [12.5, 0, 13.5, 2.25], "texture": "#0"} + } + }, + { + "name": "funnycone", + "from": [3, 7, 1], + "to": [6, 9, 10], + "rotation": {"angle": 22.5, "axis": "y", "origin": [4, 8, 9.5]}, + "faces": { + "north": {"uv": [12.5, 2.25, 13.5, 3], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 1, 4.5], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [0, 0, 1.5, 1], "texture": "#missing"}, + "west": {"uv": [12.5, 0, 13.5, 2.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [11, 0, 12.5, 2.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [13.5, 0, 15, 2.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [0, 14, 9], + "to": [2, 16, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "texture": "#0"}, + "east": {"uv": [9, 0.5, 8, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [9, 0.5, 8, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 0.5, 9, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8, 0.5, 9, 1.75], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 14, 4], + "to": [2, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 1, 4], + "to": [2, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 1, 4], + "to": [15, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 14, 4], + "to": [15, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [9, 1.5, 9.5, 1.75], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, + "west": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.25, 9.5, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.25, 9.5, 1.5], "texture": "#0"} + } + }, + { + "from": [14, 2, 7], + "to": [15, 14, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9.5, 0.25, 10.5, 3.25], "texture": "#0"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "texture": "#0"}, + "west": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 0, 0.5, 1], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [0, 0, 0.5, 1], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [1, 2, 7], + "to": [2, 14, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9.5, 0.25, 10.5, 3.25], "texture": "#0"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "texture": "#0"}, + "west": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 0, 0.5, 1], "rotation": 180, "texture": "#missing"}, + "down": {"uv": [0, 0, 0.5, 1], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [2, 14, 7], + "to": [14, 15, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 0.5, 1], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.5, 1], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 270, "texture": "#0"}, + "down": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [2, 1, 7], + "to": [14, 2, 9], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [0, 0, 0.5, 1], "rotation": 270, "texture": "#missing"}, + "south": {"uv": [10.5, 0.25, 11, 3.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.5, 1], "rotation": 90, "texture": "#missing"}, + "up": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 270, "texture": "#0"}, + "down": {"uv": [9.5, 0.25, 10.5, 3.25], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 14, 9], + "to": [16, 16, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 270, "texture": "#0"}, + "east": {"uv": [8, 0.5, 9, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [8, 0.5, 9, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.5, 8, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.5, 8, 1.75], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [14, 0, 9], + "to": [16, 2, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 180, "texture": "#0"}, + "east": {"uv": [9, 0.5, 8, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [9, 0.5, 8, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 0.5, 9, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8, 0.5, 9, 1.75], "texture": "#0"} + } + }, + { + "from": [0, 0, 9], + "to": [2, 2, 14], + "rotation": {"angle": 0, "axis": "y", "origin": [0, 16, 0]}, + "faces": { + "north": {"uv": [8, 1.75, 9, 2.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 0.5, 9, 1.75], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "west": {"uv": [8, 0.5, 9, 1.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 0.5, 8, 1.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [9, 0.5, 8, 1.75], "texture": "#0"} + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/essence_storage.json b/src/main/resources/assets/overdrive_that_matters/models/block/essence_storage.json new file mode 100644 index 000000000..b8cd1d055 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/essence_storage.json @@ -0,0 +1,167 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/essence_storage", + "particle": "overdrive_that_matters:block/essence_storage" + }, + "elements": [ + { + "name": "body", + "from": [0, 0, 0], + "to": [16, 2, 16], + "faces": { + "north": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, + "east": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, + "south": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, + "west": {"uv": [0, 4, 8, 4.5], "texture": "#0"}, + "up": {"uv": [0, 0, 8, 4], "texture": "#0"}, + "down": {"uv": [0, 0, 8, 4], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 3, 0], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [8, 7.25, 16, 7.75], "texture": "#0"}, + "east": {"uv": [8, 7.25, 16, 7.75], "texture": "#0"}, + "south": {"uv": [8, 7.25, 16, 7.75], "texture": "#0"}, + "west": {"uv": [8, 7.25, 16, 7.75], "texture": "#0"}, + "up": {"uv": [0, 7.25, 8, 11.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 0, 8, 4], "texture": "#0"} + } + }, + { + "name": "body", + "from": [9, 5, 0], + "to": [16, 16, 14], + "faces": { + "north": {"uv": [12, 0, 15.5, 2.75], "texture": "#0"}, + "east": {"uv": [9, 7.75, 16, 10.5], "texture": "#0"}, + "south": {"uv": [11, 4.5, 14.5, 7.25], "texture": "#0"}, + "west": {"uv": [0, 4.5, 7, 7.25], "texture": "#0"}, + "up": {"uv": [0, 7.75, 3.5, 11.25], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 10, 14], + "to": [16, 16, 16], + "faces": { + "east": {"uv": [8, 7.75, 9, 9.25], "texture": "#0"}, + "south": {"uv": [0, 11.25, 8, 12.75], "texture": "#0"}, + "west": {"uv": [14.5, 4.5, 15.5, 6], "texture": "#0"}, + "up": {"uv": [0, 7.25, 8, 7.75], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 7.25, 8, 7.75], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 5, 9], + "to": [9, 16, 14], + "faces": { + "north": {"uv": [7, 4.5, 11.5, 7.25], "texture": "#0"}, + "south": {"uv": [5, 4.5, 9.5, 7.25], "texture": "#0"}, + "west": {"uv": [12, 4.5, 14.5, 7.25], "texture": "#0"}, + "up": {"uv": [3.5, 7.75, 8, 9], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [1, 2, 1], + "to": [15, 3, 15], + "faces": { + "north": {"uv": [0.5, 4.25, 7.5, 4.5], "texture": "#0"}, + "east": {"uv": [0.5, 4.25, 7.5, 4.5], "texture": "#0"}, + "south": {"uv": [0.5, 4.25, 7.5, 4.5], "texture": "#0"}, + "west": {"uv": [0.5, 4.25, 7.5, 4.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [1, 5, 14], + "to": [15, 10, 15], + "faces": { + "east": {"uv": [15.5, 10.5, 16, 11.75], "texture": "#0"}, + "south": {"uv": [8.5, 10.5, 15.5, 11.75], "texture": "#0"}, + "west": {"uv": [8, 10.5, 8.5, 11.75], "texture": "#0"} + } + }, + { + "name": "experience", + "from": [1.5, 5, 1.5], + "to": [8.5, 11, 8.5], + "faces": { + "north": {"uv": [8, 14.5, 11.5, 16], "texture": "#0"}, + "east": {"uv": [12.5, 12, 16, 13.5], "texture": "#0"}, + "south": {"uv": [9, 12, 12.5, 13.5], "texture": "#0"}, + "west": {"uv": [11.5, 14.5, 15, 16], "texture": "#0"}, + "up": {"uv": [9.5, 12.5, 13, 14.25], "texture": "#0"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "bottle", + "from": [1, 13, 1], + "to": [9, 15, 9], + "faces": { + "north": {"uv": [8, 2, 12, 2.5], "texture": "#0"}, + "west": {"uv": [8, 2, 12, 2.5], "texture": "#0"}, + "up": {"uv": [8, 0, 12, 2], "texture": "#0"}, + "down": {"uv": [8, 0, 12, 2], "texture": "#0"} + } + }, + { + "name": "bottle", + "from": [1, 5, 1], + "to": [9, 6, 9], + "faces": { + "north": {"uv": [8, 4.25, 12, 4.5], "texture": "#0"}, + "west": {"uv": [8, 4.25, 12, 4.5], "texture": "#0"}, + "up": {"uv": [8, 0, 12, 2], "texture": "#0"} + } + }, + { + "name": "bottle", + "from": [1, 6, 1], + "to": [9, 13, 9], + "faces": { + "north": {"uv": [8, 2.5, 12, 4.25], "texture": "#0"}, + "west": {"uv": [8, 2.5, 12, 4.25], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/fluid_tank.json b/src/main/resources/assets/overdrive_that_matters/models/block/fluid_tank.json new file mode 100644 index 000000000..a5d92e79e --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/fluid_tank.json @@ -0,0 +1,116 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 32], + "textures": { + "0": "overdrive_that_matters:block/fluid_tank", + "particle": "overdrive_that_matters:block/fluid_tank" + }, + "elements": [ + { + "name": "frame", + "from": [0, 0, 0], + "to": [16, 2, 16], + "faces": { + "north": {"uv": [0, 8, 8, 9], "texture": "#0"}, + "east": {"uv": [0, 14, 8, 15], "texture": "#0"}, + "south": {"uv": [0, 8, 8, 9], "texture": "#0"}, + "west": {"uv": [0, 14, 8, 15], "texture": "#0"}, + "up": {"uv": [8, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [0, 0, 8, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [0, 14, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 8, 8, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 14, 8, 15], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 8, 8, 9], "rotation": 180, "texture": "#0"}, + "west": {"uv": [0, 14, 8, 15], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 0, 8, 8], "texture": "#0"}, + "down": {"uv": [8, 0, 16, 8], "texture": "#0"} + } + }, + { + "name": "thingmajigs", + "from": [0.5, 2, 0.5], + "to": [15.5, 3, 15.5], + "faces": { + "north": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "east": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "south": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "west": {"uv": [0, 15, 7, 15.5], "texture": "#0"} + } + }, + { + "name": "thingmajigs", + "from": [0.5, 13, 0.5], + "to": [15.5, 14, 15.5], + "faces": { + "north": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "east": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "south": {"uv": [0, 15, 7, 15.5], "texture": "#0"}, + "west": {"uv": [0, 15, 7, 15.5], "texture": "#0"} + } + }, + { + "name": "tank", + "from": [0, 3, 0], + "to": [16, 13, 16], + "faces": { + "north": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "east": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "south": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "west": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "up": {"uv": [8, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [8, 0, 16, 8], "texture": "#0"} + } + }, + { + "name": "tank inside", + "from": [0.1, 13, 0.1], + "to": [15.9, 3, 15.9], + "faces": { + "north": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "east": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "south": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "west": {"uv": [0, 9, 8, 14], "texture": "#0"}, + "up": {"uv": [8, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [8, 0, 16, 8], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 2], + "translation": [0, 2.75, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/holo_sign.json b/src/main/resources/assets/overdrive_that_matters/models/block/holo_sign.json new file mode 100644 index 000000000..fb314ebd6 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/holo_sign.json @@ -0,0 +1,192 @@ +{ + "credit": "Made with Blockbench", + "parent": "block/cube_all", + "render_type": "translucent", + "textures": { + "0": "overdrive_that_matters:block/holo_sign", + "particle": "overdrive_that_matters:block/holo_sign" + }, + "elements": [ + { + "name": "body", + "from": [0, 0, 14], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 8, 8], "texture": "#0"}, + "east": {"uv": [0, 11.5, 8, 12.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [0, 0, 8, 8], "texture": "#0"}, + "west": {"uv": [0, 11.5, 8, 12.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 11.5, 8, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 11.5, 8, 12.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [1, 1, 13], + "to": [15, 15, 14], + "faces": { + "north": {"uv": [0, 0, 7, 7], "texture": "#missing"}, + "east": {"uv": [0, 11, 7, 11.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 0, 7, 7], "texture": "#missing"}, + "west": {"uv": [0, 11, 7, 11.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 11, 7, 11.5], "texture": "#0"}, + "down": {"uv": [0, 11, 7, 11.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 0, 10], + "to": [16, 16, 13], + "faces": { + "north": {"uv": [8, 0, 16, 8], "texture": "#0"}, + "east": {"uv": [0, 9.5, 8, 11], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 0, 8, 8], "texture": "#0"}, + "west": {"uv": [0, 9.5, 8, 11], "rotation": 270, "texture": "#0"}, + "up": {"uv": [0, 8, 8, 9.5], "texture": "#0"}, + "down": {"uv": [0, 8, 8, 9.5], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [14, 5, 9], + "to": [16, 11, 10], + "faces": { + "north": {"uv": [0.5, 12.5, 1.5, 15.5], "texture": "#0"}, + "east": {"uv": [0, 12.5, 0.5, 15.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 3], "texture": "#missing"}, + "west": {"uv": [1.5, 12.5, 2, 15.5], "texture": "#0"}, + "up": {"uv": [1.5, 16, 0.5, 15.5], "texture": "#0"}, + "down": {"uv": [1.5, 15.5, 0.5, 16], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 5, 9], + "to": [2, 11, 10], + "faces": { + "north": {"uv": [1.5, 12.5, 0.5, 15.5], "texture": "#0"}, + "east": {"uv": [1.5, 12.5, 2, 15.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 3], "texture": "#missing"}, + "west": {"uv": [0, 12.5, 0.5, 15.5], "texture": "#0"}, + "up": {"uv": [0.5, 15.5, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0.5, 15.5, 1.5, 16], "texture": "#0"} + } + }, + { + "name": "light", + "from": [14, 14, 9.99], + "to": [15, 15, 9.99], + "faces": { + "north": {"uv": [8.5, 8.5, 9, 9], "texture": "#0", "forge_data": {"block_light": 15}}, + "east": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#missing"}, + "west": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "up": {"uv": [0, 0, 16, 0], "texture": "#missing"}, + "down": {"uv": [0, 0, 16, 0], "texture": "#missing"} + } + }, + { + "name": "light", + "from": [1, 14, 9.99], + "to": [2, 15, 9.99], + "faces": { + "north": {"uv": [15, 8.5, 15.5, 9], "texture": "#0", "forge_data": {"block_light": 15}}, + "east": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#missing"}, + "west": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "up": {"uv": [0, 0, 16, 0], "texture": "#missing"}, + "down": {"uv": [0, 0, 16, 0], "texture": "#missing"} + } + }, + { + "name": "light", + "from": [1, 1, 9.99], + "to": [2, 2, 9.99], + "faces": { + "north": {"uv": [15, 15, 15.5, 15.5], "texture": "#0", "forge_data": {"block_light": 15}}, + "east": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#missing"}, + "west": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "up": {"uv": [0, 0, 16, 0], "texture": "#missing"}, + "down": {"uv": [0, 0, 16, 0], "texture": "#missing"} + } + }, + { + "name": "light", + "from": [14, 1, 9.99], + "to": [15, 2, 9.99], + "faces": { + "north": {"uv": [8.5, 15, 9, 15.5], "texture": "#0", "forge_data": {"block_light": 15}}, + "east": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "south": {"uv": [0, 0, 16, 16], "texture": "#missing"}, + "west": {"uv": [0, 0, 0, 16], "texture": "#missing"}, + "up": {"uv": [0, 0, 16, 0], "texture": "#missing"}, + "down": {"uv": [0, 0, 16, 0], "texture": "#missing"} + } + }, + { + "name": "light", + "from": [2.01, 6, 9], + "to": [2.01, 10, 10], + "faces": { + "north": {"uv": [0, 0, 0, 4], "texture": "#missing"}, + "east": {"uv": [1.5, 13, 2, 15], "texture": "#0", "forge_data": {"block_light": 15}}, + "south": {"uv": [0, 0, 0, 4], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 4], "texture": "#missing"}, + "up": {"uv": [0, 0, 0, 1], "texture": "#missing"}, + "down": {"uv": [0, 0, 0, 1], "texture": "#missing"} + } + }, + { + "name": "light", + "from": [13.99, 6, 9], + "to": [13.99, 10, 10], + "faces": { + "north": {"uv": [0, 0, 0, 4], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 4], "texture": "#missing"}, + "south": {"uv": [0, 0, 0, 4], "texture": "#missing"}, + "west": {"uv": [1.5, 13, 2, 15], "texture": "#0", "forge_data": {"block_light": 15}}, + "up": {"uv": [0, 0, 0, 1], "texture": "#missing"}, + "down": {"uv": [0, 0, 0, 1], "texture": "#missing"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "rotation": [90, 0, 0], + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "translation": [2, -1.25, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 0, -10], + "scale": [0.94, 0.94, 0.94] + }, + "fixed": { + "translation": [0, 0, -2.75], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_error.json index 4207d56fd..6ba86b60e 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_error.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_error.json @@ -1,9 +1,10 @@ { + "credit": "Made with Blockbench", "parent": "block/block", "texture_size": [64, 64], "textures": { - "texture": "overdrive_that_matters:block/matter_decomposer", - "particle": "overdrive_that_matters:block/matter_decomposer" + "particle": "overdrive_that_matters:block/matter_decomposer", + "texture": "overdrive_that_matters:block/matter_decomposer" }, "elements": [ { @@ -26,32 +27,83 @@ "east": {"uv": [4, 10.5, 8, 13.75], "texture": "#texture"}, "south": {"uv": [6, 7.25, 8, 10.5], "texture": "#texture"}, "west": {"uv": [8, 10.5, 12, 13.75], "texture": "#texture"}, - "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 7.25, 2, 11.25], "texture": "#texture"} + "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"} + } + }, + { + "name": "glow", + "from": [1, 5, 9], + "to": [7, 12, 15], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 3, 9], + "to": [7, 5, 15], + "faces": { + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { "from": [1, 3, 1], - "to": [7, 14, 7], + "to": [7, 5, 7], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { - "from": [1, 3, 9], + "name": "glow", + "from": [1, 5, 1], + "to": [7, 12, 7], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 12, 1], + "to": [7, 14, 7], + "faces": { + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + } + }, + { + "from": [1, 12, 9], "to": [7, 14, 15], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { @@ -59,11 +111,11 @@ "to": [8, 15, 13], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { @@ -71,11 +123,11 @@ "to": [8, 15, 5], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } } ] diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_idle.json index 4207d56fd..64954b86b 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_idle.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_idle.json @@ -1,9 +1,10 @@ { + "credit": "Made with Blockbench", "parent": "block/block", "texture_size": [64, 64], "textures": { - "texture": "overdrive_that_matters:block/matter_decomposer", - "particle": "overdrive_that_matters:block/matter_decomposer" + "particle": "overdrive_that_matters:block/matter_decomposer", + "texture": "overdrive_that_matters:block/matter_decomposer" }, "elements": [ { @@ -26,32 +27,73 @@ "east": {"uv": [4, 10.5, 8, 13.75], "texture": "#texture"}, "south": {"uv": [6, 7.25, 8, 10.5], "texture": "#texture"}, "west": {"uv": [8, 10.5, 12, 13.75], "texture": "#texture"}, - "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 7.25, 2, 11.25], "texture": "#texture"} + "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"} } }, { - "from": [1, 3, 1], + "from": [1, 12, 1], "to": [7, 14, 7], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + } + }, + { + "name": "glow", + "from": [1, 5, 1], + "to": [7, 12, 7], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 3, 1], + "to": [7, 5, 7], + "faces": { + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"} } }, { "from": [1, 3, 9], + "to": [7, 5, 15], + "faces": { + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"} + } + }, + { + "name": "glow", + "from": [1, 5, 9], + "to": [7, 12, 15], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 12, 9], "to": [7, 14, 15], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} } }, { @@ -59,11 +101,9 @@ "to": [8, 15, 13], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, - "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"} } }, { @@ -71,11 +111,9 @@ "to": [8, 15, 5], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, - "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"} } } ] diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_working.json index 6f9ca6c20..9d6fe8781 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_working.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_decomposer_working.json @@ -1,9 +1,10 @@ { + "credit": "Made with Blockbench", "parent": "block/block", "texture_size": [64, 64], "textures": { - "texture": "overdrive_that_matters:block/matter_decomposer", - "particle": "overdrive_that_matters:block/matter_decomposer" + "particle": "overdrive_that_matters:block/matter_decomposer", + "texture": "overdrive_that_matters:block/matter_decomposer" }, "elements": [ { @@ -26,32 +27,83 @@ "east": {"uv": [8, 7.25, 12, 10.5], "texture": "#texture"}, "south": {"uv": [6, 7.25, 8, 10.5], "texture": "#texture"}, "west": {"uv": [8, 10.5, 12, 13.75], "texture": "#texture"}, - "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 7.25, 2, 11.25], "texture": "#texture"} + "up": {"uv": [4, 13.75, 8, 15.25], "rotation": 90, "texture": "#texture"} + } + }, + { + "name": "glow", + "from": [1, 5, 9], + "to": [7, 12, 15], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 3, 9], + "to": [7, 5, 15], + "faces": { + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { "from": [1, 3, 1], - "to": [7, 14, 7], + "to": [7, 5, 7], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "north": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "east": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "south": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "west": {"uv": [0, 6.75, 1.5, 7.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { - "from": [1, 3, 9], + "name": "glow", + "from": [1, 5, 1], + "to": [7, 12, 7], + "faces": { + "north": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "east": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "south": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "west": {"uv": [0, 5, 1.5, 6.75], "texture": "#texture"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "from": [1, 12, 1], + "to": [7, 14, 7], + "faces": { + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} + } + }, + { + "from": [1, 12, 9], "to": [7, 14, 15], "faces": { - "north": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "east": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "south": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, - "west": {"uv": [0, 4.5, 1.5, 7.25], "texture": "#texture"}, + "north": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "east": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "south": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, + "west": {"uv": [0, 4.5, 1.5, 5], "texture": "#texture"}, "up": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"}, - "down": {"uv": [0, 3, 1.5, 4.5], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { @@ -59,11 +111,11 @@ "to": [8, 15, 13], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } }, { @@ -71,11 +123,11 @@ "to": [8, 15, 5], "faces": { "north": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0.5, 3.5, 1, 3.75], "texture": "#texture"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#texture"}, "south": {"uv": [1.75, 6, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.75, 6.75, 2.25, 7.25], "rotation": 90, "texture": "#texture"}, "up": {"uv": [2.25, 6, 2.75, 7.25], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [1.5, 5.25, 1.75, 7.25], "texture": "#texture"} + "down": {"uv": [0, 0, 0, 0], "texture": "#texture"} } } ] diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_reconstructor.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_reconstructor.json new file mode 100644 index 000000000..94d03904b --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_reconstructor.json @@ -0,0 +1,232 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "textures": { + "0": "overdrive_that_matters:block/matter_reconstructor", + "particle": "overdrive_that_matters:block/matter_reconstructor" + }, + "elements": [ + { + "name": "body", + "from": [0, 4, 0], + "to": [16, 8, 16], + "faces": { + "north": {"uv": [0, 0, 4, 2], "texture": "#0"}, + "east": {"uv": [8, 0, 4, 2], "texture": "#0"}, + "south": {"uv": [8, 0, 12, 2], "texture": "#0"}, + "west": {"uv": [4, 0, 8, 2], "texture": "#0"}, + "up": {"uv": [4, 4, 8, 12], "texture": "#0"}, + "down": {"uv": [4, 4, 8, 12], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 0, 0], + "to": [16, 4, 11], + "faces": { + "north": {"uv": [0, 2, 4, 4], "texture": "#0"}, + "east": {"uv": [6.75, 2, 4, 4], "texture": "#0"}, + "south": {"uv": [0, 2, 4, 4], "texture": "#0"}, + "west": {"uv": [4, 2, 6.75, 4], "texture": "#0"}, + "down": {"uv": [0, 4, 4, 9.5], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [13, 8, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 12, 0.75, 16], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0.75, 12, 4.75, 16], "texture": "#0"}, + "south": {"uv": [0, 12, 0.75, 16], "texture": "#0"}, + "west": {"uv": [4.75, 12, 8.75, 16], "texture": "#0"}, + "up": {"uv": [0, 10.5, 4, 12], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 8, 0], + "to": [3, 16, 16], + "faces": { + "north": {"uv": [0, 12, 0.75, 16], "texture": "#0"}, + "east": {"uv": [4.75, 12, 8.75, 16], "texture": "#0"}, + "south": {"uv": [0, 12, 0.75, 16], "rotation": 180, "texture": "#0"}, + "west": {"uv": [0.75, 12, 4.75, 16], "texture": "#0"}, + "up": {"uv": [0, 10.5, 4, 12], "rotation": 270, "texture": "#0"} + } + }, + { + "name": "chamber glass", + "from": [3, 8, 1], + "to": [13, 15, 15], + "faces": { + "north": {"uv": [8.75, 5.5, 11.25, 9], "texture": "#0"}, + "south": {"uv": [8.75, 5.5, 11.25, 9], "texture": "#0"}, + "up": {"uv": [8.75, 9, 11.25, 16], "texture": "#0"} + } + }, + { + "name": "shields", + "from": [-1, 8, 1], + "to": [0, 15, 15], + "faces": { + "north": {"uv": [11.25, 4, 13, 4.5], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 0, 3.5, 3.5], "texture": "#missing"}, + "south": {"uv": [11.25, 4, 13, 4.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [11.25, 4.5, 13, 11.5], "rotation": 270, "texture": "#0"}, + "up": {"uv": [13, 4.5, 13.25, 11.5], "texture": "#0"}, + "down": {"uv": [13, 4.5, 13.25, 11.5], "texture": "#0"} + } + }, + { + "name": "shields", + "from": [16, 8, 1], + "to": [17, 15, 15], + "faces": { + "north": {"uv": [11.25, 4, 13, 4.5], "rotation": 90, "texture": "#0"}, + "east": {"uv": [11.25, 4.5, 13, 11.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [11.25, 4, 13, 4.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 3.5, 3.5], "texture": "#missing"}, + "up": {"uv": [13, 4.5, 13.25, 11.5], "texture": "#0"}, + "down": {"uv": [13, 4.5, 13.25, 11.5], "texture": "#0"} + } + }, + { + "name": "item platform", + "from": [4, 8, 4], + "to": [12, 9, 12], + "faces": { + "north": {"uv": [11.25, 11.5, 13.25, 12], "texture": "#0"}, + "east": {"uv": [11.25, 11.5, 13.25, 12], "texture": "#0"}, + "south": {"uv": [11.25, 11.5, 13.25, 12], "texture": "#0"}, + "west": {"uv": [11.25, 11.5, 13.25, 12], "texture": "#0"}, + "up": {"uv": [11.25, 12, 13.25, 16], "texture": "#0"} + } + }, + { + "name": "matter storage", + "from": [14, 0, 11], + "to": [15, 4, 15], + "faces": { + "east": {"uv": [6.75, 2, 7.75, 4], "texture": "#0"}, + "south": {"uv": [11, 2, 11.25, 4], "texture": "#0"}, + "down": {"uv": [11, 2, 11.25, 4], "texture": "#0"} + } + }, + { + "name": "matter storage", + "from": [1, 0, 11], + "to": [3, 4, 15], + "faces": { + "south": {"uv": [7.75, 2, 8.25, 4], "texture": "#0"}, + "west": {"uv": [6.75, 2, 7.75, 4], "texture": "#0"}, + "down": {"uv": [7.75, 2, 8.25, 4], "texture": "#0"} + } + }, + { + "name": "matter glow", + "from": [3, 0, 11], + "to": [14, 4, 15], + "faces": { + "south": {"uv": [8.25, 2, 11, 4], "texture": "#0"}, + "down": {"uv": [8.25, 2, 11, 4], "texture": "#0"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "manipulator", + "from": [6, 6, 10.9], + "to": [7, 11, 11.9], + "rotation": {"angle": 45, "axis": "x", "origin": [6.5, 8.5, 12.4]}, + "faces": { + "north": {"uv": [8, 4.5, 9.25, 5], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 4, 9.25, 4.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [8, 4.5, 9.25, 5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [8, 4, 9.25, 4.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "manipulator", + "from": [6, 10, 8.1], + "to": [7, 11, 9.1], + "rotation": {"angle": 45, "axis": "x", "origin": [6.5, 10, 10.4]}, + "faces": { + "north": {"uv": [9.25, 4, 9.5, 4.5], "texture": "#0"}, + "east": {"uv": [9.25, 4, 9.5, 4.5], "texture": "#0"}, + "south": {"uv": [9.25, 4, 9.5, 4.5], "texture": "#0"}, + "west": {"uv": [9.25, 4, 9.5, 4.5], "texture": "#0"} + } + }, + { + "name": "manipulator tip", + "from": [6, 9, 8.1], + "to": [7, 10, 9.1], + "rotation": {"angle": 45, "axis": "x", "origin": [6.5, 10, 10.4]}, + "faces": { + "north": {"uv": [9.25, 4.5, 9.5, 5], "texture": "#0"}, + "east": {"uv": [9.25, 4.5, 9.5, 5], "texture": "#0"}, + "south": {"uv": [9.25, 4.5, 9.5, 5], "texture": "#0"}, + "west": {"uv": [9.25, 4.5, 9.5, 5], "texture": "#0"}, + "down": {"uv": [9.5, 4.5, 9.75, 5], "texture": "#0"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "manipulator", + "from": [5.9, 10.5, 9.4], + "to": [7.1, 12.5, 14.4], + "rotation": {"angle": 0, "axis": "x", "origin": [6.5, 12.5, 13.9]}, + "faces": { + "north": {"uv": [8, 5.5, 8.5, 6.1], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 6, 8.5, 8.5], "rotation": 270, "texture": "#0"}, + "south": {"uv": [8, 5.5, 8.5, 6.1], "rotation": 90, "texture": "#0"}, + "west": {"uv": [8, 6, 8.5, 8.5], "rotation": 270, "texture": "#0"}, + "up": {"uv": [8.5, 6, 8.75, 8.5], "texture": "#0"}, + "down": {"uv": [8, 6, 8.25, 8.5], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "emerald lens", + "from": [7, 8, 3], + "to": [9, 10, 5], + "faces": { + "north": {"uv": [8, 10.5, 8.5, 11.5], "texture": "#0"}, + "east": {"uv": [8, 10.5, 8.5, 11.5], "texture": "#0"}, + "south": {"uv": [8, 10.5, 8.5, 11.5], "texture": "#0"}, + "west": {"uv": [8, 10.5, 8.5, 11.5], "texture": "#0"}, + "up": {"uv": [8, 9.5, 8.5, 10.5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_error.json index 5b894ac93..676bb08aa 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_error.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_error.json @@ -1,8 +1,9 @@ { "parent": "overdrive_that_matters:block/matter_replicator_working", - "texture_size": [32, 64], + "texture_size": [32, 32], "textures": { - "particle": "overdrive_that_matters:block/matter_replicator_halted", + "1": "overdrive_that_matters:block/matter_replicator_base", + "particle": "overdrive_that_matters:block/matter_replicator_base", "texture": "overdrive_that_matters:block/matter_replicator_halted" } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_idle.json index 06f1f7254..dc7a117ad 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_idle.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_idle.json @@ -1,8 +1,9 @@ { "parent": "overdrive_that_matters:block/matter_replicator_working", - "texture_size": [32, 64], + "texture_size": [32, 32], "textures": { - "particle": "overdrive_that_matters:block/matter_replicator_offline", + "1": "overdrive_that_matters:block/matter_replicator_base", + "particle": "overdrive_that_matters:block/matter_replicator_base", "texture": "overdrive_that_matters:block/matter_replicator_offline" } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_working.json index 4d9184f06..db7a07bee 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_working.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_replicator_working.json @@ -1,7 +1,11 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "render_type": "translucent", + "texture_size": [32, 32], "textures": { - "particle": "overdrive_that_matters:block/matter_replicator", + "1": "overdrive_that_matters:block/matter_replicator_base", + "particle": "overdrive_that_matters:block/matter_replicator_base", "texture": "overdrive_that_matters:block/matter_replicator" }, "elements": [ @@ -10,12 +14,12 @@ "from": [15, 0, 0], "to": [16, 11, 16], "faces": { - "north": {"uv": [0, 1.25, 0.5, 4], "texture": "#texture"}, - "east": {"uv": [0, 5.25, 8, 8], "texture": "#texture"}, - "south": {"uv": [15.5, 1.25, 16, 4], "texture": "#texture"}, - "west": {"uv": [8, 9, 16, 11.75], "texture": "#texture"}, - "up": {"uv": [0, 8, 0.5, 12], "rotation": 180, "texture": "#texture"}, - "down": {"uv": [8, 8, 8.5, 12], "texture": "#texture"} + "north": {"uv": [0, 2.5, 0.5, 8], "texture": "#texture"}, + "east": {"uv": [0, 2.5, 8, 8], "texture": "#1"}, + "south": {"uv": [15.5, 2.5, 16, 8], "texture": "#texture"}, + "west": {"uv": [8, 9.5, 16, 15], "texture": "#1"}, + "up": {"uv": [0, 8, 0.5, 16], "rotation": 180, "texture": "#1"}, + "down": {"uv": [8, 8, 8.5, 16], "texture": "#1"} } }, { @@ -23,12 +27,12 @@ "from": [0, 0, 0], "to": [1, 11, 16], "faces": { - "north": {"uv": [7.5, 1.25, 8, 4], "texture": "#texture"}, - "east": {"uv": [8, 9, 16, 11.75], "texture": "#texture"}, - "south": {"uv": [8, 1.25, 8.5, 4], "texture": "#texture"}, - "west": {"uv": [8, 5.25, 16, 8], "texture": "#texture"}, - "up": {"uv": [7.5, 8, 8, 12], "rotation": 180, "texture": "#texture"}, - "down": {"uv": [15.5, 8, 16, 12], "texture": "#texture"} + "north": {"uv": [7.5, 2.5, 8, 8], "texture": "#texture"}, + "east": {"uv": [8, 9.5, 16, 15], "texture": "#1"}, + "south": {"uv": [8, 2.5, 8.5, 8], "texture": "#1"}, + "west": {"uv": [8, 2.5, 16, 8], "texture": "#1"}, + "up": {"uv": [7.5, 8, 8, 16], "rotation": 180, "texture": "#1"}, + "down": {"uv": [15.5, 8, 16, 16], "texture": "#1"} } }, { @@ -36,12 +40,10 @@ "from": [0, 11, 1], "to": [8, 16, 6], "faces": { - "north": {"uv": [4, 13.25, 8, 14.5], "texture": "#texture"}, - "east": {"uv": [0, 0, 5, 5], "texture": "#missing"}, - "south": {"uv": [4, 14.5, 8, 15.75], "texture": "#texture"}, - "west": {"uv": [8.5, 4, 11, 5.25], "texture": "#texture"}, - "up": {"uv": [4, 12, 8, 13.25], "texture": "#texture"}, - "down": {"uv": [0, 0, 8, 5], "texture": "#missing"} + "north": {"uv": [4, 10.5, 8, 13], "texture": "#texture"}, + "south": {"uv": [4, 13, 8, 15.5], "texture": "#texture"}, + "west": {"uv": [8.5, 0, 11, 2.5], "texture": "#1"}, + "up": {"uv": [4, 8, 8, 10.5], "texture": "#texture"} } }, { @@ -49,64 +51,122 @@ "from": [8, 11, 0], "to": [16, 16, 6], "faces": { - "north": {"uv": [0, 0, 4, 1.25], "texture": "#texture"}, - "east": {"uv": [5, 4, 8, 5.25], "texture": "#texture"}, - "south": {"uv": [0, 12.25, 4, 13.5], "rotation": 180, "texture": "#texture"}, - "west": {"uv": [5, 4, 8, 5.25], "texture": "#texture"}, - "up": {"uv": [0, 12, 4, 13.5], "texture": "#texture"}, - "down": {"uv": [0, 0, 8, 6], "texture": "#missing"} + "north": {"uv": [0, 0, 4, 2.5], "texture": "#texture"}, + "east": {"uv": [5, 0, 8, 2.5], "texture": "#1"}, + "south": {"uv": [0, 8.5, 4, 11], "rotation": 180, "texture": "#texture"}, + "west": {"uv": [5, 0, 8, 2.5], "texture": "#1"}, + "up": {"uv": [0, 8, 4, 11], "texture": "#texture"} + } + }, + { + "name": "canisterlight", + "from": [5, 11, 11], + "to": [14, 15, 15], + "faces": { + "north": {"uv": [11, 0, 15.5, 2], "rotation": 180, "texture": "#texture"}, + "south": {"uv": [11, 0, 15.5, 2], "texture": "#texture"}, + "up": {"uv": [11, 0, 15.5, 2], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "canister", + "from": [14, 11, 11], + "to": [15, 15, 15], + "faces": { + "north": {"uv": [15.5, 0, 16, 2], "rotation": 180, "texture": "#texture"}, + "east": {"uv": [8, 0, 10, 2], "texture": "#texture"}, + "south": {"uv": [15.5, 0, 16, 2], "texture": "#texture"}, + "up": {"uv": [15.5, 0, 16, 2], "texture": "#texture"} } }, { "name": "canister", "from": [3, 11, 11], - "to": [15, 15, 15], + "to": [5, 15, 15], "faces": { - "north": {"uv": [10, 12, 16, 13], "rotation": 180, "texture": "#texture"}, - "east": {"uv": [8, 12, 10, 13], "texture": "#texture"}, - "south": {"uv": [10, 12, 16, 13], "texture": "#texture"}, - "west": {"uv": [8, 12, 10, 13], "texture": "#texture"}, - "up": {"uv": [10, 12, 16, 13], "texture": "#texture"}, - "down": {"uv": [0, 0, 6, 1], "texture": "#missing"} + "north": {"uv": [10, 0, 11, 2], "rotation": 180, "texture": "#texture"}, + "south": {"uv": [10, 0, 11, 2], "texture": "#texture"}, + "west": {"uv": [8, 0, 10, 2], "texture": "#texture"}, + "up": {"uv": [10, 0, 11, 2], "texture": "#texture"} } }, { "name": "canister", "from": [3, 11, 6], + "to": [5, 15, 10], + "faces": { + "south": {"uv": [10, 0, 11, 2], "texture": "#texture"}, + "west": {"uv": [8, 0, 10, 2], "texture": "#texture"}, + "up": {"uv": [10, 0, 11, 2], "texture": "#texture"}, + "down": {"uv": [0, 0, 6, 1], "texture": "#missing"} + } + }, + { + "name": "canisterlight", + "from": [5, 11, 6], + "to": [14, 15, 10], + "faces": { + "south": {"uv": [11, 0, 15.5, 2], "texture": "#texture"}, + "up": {"uv": [11, 0, 15.5, 2], "texture": "#texture"}, + "down": {"uv": [0, 0, 6, 1], "texture": "#missing"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "canister", + "from": [14, 11, 6], "to": [15, 15, 10], "faces": { - "north": {"uv": [0, 0, 6, 1], "texture": "#missing"}, - "east": {"uv": [8, 12, 10, 13], "texture": "#texture"}, - "south": {"uv": [10, 12, 16, 13], "texture": "#texture"}, - "west": {"uv": [8, 12, 10, 13], "texture": "#texture"}, - "up": {"uv": [10, 12, 16, 13], "texture": "#texture"}, + "east": {"uv": [8, 0, 10, 2], "texture": "#texture"}, + "south": {"uv": [15.5, 0, 16, 2], "texture": "#texture"}, + "up": {"uv": [15.5, 0, 16, 2], "texture": "#texture"}, "down": {"uv": [0, 0, 6, 1], "texture": "#missing"} } }, { "name": "pipe", - "from": [1, 11, 7], + "from": [0, 11, 7], "to": [3, 14, 9], "faces": { - "north": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#missing"}, - "south": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "west": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "up": {"uv": [0, 13.5, 1, 14], "texture": "#texture"}, - "down": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} + "north": {"uv": [1, 12, 0, 13.5], "texture": "#texture"}, + "south": {"uv": [0, 12, 1, 13.5], "texture": "#texture"}, + "west": {"uv": [0, 12, 1, 13.5], "texture": "#texture"}, + "up": {"uv": [0, 11, 1, 12], "texture": "#texture"} } }, { "name": "pipe", - "from": [1, 11, 12], + "from": [0, 11, 12], "to": [3, 14, 14], "faces": { - "north": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "east": {"uv": [0, 0, 1, 0.75], "texture": "#missing"}, - "south": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "west": {"uv": [0, 14, 1, 14.75], "texture": "#texture"}, - "up": {"uv": [0, 13.5, 1, 14], "texture": "#texture"}, - "down": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} + "north": {"uv": [1, 12, 0, 13.5], "texture": "#texture"}, + "south": {"uv": [0, 12, 1, 13.5], "texture": "#texture"}, + "west": {"uv": [0, 12, 1, 13.5], "texture": "#texture"}, + "up": {"uv": [0, 11, 1, 12], "texture": "#texture"} + } + }, + { + "name": "frame", + "from": [15.5, 11, 15], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "east": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "south": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "west": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "up": {"uv": [2, 15, 2.5, 15.5], "texture": "#texture"} + } + }, + { + "name": "frame", + "from": [15.5, 15, 6], + "to": [16, 16, 15], + "faces": { + "east": {"uv": [0, 15.5, 4.5, 16], "texture": "#texture"}, + "west": {"uv": [0, 15.5, 4.5, 16], "texture": "#texture"}, + "up": {"uv": [0, 15.5, 4.5, 16], "rotation": 90, "texture": "#texture"}, + "down": {"uv": [0, 15.5, 4.5, 16], "rotation": 90, "texture": "#texture"} } }, { @@ -114,12 +174,11 @@ "from": [0, 11, 15], "to": [0.5, 16, 16], "faces": { - "north": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "south": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "west": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "up": {"uv": [2, 15.5, 2.5, 15.75], "texture": "#texture"}, - "down": {"uv": [0, 0, 0, 1], "texture": "#missing"} + "north": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "east": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "south": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "west": {"uv": [0, 15, 2.5, 15.5], "rotation": 270, "texture": "#texture"}, + "up": {"uv": [2, 15, 2.5, 15.5], "texture": "#texture"} } }, { @@ -127,146 +186,143 @@ "from": [0, 15, 6], "to": [0.5, 16, 15], "faces": { - "north": {"uv": [0, 0, 0, 1], "texture": "#missing"}, - "east": {"uv": [0, 15.75, 4.5, 16], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 1], "texture": "#missing"}, - "west": {"uv": [0, 15.75, 4.5, 16], "texture": "#texture"}, - "up": {"uv": [0, 15.75, 4.5, 16], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 15.75, 4.5, 16], "rotation": 90, "texture": "#texture"} - } - }, - { - "name": "frame", - "from": [15.4, 11, 15], - "to": [15.9, 16, 16], - "faces": { - "north": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "east": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "south": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "west": {"uv": [0, 15.5, 2.5, 15.75], "rotation": 270, "texture": "#texture"}, - "up": {"uv": [2, 15.5, 2.5, 15.75], "texture": "#texture"}, - "down": {"uv": [0, 0, 0, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [15.4, 15, 6], - "to": [15.9, 16, 15], - "faces": { - "north": {"uv": [0, 0, 0, 1], "texture": "#missing"}, - "east": {"uv": [0, 15.75, 4.5, 16], "texture": "#texture"}, - "south": {"uv": [0, 0, 0, 1], "texture": "#missing"}, - "west": {"uv": [0, 15.75, 4.5, 16], "texture": "#texture"}, - "up": {"uv": [0, 15.75, 4.5, 16], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 15.75, 4.5, 16], "rotation": 90, "texture": "#texture"} + "east": {"uv": [0, 15.5, 4.5, 16], "texture": "#texture"}, + "west": {"uv": [0, 15.5, 4.5, 16], "texture": "#texture"}, + "up": {"uv": [0, 15.5, 4.5, 16], "rotation": 90, "texture": "#texture"}, + "down": {"uv": [0, 15.5, 4.5, 16], "rotation": 90, "texture": "#texture"} } }, { + "name": "body", "from": [1, 0, 0], "to": [15, 1, 16], "faces": { - "north": {"uv": [0.5, 3.75, 7.5, 4], "texture": "#texture"}, - "east": {"uv": [0, 0, 8, 0.25], "texture": "#missing"}, - "south": {"uv": [8.5, 3.75, 15.5, 4], "texture": "#texture"}, - "west": {"uv": [0, 0, 8, 0.25], "texture": "#missing"}, - "up": {"uv": [0.5, 8, 7.5, 12], "rotation": 180, "texture": "#texture"}, - "down": {"uv": [8.5, 8, 15.5, 12], "texture": "#texture"} + "north": {"uv": [0.5, 7.5, 7.5, 8], "texture": "#texture"}, + "south": {"uv": [8.5, 7.5, 15.5, 8], "texture": "#texture"}, + "up": {"uv": [0.5, 8.5, 7.5, 15.5], "rotation": 180, "texture": "#1"}, + "down": {"uv": [8.5, 8, 15.5, 16], "texture": "#1"} } }, { + "name": "body", "from": [8, 7, 0], "to": [15, 11, 1], "faces": { - "north": {"uv": [0.5, 1.25, 4, 2.25], "texture": "#texture"}, - "east": {"uv": [0, 0, 0.5, 0.75], "texture": "#missing"}, - "south": {"uv": [0, 0, 3.5, 1], "texture": "#texture"}, - "west": {"uv": [3.5, 1.25, 4, 2], "texture": "#texture"}, - "up": {"uv": [0, 0, 3.5, 0.25], "texture": "#missing"}, - "down": {"uv": [0.5, 2, 4, 2.25], "texture": "#texture"} + "north": {"uv": [0.5, 2.5, 4, 4.5], "texture": "#texture"}, + "south": {"uv": [4, 2.5, 0.5, 4.5], "texture": "#texture"}, + "west": {"uv": [3.5, 2.5, 4, 4.5], "texture": "#texture"}, + "down": {"uv": [0.5, 4, 4, 4.5], "texture": "#texture"} } }, { + "name": "body", "from": [1, 9, 0], "to": [8, 11, 1], "faces": { - "north": {"uv": [4, 1.25, 7.5, 1.75], "texture": "#texture"}, + "north": {"uv": [4, 2.5, 7, 3.5], "texture": "#texture"}, "east": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, - "south": {"uv": [4, 1.25, 7, 1.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0.5, 0.5], "texture": "#missing"}, - "up": {"uv": [4, 11.75, 7.5, 12], "texture": "#texture"}, - "down": {"uv": [1.5, 3.5, 5, 3.75], "texture": "#texture"} + "south": {"uv": [4, 3, 7, 3.5], "rotation": 180, "texture": "#texture"}, + "up": {"uv": [4, 15.5, 7.5, 16], "texture": "#1"}, + "down": {"uv": [4, 3, 7, 3.5], "rotation": 180, "texture": "#texture"} } }, { + "name": "body", "from": [1, 1, 0], "to": [2, 9, 1], "faces": { - "north": {"uv": [7, 1.75, 7.5, 3.75], "texture": "#texture"}, - "east": {"uv": [7, 1.75, 7.5, 3.75], "texture": "#texture"}, - "south": {"uv": [7, 1.75, 7.5, 3.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0.5, 2], "texture": "#missing"}, - "up": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, - "down": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"} + "north": {"uv": [7, 3.5, 7.5, 7], "texture": "#texture"}, + "east": {"uv": [7, 3.5, 7.5, 7], "texture": "#texture"}, + "south": {"uv": [7, 3.5, 7.5, 7], "texture": "#texture"} } }, { + "name": "body", "from": [14, 1, 0], "to": [15, 7, 1], "faces": { - "north": {"uv": [0.5, 2.25, 1, 3.75], "texture": "#texture"}, - "east": {"uv": [0, 0, 0.5, 1.75], "texture": "#missing"}, - "south": {"uv": [0.5, 2.25, 1, 3.75], "texture": "#texture"}, - "west": {"uv": [0.5, 2.25, 1, 3.75], "texture": "#texture"}, - "up": {"uv": [0.5, 3.25, 1, 3.5], "texture": "#texture"}, - "down": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"} + "north": {"uv": [0.5, 4.5, 1, 7], "texture": "#texture"}, + "south": {"uv": [0.5, 4.5, 1, 7], "texture": "#texture"}, + "west": {"uv": [0.5, 4.5, 1, 7], "texture": "#texture"} } }, { + "name": "body", "from": [2, 1, 0], "to": [14, 2, 1], "faces": { - "north": {"uv": [1, 3.5, 7, 3.75], "texture": "#texture"}, - "east": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, - "south": {"uv": [1, 3.5, 7, 3.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"}, - "up": {"uv": [1, 3.5, 7, 3.75], "texture": "#texture"}, - "down": {"uv": [0, 0, 6, 0.25], "texture": "#missing"} + "north": {"uv": [1, 7, 7, 7.5], "texture": "#texture"}, + "south": {"uv": [1, 7, 7, 7.5], "texture": "#texture"}, + "up": {"uv": [1, 7, 7, 7.5], "texture": "#texture"} } }, { + "name": "body", "from": [1, 10, 1], "to": [15, 11, 15], "faces": { - "north": {"uv": [0, 0, 7, 0.25], "texture": "#missing"}, - "east": {"uv": [0, 0, 7, 0.25], "texture": "#missing"}, - "south": {"uv": [0, 0, 7, 0.25], "texture": "#missing"}, - "west": {"uv": [0, 0, 7, 0.25], "texture": "#missing"}, - "up": {"uv": [0.5, 8.25, 7.5, 11.75], "rotation": 180, "texture": "#texture"}, - "down": {"uv": [8.5, 8.25, 15.5, 11.75], "texture": "#texture"} + "up": {"uv": [0.5, 8.5, 7.5, 15.5], "rotation": 180, "texture": "#1"}, + "down": {"uv": [8.5, 8.5, 15.5, 15.5], "texture": "#1"} } }, { + "name": "body", "from": [1, 1, 15], "to": [15, 11, 16], "faces": { - "north": {"uv": [8.5, 9, 15.5, 11.5], "texture": "#texture"}, - "east": {"uv": [0, 0, 0.5, 2.5], "texture": "#missing"}, - "south": {"uv": [8.5, 1.25, 15.5, 3.75], "texture": "#texture"}, - "west": {"uv": [0, 0, 0.5, 2.5], "texture": "#missing"}, - "up": {"uv": [0.5, 8, 7.5, 8.25], "texture": "#texture"}, - "down": {"uv": [0, 0, 7, 0.25], "texture": "#missing"} + "north": {"uv": [8.5, 9.5, 15.5, 14.5], "texture": "#1"}, + "south": {"uv": [8.5, 2.5, 15.5, 7.5], "texture": "#texture"}, + "up": {"uv": [0.5, 8, 7.5, 8.5], "texture": "#1"} } }, { + "name": "body", "from": [3, 1, 3], "to": [13, 2, 13], "faces": { - "north": {"uv": [10.5, 13, 16, 13.25], "texture": "#texture"}, - "east": {"uv": [10.5, 13, 16, 13.25], "texture": "#texture"}, - "south": {"uv": [10.5, 13, 16, 13.25], "texture": "#texture"}, - "west": {"uv": [10.5, 13, 16, 13.25], "texture": "#texture"}, - "up": {"uv": [10.5, 13, 16, 15.75], "texture": "#texture"}, - "down": {"uv": [0, 0, 0.5, 0.25], "texture": "#missing"} + "north": {"uv": [10.5, 13, 16, 13.5], "texture": "#texture"}, + "east": {"uv": [10.5, 13, 16, 13.5], "texture": "#texture"}, + "south": {"uv": [10.5, 13, 16, 13.5], "texture": "#texture"}, + "west": {"uv": [10.5, 13, 16, 13.5], "texture": "#texture"}, + "up": {"uv": [10.5, 8, 16, 13.5], "texture": "#texture"} + } + }, + { + "from": [-2, 10, 7.1], + "to": [0, 14, 8.9], + "rotation": {"angle": 22.5, "axis": "z", "origin": [0, 14, 8]}, + "faces": { + "north": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "south": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "west": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "up": {"uv": [8.5, 8.5, 9.5, 9.5], "texture": "#texture"}, + "down": {"uv": [8.5, 11.5, 9.5, 12.5], "texture": "#texture"} + } + }, + { + "name": "pipestuff", + "from": [-2, 10, 12.1], + "to": [0, 14, 13.9], + "rotation": {"angle": 22.5, "axis": "z", "origin": [0, 14, 13]}, + "faces": { + "north": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "south": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "west": {"uv": [8.5, 9.5, 9.5, 11.5], "texture": "#texture"}, + "up": {"uv": [8.5, 8.5, 9.5, 9.5], "texture": "#texture"}, + "down": {"uv": [8.5, 11.5, 9.5, 12.5], "texture": "#texture"} + } + }, + { + "name": "pipestuff", + "from": [-2, 10, 12.1], + "to": [0, 14, 13.9], + "rotation": {"angle": 22.5, "axis": "z", "origin": [0, 14, 8]}, + "faces": { + "north": {"uv": [8.5, 13.75, 9.5, 14.75], "texture": "#texture"}, + "south": {"uv": [8.5, 13.75, 9.5, 14.75], "texture": "#texture"}, + "west": {"uv": [8.5, 13.75, 9.5, 14.75], "texture": "#texture"}, + "up": {"uv": [8.5, 13.25, 9.5, 13.75], "texture": "#texture"}, + "down": {"uv": [8.5, 14.75, 9.5, 15.25], "texture": "#texture"} } } ] diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk.json b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk.json new file mode 100644 index 000000000..02695dce8 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "overdrive_that_matters:block/decorative/metal_junk_a" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_b.json b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_b.json new file mode 100644 index 000000000..f0e21c790 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_b.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "overdrive_that_matters:block/decorative/metal_junk_b" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_c.json b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_c.json new file mode 100644 index 000000000..29eebec2b --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_c.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "overdrive_that_matters:block/decorative/metal_junk_c" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_d.json b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_d.json new file mode 100644 index 000000000..aa4b745af --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/metal_junk_d.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "overdrive_that_matters:block/decorative/metal_junk_d" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/metal_mesh.json b/src/main/resources/assets/overdrive_that_matters/models/block/metal_mesh.json new file mode 100644 index 000000000..b18a8d695 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/metal_mesh.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/cube_all", + "render_type": "cutout", + "textures": { + "all": "overdrive_that_matters:block/decorative/metal_mesh" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model1.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model1.json new file mode 100644 index 000000000..bb1a26d99 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model1.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 13, + 2, + 2 + ], + "to": [ + 13.3, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model2.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model2.json new file mode 100644 index 000000000..4be6cdcce --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model2.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 11, + 2, + 2 + ], + "to": [ + 11.3, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model3.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model3.json new file mode 100644 index 000000000..3b85bbf4a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model3.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 9, + 2, + 2 + ], + "to": [ + 9.3, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model4.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model4.json new file mode 100644 index 000000000..eb86425dd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model4.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 7, + 2, + 2 + ], + "to": [ + 7.3, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model5.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model5.json new file mode 100644 index 000000000..585684cc6 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model5.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 5, + 2, + 2 + ], + "to": [ + 5.3, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model6.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model6.json new file mode 100644 index 000000000..4f5a1ee88 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model6.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 3, + 2, + 2 + ], + "to": [ + 3.3000002, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model7.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model7.json new file mode 100644 index 000000000..fea7a32c6 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern/model7.json @@ -0,0 +1,80 @@ +{ + "elements": [ + { + "faces": { + "down": { + "texture": "#texture", + "uv": [ + 1.0, + 14.0, + 15.0, + 15.0 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 15.0, + 1.0, + 1.0, + 15.0 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 2.0, + 15.0 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 14.0, + 1.0, + 15.0, + 15.0 + ] + }, + "up": { + "rotation": 90, + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 2.0 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 1.0, + 1.0, + 15.0, + 15.0 + ] + } + }, + "from": [ + 1, + 2, + 2 + ], + "to": [ + 1.3000002, + 14, + 14 + ] + } + ], + "texture_size": [ + 32.0, + 32.0 + ], + "textures": { + "texture": "overdrive_that_matters:item/pattern_drive_tier0" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/pattern_storage_pattern.json b/src/main/resources/assets/overdrive_that_matters/models/block/pattern_storage_pattern.json index 52099239b..5a7ad6e22 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/pattern_storage_pattern.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/pattern_storage_pattern.json @@ -16,5 +16,5 @@ "down": {"uv": [1, 14, 15, 15], "texture": "#texture"} } } - ], + ] } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_error.json index b28894a7f..f94bc7c12 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_error.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_error.json @@ -1,218 +1,243 @@ { - "parent" : "block/block", + "credit": "Made with Blockbench", + "texture_size": [64, 32], "textures": { - "particle": "overdrive_that_matters:block/plate_press", - "body": "overdrive_that_matters:block/plate_press" + "0": "overdrive_that_matters:block/plate_press2", + "particle": "overdrive_that_matters:block/plate_press2" }, "elements": [ { - "name": "body", - "from": [0, 0, 0], - "to": [16, 8, 16], + "from": [10, 0, 0], + "to": [16, 6, 16], "faces": { - "north": {"uv": [0, 4, 4, 8], "texture": "#body"}, - "east": {"uv": [12, 4, 16, 8], "texture": "#body"}, - "south": {"uv": [4, 4, 8, 8], "texture": "#body"}, - "west": {"uv": [8, 4, 12, 8], "texture": "#body"}, - "up": {"uv": [4, 8, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 8, 4, 16], "texture": "#body"} + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [0, 8, 7], - "to": [16, 12, 16], + "from": [10, 10, 0], + "to": [16, 16, 16], "faces": { - "north": {"uv": [4, 12, 8, 14], "texture": "#body"}, - "east": {"uv": [12, 2, 14.25, 4], "texture": "#body"}, - "south": {"uv": [4, 2, 8, 4], "texture": "#body"}, - "west": {"uv": [9.75, 2, 12, 4], "texture": "#body"}, - "up": {"uv": [4, 12, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 0, 16, 9], "texture": "#missing"} + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [12, 8, 1], - "to": [16, 12, 7], + "from": [10, 6, 2], + "to": [16, 10, 16], "faces": { - "north": {"uv": [13.25, 0, 14.25, 2], "texture": "#body"}, - "east": {"uv": [14.25, 2, 15.75, 4], "texture": "#body"}, - "south": {"uv": [0, 0, 4, 4], "texture": "#missing"}, - "west": {"uv": [15.75, 2, 14.25, 4], "texture": "#body"}, - "up": {"uv": [14.25, 0, 15.75, 2], "rotation": 270, "texture": "#body"}, - "down": {"uv": [0, 0, 4, 6], "texture": "#missing"} + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} } }, { - "name": "press", - "from": [2, 8, 4], - "to": [11, 16, 13], + "from": [11, 6, 1], + "to": [15, 10, 2], "faces": { - "north": {"uv": [8.1, 8, 10.25, 12], "texture": "#body"}, - "east": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "south": {"uv": [8, 12, 10.25, 16], "texture": "#body"}, - "west": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "up": {"uv": [12.5, 8, 14.75, 12.5], "texture": "#body"}, - "down": {"uv": [0, 0, 9, 9], "texture": "#missing"} - } - }, - { - "name": "screenholder", - "from": [13, 8, 0], - "to": [15, 12, 1], - "faces": { - "north": {"uv": [4, 0, 4.5, 2], "texture": "#body"}, - "east": {"uv": [4.25, 0, 4.5, 2], "texture": "#body"}, - "south": {"uv": [0, 0, 2, 4], "texture": "#missing"}, - "west": {"uv": [4, 0, 4.25, 2], "texture": "#body"}, - "up": {"uv": [4, 0, 4.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 2, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [15, 12, 15], - "to": [16, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0, 12, 15], - "to": [1, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0.3, 13, 1], - "to": [0.8, 14, 15], - "faces": { - "north": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "east": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "south": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "west": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "up": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"} - } - }, - { - "name": "frame", - "from": [0, 8, 0], - "to": [1, 14, 1], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [1, 13, 15.2], - "to": [15, 14, 15.7], - "faces": { - "north": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "east": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "south": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "west": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "up": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"} + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} } }, { "name": "screen", - "from": [12, 10, 0], - "to": [16, 14, 1], + "from": [11, 4, 2], + "to": [15, 8, 3], "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, "faces": { - "north": {"uv": [2, 0, 3, 2], "texture": "#body"}, - "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "south": {"uv": [3, 0, 4, 2], "texture": "#body"}, - "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "up": {"uv": [3, 2, 4, 2.5], "texture": "#body"}, - "down": {"uv": [3, 2, 4, 2.5], "texture": "#body"} + "north": {"uv": [2, 0, 3, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 9], - "to": [12, 14, 12], + "from": [1, 8, 3], + "to": [9, 15, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 5], - "to": [12, 14, 8], + "from": [1, 0, 0], + "to": [9, 7, 16], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "east": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "south": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "west": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "up": {"uv": [14, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [14, 0, 16, 8], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 5], - "to": [2, 14, 8], + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 9], - "to": [2, 14, 12], + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gas", - "from": [12.4, 12, 2], - "to": [15.4, 15, 14], + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], "faces": { - "north": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "east": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 180, "texture": "#body"}, - "south": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "west": {"uv": [4.5, 0, 7.5, 1.5], "texture": "#body"}, - "up": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 3, 12], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} } } - ] + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + } + } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_idle.json index 7a89ce8e7..0dd2264f2 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_idle.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_idle.json @@ -1,218 +1,267 @@ { - "parent" : "block/block", + "credit": "Made with Blockbench", + "texture_size": [64, 32], "textures": { - "particle": "overdrive_that_matters:block/plate_press", - "body": "overdrive_that_matters:block/plate_press" + "0": "overdrive_that_matters:block/plate_press2", + "particle": "overdrive_that_matters:block/plate_press2" }, "elements": [ { - "name": "body", - "from": [0, 0, 0], - "to": [16, 8, 16], + "from": [10, 0, 0], + "to": [16, 6, 16], "faces": { - "north": {"uv": [0, 4, 4, 8], "texture": "#body"}, - "east": {"uv": [12, 4, 16, 8], "texture": "#body"}, - "south": {"uv": [4, 4, 8, 8], "texture": "#body"}, - "west": {"uv": [8, 4, 12, 8], "texture": "#body"}, - "up": {"uv": [4, 8, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 8, 4, 16], "texture": "#body"} + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [0, 8, 7], - "to": [16, 12, 16], + "from": [10, 10, 0], + "to": [16, 16, 16], "faces": { - "north": {"uv": [4, 12, 8, 14], "texture": "#body"}, - "east": {"uv": [12, 2, 14.25, 4], "texture": "#body"}, - "south": {"uv": [4, 2, 8, 4], "texture": "#body"}, - "west": {"uv": [9.75, 2, 12, 4], "texture": "#body"}, - "up": {"uv": [4, 12, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 0, 16, 9], "texture": "#missing"} + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [12, 8, 1], - "to": [16, 12, 7], + "from": [10, 6, 2], + "to": [16, 10, 16], "faces": { - "north": {"uv": [13.25, 0, 14.25, 2], "texture": "#body"}, - "east": {"uv": [14.25, 2, 15.75, 4], "texture": "#body"}, - "south": {"uv": [0, 0, 4, 4], "texture": "#missing"}, - "west": {"uv": [15.75, 2, 14.25, 4], "texture": "#body"}, - "up": {"uv": [14.25, 0, 15.75, 2], "rotation": 270, "texture": "#body"}, - "down": {"uv": [0, 0, 4, 6], "texture": "#missing"} + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} } }, { - "name": "press", - "from": [2, 8, 4], - "to": [11, 16, 13], + "from": [11, 6, 1], + "to": [15, 10, 2], "faces": { - "north": {"uv": [8, 8, 10.25, 12], "texture": "#body"}, - "east": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "south": {"uv": [8, 12, 10.25, 16], "texture": "#body"}, - "west": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "up": {"uv": [12.5, 8, 14.75, 12.5], "texture": "#body"}, - "down": {"uv": [0, 0, 9, 9], "texture": "#missing"} - } - }, - { - "name": "screenholder", - "from": [13, 8, 0], - "to": [15, 12, 1], - "faces": { - "north": {"uv": [4, 0, 4.5, 2], "texture": "#body"}, - "east": {"uv": [4.25, 0, 4.5, 2], "texture": "#body"}, - "south": {"uv": [0, 0, 2, 4], "texture": "#missing"}, - "west": {"uv": [4, 0, 4.25, 2], "texture": "#body"}, - "up": {"uv": [4, 0, 4.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 2, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [15, 12, 15], - "to": [16, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0, 12, 15], - "to": [1, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0.3, 13, 1], - "to": [0.8, 14, 15], - "faces": { - "north": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "east": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "south": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "west": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "up": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"} - } - }, - { - "name": "frame", - "from": [0, 8, 0], - "to": [1, 14, 1], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [1, 13, 15.2], - "to": [15, 14, 15.7], - "faces": { - "north": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "east": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "south": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "west": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "up": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"} + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} } }, { "name": "screen", - "from": [12, 10, 0], - "to": [16, 14, 1], + "from": [11, 4, 2], + "to": [15, 8, 3], "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, "faces": { - "north": {"uv": [1, 0, 2, 2], "texture": "#body"}, - "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "south": {"uv": [3, 0, 4, 2], "texture": "#body"}, - "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "up": {"uv": [3, 2, 4, 2.5], "texture": "#body"}, - "down": {"uv": [3, 2, 4, 2.5], "texture": "#body"} + "north": {"uv": [1, 0, 2, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 9], - "to": [12, 14, 12], + "from": [1, 8, 3], + "to": [9, 15, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 5], - "to": [12, 14, 8], + "from": [1, 0, 0], + "to": [9, 7, 16], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "east": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "south": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "west": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "up": {"uv": [14, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [14, 0, 16, 8], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 5], - "to": [2, 14, 8], + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 9], - "to": [2, 14, 12], + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gas", - "from": [12.4, 12, 2], - "to": [15.4, 15, 14], + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], "faces": { - "north": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "east": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 180, "texture": "#body"}, - "south": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "west": {"uv": [4.5, 0, 7.5, 1.5], "texture": "#body"}, - "up": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 3, 12], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} } } - ] + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_working.json index 875259071..624ba9586 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_working.json +++ b/src/main/resources/assets/overdrive_that_matters/models/block/plate_press_working.json @@ -1,219 +1,244 @@ -{ - "parent" : "block/block", +{ + "credit": "Made with Blockbench", + "texture_size": [64, 32], "textures": { - "particle": "overdrive_that_matters:block/plate_press", - "body": "overdrive_that_matters:block/plate_press", - "press": "overdrive_that_matters:block/plate_press_piston" + "0": "overdrive_that_matters:block/plate_press2", + "1": "overdrive_that_matters:block/plate_press_piston", + "particle": "overdrive_that_matters:block/plate_press2" }, "elements": [ { - "name": "body", - "from": [0, 0, 0], - "to": [16, 8, 16], + "from": [10, 0, 0], + "to": [16, 6, 16], "faces": { - "north": {"uv": [0, 4, 4, 8], "texture": "#body"}, - "east": {"uv": [12, 4, 16, 8], "texture": "#body"}, - "south": {"uv": [4, 4, 8, 8], "texture": "#body"}, - "west": {"uv": [8, 4, 12, 8], "texture": "#body"}, - "up": {"uv": [4, 8, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 8, 4, 16], "texture": "#body"} + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [0, 8, 7], - "to": [16, 12, 16], + "from": [10, 10, 0], + "to": [16, 16, 16], "faces": { - "north": {"uv": [4, 12, 8, 14], "texture": "#body"}, - "east": {"uv": [12, 2, 14.25, 4], "texture": "#body"}, - "south": {"uv": [4, 2, 8, 4], "texture": "#body"}, - "west": {"uv": [9.75, 2, 12, 4], "texture": "#body"}, - "up": {"uv": [4, 12, 8, 16], "texture": "#body"}, - "down": {"uv": [0, 0, 16, 9], "texture": "#missing"} + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} } }, { - "name": "body", - "from": [12, 8, 1], - "to": [16, 12, 7], + "from": [10, 6, 2], + "to": [16, 10, 16], "faces": { - "north": {"uv": [13.25, 0, 14.25, 2], "texture": "#body"}, - "east": {"uv": [14.25, 2, 15.75, 4], "texture": "#body"}, - "south": {"uv": [0, 0, 4, 4], "texture": "#missing"}, - "west": {"uv": [15.75, 2, 14.25, 4], "texture": "#body"}, - "up": {"uv": [14.25, 0, 15.75, 2], "rotation": 270, "texture": "#body"}, - "down": {"uv": [0, 0, 4, 6], "texture": "#missing"} + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} } }, { - "name": "press", - "from": [2, 8, 4], - "to": [11, 16, 13], + "from": [11, 6, 1], + "to": [15, 10, 2], "faces": { - "north": {"uv": [0, 0, 8.9, 8], "texture": "#press"}, - "east": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "south": {"uv": [8, 12, 10.25, 16], "texture": "#body"}, - "west": {"uv": [10.25, 8, 12.5, 12], "texture": "#body"}, - "up": {"uv": [12.5, 8, 14.75, 12.5], "texture": "#body"}, - "down": {"uv": [0, 0, 9, 9], "texture": "#missing"} - } - }, - { - "name": "screenholder", - "from": [13, 8, 0], - "to": [15, 12, 1], - "faces": { - "north": {"uv": [4, 0, 4.5, 2], "texture": "#body"}, - "east": {"uv": [4.25, 0, 4.5, 2], "texture": "#body"}, - "south": {"uv": [0, 0, 2, 4], "texture": "#missing"}, - "west": {"uv": [4, 0, 4.25, 2], "texture": "#body"}, - "up": {"uv": [4, 0, 4.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 2, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [15, 12, 15], - "to": [16, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0, 12, 15], - "to": [1, 14, 16], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [0.3, 13, 1], - "to": [0.8, 14, 15], - "faces": { - "north": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "east": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "south": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "west": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "up": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "rotation": 90, "texture": "#body"} - } - }, - { - "name": "frame", - "from": [0, 8, 0], - "to": [1, 14, 1], - "faces": { - "north": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "east": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "south": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "west": {"uv": [8.25, 0, 8.5, 3], "texture": "#body"}, - "up": {"uv": [8.25, 0, 8.5, 0.5], "texture": "#body"}, - "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} - } - }, - { - "name": "frame", - "from": [1, 13, 15.2], - "to": [15, 14, 15.7], - "faces": { - "north": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "east": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "south": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "west": {"uv": [0, 0, 0.5, 1], "texture": "#missing"}, - "up": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"}, - "down": {"uv": [0, 2.5, 3.75, 3], "texture": "#body"} + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} } }, { "name": "screen", - "from": [12, 10, 0], - "to": [16, 14, 1], + "from": [11, 4, 2], + "to": [15, 8, 3], "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, "faces": { - "north": {"uv": [0, 0, 1, 2], "texture": "#body"}, - "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "south": {"uv": [3, 0, 4, 2], "texture": "#body"}, - "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#body"}, - "up": {"uv": [3, 2, 4, 2.5], "texture": "#body"}, - "down": {"uv": [3, 2, 4, 2.5], "texture": "#body"} + "north": {"uv": [0, 0, 1, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 9], - "to": [12, 14, 12], + "from": [1, 8, 3], + "to": [9, 15, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [0, 2, 16, 16], "texture": "#1"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [11, 8, 5], - "to": [12, 14, 8], + "from": [1, 0, 0], + "to": [9, 7, 16], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "east": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "south": {"uv": [8, 0, 10, 3.5], "texture": "#0"}, + "west": {"uv": [10, 0, 14, 3.5], "texture": "#0"}, + "up": {"uv": [14, 0, 16, 8], "texture": "#0"}, + "down": {"uv": [14, 0, 16, 8], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 5], - "to": [2, 14, 8], + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gaspipes", - "from": [1, 8, 9], - "to": [2, 14, 12], + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], "faces": { - "north": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "east": {"uv": [0, 0, 0.75, 3], "texture": "#missing"}, - "south": {"uv": [9.25, 0.5, 9.5, 3.5], "texture": "#body"}, - "west": {"uv": [8.5, 0.5, 9.25, 3.5], "texture": "#body"}, - "up": {"uv": [8.5, 0.5, 9.25, 0], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 0.25, 1.5], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} } }, { - "name": "gas", - "from": [12.4, 12, 2], - "to": [15.4, 15, 14], + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], "faces": { - "north": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "east": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 180, "texture": "#body"}, - "south": {"uv": [7.5, 0, 8.25, 1.5], "texture": "#body"}, - "west": {"uv": [4.5, 0, 7.5, 1.5], "texture": "#body"}, - "up": {"uv": [4.5, 0, 7.5, 1.5], "rotation": 90, "texture": "#body"}, - "down": {"uv": [0, 0, 3, 12], "texture": "#missing"} + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} } } - ] + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + } + } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_error.json new file mode 100644 index 000000000..74b94f7bd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_error.json @@ -0,0 +1,259 @@ +{ + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "particle": "overdrive_that_matters:block/induction_furnace", + "texture": "overdrive_that_matters:block/induction_furnace" + }, + "elements": [ + { + "name": "heatsink", + "from": [1, 10, 11], + "to": [15, 11, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "heatsink", + "from": [1, 8, 11], + "to": [15, 9, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "heatsink", + "from": [1, 6, 11], + "to": [15, 7, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "base", + "from": [0, 0, 0], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 9.5, 8, 10.75], "texture": "#texture"}, + "east": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "south": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "west": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 5, 6], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [0, 3.5, 8, 6.25], "texture": "#texture"}, + "east": {"uv": [13, 7.25, 10.5, 10], "texture": "#texture"}, + "south": {"uv": [0, 12, 8, 14.75], "texture": "#texture"}, + "west": {"uv": [10.5, 7.25, 13, 10], "texture": "#texture"}, + "up": {"uv": [0, 2.25, 8, 3.5], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 12, 11], + "to": [16, 16, 16], + "faces": { + "east": {"uv": [15.5, 7.25, 13, 8.25], "texture": "#texture"}, + "south": {"uv": [0, 6.25, 8, 7.25], "texture": "#texture"}, + "west": {"uv": [13, 7.25, 15.5, 8.25], "texture": "#texture"}, + "up": {"uv": [0, 1, 8, 2.25], "rotation": 180, "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 13.25], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [2, 5, 11], + "to": [14, 12, 14], + "faces": { + "east": {"uv": [6, 7.75, 7.5, 9.5], "texture": "#texture"}, + "south": {"uv": [1, 7.75, 7, 9.5], "texture": "#texture"}, + "west": {"uv": [0, 7.75, 1.5, 9.5], "texture": "#texture"} + } + }, + { + "name": "chamber", + "from": [3, 5, 0], + "to": [13, 16, 6], + "faces": { + "north": {"uv": [8, 13.25, 13, 16], "texture": "#texture"}, + "east": {"uv": [16, 13.25, 13, 16], "texture": "#texture"}, + "west": {"uv": [13, 13.25, 16, 16], "texture": "#texture"}, + "up": {"uv": [8, 11.75, 13, 13.25], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "smokething", + "from": [4, 16, 4], + "to": [12, 17, 12], + "faces": { + "north": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "east": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "south": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "west": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "up": {"uv": [8, 1, 12, 3], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [0.5, 5, 10.5], + "to": [15.5, 8, 15.5], + "faces": { + "east": {"uv": [15.5, 9.25, 13, 10], "texture": "#texture"}, + "south": {"uv": [8, 11, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 9.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [15.5, 5, 10.5], + "to": [0.5, 12, 15.5], + "faces": { + "east": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"}, + "south": {"uv": [8, 10, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [13, 5, 1], + "to": [15, 15, 6], + "faces": { + "north": {"uv": [8, 5, 9, 7.5], "texture": "#texture"}, + "east": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [10, 5, 9, 6.25], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [1, 5, 1], + "to": [3, 15, 6], + "faces": { + "north": {"uv": [9, 5, 8, 7.5], "texture": "#texture"}, + "west": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [9, 5, 10, 6.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [1, 9, 2], + "to": [1, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [1, 11, 2], + "to": [1, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [1, 13, 2], + "to": [1, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15, 13, 2], + "to": [15, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15, 9, 2], + "to": [15, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15, 11, 2], + "to": [15, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [5, 16, 2], + "to": [11, 16, 3], + "faces": { + "up": {"uv": [12.5, 1.5, 15.5, 1.75], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_idle.json new file mode 100644 index 000000000..e8c1914bc --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_idle.json @@ -0,0 +1,250 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "particle": "overdrive_that_matters:block/induction_furnace_offline", + "texture": "overdrive_that_matters:block/induction_furnace_offline" + }, + "elements": [ + { + "name": "heatsink", + "from": [1, 10, 11], + "to": [15, 11, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "heatsink", + "from": [1, 8, 11], + "to": [15, 9, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "heatsink", + "from": [1, 6, 11], + "to": [15, 7, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 0, 0], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 9.5, 8, 10.75], "texture": "#texture"}, + "east": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "south": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "west": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 5, 6], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [0, 3.5, 8, 6.25], "texture": "#texture"}, + "east": {"uv": [13, 7.25, 10.5, 10], "texture": "#texture"}, + "south": {"uv": [0, 12, 8, 14.75], "texture": "#texture"}, + "west": {"uv": [10.5, 7.25, 13, 10], "texture": "#texture"}, + "up": {"uv": [0, 2.25, 8, 3.5], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 12, 11], + "to": [16, 16, 16], + "faces": { + "east": {"uv": [15.5, 7.25, 13, 8.25], "texture": "#texture"}, + "south": {"uv": [0, 6.25, 8, 7.25], "texture": "#texture"}, + "west": {"uv": [13, 7.25, 15.5, 8.25], "texture": "#texture"}, + "up": {"uv": [0, 1, 8, 2.25], "rotation": 180, "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 13.25], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [2, 5, 11], + "to": [14, 12, 14], + "faces": { + "east": {"uv": [6, 7.75, 7.5, 9.5], "texture": "#texture"}, + "south": {"uv": [1, 7.75, 7, 9.5], "texture": "#texture"}, + "west": {"uv": [0, 7.75, 1.5, 9.5], "texture": "#texture"} + } + }, + { + "name": "chamber", + "from": [3, 5, 0], + "to": [13, 16, 6], + "faces": { + "north": {"uv": [8, 13.25, 13, 16], "texture": "#texture"}, + "east": {"uv": [16, 13.25, 13, 16], "texture": "#texture"}, + "west": {"uv": [13, 13.25, 16, 16], "texture": "#texture"}, + "up": {"uv": [8, 11.75, 13, 13.25], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "smokething", + "from": [4, 16, 4], + "to": [12, 17, 12], + "faces": { + "north": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "east": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "south": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "west": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "up": {"uv": [8, 1, 12, 3], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [0.5, 5, 10.5], + "to": [15.5, 8, 15.5], + "faces": { + "east": {"uv": [15.5, 9.25, 13, 10], "texture": "#texture"}, + "south": {"uv": [8, 11, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 9.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [15.5, 5, 10.5], + "to": [0.5, 12, 15.5], + "faces": { + "east": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"}, + "south": {"uv": [8, 10, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [13, 5, 1], + "to": [15, 15, 6], + "faces": { + "north": {"uv": [8, 5, 9, 7.5], "texture": "#texture"}, + "east": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [10, 5, 9, 6.25], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [1, 5, 1], + "to": [3, 15, 6], + "faces": { + "north": {"uv": [9, 5, 8, 7.5], "texture": "#texture"}, + "west": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [9, 5, 10, 6.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [0.99, 9, 2], + "to": [0.99, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [0.99, 11, 2], + "to": [0.99, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [0.99, 13, 2], + "to": [0.99, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [15.01, 13, 2], + "to": [15.01, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [15.01, 9, 2], + "to": [15.01, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [15.01, 11, 2], + "to": [15.01, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [5, 16.01, 2], + "to": [11, 16.01, 3], + "faces": { + "up": {"uv": [12.5, 1.5, 15.5, 1.75], "texture": "#texture"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_working.json new file mode 100644 index 000000000..800bcd44f --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_blast_furnace_working.json @@ -0,0 +1,260 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "particle": "overdrive_that_matters:block/induction_furnace", + "texture": "overdrive_that_matters:block/induction_furnace" + }, + "elements": [ + { + "name": "heatsink", + "from": [1, 10, 11], + "to": [15, 11, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "heatsink", + "from": [1, 8, 11], + "to": [15, 9, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "heatsink", + "from": [1, 6, 11], + "to": [15, 7, 15], + "faces": { + "east": {"uv": [7, 0.25, 9, 0.5], "texture": "#texture"}, + "south": {"uv": [9, 0, 16, 0.25], "texture": "#texture"}, + "west": {"uv": [7, 0, 9, 0.25], "texture": "#texture"}, + "up": {"uv": [0, 0, 7, 1], "texture": "#texture"}, + "down": {"uv": [0, 0, 7, 1], "rotation": 180, "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "base", + "from": [0, 0, 0], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 9.5, 8, 10.75], "texture": "#texture"}, + "east": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "south": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "west": {"uv": [0, 10.75, 8, 12], "texture": "#texture"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 5, 6], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [0, 3.5, 8, 6.25], "texture": "#texture"}, + "east": {"uv": [13, 7.25, 10.5, 10], "texture": "#texture"}, + "south": {"uv": [0, 12, 8, 14.75], "texture": "#texture"}, + "west": {"uv": [10.5, 7.25, 13, 10], "texture": "#texture"}, + "up": {"uv": [0, 2.25, 8, 3.5], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "base", + "from": [0, 12, 11], + "to": [16, 16, 16], + "faces": { + "east": {"uv": [15.5, 7.25, 13, 8.25], "texture": "#texture"}, + "south": {"uv": [0, 6.25, 8, 7.25], "texture": "#texture"}, + "west": {"uv": [13, 7.25, 15.5, 8.25], "texture": "#texture"}, + "up": {"uv": [0, 1, 8, 2.25], "rotation": 180, "texture": "#texture"}, + "down": {"uv": [0, 12, 8, 13.25], "texture": "#texture"} + } + }, + { + "name": "base", + "from": [2, 5, 11], + "to": [14, 12, 14], + "faces": { + "east": {"uv": [6, 7.75, 7.5, 9.5], "texture": "#texture"}, + "south": {"uv": [1, 7.75, 7, 9.5], "texture": "#texture"}, + "west": {"uv": [0, 7.75, 1.5, 9.5], "texture": "#texture"} + } + }, + { + "name": "chamber", + "from": [3, 5, 0], + "to": [13, 16, 6], + "faces": { + "north": {"uv": [8, 13.25, 13, 16], "texture": "#texture"}, + "east": {"uv": [16, 13.25, 13, 16], "texture": "#texture"}, + "west": {"uv": [13, 13.25, 16, 16], "texture": "#texture"}, + "up": {"uv": [8, 11.75, 13, 13.25], "rotation": 180, "texture": "#texture"} + } + }, + { + "name": "smokething", + "from": [4, 16, 4], + "to": [12, 17, 12], + "faces": { + "north": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "east": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "south": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "west": {"uv": [8, 3, 12, 3.25], "texture": "#texture"}, + "up": {"uv": [8, 1, 12, 3], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [0.5, 5, 10.5], + "to": [15.5, 8, 15.5], + "faces": { + "east": {"uv": [15.5, 9.25, 13, 10], "texture": "#texture"}, + "south": {"uv": [8, 11, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 9.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "net", + "from": [15.5, 5, 10.5], + "to": [0.5, 12, 15.5], + "faces": { + "east": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"}, + "south": {"uv": [8, 10, 15.5, 11.75], "texture": "#texture"}, + "west": {"uv": [13, 8.25, 15.5, 10], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [13, 5, 1], + "to": [15, 15, 6], + "faces": { + "north": {"uv": [8, 5, 9, 7.5], "texture": "#texture"}, + "east": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [10, 5, 9, 6.25], "texture": "#texture"} + } + }, + { + "name": "detals", + "from": [1, 5, 1], + "to": [3, 15, 6], + "faces": { + "north": {"uv": [9, 5, 8, 7.5], "texture": "#texture"}, + "west": {"uv": [8, 7.5, 10.5, 10], "texture": "#texture"}, + "up": {"uv": [9, 5, 10, 6.25], "texture": "#texture"} + } + }, + { + "name": "light", + "from": [0.99, 9, 2], + "to": [0.99, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [0.99, 11, 2], + "to": [0.99, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [0.99, 13, 2], + "to": [0.99, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15.01, 13, 2], + "to": [15.01, 14, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15.01, 9, 2], + "to": [15.01, 10, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [15.01, 11, 2], + "to": [15.01, 12, 5], + "faces": { + "east": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"}, + "west": {"uv": [12.5, 1, 14, 1.25], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + }, + { + "name": "light", + "from": [5, 16.01, 2], + "to": [11, 16.01, 3], + "faces": { + "up": {"uv": [12.5, 1.5, 15.5, 1.75], "texture": "#texture"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_error.json new file mode 100644 index 000000000..d9619a3de --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_error.json @@ -0,0 +1,353 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/electric_furnace_offline", + "particle": "overdrive_that_matters:block/electric_furnace_offline" + }, + "elements": [ + { + "name": "chamber", + "from": [15, 8, 0], + "to": [16, 16, 1], + "faces": { + "north": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "east": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"}, + "down": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 0], + "to": [1, 16, 1], + "faces": { + "north": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "up": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"}, + "down": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 1], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [4, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [1.25, 9.5, 3.75, 13.5], "texture": "#0"}, + "south": {"uv": [4, 4, 8, 8], "texture": "#0"}, + "west": {"uv": [3.75, 9.5, 1.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 4, 8, 9], "rotation": 180, "texture": "#0"}, + "down": {"uv": [4, 4, 8, 9], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 8, 11], + "to": [16, 16, 15], + "faces": { + "north": {"uv": [0, 0, 5, 8], "texture": "#missing"}, + "east": {"uv": [8, 7.5, 9, 11.5], "texture": "#0"}, + "south": {"uv": [12.25, 0, 13.5, 4], "texture": "#0"}, + "west": {"uv": [9, 7.5, 8, 11.5], "texture": "#0"}, + "up": {"uv": [2.75, 13.5, 4, 15.5], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 4], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [11, 5, 15], + "to": [12, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "south": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "west": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "up": {"uv": [0, 9, 0.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [15, 5, 15], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "south": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "west": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "up": {"uv": [1, 9, 1.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [12, 15, 15], + "to": [15, 16, 16], + "faces": { + "north": {"uv": [0, 0, 3, 1], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "down": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 0, 15], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 0, 5, 5], "texture": "#missing"}, + "east": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "south": {"uv": [1.25, 13.5, 2.5, 16], "texture": "#0"}, + "west": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "up": {"uv": [1.25, 13.5, 2.5, 14], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 1], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [0, 6, 15], + "to": [11, 8, 16], + "faces": { + "north": {"uv": [0, 0, 11, 2], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 2], "texture": "#missing"}, + "south": {"uv": [8.25, 0, 11, 1], "texture": "#0"}, + "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#0"}, + "up": {"uv": [8.75, 0, 11, 0.5], "texture": "#0"}, + "down": {"uv": [8.5, 0.5, 11, 1], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 8, 0], + "to": [15, 9, 1], + "faces": { + "north": {"uv": [4, 13, 8, 13.5], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 13, 7.75, 13.5], "texture": "#0"}, + "down": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 15, 0], + "to": [15, 16, 1], + "faces": { + "north": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"}, + "down": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"} + } + }, + { + "name": "cylinder", + "from": [0, 1, 1], + "to": [16, 7, 7], + "faces": { + "north": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "east": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "south": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "west": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "up": {"uv": [0, 0, 4, 3], "texture": "#0"}, + "down": {"uv": [0, 6, 4, 9], "texture": "#0"} + } + }, + { + "name": "details", + "from": [13, 0, 0], + "to": [15, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "details", + "from": [1, 0, 0], + "to": [3, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [1, 8, 12], + "to": [5, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [6, 8, 12], + "to": [10, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 0, 8], + "to": [16, 8, 15], + "faces": { + "north": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "east": {"uv": [8.25, 0, 6.5, 4], "texture": "#0"}, + "south": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "west": {"uv": [6.5, 0, 8.25, 4], "texture": "#0"}, + "up": {"uv": [0, 0, 16, 7], "texture": "#missing"}, + "down": {"uv": [8, 4, 12, 7.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 8, 11], + "to": [11, 14, 15], + "faces": { + "north": {"uv": [0, 0, 11, 6], "texture": "#missing"}, + "east": {"uv": [0, 0, 4, 6], "texture": "#missing"}, + "south": {"uv": [13.25, 6, 16, 9], "texture": "#0"}, + "west": {"uv": [13.5, 0, 14.5, 3], "texture": "#0"}, + "up": {"uv": [13.25, 4, 16, 6], "texture": "#0"}, + "down": {"uv": [0, 0, 11, 4], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [-0.5, 16.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "up": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"}, + "down": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 1], + "to": [-0.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 2], + "to": [-0.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 2], + "to": [16.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-1, 10, 6], + "to": [0, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16, 10, 6], + "to": [17, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "furnace_burn", + "from": [1, 9, 0.99], + "to": [15, 15, 0.99], + "faces": { + "north": {"uv": [4.25, 10, 7.75, 13], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_idle.json new file mode 100644 index 000000000..d9619a3de --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_idle.json @@ -0,0 +1,353 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/electric_furnace_offline", + "particle": "overdrive_that_matters:block/electric_furnace_offline" + }, + "elements": [ + { + "name": "chamber", + "from": [15, 8, 0], + "to": [16, 16, 1], + "faces": { + "north": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "east": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"}, + "down": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 0], + "to": [1, 16, 1], + "faces": { + "north": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "up": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"}, + "down": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 1], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [4, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [1.25, 9.5, 3.75, 13.5], "texture": "#0"}, + "south": {"uv": [4, 4, 8, 8], "texture": "#0"}, + "west": {"uv": [3.75, 9.5, 1.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 4, 8, 9], "rotation": 180, "texture": "#0"}, + "down": {"uv": [4, 4, 8, 9], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 8, 11], + "to": [16, 16, 15], + "faces": { + "north": {"uv": [0, 0, 5, 8], "texture": "#missing"}, + "east": {"uv": [8, 7.5, 9, 11.5], "texture": "#0"}, + "south": {"uv": [12.25, 0, 13.5, 4], "texture": "#0"}, + "west": {"uv": [9, 7.5, 8, 11.5], "texture": "#0"}, + "up": {"uv": [2.75, 13.5, 4, 15.5], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 4], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [11, 5, 15], + "to": [12, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "south": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "west": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "up": {"uv": [0, 9, 0.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [15, 5, 15], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "south": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "west": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "up": {"uv": [1, 9, 1.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [12, 15, 15], + "to": [15, 16, 16], + "faces": { + "north": {"uv": [0, 0, 3, 1], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "down": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 0, 15], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 0, 5, 5], "texture": "#missing"}, + "east": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "south": {"uv": [1.25, 13.5, 2.5, 16], "texture": "#0"}, + "west": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "up": {"uv": [1.25, 13.5, 2.5, 14], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 1], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [0, 6, 15], + "to": [11, 8, 16], + "faces": { + "north": {"uv": [0, 0, 11, 2], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 2], "texture": "#missing"}, + "south": {"uv": [8.25, 0, 11, 1], "texture": "#0"}, + "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#0"}, + "up": {"uv": [8.75, 0, 11, 0.5], "texture": "#0"}, + "down": {"uv": [8.5, 0.5, 11, 1], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 8, 0], + "to": [15, 9, 1], + "faces": { + "north": {"uv": [4, 13, 8, 13.5], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 13, 7.75, 13.5], "texture": "#0"}, + "down": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 15, 0], + "to": [15, 16, 1], + "faces": { + "north": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"}, + "down": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"} + } + }, + { + "name": "cylinder", + "from": [0, 1, 1], + "to": [16, 7, 7], + "faces": { + "north": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "east": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "south": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "west": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "up": {"uv": [0, 0, 4, 3], "texture": "#0"}, + "down": {"uv": [0, 6, 4, 9], "texture": "#0"} + } + }, + { + "name": "details", + "from": [13, 0, 0], + "to": [15, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "details", + "from": [1, 0, 0], + "to": [3, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [1, 8, 12], + "to": [5, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [6, 8, 12], + "to": [10, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 0, 8], + "to": [16, 8, 15], + "faces": { + "north": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "east": {"uv": [8.25, 0, 6.5, 4], "texture": "#0"}, + "south": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "west": {"uv": [6.5, 0, 8.25, 4], "texture": "#0"}, + "up": {"uv": [0, 0, 16, 7], "texture": "#missing"}, + "down": {"uv": [8, 4, 12, 7.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 8, 11], + "to": [11, 14, 15], + "faces": { + "north": {"uv": [0, 0, 11, 6], "texture": "#missing"}, + "east": {"uv": [0, 0, 4, 6], "texture": "#missing"}, + "south": {"uv": [13.25, 6, 16, 9], "texture": "#0"}, + "west": {"uv": [13.5, 0, 14.5, 3], "texture": "#0"}, + "up": {"uv": [13.25, 4, 16, 6], "texture": "#0"}, + "down": {"uv": [0, 0, 11, 4], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [-0.5, 16.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "up": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"}, + "down": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 1], + "to": [-0.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 2], + "to": [-0.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 2], + "to": [16.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-1, 10, 6], + "to": [0, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16, 10, 6], + "to": [17, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "furnace_burn", + "from": [1, 9, 0.99], + "to": [15, 15, 0.99], + "faces": { + "north": {"uv": [4.25, 10, 7.75, 13], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_working.json new file mode 100644 index 000000000..a019fc0b4 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/powered_furnace_working.json @@ -0,0 +1,354 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/electric_furnace", + "particle": "overdrive_that_matters:block/electric_furnace" + }, + "elements": [ + { + "name": "chamber", + "from": [15, 8, 0], + "to": [16, 16, 1], + "faces": { + "north": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "east": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [4, 9.5, 4.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"}, + "down": {"uv": [4, 9, 4.25, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 0], + "to": [1, 16, 1], + "faces": { + "north": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [7.75, 9.5, 8, 13.5], "texture": "#0"}, + "south": {"uv": [0, 0, 1, 8], "texture": "#missing"}, + "west": {"uv": [3.75, 9.5, 4, 13.5], "texture": "#0"}, + "up": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"}, + "down": {"uv": [7.75, 9, 8, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [0, 8, 1], + "to": [16, 16, 11], + "faces": { + "north": {"uv": [4, 9.5, 8, 13.5], "texture": "#0"}, + "east": {"uv": [1.25, 9.5, 3.75, 13.5], "texture": "#0"}, + "south": {"uv": [4, 4, 8, 8], "texture": "#0"}, + "west": {"uv": [3.75, 9.5, 1.25, 13.5], "texture": "#0"}, + "up": {"uv": [4, 4, 8, 9], "rotation": 180, "texture": "#0"}, + "down": {"uv": [4, 4, 8, 9], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 8, 11], + "to": [16, 16, 15], + "faces": { + "north": {"uv": [0, 0, 5, 8], "texture": "#missing"}, + "east": {"uv": [8, 7.5, 9, 11.5], "texture": "#0"}, + "south": {"uv": [12.25, 0, 13.5, 4], "texture": "#0"}, + "west": {"uv": [9, 7.5, 8, 11.5], "texture": "#0"}, + "up": {"uv": [2.75, 13.5, 4, 15.5], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 4], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [11, 5, 15], + "to": [12, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "south": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "west": {"uv": [0, 9, 0.25, 14.5], "texture": "#0"}, + "up": {"uv": [0, 9, 0.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [15, 5, 15], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [0, 0, 1, 11], "texture": "#missing"}, + "east": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "south": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "west": {"uv": [1, 9, 1.25, 14.5], "texture": "#0"}, + "up": {"uv": [1, 9, 1.25, 9.5], "texture": "#0"}, + "down": {"uv": [0, 0, 1, 1], "texture": "#missing"} + } + }, + { + "name": "exhaust", + "from": [12, 15, 15], + "to": [15, 16, 16], + "faces": { + "north": {"uv": [0, 0, 3, 1], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"}, + "down": {"uv": [0.25, 9, 1, 9.5], "texture": "#0"} + } + }, + { + "name": "exhaust", + "from": [11, 0, 15], + "to": [16, 5, 16], + "faces": { + "north": {"uv": [0, 0, 5, 5], "texture": "#missing"}, + "east": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "south": {"uv": [1.25, 13.5, 2.5, 16], "texture": "#0"}, + "west": {"uv": [2.5, 13.5, 2.75, 16], "texture": "#0"}, + "up": {"uv": [1.25, 13.5, 2.5, 14], "texture": "#0"}, + "down": {"uv": [0, 0, 5, 1], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [0, 6, 15], + "to": [11, 8, 16], + "faces": { + "north": {"uv": [0, 0, 11, 2], "texture": "#missing"}, + "east": {"uv": [0, 0, 1, 2], "texture": "#missing"}, + "south": {"uv": [8.25, 0, 11, 1], "texture": "#0"}, + "west": {"uv": [8.25, 0, 8.5, 1], "texture": "#0"}, + "up": {"uv": [8.75, 0, 11, 0.5], "texture": "#0"}, + "down": {"uv": [8.5, 0.5, 11, 1], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 8, 0], + "to": [15, 9, 1], + "faces": { + "north": {"uv": [4, 13, 8, 13.5], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 13, 7.75, 13.5], "texture": "#0"}, + "down": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"} + } + }, + { + "name": "chamber", + "from": [1, 15, 0], + "to": [15, 16, 1], + "faces": { + "north": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"}, + "east": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "south": {"uv": [0, 0, 14, 1], "texture": "#missing"}, + "west": {"uv": [0, 0, 1, 1], "texture": "#missing"}, + "up": {"uv": [4.25, 9, 7.75, 9.5], "texture": "#0"}, + "down": {"uv": [4.25, 9.5, 7.75, 10], "texture": "#0"} + } + }, + { + "name": "cylinder", + "from": [0, 1, 1], + "to": [16, 7, 7], + "faces": { + "north": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "east": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "south": {"uv": [0, 3, 4, 6], "texture": "#0"}, + "west": {"uv": [4.25, 0.5, 5.75, 3.5], "texture": "#0"}, + "up": {"uv": [0, 0, 4, 3], "texture": "#0"}, + "down": {"uv": [0, 6, 4, 9], "texture": "#0"} + } + }, + { + "name": "details", + "from": [13, 0, 0], + "to": [15, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "details", + "from": [1, 0, 0], + "to": [3, 8, 8], + "faces": { + "north": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "east": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "south": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "west": {"uv": [4, 0, 6, 4], "texture": "#0"}, + "up": {"uv": [6, 0, 6.5, 4], "texture": "#0"}, + "down": {"uv": [6, 0, 6.5, 4], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [1, 8, 12], + "to": [5, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "pipe", + "from": [6, 8, 12], + "to": [10, 16, 16], + "faces": { + "north": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "east": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "south": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "west": {"uv": [9, 7.5, 10, 11.5], "texture": "#0"}, + "up": {"uv": [10, 7.5, 11, 9.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 0, 8], + "to": [16, 8, 15], + "faces": { + "north": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "east": {"uv": [8.25, 0, 6.5, 4], "texture": "#0"}, + "south": {"uv": [8.25, 0, 12.25, 4], "texture": "#0"}, + "west": {"uv": [6.5, 0, 8.25, 4], "texture": "#0"}, + "up": {"uv": [0, 0, 16, 7], "texture": "#missing"}, + "down": {"uv": [8, 4, 12, 7.5], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 8, 11], + "to": [11, 14, 15], + "faces": { + "north": {"uv": [0, 0, 11, 6], "texture": "#missing"}, + "east": {"uv": [0, 0, 4, 6], "texture": "#missing"}, + "south": {"uv": [13.25, 6, 16, 9], "texture": "#0"}, + "west": {"uv": [13.5, 0, 14.5, 3], "texture": "#0"}, + "up": {"uv": [13.25, 4, 16, 6], "texture": "#0"}, + "down": {"uv": [0, 0, 11, 4], "texture": "#missing"} + } + }, + { + "name": "details", + "from": [-0.5, 16.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "up": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"}, + "down": {"uv": [11.75, 15.5, 16, 16], "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 1], + "to": [-0.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-0.5, 11.5, 2], + "to": [-0.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 2], + "to": [16.5, 12.5, 10], + "faces": { + "east": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"}, + "west": {"uv": [11.75, 15, 13.75, 15.5], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16.5, 11.5, 1], + "to": [16.5, 16.5, 2], + "faces": { + "east": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14.75, 15, 16, 15.5], "rotation": 90, "texture": "#0"} + } + }, + { + "name": "details", + "from": [-1, 10, 6], + "to": [0, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "details", + "from": [16, 10, 6], + "to": [17, 14, 10], + "faces": { + "north": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "east": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "south": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "west": {"uv": [15, 0, 16, 2], "texture": "#0"}, + "up": {"uv": [14.75, 0, 15, 2], "texture": "#0"}, + "down": {"uv": [14.75, 0, 15, 2], "texture": "#0"} + } + }, + { + "name": "furnace_burn", + "from": [1, 9, 0.99], + "to": [15, 15, 0.99], + "faces": { + "north": {"uv": [4.25, 10, 7.75, 13], "texture": "#0"} + }, + "forge_data": { "block_light": 15, "sky_light": 15 } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil0.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil0.json new file mode 100644 index 000000000..5e50a41ca --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil0.json @@ -0,0 +1,197 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [11, 0, 16, 8], "rotation": 180, "texture": "#3"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil1.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil1.json new file mode 100644 index 000000000..848b50a1a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil1.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 5, 8], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil2.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil2.json new file mode 100644 index 000000000..e51b97f72 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil2.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [5, 0, 10, 8], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil3.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil3.json new file mode 100644 index 000000000..8eec83631 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil3.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [10, 0, 15, 8], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil4.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil4.json new file mode 100644 index 000000000..ab6d26a62 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil4.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 8, 5, 16], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil5.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil5.json new file mode 100644 index 000000000..164a165d1 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil5.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [5, 8, 10, 16], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil6.json b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil6.json new file mode 100644 index 000000000..9118700b3 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/tritanium_anvil6.json @@ -0,0 +1,198 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "2": "overdrive_that_matters:block/tritanium_anvil_top", + "3": "overdrive_that_matters:block/tritanium_anvil", + "particle": "overdrive_that_matters:block/tritanium_anvil" + }, + "elements": [ + { + "name": "Anvil base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "east": {"uv": [0, 10, 6, 8], "rotation": 180, "texture": "#3"}, + "south": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "west": {"uv": [0, 8, 6, 10], "texture": "#3"}, + "up": {"uv": [1, 1, 7, 7], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 10, 6, 16], "rotation": 180, "texture": "#3", "cullface": "down"} + } + }, + { + "name": "Wider section beneath top portion", + "from": [6, 5, 4], + "to": [10, 10, 12], + "faces": { + "north": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "east": {"uv": [5, 2, 2.5, 6], "rotation": 270, "texture": "#3"}, + "south": {"uv": [3, 3, 5, 5.5], "texture": "#3"}, + "west": {"uv": [2.5, 2, 5, 6], "rotation": 90, "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion", + "from": [4, 4, 3], + "to": [12, 5, 13], + "faces": { + "north": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "east": {"uv": [2.5, 1.5, 2, 6.5], "rotation": 270, "texture": "#3"}, + "south": {"uv": [2, 5.5, 6, 6], "texture": "#3"}, + "west": {"uv": [2, 1.5, 2.5, 6.5], "rotation": 90, "texture": "#3"}, + "up": {"uv": [2, 1.5, 6, 6.5], "rotation": 180, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Anvil top", + "from": [3, 10, 0], + "to": [13, 16, 16], + "faces": { + "north": {"uv": [11, 8, 16, 11], "texture": "#3"}, + "east": {"uv": [8, 0, 5, 8], "rotation": 270, "texture": "#3"}, + "south": {"uv": [11, 8, 16, 11], "rotation": 180, "texture": "#3"}, + "west": {"uv": [5, 0, 8, 8], "rotation": 90, "texture": "#3"}, + "up": {"uv": [10, 8, 15, 16], "rotation": 180, "texture": "#2"}, + "down": {"uv": [6, 11, 14, 16], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 2], + "to": [14, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [13, 10, 12], + "to": [14, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 12], + "to": [3, 15, 14], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 10, 2], + "to": [3, 15, 4], + "faces": { + "north": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [9, 0.5, 9.5, 3], "texture": "#3"}, + "west": {"uv": [8, 0.5, 9, 3], "texture": "#3"}, + "up": {"uv": [8, 0, 9, 0.5], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 12], + "to": [14, 10, 14], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Support frame", + "from": [2, 8, 2], + "to": [14, 10, 4], + "faces": { + "north": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "east": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "south": {"uv": [8, 3, 9, 9], "rotation": 90, "texture": "#3"}, + "west": {"uv": [9.5, 2, 10.5, 3], "texture": "#3"}, + "up": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "down": {"uv": [9, 3, 10, 9], "rotation": 90, "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [5, 5, 6], + "to": [6, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + }, + { + "name": "Lower narrow portion support", + "from": [10, 5, 6], + "to": [11, 8, 10], + "faces": { + "north": {"uv": [12, 1, 12.5, 2.5], "texture": "#3"}, + "east": {"uv": [12.5, 1, 14.5, 2.5], "texture": "#3"}, + "south": {"uv": [14.5, 1, 15, 2.5], "texture": "#3"}, + "west": {"uv": [0, 0, 0, 0], "texture": "#3"}, + "up": {"uv": [12.5, 0.5, 14.5, 1], "rotation": 90, "texture": "#3"}, + "down": {"uv": [0, 0, 0, 0], "texture": "#3"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_error.json b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_error.json new file mode 100644 index 000000000..49e49a54f --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_error.json @@ -0,0 +1,291 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/plate_press2", + "particle": "overdrive_that_matters:block/plate_press2" + }, + "elements": [ + { + "from": [10, 0, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 10, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 6, 2], + "to": [16, 10, 16], + "faces": { + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} + } + }, + { + "from": [11, 6, 1], + "to": [15, 10, 2], + "faces": { + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} + } + }, + { + "name": "screen", + "from": [11, 4, 2], + "to": [15, 8, 3], + "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, + "faces": { + "north": {"uv": [2, 0, 3, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} + } + }, + { + "from": [1, 8, 3], + "to": [9, 15, 15], + "faces": { + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 3], + "to": [9, 7, 15], + "faces": { + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "from": [8, 1, 1], + "to": [9, 6, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 6, 1], + "to": [9, 7, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 1, 1], + "to": [2, 6, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 1], + "to": [9, 1, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [2, 1, -1], + "to": [8, 1, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_idle.json b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_idle.json new file mode 100644 index 000000000..d66cbfd14 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_idle.json @@ -0,0 +1,315 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/plate_press2", + "particle": "overdrive_that_matters:block/plate_press2" + }, + "elements": [ + { + "from": [10, 0, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 10, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 6, 2], + "to": [16, 10, 16], + "faces": { + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} + } + }, + { + "from": [11, 6, 1], + "to": [15, 10, 2], + "faces": { + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} + } + }, + { + "name": "screen", + "from": [11, 4, 2], + "to": [15, 8, 3], + "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, + "faces": { + "north": {"uv": [1, 0, 2, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} + } + }, + { + "from": [1, 8, 3], + "to": [9, 15, 15], + "faces": { + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 3], + "to": [9, 7, 15], + "faces": { + "north": {"uv": [8.5, 12.1, 10.5, 16], "texture": "#0"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "from": [8, 1, 1], + "to": [9, 6, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 6, 1], + "to": [9, 7, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 1, 1], + "to": [2, 6, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 1], + "to": [9, 1, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [2, 1, -1], + "to": [8, 1, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_working.json b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_working.json new file mode 100644 index 000000000..1eaa521f3 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/twin_plate_press_working.json @@ -0,0 +1,293 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 32], + "textures": { + "0": "overdrive_that_matters:block/plate_press2", + "1": "overdrive_that_matters:block/plate_press_piston", + "2": "overdrive_that_matters:block/piston_offset", + "particle": "overdrive_that_matters:block/plate_press2" + }, + "elements": [ + { + "from": [10, 0, 0], + "to": [16, 6, 16], + "faces": { + "north": {"uv": [5.5, 13, 7, 16], "texture": "#0"}, + "east": {"uv": [1.5, 13, 5.5, 16], "texture": "#0"}, + "south": {"uv": [7, 13, 8.5, 16], "texture": "#0"}, + "west": {"uv": [5.5, 13, 1.5, 16], "texture": "#0"}, + "up": {"uv": [0, 8, 1.5, 16], "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 10, 0], + "to": [16, 16, 16], + "faces": { + "north": {"uv": [5.5, 8, 7, 11], "texture": "#0"}, + "east": {"uv": [1.5, 8, 5.5, 11], "texture": "#0"}, + "south": {"uv": [7, 8, 8.5, 11], "texture": "#0"}, + "west": {"uv": [5.5, 8, 1.5, 11], "texture": "#0"}, + "up": {"uv": [0, 5, 4, 8], "rotation": 90, "texture": "#0"}, + "down": {"uv": [0, 8, 1.5, 16], "texture": "#0"} + } + }, + { + "from": [10, 6, 2], + "to": [16, 10, 16], + "faces": { + "north": {"uv": [5.5, 11, 7, 13], "texture": "#0"}, + "east": {"uv": [1.5, 11, 5, 13], "texture": "#0"}, + "south": {"uv": [7, 11, 8.5, 13], "texture": "#0"}, + "west": {"uv": [5, 11, 1.5, 13], "texture": "#0"} + } + }, + { + "from": [11, 6, 1], + "to": [15, 10, 2], + "faces": { + "north": {"uv": [4.25, 5, 5.25, 7], "texture": "#0"}, + "east": {"uv": [4, 5, 4.25, 7], "texture": "#0"}, + "west": {"uv": [5, 5, 5.25, 7], "texture": "#0"}, + "up": {"uv": [0, 0, 1, 0.5], "texture": "#missing"} + } + }, + { + "name": "screen", + "from": [11, 4, 2], + "to": [15, 8, 3], + "rotation": {"angle": 22.5, "axis": "x", "origin": [14, 13, -0.5]}, + "faces": { + "north": {"uv": [0, 0, 1, 2], "texture": "#0"}, + "east": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3, 0, 4, 2], "texture": "#0"}, + "west": {"uv": [3, 2, 4, 2.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3, 2, 4, 2.5], "texture": "#0"}, + "down": {"uv": [3, 2, 4, 2.5], "texture": "#0"} + } + }, + { + "from": [1, 8, 3], + "to": [9, 15, 15], + "faces": { + "north": {"uv": [0, 2, 16, 16], "texture": "#1"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 1], + "to": [10, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 0, 13], + "to": [10, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 1], + "to": [1, 16, 3], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 0, 13], + "to": [1, 16, 15], + "faces": { + "north": {"uv": [5.5, 0, 5.75, 8], "texture": "#0"}, + "east": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "south": {"uv": [5.75, 0, 6, 8], "texture": "#0"}, + "west": {"uv": [5.5, 0, 6, 8], "texture": "#0"}, + "up": {"uv": [5, 0, 5.25, 1], "texture": "#0"}, + "down": {"uv": [5.25, 0, 5.5, 1], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 13, 3], + "to": [10, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [9, 1, 3], + "to": [10, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 13, 3], + "to": [1, 15, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "name": "cubeframe", + "from": [0, 1, 3], + "to": [1, 3, 13], + "faces": { + "east": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "west": {"uv": [6, 1.5, 6.5, 6.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [6, 1.5, 6.25, 6.5], "texture": "#0"}, + "down": {"uv": [6.25, 1.5, 6.5, 6.5], "texture": "#0"} + } + }, + { + "from": [2, 9, -1], + "to": [8, 9, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + }, + { + "from": [1, 8, 1], + "to": [9, 9, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 14, 1], + "to": [9, 15, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [8, 9, 1], + "to": [9, 14, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 9, 1], + "to": [2, 14, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 3], + "to": [9, 7, 15], + "faces": { + "north": {"uv": [0, 2, 16, 16], "texture": "#2"}, + "east": {"uv": [14, 12.5, 11, 16], "texture": "#0"}, + "south": {"uv": [14, 12.5, 16, 16], "texture": "#0"}, + "west": {"uv": [11, 12.5, 14, 16], "texture": "#0"}, + "up": {"uv": [8.5, 5.5, 10.5, 11.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 5.5, 12.5, 11.5], "texture": "#0"} + } + }, + { + "from": [8, 1, 1], + "to": [9, 6, 3], + "faces": { + "north": {"uv": [8.5, 13, 8.75, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 6, 1], + "to": [9, 7, 3], + "faces": { + "north": {"uv": [8.5, 12.5, 10.5, 13], "texture": "#0"}, + "east": {"uv": [11, 12.5, 10.5, 13], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 13.5], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [8.5, 11.5, 10.5, 12.5], "texture": "#0"} + } + }, + { + "from": [1, 1, 1], + "to": [2, 6, 3], + "faces": { + "north": {"uv": [10.25, 13, 10.5, 15.5], "texture": "#0"}, + "east": {"uv": [11, 13, 10.5, 15.5], "texture": "#0"}, + "west": {"uv": [10.5, 13, 11, 15.5], "texture": "#0"} + } + }, + { + "from": [1, 0, 1], + "to": [9, 1, 3], + "faces": { + "north": {"uv": [8.5, 15.5, 10.5, 16], "texture": "#0"}, + "east": {"uv": [11, 15.5, 10.5, 16], "texture": "#0"}, + "west": {"uv": [10.5, 15.5, 11, 16], "texture": "#0"}, + "up": {"uv": [8.5, 11.5, 10.5, 12.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 11.5, 12.5, 12.5], "texture": "#0"} + } + }, + { + "from": [2, 1, -1], + "to": [8, 1, 1], + "faces": { + "up": {"uv": [0, 4, 1.5, 5], "texture": "#0"}, + "down": {"uv": [0, 4, 1.5, 5], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/android_charger.json b/src/main/resources/assets/overdrive_that_matters/models/item/android_charger.json new file mode 100644 index 000000000..fa38f4ae2 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/android_charger.json @@ -0,0 +1,456 @@ +{ + "credit": "Made with Blockbench", + "render_type": "cutout", + "texture_size": [32, 64], + "textures": { + "0": "overdrive_that_matters:block/android_charger", + "particle": "overdrive_that_matters:block/android_charger" + }, + "elements": [ + { + "name": "body", + "from": [0, -16, 0], + "to": [16, -8, 16], + "faces": { + "north": {"uv": [0, 5, 8, 7], "texture": "#0"}, + "east": {"uv": [0, 8, 8, 10], "texture": "#0"}, + "south": {"uv": [0, 10, 8, 12], "texture": "#0"}, + "west": {"uv": [8, 8, 0, 10], "texture": "#0"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#0"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, -8, 1], + "to": [15, -4, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "east": {"uv": [9, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [5, -8, 1], + "to": [1, -4, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "east": {"uv": [9, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "texture": "#0"} + } + }, + { + "name": "body", + "from": [2, -8, 2], + "to": [14, -5, 14], + "faces": { + "north": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "east": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "south": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "west": {"uv": [14, 13, 15.5, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [8, 13, 14, 16], "texture": "#0"}, + "down": {"uv": [0, 0, 12, 12], "texture": "#missing"} + } + }, + { + "name": "frame", + "from": [1, -8, 1], + "to": [5, -4, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [15, -8, 1], + "to": [11, -4, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, -5, 3], + "to": [5, 0, 5], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"} + } + }, + { + "name": "core", + "from": [4, -5, 4], + "to": [12, 0, 12], + "faces": { + "north": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "east": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "south": {"uv": [8, 9, 12, 10.25], "texture": "#0"}, + "west": {"uv": [8, 9, 12, 10.25], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [11, -5, 3], + "to": [13, 0, 5], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, -5, 11], + "to": [13, 0, 13], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, -5, 11], + "to": [5, 0, 13], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 1.25], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 1.25], "texture": "#0"} + } + }, + { + "name": "spine", + "from": [5, -8, 12], + "to": [11, 0, 16], + "faces": { + "east": {"uv": [16, 5, 14, 7], "texture": "#0"}, + "south": {"uv": [12, 9, 15, 11], "texture": "#0"}, + "west": {"uv": [14, 5, 16, 7], "texture": "#0"} + } + }, + { + "name": "core", + "from": [4, 0, 4], + "to": [12, 16, 12], + "faces": { + "north": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "east": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "south": {"uv": [8, 9, 12, 13], "texture": "#0"}, + "west": {"uv": [8, 9, 12, 13], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [3, 0, 11], + "to": [5, 16, 13], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 3], + "to": [13, 16, 5], + "faces": { + "north": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 0, 3], + "to": [5, 16, 5], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 0, 11], + "to": [13, 16, 13], + "faces": { + "north": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 0, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 0, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "spine", + "from": [5, 0, 12], + "to": [11, 16, 16], + "faces": { + "east": {"uv": [16, 5, 14, 9], "texture": "#0"}, + "south": {"uv": [12, 9, 15, 13], "texture": "#0"}, + "west": {"uv": [14, 5, 16, 9], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, -1, 2], + "to": [14, 5, 14], + "faces": { + "north": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "east": {"uv": [0, 1.5, 6, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "west": {"uv": [0, 1.5, 6, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, -1, 14], + "to": [14, 5, 2], + "faces": { + "north": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "east": {"uv": [6, 1.5, 0, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "west": {"uv": [6, 1.5, 0, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, 11, 14], + "to": [14, 17, 2], + "faces": { + "north": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "east": {"uv": [6, 1.5, 0, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "west": {"uv": [6, 1.5, 0, 3], "texture": "#0"} + } + }, + { + "name": "grid", + "from": [2, 11, 2], + "to": [14, 17, 14], + "faces": { + "north": {"uv": [0, 0, 6, 1.5], "texture": "#0"}, + "east": {"uv": [0, 1.5, 6, 3], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 3, 6, 4.5], "texture": "#0"}, + "west": {"uv": [0, 1.5, 6, 3], "texture": "#0"} + } + }, + { + "name": "screen", + "from": [3, 27, 0], + "to": [13, 30, 0], + "faces": { + "north": {"uv": [11, 2.25, 12.5, 4.75], "rotation": 90, "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "frame", + "from": [3, 16, 11], + "to": [5, 21, 13], + "faces": { + "north": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 16, 3], + "to": [13, 21, 5], + "faces": { + "north": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "east": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "south": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "west": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [3, 16, 3], + "to": [5, 21, 5], + "faces": { + "north": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 16, 11], + "to": [13, 21, 13], + "faces": { + "north": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "east": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"}, + "south": {"uv": [7.5, 2.75, 8.5, 4], "texture": "#0"}, + "west": {"uv": [8.5, 2.75, 9.5, 4], "texture": "#0"} + } + }, + { + "name": "core", + "from": [4, 16, 4], + "to": [12, 21, 12], + "faces": { + "north": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "east": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "south": {"uv": [8, 11.75, 12, 13], "texture": "#0"}, + "west": {"uv": [8, 11.75, 12, 13], "texture": "#0"} + }, + "forge_data": { + "block_light": 15 + } + }, + { + "name": "spine", + "from": [5, 16, 12], + "to": [11, 24, 16], + "faces": { + "east": {"uv": [16, 7, 14, 9], "texture": "#0"}, + "south": {"uv": [12, 11, 15, 13], "texture": "#0"}, + "west": {"uv": [14, 7, 16, 9], "texture": "#0"} + } + }, + { + "name": "frame", + "from": [1, 20, 1], + "to": [5, 24, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [15, 20, 1], + "to": [11, 24, 15], + "faces": { + "north": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "west": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [11, 20, 1], + "to": [15, 24, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "east": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "frame", + "from": [5, 20, 1], + "to": [1, 24, 15], + "faces": { + "north": {"uv": [0, 7, 2, 8], "rotation": 180, "texture": "#0"}, + "east": {"uv": [2, 7, 9, 8], "rotation": 180, "texture": "#0"}, + "south": {"uv": [2, 7, 0, 8], "rotation": 180, "texture": "#0"} + } + }, + { + "name": "body", + "from": [2, 21, 2], + "to": [14, 24, 14], + "faces": { + "north": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "east": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "west": {"uv": [14, 13, 15.5, 16], "rotation": 270, "texture": "#0"}, + "down": {"uv": [8, 13, 14, 16], "texture": "#0"} + } + }, + { + "name": "body", + "from": [0, 24, 0], + "to": [16, 32, 16], + "faces": { + "north": {"uv": [10, 5, 14, 9], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 8, 0, 10], "rotation": 180, "texture": "#0"}, + "south": {"uv": [0, 10, 8, 12], "rotation": 180, "texture": "#0"}, + "west": {"uv": [0, 8, 8, 10], "rotation": 180, "texture": "#0"}, + "up": {"uv": [0, 12, 8, 16], "texture": "#0"}, + "down": {"uv": [0, 12, 8, 16], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.3, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, -135, 0], + "scale": [0.3, 0.3, 0.3] + }, + "head": { + "translation": [0, 30, 0] + }, + "fixed": { + "translation": [0, 0, -2], + "scale": [0.375, 0.375, 0.375] + } + }, + "groups": [ + { + "name": "base", + "origin": [0, 0, 0], + "color": 0, + "children": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + }, + { + "name": "middle", + "origin": [0, 0, 0], + "color": 0, + "children": [12, 13, 14, 15, 16, 17, 18, 19, 20, 21] + }, + { + "name": "top", + "origin": [0, 0, 0], + "color": 0, + "children": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/energy_sword_powered.json b/src/main/resources/assets/overdrive_that_matters/models/item/energy_sword_powered.json index 2e897f089..54f7f965a 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/item/energy_sword_powered.json +++ b/src/main/resources/assets/overdrive_that_matters/models/item/energy_sword_powered.json @@ -260,13 +260,14 @@ "from": [7.1, 7, 6.9], "to": [8.8, 29.3, 12], "faces": { - "north": {"uv": [2.45, 1, 3.45, 12], "texture": "#1", "emissivity": 15}, - "east": {"uv": [0, 1, 2.5, 12], "texture": "#1", "emissivity": 15}, - "south": {"uv": [6, 1, 7, 12], "texture": "#1", "emissivity": 15}, - "west": {"uv": [3.5, 1, 6, 12], "texture": "#1", "emissivity": 15}, - "up": {"uv": [3.5, 0, 6, 1], "rotation": 90, "texture": "#1", "emissivity": 15}, + "north": {"uv": [2.45, 1, 3.45, 12], "texture": "#1"}, + "east": {"uv": [0, 1, 2.5, 12], "texture": "#1"}, + "south": {"uv": [6, 1, 7, 12], "texture": "#1"}, + "west": {"uv": [3.5, 1, 6, 12], "texture": "#1"}, + "up": {"uv": [3.5, 0, 6, 1], "rotation": 90, "texture": "#1"}, "down": {"uv": [0, 0, 1, 2.5], "texture": "#missing"} - } + }, + "forge_data": { "block_light": 15, "sky_light": 15 } }, { "name": "capacitor", diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer.json b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer.json new file mode 100644 index 000000000..87f5ba7c0 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer.json @@ -0,0 +1,23 @@ +{ + "loader": "forge:separate_transforms", + "gui_light": "front", + "base": + { + "parent": "overdrive_that_matters:item/explosive_hammer_unprimed" + }, + "perspectives": { + "gui": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "fixed": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "ground": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + } + }, + "overrides": [ + { "predicate": { "overdrive_that_matters:is_primed": 0.01 }, "model": "overdrive_that_matters:item/explosive_hammer_charging" }, + { "predicate": { "overdrive_that_matters:is_primed": 1.0 }, "model": "overdrive_that_matters:item/explosive_hammer_primed" } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_charging.json b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_charging.json new file mode 100644 index 000000000..f1ab82056 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_charging.json @@ -0,0 +1,37 @@ +{ + "loader": "forge:separate_transforms", + "gui_light": "front", + "base": + { + "parent": "overdrive_that_matters:item/explosive_hammer_unprimed", + "display": { + "thirdperson_righthand": { + "rotation": [0, 45, 0], + "translation": [0, -10, 5] + }, + "thirdperson_lefthand": { + "rotation": [0, 45, 0], + "translation": [0, -10, 5] + }, + "firstperson_righthand": { + "rotation": [-65, 0, 75], + "translation": [5, -7.5, -10] + }, + "firstperson_lefthand": { + "rotation": [-65, 0, 75], + "translation": [5, -7.5, -10] + } + } + }, + "perspectives": { + "gui": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "fixed": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "ground": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_inventory.json b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_inventory.json new file mode 100644 index 000000000..1d7322469 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_inventory.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "overdrive_that_matters:item/explosive_hammer_item" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_primed.json b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_primed.json new file mode 100644 index 000000000..426e784cd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_primed.json @@ -0,0 +1,290 @@ +{ + "loader": "forge:separate_transforms", + "gui_light": "front", + "base": + { + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "0": "overdrive_that_matters:item/hammer", + "particle": "overdrive_that_matters:item/hammer" + }, + "elements": [ + { + "from": [7, 0, 7], + "to": [9, 2, 9], + "faces": { + "north": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "east": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "south": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "west": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "up": {"uv": [4, 10, 5, 11], "texture": "#0"}, + "down": {"uv": [3, 10, 4, 11], "texture": "#0"} + } + }, + { + "from": [7, 14, 7], + "to": [9, 20, 9], + "faces": { + "north": {"uv": [9.5, 3, 10.5, 6], "texture": "#0"}, + "east": {"uv": [11.5, 3, 10.5, 6], "texture": "#0"}, + "south": {"uv": [11.5, 3, 12.5, 6], "texture": "#0"}, + "west": {"uv": [10.5, 3, 11.5, 6], "texture": "#0"}, + "down": {"uv": [4.75, 6, 5.25, 7], "texture": "#0"} + } + }, + { + "from": [7, 20, 1], + "to": [9, 23, 13], + "faces": { + "north": {"uv": [8, 7, 9.5, 10], "texture": "#0"}, + "east": {"uv": [11.5, 0, 5.5, 1.5], "texture": "#0"}, + "south": {"uv": [13, 1, 14, 2.5], "texture": "#0"}, + "west": {"uv": [5.5, 0, 11.5, 1.5], "texture": "#0"}, + "up": {"uv": [0, 12, 6, 13], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7, 1.5, 13, 2.5], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [7.5, 2, 7.5], + "to": [8.5, 14, 8.5], + "faces": { + "north": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [7.5, 8, 9.5], + "to": [8.5, 20, 10.5], + "faces": { + "north": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "down": {"uv": [6, 11.5, 6.5, 12], "texture": "#0"} + } + }, + { + "from": [7.5, 12, 8.5], + "to": [8.5, 13, 9.5], + "faces": { + "east": {"uv": [2.5, 3.5, 3, 4], "texture": "#0"}, + "west": {"uv": [2.5, 3, 3, 3.5], "texture": "#0"}, + "up": {"uv": [2, 3, 2.5, 3.5], "texture": "#0"}, + "down": {"uv": [2, 3.5, 2.5, 4], "texture": "#0"} + } + }, + { + "from": [6, 18, 0], + "to": [10, 21, 3], + "faces": { + "north": {"uv": [0, 8.5, 2, 10], "texture": "#0"}, + "east": {"uv": [3.5, 8.5, 2, 10], "texture": "#0"}, + "south": {"uv": [2, 7, 4, 8.5], "texture": "#0"}, + "west": {"uv": [2, 8.5, 3.5, 10], "texture": "#0"}, + "up": {"uv": [0, 7, 2, 8.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 10, 2, 11.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6, 22, 0], + "to": [10, 25, 3], + "faces": { + "north": {"uv": [0, 1.5, 2, 3], "texture": "#0"}, + "east": {"uv": [3.5, 1.5, 2, 3], "texture": "#0"}, + "south": {"uv": [2, 0, 4, 1.5], "texture": "#0"}, + "west": {"uv": [2, 1.5, 3.5, 3], "texture": "#0"}, + "up": {"uv": [0, 0, 2, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 3, 2, 4.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 6], + "to": [9.5, 24, 10], + "faces": { + "east": {"uv": [7.5, 4.5, 5.5, 5.5], "texture": "#0"}, + "south": {"uv": [7.5, 4.5, 9, 5.5], "texture": "#0"}, + "west": {"uv": [5.5, 4.5, 7.5, 5.5], "texture": "#0"}, + "up": {"uv": [5.5, 3, 7.5, 4.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.5, 3, 9.5, 4.5], "texture": "#0"} + } + }, + { + "from": [7.1, 13.6, 9.1], + "to": [8.9, 19.6, 10.9], + "faces": { + "north": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"}, + "down": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"} + } + }, + { + "from": [7, 16, 9], + "to": [7, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 18, 9], + "to": [7, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 16, 9], + "to": [9, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 18, 9], + "to": [9, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 5], + "to": [10, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 5], + "to": [6, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 14], + "to": [10, 22.1, 14], + "faces": { + "north": {"uv": [0, 4.5, 2, 5], "texture": "#0"}, + "south": {"uv": [0, 4.5, 2, 5], "texture": "#0"} + } + }, + { + "from": [7, 18, 11], + "to": [9, 19, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 16, 11], + "to": [9, 17, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [6, 25.1, 4], + "to": [10, 25.1, 5], + "faces": { + "up": {"uv": [0, 6, 2, 6.5], "texture": "#0"}, + "down": {"uv": [0, 6, 2, 6.5], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 4], + "to": [6, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 4], + "to": [10, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [7, 17, 2], + "to": [9, 19, 6], + "faces": { + "north": {"uv": [0, 13, 1, 14], "texture": "#0"}, + "east": {"uv": [1, 14, 3, 15], "texture": "#0"}, + "south": {"uv": [3, 13, 4, 14], "texture": "#0"}, + "west": {"uv": [3, 13, 1, 14], "texture": "#0"}, + "down": {"uv": [1, 14, 3, 15], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 3], + "to": [9.5, 25, 6], + "faces": { + "north": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "east": {"uv": [5.5, 1.5, 4, 3], "texture": "#0"}, + "south": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "west": {"uv": [4, 1.5, 5.5, 3], "texture": "#0"}, + "up": {"uv": [4, 0, 5.5, 1.5], "texture": "#0"}, + "down": {"uv": [4, 3, 5.5, 4.5], "texture": "#0"} + } + }, + { + "from": [6.5, 18, 3], + "to": [9.5, 21, 7], + "faces": { + "north": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "east": {"uv": [5.5, 8.5, 3.5, 10], "texture": "#0"}, + "south": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "west": {"uv": [3.5, 8.5, 5.5, 10], "texture": "#0"}, + "up": {"uv": [4, 7, 6, 8.5], "rotation": 270, "texture": "#0"}, + "down": {"uv": [5.5, 8.5, 7.5, 10], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [7.5, 21, -2], + "to": [8.5, 22, 1], + "faces": { + "north": {"uv": [4, 4.5, 4.5, 5], "texture": "#0"}, + "east": {"uv": [4, 4.5, 4.5, 6], "rotation": 90, "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6], "rotation": 270, "texture": "#0"}, + "up": {"uv": [4, 4.5, 4.5, 6], "texture": "#0"}, + "down": {"uv": [4, 4.5, 4.5, 6], "rotation": 180, "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [0, 0, -1], + "translation": [0, 0.5, 0] + }, + "thirdperson_lefthand": { + "translation": [0, 0.5, 0] + } + } + }, + "perspectives": { + "gui": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "fixed": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + }, + "ground": { + "parent": "overdrive_that_matters:item/explosive_hammer_inventory" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_unprimed.json b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_unprimed.json new file mode 100644 index 000000000..1d20ae8c5 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/explosive_hammer_unprimed.json @@ -0,0 +1,263 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "0": "overdrive_that_matters:item/hammer", + "particle": "overdrive_that_matters:item/hammer" + }, + "elements": [ + { + "from": [7, 0, 7], + "to": [9, 2, 9], + "faces": { + "north": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "east": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "south": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "west": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "up": {"uv": [4, 10, 5, 11], "texture": "#0"}, + "down": {"uv": [3, 10, 4, 11], "texture": "#0"} + } + }, + { + "from": [7, 14, 7], + "to": [9, 20, 9], + "faces": { + "north": {"uv": [9.5, 3, 10.5, 6], "texture": "#0"}, + "east": {"uv": [11.5, 3, 10.5, 6], "texture": "#0"}, + "south": {"uv": [11.5, 3, 12.5, 6], "texture": "#0"}, + "west": {"uv": [10.5, 3, 11.5, 6], "texture": "#0"}, + "down": {"uv": [4.75, 6, 5.25, 7], "texture": "#0"} + } + }, + { + "from": [7, 20, 1], + "to": [9, 23, 13], + "faces": { + "north": {"uv": [8, 7, 9.5, 10], "texture": "#0"}, + "east": {"uv": [11.5, 0, 5.5, 1.5], "texture": "#0"}, + "south": {"uv": [13, 1, 14, 2.5], "texture": "#0"}, + "west": {"uv": [5.5, 0, 11.5, 1.5], "texture": "#0"}, + "up": {"uv": [0, 12, 6, 13], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7, 1.5, 13, 2.5], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [7.5, 2, 7.5], + "to": [8.5, 14, 8.5], + "faces": { + "north": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [7.5, 8, 9.5], + "to": [8.5, 20, 10.5], + "faces": { + "north": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "down": {"uv": [6, 11.5, 6.5, 12], "texture": "#0"} + } + }, + { + "from": [7.5, 12, 8.5], + "to": [8.5, 13, 9.5], + "faces": { + "east": {"uv": [2.5, 3.5, 3, 4], "texture": "#0"}, + "west": {"uv": [2.5, 3, 3, 3.5], "texture": "#0"}, + "up": {"uv": [2, 3, 2.5, 3.5], "texture": "#0"}, + "down": {"uv": [2, 3.5, 2.5, 4], "texture": "#0"} + } + }, + { + "from": [6, 17.1, 0], + "to": [10, 20.1, 3], + "faces": { + "north": {"uv": [0, 8.5, 2, 10], "texture": "#0"}, + "east": {"uv": [3.5, 8.5, 2, 10], "texture": "#0"}, + "south": {"uv": [2, 7, 4, 8.5], "texture": "#0"}, + "west": {"uv": [2, 8.5, 3.5, 10], "texture": "#0"}, + "up": {"uv": [0, 7, 2, 8.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 10, 2, 11.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6, 23, 0], + "to": [10, 26, 3], + "faces": { + "north": {"uv": [0, 1.5, 2, 3], "texture": "#0"}, + "east": {"uv": [3.5, 1.5, 2, 3], "texture": "#0"}, + "south": {"uv": [2, 0, 4, 1.5], "texture": "#0"}, + "west": {"uv": [2, 1.5, 3.5, 3], "texture": "#0"}, + "up": {"uv": [0, 0, 2, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 3, 2, 4.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 6], + "to": [9.5, 24, 10], + "faces": { + "east": {"uv": [7.5, 4.5, 5.5, 5.5], "texture": "#0"}, + "south": {"uv": [7.5, 4.5, 9, 5.5], "texture": "#0"}, + "west": {"uv": [5.5, 4.5, 7.5, 5.5], "texture": "#0"}, + "up": {"uv": [5.5, 3, 7.5, 4.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.5, 3, 9.5, 4.5], "texture": "#0"} + } + }, + { + "from": [7.1, 13.6, 9.1], + "to": [8.9, 19.6, 10.9], + "faces": { + "north": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"}, + "down": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"} + } + }, + { + "from": [7, 16, 9], + "to": [7, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 18, 9], + "to": [7, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 16, 9], + "to": [9, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 18, 9], + "to": [9, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 5], + "to": [10, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 5], + "to": [6, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 14], + "to": [10, 22.1, 14], + "faces": { + "north": {"uv": [0, 4.5, 2, 5], "texture": "#0"}, + "south": {"uv": [0, 4.5, 2, 5], "texture": "#0"} + } + }, + { + "from": [7, 18, 11], + "to": [9, 19, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 16, 11], + "to": [9, 17, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [6, 25.1, 4], + "to": [10, 25.1, 5], + "faces": { + "up": {"uv": [0, 6, 2, 6.5], "texture": "#0"}, + "down": {"uv": [0, 6, 2, 6.5], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 4], + "to": [6, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 4], + "to": [10, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [7, 17, 2], + "to": [9, 19, 6], + "faces": { + "north": {"uv": [0, 13, 1, 14], "texture": "#0"}, + "east": {"uv": [1, 14, 3, 15], "texture": "#0"}, + "south": {"uv": [3, 13, 4, 14], "texture": "#0"}, + "west": {"uv": [3, 13, 1, 14], "texture": "#0"}, + "down": {"uv": [1, 14, 3, 15], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 3], + "to": [9.5, 25, 6], + "faces": { + "north": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "east": {"uv": [5.5, 1.5, 4, 3], "texture": "#0"}, + "south": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "west": {"uv": [4, 1.5, 5.5, 3], "texture": "#0"}, + "up": {"uv": [4, 0, 5.5, 1.5], "texture": "#0"}, + "down": {"uv": [4, 3, 5.5, 4.5], "texture": "#0"} + } + }, + { + "from": [6.5, 18, 3], + "to": [9.5, 21, 7], + "faces": { + "north": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "east": {"uv": [5.5, 8.5, 3.5, 10], "texture": "#0"}, + "south": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "west": {"uv": [3.5, 8.5, 5.5, 10], "texture": "#0"}, + "up": {"uv": [4, 7, 6, 8.5], "rotation": 270, "texture": "#0"}, + "down": {"uv": [5.5, 8.5, 7.5, 10], "rotation": 270, "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [0, 0, -1], + "translation": [0, 0.5, 0] + }, + "thirdperson_lefthand": { + "translation": [0, 0.5, 0] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/fluid_capsule.json b/src/main/resources/assets/overdrive_that_matters/models/item/fluid_capsule.json new file mode 100644 index 000000000..312408e50 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/fluid_capsule.json @@ -0,0 +1,9 @@ +{ + "parent": "forge:item/default", + "loader": "forge:fluid_container", + "fluid": "minecraft:empty", + "textures": { + "base": "overdrive_that_matters:item/fluid_capsule", + "fluid": "overdrive_that_matters:item/fluid_capsule_liquid_mask" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/fluid_tank.json b/src/main/resources/assets/overdrive_that_matters/models/item/fluid_tank.json new file mode 100644 index 000000000..ee9352db8 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/fluid_tank.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:builtin/entity", + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 2], + "translation": [0, 2.75, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 45, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 225, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 3, 0], + "scale": [0.25, 0.25, 0.25] + }, + "gui": { + "rotation": [30, 225, 0], + "scale": [0.625, 0.625, 0.625] + }, + "fixed": { + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/hammer.json b/src/main/resources/assets/overdrive_that_matters/models/item/hammer.json new file mode 100644 index 000000000..5d00f54fc --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/hammer.json @@ -0,0 +1,277 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [32, 32], + "textures": { + "0": "overdrive_that_matters:item/hammer", + "particle": "overdrive_that_matters:item/hammer" + }, + "elements": [ + { + "from": [7, 0, 7], + "to": [9, 2, 9], + "faces": { + "north": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "east": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "south": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "west": {"uv": [2, 10, 3, 11], "texture": "#0"}, + "up": {"uv": [4, 10, 5, 11], "texture": "#0"}, + "down": {"uv": [3, 10, 4, 11], "texture": "#0"} + } + }, + { + "from": [7, 14, 7], + "to": [9, 20, 9], + "faces": { + "north": {"uv": [9.5, 3, 10.5, 6], "texture": "#0"}, + "east": {"uv": [11.5, 3, 10.5, 6], "texture": "#0"}, + "south": {"uv": [11.5, 3, 12.5, 6], "texture": "#0"}, + "west": {"uv": [10.5, 3, 11.5, 6], "texture": "#0"}, + "down": {"uv": [4.75, 6, 5.25, 7], "texture": "#0"} + } + }, + { + "from": [7, 20, 1], + "to": [9, 23, 13], + "faces": { + "north": {"uv": [8, 7, 9.5, 10], "texture": "#0"}, + "east": {"uv": [11.5, 0, 5.5, 1.5], "texture": "#0"}, + "south": {"uv": [13, 1, 14, 2.5], "texture": "#0"}, + "west": {"uv": [5.5, 0, 11.5, 1.5], "texture": "#0"}, + "up": {"uv": [0, 12, 6, 13], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7, 1.5, 13, 2.5], "rotation": 270, "texture": "#0"} + } + }, + { + "from": [7.5, 2, 7.5], + "to": [8.5, 14, 8.5], + "faces": { + "north": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 6.5, 6, 7], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [7.5, 8, 9.5], + "to": [8.5, 20, 10.5], + "faces": { + "north": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 11.5, 6, 12], "rotation": 90, "texture": "#0"}, + "down": {"uv": [6, 11.5, 6.5, 12], "texture": "#0"} + } + }, + { + "from": [7.5, 12, 8.5], + "to": [8.5, 13, 9.5], + "faces": { + "east": {"uv": [2.5, 3.5, 3, 4], "texture": "#0"}, + "west": {"uv": [2.5, 3, 3, 3.5], "texture": "#0"}, + "up": {"uv": [2, 3, 2.5, 3.5], "texture": "#0"}, + "down": {"uv": [2, 3.5, 2.5, 4], "texture": "#0"} + } + }, + { + "from": [6, 17.1, 0], + "to": [10, 20.1, 3], + "faces": { + "north": {"uv": [0, 8.5, 2, 10], "texture": "#0"}, + "east": {"uv": [3.5, 8.5, 2, 10], "texture": "#0"}, + "south": {"uv": [2, 7, 4, 8.5], "texture": "#0"}, + "west": {"uv": [2, 8.5, 3.5, 10], "texture": "#0"}, + "up": {"uv": [0, 7, 2, 8.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 10, 2, 11.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6, 23, 0], + "to": [10, 26, 3], + "faces": { + "north": {"uv": [0, 1.5, 2, 3], "texture": "#0"}, + "east": {"uv": [3.5, 1.5, 2, 3], "texture": "#0"}, + "south": {"uv": [2, 0, 4, 1.5], "texture": "#0"}, + "west": {"uv": [2, 1.5, 3.5, 3], "texture": "#0"}, + "up": {"uv": [0, 0, 2, 1.5], "rotation": 180, "texture": "#0"}, + "down": {"uv": [0, 3, 2, 4.5], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 6], + "to": [9.5, 24, 10], + "faces": { + "east": {"uv": [7.5, 4.5, 5.5, 5.5], "texture": "#0"}, + "south": {"uv": [7.5, 4.5, 9, 5.5], "texture": "#0"}, + "west": {"uv": [5.5, 4.5, 7.5, 5.5], "texture": "#0"}, + "up": {"uv": [5.5, 3, 7.5, 4.5], "rotation": 90, "texture": "#0"}, + "down": {"uv": [7.5, 3, 9.5, 4.5], "texture": "#0"} + } + }, + { + "from": [7.1, 13.6, 9.1], + "to": [8.9, 19.6, 10.9], + "faces": { + "north": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 5.05, 3, 5.95], "rotation": 90, "texture": "#0"}, + "up": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"}, + "down": {"uv": [3.025, 5.05, 3.975, 5.95], "texture": "#0"} + } + }, + { + "from": [7, 16, 9], + "to": [7, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 18, 9], + "to": [7, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 16, 9], + "to": [9, 17, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [9, 18, 9], + "to": [9, 19, 11], + "faces": { + "east": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "west": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 5], + "to": [10, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 5], + "to": [6, 22.1, 14], + "faces": { + "east": {"uv": [9, 5.5, 4.5, 6], "texture": "#0"}, + "west": {"uv": [4.5, 5.5, 9, 6], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 14], + "to": [10, 22.1, 14], + "faces": { + "north": {"uv": [0, 4.5, 2, 5], "texture": "#0"}, + "south": {"uv": [0, 4.5, 2, 5], "texture": "#0"} + } + }, + { + "from": [7, 18, 11], + "to": [9, 19, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [7, 16, 11], + "to": [9, 17, 11], + "faces": { + "north": {"uv": [2, 4, 3, 4.5], "texture": "#0"}, + "south": {"uv": [2, 4, 3, 4.5], "texture": "#0"} + } + }, + { + "from": [6, 25.1, 4], + "to": [10, 25.1, 5], + "faces": { + "up": {"uv": [0, 6, 2, 6.5], "texture": "#0"}, + "down": {"uv": [0, 6, 2, 6.5], "texture": "#0"} + } + }, + { + "from": [6, 21.1, 4], + "to": [6, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [10, 21.1, 4], + "to": [10, 25.1, 5], + "faces": { + "east": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"}, + "west": {"uv": [4, 4.5, 4.5, 6.5], "texture": "#0"} + } + }, + { + "from": [7, 17, 2], + "to": [9, 19, 6], + "faces": { + "north": {"uv": [0, 13, 1, 14], "texture": "#0"}, + "east": {"uv": [1, 14, 3, 15], "texture": "#0"}, + "south": {"uv": [3, 13, 4, 14], "texture": "#0"}, + "west": {"uv": [3, 13, 1, 14], "texture": "#0"}, + "down": {"uv": [1, 14, 3, 15], "rotation": 90, "texture": "#0"} + } + }, + { + "from": [6.5, 22, 3], + "to": [9.5, 25, 6], + "faces": { + "north": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "east": {"uv": [5.5, 1.5, 4, 3], "texture": "#0"}, + "south": {"uv": [5.5, 1.5, 7, 3], "texture": "#0"}, + "west": {"uv": [4, 1.5, 5.5, 3], "texture": "#0"}, + "up": {"uv": [4, 0, 5.5, 1.5], "texture": "#0"}, + "down": {"uv": [4, 3, 5.5, 4.5], "texture": "#0"} + } + }, + { + "from": [6.5, 18, 3], + "to": [9.5, 21, 7], + "faces": { + "north": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "east": {"uv": [5.5, 8.5, 3.5, 10], "texture": "#0"}, + "south": {"uv": [4.5, 7, 6, 8.5], "texture": "#0"}, + "west": {"uv": [3.5, 8.5, 5.5, 10], "texture": "#0"}, + "up": {"uv": [4, 7, 6, 8.5], "rotation": 270, "texture": "#0"}, + "down": {"uv": [5.5, 8.5, 7.5, 10], "rotation": 270, "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [0, 0, -1], + "translation": [0, 0.5, 0] + }, + "thirdperson_lefthand": { + "translation": [0, 0.5, 0] + }, + "ground": { + "rotation": [29, 0, 0], + "translation": [0, 2, -0.75], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [-17, 143, 39], + "translation": [-2, -3.25, 0], + "scale": [0.76, 0.76, 0.76] + }, + "fixed": { + "rotation": [0, 90, 0], + "translation": [0, -4, 0] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/machine_frame.json b/src/main/resources/assets/overdrive_that_matters/models/item/machine_frame.json index 4f1a2d675..00faf3b8e 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/item/machine_frame.json +++ b/src/main/resources/assets/overdrive_that_matters/models/item/machine_frame.json @@ -1,5 +1,7 @@ { + "credit": "Made with Blockbench", "parent": "block/block", + "texture_size": [32, 16], "textures": { "texture": "overdrive_that_matters:item/component/machine_frame" }, @@ -36,9 +38,7 @@ "to": [14, 2, 2], "faces": { "north": {"uv": [1, 14, 7, 16], "texture": "#texture"}, - "east": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "south": {"uv": [9, 0, 15, 2], "texture": "#texture"}, - "west": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "up": {"uv": [9, 0, 15, 2], "texture": "#texture"}, "down": {"uv": [1, 14, 7, 16], "texture": "#texture"} } @@ -49,9 +49,7 @@ "to": [14, 2, 16], "faces": { "north": {"uv": [9, 0, 15, 2], "texture": "#texture"}, - "east": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "south": {"uv": [1, 14, 7, 16], "texture": "#texture"}, - "west": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "up": {"uv": [9, 0, 15, 2], "texture": "#texture"}, "down": {"uv": [1, 0, 7, 2], "texture": "#texture"} } @@ -62,9 +60,7 @@ "to": [14, 16, 2], "faces": { "north": {"uv": [1, 0, 7, 2], "texture": "#texture"}, - "east": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "south": {"uv": [9, 0, 15, 2], "texture": "#texture"}, - "west": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "up": {"uv": [1, 0, 7, 2], "texture": "#texture"}, "down": {"uv": [9, 0, 15, 2], "texture": "#texture"} } @@ -88,9 +84,7 @@ "to": [14, 16, 16], "faces": { "north": {"uv": [9, 0, 15, 2], "texture": "#texture"}, - "east": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "south": {"uv": [1, 0, 7, 2], "texture": "#texture"}, - "west": {"uv": [0, 0, 2, 2], "texture": "#texture"}, "up": {"uv": [1, 14, 7, 16], "texture": "#texture"}, "down": {"uv": [9, 0, 15, 2], "texture": "#texture"} } @@ -116,9 +110,7 @@ "north": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, "east": {"uv": [0, 2, 1, 14], "texture": "#texture"}, "south": {"uv": [7, 2, 8, 14], "texture": "#texture"}, - "west": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, - "up": {"uv": [0, 0, 2, 2], "texture": "#texture"}, - "down": {"uv": [0, 0, 2, 2], "texture": "#texture"} + "west": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"} } }, { @@ -129,9 +121,7 @@ "north": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, "east": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, "south": {"uv": [0, 2, 1, 14], "texture": "#texture"}, - "west": {"uv": [7, 2, 8, 14], "texture": "#texture"}, - "up": {"uv": [0, 0, 2, 2], "texture": "#texture"}, - "down": {"uv": [0, 0, 2, 2], "texture": "#texture"} + "west": {"uv": [7, 2, 8, 14], "texture": "#texture"} } }, { @@ -142,9 +132,7 @@ "north": {"uv": [0, 2, 1, 14], "texture": "#texture"}, "east": {"uv": [7, 2, 8, 14], "texture": "#texture"}, "south": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, - "west": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, - "up": {"uv": [0, 0, 2, 2], "texture": "#texture"}, - "down": {"uv": [0, 0, 2, 2], "texture": "#texture"} + "west": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"} } }, { @@ -155,9 +143,7 @@ "north": {"uv": [7, 2, 8, 14], "texture": "#texture"}, "east": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, "south": {"uv": [9, 0, 15, 2], "rotation": 90, "texture": "#texture"}, - "west": {"uv": [0, 2, 1, 14], "texture": "#texture"}, - "up": {"uv": [0, 0, 2, 2], "texture": "#texture"}, - "down": {"uv": [0, 0, 2, 2], "texture": "#texture"} + "west": {"uv": [0, 2, 1, 14], "texture": "#texture"} } }, { @@ -179,7 +165,6 @@ "to": [13, 13, 15], "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 8]}, "faces": { - "north": {"uv": [0, 0, 5, 10], "texture": "#missing"}, "east": {"uv": [6, 3, 6.5, 13], "texture": "#texture"}, "south": {"uv": [1.5, 3, 6.5, 13], "texture": "#texture"}, "west": {"uv": [1.5, 3, 2, 13], "texture": "#texture"}, @@ -195,7 +180,6 @@ "faces": { "north": {"uv": [1.5, 3, 6.5, 13], "texture": "#texture"}, "east": {"uv": [1.5, 3, 2, 13], "texture": "#texture"}, - "south": {"uv": [0, 0, 5, 10], "texture": "#missing"}, "west": {"uv": [6, 3, 6.5, 13], "texture": "#texture"}, "up": {"uv": [1.5, 3, 6.5, 4], "texture": "#texture"}, "down": {"uv": [1.5, 3, 6.5, 4], "texture": "#texture"} @@ -210,7 +194,6 @@ "north": {"uv": [6, 3, 6.5, 13], "texture": "#texture"}, "east": {"uv": [1.5, 3, 6.5, 13], "texture": "#texture"}, "south": {"uv": [1.5, 3, 2, 13], "texture": "#texture"}, - "west": {"uv": [0, 0, 5, 10], "texture": "#missing"}, "up": {"uv": [1.5, 3, 6.5, 4], "rotation": 90, "texture": "#texture"}, "down": {"uv": [1.5, 3, 6.5, 4], "rotation": 270, "texture": "#texture"} } @@ -222,7 +205,6 @@ "rotation": {"angle": 0, "axis": "y", "origin": [8, 8, 8]}, "faces": { "north": {"uv": [1.5, 3, 2, 13], "texture": "#texture"}, - "east": {"uv": [0, 0, 5, 10], "texture": "#missing"}, "south": {"uv": [6, 3, 6.5, 13], "texture": "#texture"}, "west": {"uv": [1.5, 3, 6.5, 13], "texture": "#texture"}, "up": {"uv": [1.5, 3, 6.5, 4], "rotation": 270, "texture": "#texture"}, @@ -239,7 +221,6 @@ "east": {"uv": [1.5, 3, 6.5, 4], "rotation": 180, "texture": "#texture"}, "south": {"uv": [1.5, 3, 2, 13], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.5, 3, 6.5, 4], "texture": "#texture"}, - "up": {"uv": [0, 0, 5, 10], "rotation": 90, "texture": "#missing"}, "down": {"uv": [1.5, 3, 6.5, 13], "rotation": 90, "texture": "#texture"} } }, @@ -253,8 +234,7 @@ "east": {"uv": [1.5, 3, 6.5, 4], "texture": "#texture"}, "south": {"uv": [6, 3, 6.5, 13], "rotation": 90, "texture": "#texture"}, "west": {"uv": [1.5, 3, 6.5, 4], "rotation": 180, "texture": "#texture"}, - "up": {"uv": [1.5, 3, 6.5, 13], "rotation": 90, "texture": "#texture"}, - "down": {"uv": [0, 0, 5, 10], "rotation": 90, "texture": "#missing"} + "up": {"uv": [1.5, 3, 6.5, 13], "rotation": 90, "texture": "#texture"} } } ] diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield.json b/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield.json new file mode 100644 index 000000000..5789c6bf2 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield.json @@ -0,0 +1,147 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "0": "overdrive_that_matters:item/tritanium_shield", + "particle": "overdrive_that_matters:item/tritanium_shield" + }, + "elements": [ + { + "from": [5, 7, 5], + "to": [11, 9, 11], + "faces": { + "north": {"uv": [8, 1.5, 8.5, 3], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 0, 8.5, 1.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [10, 1.5, 10.5, 3], "rotation": 90, "texture": "#0"}, + "west": {"uv": [8.5, 0, 9, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [10, 3, 8.5, 1.5], "texture": "#0"}, + "down": {"uv": [8, 1.5, 6.5, 3], "texture": "#0"} + } + }, + { + "from": [11, 2, -3], + "to": [12, 14, 19], + "faces": { + "north": {"uv": [0.25, 0.25, 3.25, 0], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 0.25, 3.25, 5.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3.25, 0, 6.25, 0.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [3.5, 0.25, 6.5, 5.75], "rotation": 270, "texture": "#0"}, + "up": {"uv": [0.25, 5.75, 0, 0.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3.5, 0.25, 3.25, 5.75], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [11.6, 12, -4], + "to": [12.6, 13, 20], + "faces": { + "north": {"uv": [0, 9.5, 0.25, 9.75], "texture": "#0"}, + "east": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 15.75, 0.25, 16], "texture": "#0"}, + "west": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"}, + "down": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"} + } + }, + { + "from": [11.6, 3, -4], + "to": [12.6, 4, 20], + "faces": { + "north": {"uv": [0, 9.5, 0.25, 9.75], "texture": "#0"}, + "east": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 15.75, 0.25, 16], "texture": "#0"}, + "west": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"}, + "down": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"} + } + }, + { + "from": [11.6, 4, -1.1], + "to": [12.6, 12, -0.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.25, 2], "texture": "#missing"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 4, -3.1], + "to": [12.6, 12, -2.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 4, 16.1], + "to": [12.6, 12, 17.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.25, 2], "texture": "#missing"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 4, 18.1], + "to": [12.6, 12, 19.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [90, 0, 0], + "translation": [0, -2, 4] + }, + "thirdperson_lefthand": { + "rotation": [90, 0, 180], + "translation": [0, -2, 4] + }, + "firstperson_righthand": { + "rotation": [90, -5, -90], + "translation": [-1.75, -7, 4] + }, + "firstperson_lefthand": { + "rotation": [90, -5, 90], + "translation": [-1.75, -7, 4] + }, + "ground": { + "rotation": [90, 0, 0], + "translation": [0, 3, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [91, -9, 115], + "translation": [1, -0.25, 0], + "scale": [0.65, 0.65, 0.65] + }, + "fixed": { + "rotation": [90, 0, -90], + "translation": [0, 0, 0.5], + "scale": [0.5, 0.5, 0.5] + } + }, + "overrides": [ + { + "predicate": { + "overdrive_that_matters:blocking": 1 + }, + "model": "overdrive_that_matters:item/tritanium_shield_blocking" + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield_blocking.json b/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield_blocking.json new file mode 100644 index 000000000..22d720b39 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/item/tritanium_shield_blocking.json @@ -0,0 +1,139 @@ +{ + "credit": "Made with Blockbench", + "texture_size": [64, 64], + "textures": { + "0": "overdrive_that_matters:item/tritanium_shield", + "particle": "overdrive_that_matters:item/tritanium_shield" + }, + "elements": [ + { + "from": [5, 7, 5], + "to": [11, 9, 11], + "faces": { + "north": {"uv": [8, 1.5, 8.5, 3], "rotation": 90, "texture": "#0"}, + "east": {"uv": [8, 0, 8.5, 1.5], "rotation": 90, "texture": "#0"}, + "south": {"uv": [10, 1.5, 10.5, 3], "rotation": 90, "texture": "#0"}, + "west": {"uv": [8.5, 0, 9, 1.5], "rotation": 90, "texture": "#0"}, + "up": {"uv": [10, 3, 8.5, 1.5], "texture": "#0"}, + "down": {"uv": [8, 1.5, 6.5, 3], "texture": "#0"} + } + }, + { + "from": [11, 2, -3], + "to": [12, 14, 19], + "faces": { + "north": {"uv": [0.25, 0.25, 3.25, 0], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 0.25, 3.25, 5.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [3.25, 0, 6.25, 0.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [3.5, 0.25, 6.5, 5.75], "rotation": 270, "texture": "#0"}, + "up": {"uv": [0.25, 5.75, 0, 0.25], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3.5, 0.25, 3.25, 5.75], "rotation": 180, "texture": "#0"} + } + }, + { + "from": [11.6, 12, -4], + "to": [12.6, 13, 20], + "faces": { + "north": {"uv": [0, 9.5, 0.25, 9.75], "texture": "#0"}, + "east": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 15.75, 0.25, 16], "texture": "#0"}, + "west": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"}, + "down": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"} + } + }, + { + "from": [11.6, 4, 18.1], + "to": [12.6, 12, 19.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 3, -4], + "to": [12.6, 4, 20], + "faces": { + "north": {"uv": [0, 9.5, 0.25, 9.75], "texture": "#0"}, + "east": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0, 15.75, 0.25, 16], "texture": "#0"}, + "west": {"uv": [0, 9.75, 0.25, 15.75], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"}, + "down": {"uv": [0, 9.75, 0.25, 15.75], "texture": "#0"} + } + }, + { + "from": [11.6, 4, 16.1], + "to": [12.6, 12, 17.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.25, 2], "texture": "#missing"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 4, -1.1], + "to": [12.6, 12, -0.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0, 0, 0.25, 2], "texture": "#missing"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + }, + { + "from": [11.6, 4, -3.1], + "to": [12.6, 12, -2.1], + "faces": { + "north": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "east": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "south": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "west": {"uv": [0.25, 10, 2.25, 10.25], "rotation": 90, "texture": "#0"}, + "up": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"}, + "down": {"uv": [0, 0, 0.25, 0.25], "texture": "#missing"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [120, 0, -45], + "translation": [1, -2, 0] + }, + "thirdperson_lefthand": { + "rotation": [120, 0, 135], + "translation": [1, -2, 0] + }, + "firstperson_righthand": { + "rotation": [82, 6, -90], + "translation": [-1.5, -4.75, 4.75] + }, + "firstperson_lefthand": { + "rotation": [82, 6, 90], + "translation": [-1.75, -4.75, 4] + }, + "ground": { + "rotation": [90, 0, 0], + "translation": [0, 3, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [91, -9, 115], + "translation": [1, -0.25, 0], + "scale": [0.65, 0.65, 0.65] + }, + "fixed": { + "rotation": [90, 0, -90], + "translation": [0, 0, 0.5], + "scale": [0.5, 0.5, 0.5] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/models/item/zpm_battery.json b/src/main/resources/assets/overdrive_that_matters/models/item/zpm_battery.json index 6fa136c36..4e8564c07 100644 --- a/src/main/resources/assets/overdrive_that_matters/models/item/zpm_battery.json +++ b/src/main/resources/assets/overdrive_that_matters/models/item/zpm_battery.json @@ -1,4 +1,5 @@ { + "credit": "Made with Blockbench", "textures": { "0": "overdrive_that_matters:item/zpm_battery" }, diff --git a/src/main/resources/assets/overdrive_that_matters/optifine/emissive.properties b/src/main/resources/assets/overdrive_that_matters/optifine/emissive.properties deleted file mode 100644 index 55ac3b130..000000000 --- a/src/main/resources/assets/overdrive_that_matters/optifine/emissive.properties +++ /dev/null @@ -1 +0,0 @@ -suffix.emissive=_em \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.fsh b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.fsh new file mode 100644 index 000000000..0ed69a40d --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.fsh @@ -0,0 +1,14 @@ +#version 150 + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; + +in vec2 texCoord0; +in vec4 vertexColor; + +out vec4 fragColor; + +void main() { + fragColor = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; +} diff --git a/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.json b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.json new file mode 100644 index 000000000..f127dbe01 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.json @@ -0,0 +1,22 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "overdrive_that_matters:position_tex_color", + "fragment": "overdrive_that_matters:position_tex_color", + "attributes": [ + "Position", + "UV0", + "Color" + ], + "samplers": [ + { "name": "Sampler0" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] } + ] +} diff --git a/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.vsh b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.vsh new file mode 100644 index 000000000..1ed8e4068 --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/shaders/core/position_tex_color.vsh @@ -0,0 +1,18 @@ +#version 150 + +in vec3 Position; +in vec2 UV0; +in vec4 Color; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; + +out vec2 texCoord0; +out vec4 vertexColor; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); + + texCoord0 = UV0; + vertexColor = Color; +} diff --git a/src/main/resources/assets/overdrive_that_matters/sounds/android/punch_projectile.ogg b/src/main/resources/assets/overdrive_that_matters/sounds/android/punch_projectile.ogg new file mode 100644 index 000000000..fd1d63941 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/sounds/android/punch_projectile.ogg differ diff --git a/src/main/resources/assets/overdrive_that_matters/sounds/item/rifle_shot.ogg b/src/main/resources/assets/overdrive_that_matters/sounds/item/rifle_shot.ogg index 82f78ecdf..7e73c1313 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/sounds/item/rifle_shot.ogg and b/src/main/resources/assets/overdrive_that_matters/sounds/item/rifle_shot.ogg differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_charger.png b/src/main/resources/assets/overdrive_that_matters/textures/block/android_charger.png new file mode 100644 index 000000000..747af985d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/android_charger.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station.png b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station.png index eb34ef154..630516c65 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_base.png b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_base.png new file mode 100644 index 000000000..84754c0ce Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png index 2e734afc4..253476d7d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png.mcmeta b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png.mcmeta new file mode 100644 index 000000000..fa950acdd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_em.png.mcmeta @@ -0,0 +1,7 @@ +{ + "animation": { + "frametime": 16, + "width": 32, + "height": 64 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_offline.png b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_offline.png index 2e575e6e4..abd758f4f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_offline.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/android_station_offline.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/batterybank_core_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/batterybank_core_em.png deleted file mode 100644 index 268923c4e..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/batterybank_core_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate.png index f55a0c4b7..a7130ccc8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_black.png index 062cb6706..513d5e3a3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_blue.png index 9b96f96e7..a37a08dc1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_brown.png index 78c9e9e02..7a0af145f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_core.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_core.png new file mode 100644 index 000000000..22e7af19a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_core.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_cyan.png index 3b946c37f..3a62234ca 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_gray.png index f2f18b4b4..9b2399595 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_green.png index 364f0a668..9e9108e62 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_blue.png index d6fbbea41..35e8ec3c6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_gray.png index 9d573a1c2..32f7fd48c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_lime.png index 9f70f39c0..16972f96e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_magenta.png index 8f016fc6e..37b0ca179 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_orange.png index db25d3c1a..12962078b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_pink.png index c9cb19f6d..ab52ab2ec 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_purple.png index b805cbfa5..731d9dcdc 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_red.png index 21cc02660..cb8f60e32 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_white.png index c78517fb8..e711e7d0b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_yellow.png index f36b2ef95..67d2d174b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/cargo_crates/cargo_crate_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator.png b/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator.png index 3f3dfc4f2..3d787c187 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator_em.png deleted file mode 100644 index 8079b734d..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/chemical_generator_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/cobblestone_generator.png b/src/main/resources/assets/overdrive_that_matters/textures/block/cobblestone_generator.png new file mode 100644 index 000000000..46cf1b6b3 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/cobblestone_generator.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/danger_stripe_block.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/danger_stripe_block.png index d01e2e4c8..c292cfe09 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/danger_stripe_block.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/danger_stripe_block.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front.png index dfc103e87..be33b5c4d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front_off.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front_off.png index c1f603efe..3aaa9fa92 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front_off.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/laboratory_lamp_front_off.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_side.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_side.png index f4f5a7d8a..f82330696 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_side.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_side.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_top.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_top.png index c6c6a31ec..3264c1033 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_top.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_beam_top.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_a.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_a.png new file mode 100644 index 000000000..c2e99bcb6 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_a.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_b.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_b.png new file mode 100644 index 000000000..47ea78061 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_b.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_c.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_c.png new file mode 100644 index 000000000..1602fe6e3 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_c.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_d.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_d.png new file mode 100644 index 000000000..0cd41d470 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_junk_d.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_mesh.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_mesh.png new file mode 100644 index 000000000..a04f56375 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/metal_mesh.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_blue.png index a0a64f4a1..080eda87c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_brown.png index 7b70272b8..bfc2132a1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_cyan.png index 88304c398..0e6a63b0e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_gray.png index c091db0b4..c8ea0a0c2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_green.png index 45d92d823..6acd475c7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_blue.png index ddc12799e..7df66e938 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_gray.png index f3f3dc40d..589e8c777 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_lime.png index 947cddcf4..ff3f0a54d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_magenta.png index e4fd730a6..bf2de6158 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_orange.png index 43ff3cb65..d48bb2c4f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_pink.png index 6d03c16da..042d96d8c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_purple.png index 7b28def84..1a97cf7f4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_red.png index 3327feb4d..5f2170ad4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_white.png index b70e6ecce..eed5f76d3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_yellow.png index 2a8955d33..eff9a5928 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_black_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_black.png index a2037a048..dbf1ed0b4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_brown.png index f01cce557..3b7486a7a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_cyan.png index 3e77ae326..219b63b9b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_gray.png index e48cf4a04..ca61065a2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_green.png index a4cb78080..ddd41b9d7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_blue.png index 47538df82..abdee3b6f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_gray.png index a93db72a4..aa8033dc5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_lime.png index df6afdcde..577821e52 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_magenta.png index 4aba92e96..458d29823 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_orange.png index f4fe3744b..60f1c6cfe 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_pink.png index 25d53a459..dd7cbd6af 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_purple.png index ad839557d..69a299e15 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_red.png index c2aa6bd74..acc601aad 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_white.png index 0ab92eebe..c394682a4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_yellow.png index bb4a22bb6..3e53eee3e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_blue_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_black.png index d8424f6fd..fd3697e99 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_blue.png index 6544bb2a2..4f001a244 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_cyan.png index 0bbc5aa78..ff7f2dde8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_gray.png index fcce9200e..e8680debd 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_green.png index bf85e6478..e66fea0ae 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_blue.png index 4b00f3160..5575d61fe 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_gray.png index 841c83da6..73662b410 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_lime.png index 9060bbed1..c1e51a82f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_magenta.png index 28b42e93c..8d0b595d3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_orange.png index ae6157569..9d586de00 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_pink.png index 00da07f37..51b89f4a3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_purple.png index 98a05174d..a1125a03e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_red.png index 7a7773bc1..85ffc0987 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_white.png index b8cf02801..c4ff24eaf 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_yellow.png index c0f2914b6..1e57623ae 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_brown_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_black.png index 5a3394a47..97ab95d96 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_blue.png index 6ab4dd8af..d0cb1f966 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_brown.png index 8d6eb3d10..c7bbff3bb 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_gray.png index 19e54ba11..bbeccba14 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_green.png index 7a4eb234a..af930a478 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_blue.png index 5cc412026..d1a31a2cd 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_gray.png index 463648c0d..50918583a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_lime.png index 0c26ea608..ed39b0961 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_magenta.png index 603fbca15..0f72645b2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_orange.png index 2b4f7f58a..2bfb01d1f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_pink.png index d22b55f2c..c8455a07c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_purple.png index bcd18c6db..13ee645e3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_red.png index 031bc9239..5121cfef4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_white.png index a51af9ae1..d9af327a9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_yellow.png index 68cdc73e4..68cfa6d90 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_cyan_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_black.png index f76117d1c..8a2cd437f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_blue.png index 634752bea..223cd62d2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_brown.png index 7c1d4934d..ee99025dd 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_cyan.png index 6106af3ac..13d072b6f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_green.png index 2bf0905e8..4b59482c7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_blue.png index fd9ec89b2..1df29e620 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_gray.png index ebf692b82..1700d9056 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_lime.png index e77ca2a2c..e5869f654 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_magenta.png index 644a1a34e..e2e0634ba 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_orange.png index fdf3d7a1d..25cbe6e1a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_pink.png index 8621bd8f5..da9c63c6e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_purple.png index 25de0faaa..d81171f01 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_red.png index b7b344c9e..c88a6b1d8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_white.png index b7f3d15c2..392fa864c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_yellow.png index cb0b7805e..b5081905e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_gray_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_black.png index 412906125..9f38d0066 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_blue.png index 1193f5ad0..cf73f4aba 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_brown.png index b1dfe0749..9cf6a552f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_cyan.png index 3ec876831..57ae5da83 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_gray.png index d0c5bed6a..aa54e057b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_blue.png index c1b9d96a8..72e25855a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_gray.png index cbb3520e9..c4919cba4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_lime.png index 6f12c953c..4ba099b57 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_magenta.png index a95148de2..49d1b00e4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_orange.png index a8988d87e..914429da8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_pink.png index 39f2be5c8..4437698b9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_purple.png index 405c32292..726307e88 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_red.png index aab6d1fce..285e45e5e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_white.png index a83040635..4b753c998 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_yellow.png index 0b86dd431..af67466c8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_green_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_black.png index aea425936..77bd1b33a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_blue.png index 43e1f4763..91b978be3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_brown.png index ec6fab118..665c99d4a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_cyan.png index 61b3df6c0..2a895fdb4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_gray.png index 42d1a1573..d78cf7193 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_green.png index 22dbd982c..b42dc9edf 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_light_gray.png index 86adaadf3..148a890ea 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_lime.png index b283ead18..604547b1a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_magenta.png index b30084c64..2d62975f4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_orange.png index 4ce74f8bb..293d56452 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_pink.png index f5146d9b5..edb7286be 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_purple.png index 3c6f1d3c8..e30949891 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_red.png index db4335541..3cae33c64 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_white.png index 0359f4a5c..caee017b6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_yellow.png index d176720ea..cd13fb1eb 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_blue_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_black.png index 4c83fa3fb..cb239edde 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_blue.png index f579ae0f5..991a9fdb9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_brown.png index 22f3e336b..8062a88be 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_cyan.png index 8cc23217d..519a2f43f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_gray.png index 9b6bd3bcd..cc693dca8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_green.png index 8951d7c55..0741dea8e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_light_blue.png index abd76de6c..aca98b7c8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_lime.png index d2492ea36..610f13460 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_magenta.png index bae500de1..14b68ead3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_orange.png index 7e66a5e83..ea36e9ba9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_pink.png index 950c27a0d..2ac56b23f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_purple.png index ad1ddf21e..625801953 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_red.png index d5a9a2e77..ea08fbab5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_white.png index 75c965561..826f58acb 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_yellow.png index b3d550f59..6eaee8511 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_light_gray_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_black.png index dd92c0a36..b5f91c285 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_blue.png index 0f7fb227c..375860ebf 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_brown.png index 9d189647b..75cba644d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_cyan.png index 3c1a5e295..085cba88e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_gray.png index f0e6b7c4a..d143589cf 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_green.png index 9c1a05b98..abcb44a89 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_blue.png index d19d37d6a..b4c151c28 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_gray.png index ebaa885d8..e2d77d169 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_magenta.png index 91cc19413..392f03298 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_orange.png index 9ffbb10fd..8f53cf716 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_pink.png index a48c770fb..5fe741a2d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_purple.png index 0d3e968de..3b77ea757 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_red.png index dd8df0fd2..7626c3d59 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_white.png index cd17d44e9..2a94cf2a4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_yellow.png index 49db94801..d703184e3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_lime_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_black.png index e5bf0f2be..d4eb056a9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_blue.png index 59b4386ee..c4765af84 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_brown.png index 6aee57ce8..068e4b653 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_cyan.png index 0161a7452..b1dd8b1b2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_gray.png index 2ff30b565..2a3532c55 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_green.png index 8a9f3f62c..d1991e2ed 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_blue.png index fbbb10dd1..c725fba36 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_gray.png index 571f66d6b..e7b4ecdf4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_lime.png index 41845dced..f2986c014 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_orange.png index 0d10f205f..da7e8c3aa 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_pink.png index b54d78237..9446c8296 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_purple.png index ed3b8ff9b..4819bd5c8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_red.png index 7c2fe17a0..b84d822b0 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_white.png index 9ac9cdf8f..1d2815997 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_yellow.png index 612d39ea4..3b49f5d1f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_magenta_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_black.png index 6ad660aef..d768675a6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_blue.png index aad859806..5df924638 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_brown.png index cd38acf93..d9fd51bd7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_cyan.png index dc877ddbc..ed9eeef41 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_gray.png index 43d8e34bc..5a7e80c34 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_green.png index c77ef34d4..41273e568 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_blue.png index 93c6dcf4d..b4cbe9010 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_gray.png index 016babd25..36225bba6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_lime.png index 72e21ad49..1aa7bed49 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_magenta.png index 28010a3a1..0c1a44f49 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_pink.png index c71e570e9..3b43402e6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_purple.png index 40a37ef76..4146a131f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_red.png index f334679c7..8129f725d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_white.png index ec9d74784..468351fc6 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_yellow.png index 3b3f9554b..723ae20bc 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_orange_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_black.png index 2f9465a0a..44e89259c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_blue.png index d625689da..84739230c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_brown.png index 63ecc09f7..fb14cf2c8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_cyan.png index 27058cd01..b31bb3386 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_gray.png index 8aa1199a5..8791fbc0a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_green.png index f3a38f705..c9efbbdc7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_blue.png index cb56dd945..9e453aeb9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_gray.png index 8eb21d251..18efc6324 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_lime.png index 19dbd4946..bb8d8c4d8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_magenta.png index 5008ded16..c71554338 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_orange.png index 9a41dfaac..77c2e9c23 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_purple.png index 3ad14b577..2e5b75175 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_red.png index 95be7d756..93e526c51 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_white.png index 5b214f95d..70d408f77 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_yellow.png index 3c20b5881..05915e952 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_pink_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_black.png index 59c9f08ff..514dc425e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_blue.png index 5f9a5ab54..ec73c72ae 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_brown.png index 85869dcbd..2eda80ad3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_cyan.png index 26d56ecd2..3000a9295 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_gray.png index 235c9b2a5..d94151a53 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_green.png index f194a4775..cec4dce4b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_blue.png index 4d90227ed..40215ef1f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_gray.png index 218f39653..279cd423a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_lime.png index 5e5d883a6..ad2f43e40 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_magenta.png index 34e43a8be..e5f4bb6b8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_orange.png index d18c524c4..974a2c1a8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_pink.png index 181d91a0f..42ac6e7e5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_red.png index 517fa81a2..144216e5f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_white.png index 1edf4e866..ea3a98201 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_yellow.png index 0cad0ebfd..76746af82 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_purple_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_black.png index 751e9771d..9a4d3d339 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_blue.png index dbc7202a3..a87879ee9 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_brown.png index 403a21a5e..b62abe510 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_cyan.png index 786f025a4..951b7810a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_gray.png index e14cfeffd..912f25d59 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_green.png index 4ad9612c4..3ff860177 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_blue.png index ec2be46d5..e685237e1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_gray.png index 95a40429c..08c4da574 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_lime.png index 21b3f435f..9eaa8fbf1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_magenta.png index e20bf701c..0439f8f9d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_orange.png index 1bd373174..af7344172 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_pink.png index 99c5caef5..07e0837d1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_purple.png index f8b930858..e4b26576c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_white.png index 4d3bd4e9d..9dafe38ff 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_yellow.png index 3e1f65b6b..ab49a987c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_red_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_black.png index 5dca0f555..7a13b4549 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_blue.png index cda064185..daba88b1e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_brown.png index e8c1d0745..12b7a0809 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_cyan.png index d0d98cb00..280018bf7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_gray.png index 460b65168..423eaf00a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_green.png index ca39a27d2..160120798 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_blue.png index 3c1b92e1e..25dca393f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_gray.png index 60ca83db6..ac92bdfb8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_lime.png index e19d0e0d9..c8053ece5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_magenta.png index 71b05f515..02266296d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_orange.png index 8c1e2c181..332298c96 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_pink.png index 2cc87e4e4..619d10c13 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_purple.png index a426c07a9..9ae80c247 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_red.png index 5313a005d..1970c419c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_yellow.png index 0ae429d7c..7b733c54d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_white_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_black.png index 8521996fa..5e9d80d4b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_blue.png index 2d3d97f7d..85aee41f1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_brown.png index 3d2cecd1b..61b74dfb8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_cyan.png index fcd7c2e66..495c3d879 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_gray.png index 9cbcc2bfc..bb2814a1f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_green.png index 07a604232..d7b328244 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_blue.png index 6db38e43c..4d1ddb4a7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_gray.png index 6057c3d01..33063b2f5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_lime.png index dc255ce9c..44634e68d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_magenta.png index 3dc7d8249..310022b33 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_orange.png index dd34f3c85..c5346cb00 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_pink.png index 4929b8bca..7062f41f3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_purple.png index 61817ab14..c68cb80d8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_red.png index cdd2c71fe..d285dc8d1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_white.png index a39173038..7099b647b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/stripe/tritanium_striped_block_yellow_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_bars.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_bars.png new file mode 100644 index 000000000..86d2aa5aa Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_bars.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block.png index 4f389132d..a48bed438 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_black.png index 42ea98a87..7cab72904 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_blue.png index bb4605b98..e0d31aeac 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_brown.png index 0da196ade..f57625e5b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_cyan.png index 5f004ef76..450bce262 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_gray.png index 70690a2a5..0edfeaee3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_green.png index b244663fa..54b768461 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_blue.png index 3d61fe78a..30dfc0510 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_gray.png index 290fb6bb8..84402f1b1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_lime.png index 97bf58f14..f6ddf7848 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_magenta.png index 6f7f5ba60..3b2b306ac 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_orange.png index 993d660e0..d9e5e99f0 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_pink.png index 6150820a8..d096a6d45 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_purple.png index b901bce3a..7195de5aa 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_red.png index 8d1887300..5ea7763b5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_white.png index 528e7c752..5b194ea7d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_yellow.png index a7b8b6289..61a433739 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_block_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar.png new file mode 100644 index 000000000..69a92c2ef Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_black.png new file mode 100644 index 000000000..1bfc8ea10 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_blue.png new file mode 100644 index 000000000..a2bd0710c Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_brown.png new file mode 100644 index 000000000..4aa4e07ba Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_cyan.png new file mode 100644 index 000000000..f13cea880 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_gray.png new file mode 100644 index 000000000..f409767e7 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_green.png new file mode 100644 index 000000000..dbd621860 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_blue.png new file mode 100644 index 000000000..d8b914224 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_gray.png new file mode 100644 index 000000000..df0928429 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_lime.png new file mode 100644 index 000000000..62e14afda Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_magenta.png new file mode 100644 index 000000000..cc61e32d3 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_orange.png new file mode 100644 index 000000000..f7c76547a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_pink.png new file mode 100644 index 000000000..615456768 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_purple.png new file mode 100644 index 000000000..00606ba4e Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_red.png new file mode 100644 index 000000000..3d60fa894 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_white.png new file mode 100644 index 000000000..369886261 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_yellow.png new file mode 100644 index 000000000..a99585040 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_pillar_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block.png index 66118acc7..93324432b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block_colorless_base.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block_colorless_base.png index cebc4c253..8e248c0e4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block_colorless_base.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/tritanium_striped_block_colorless_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_black.png index d07a6c1c1..9414c5a5c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_blue.png index 87278e1f8..f31f8d007 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_brown.png index fdd156265..48bd12cea 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_cyan.png index 3a3d3d7de..a0d721bbb 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_gray.png index efe599032..a0a058b3c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_green.png index 72437aa8a..377201b09 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_blue.png index 27db6aba3..3fa16afa7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_gray.png index 98206fda1..ce2e87783 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_lime.png index d6e1f1076..9d1d1f807 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_magenta.png index 95c14e251..3651040a0 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_orange.png index 3b3637fe0..b230bfab4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_overlay.png index b68a65216..4fe726588 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_overlay.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_pink.png index 3e5482fbc..8e6b630c3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_purple.png index 9119e16ac..66983b884 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_red.png index 3bf12573a..6c1f55cc7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_white.png index 4f833e8db..d3216914f 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_yellow.png index 7f2f6a096..e58ac59d0 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_alternative_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_black.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_black.png index 96a3d194c..b7eaa7839 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_black.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_black.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_blue.png index 9dfb6238b..1f37f20b3 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_brown.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_brown.png index 79a1a3f5f..b39d35cec 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_brown.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_brown.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_cyan.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_cyan.png index 5505bf0a0..a27a9d583 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_cyan.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_cyan.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_gray.png index 6b6335f64..92a4adc74 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_green.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_green.png index a1be52895..2af91963d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_green.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_green.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_blue.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_blue.png index 478b507d5..fddaaf3d4 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_blue.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_blue.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_gray.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_gray.png index 6e38365fb..753976f57 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_gray.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_light_gray.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_lime.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_lime.png index e9e892a2c..070b9fea5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_lime.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_lime.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_magenta.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_magenta.png index df71299f5..200954e48 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_magenta.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_magenta.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_orange.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_orange.png index c83528225..a76a40884 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_orange.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_orange.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_overlay.png index 7b70fd799..55164b10b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_overlay.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_pink.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_pink.png index b746081b9..696132048 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_pink.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_pink.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_purple.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_purple.png index e521a3972..0732760a2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_purple.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_purple.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_red.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_red.png index 931f68b62..35bc71292 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_red.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_red.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_white.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_white.png index 2ef5c308d..eeb49f101 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_white.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_white.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_yellow.png b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_yellow.png index 2d492e7ce..8a210033d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_yellow.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/decorative/vent_yellow.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/drive_rack_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/drive_rack_em.png deleted file mode 100644 index b25583f22..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/drive_rack_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/drive_viewer_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/drive_viewer_em.png deleted file mode 100644 index 0ea676437..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/drive_viewer_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace.png b/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace.png new file mode 100644 index 000000000..8f380ea7c Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace_offline.png b/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace_offline.png new file mode 100644 index 000000000..7085ec5a9 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/electric_furnace_offline.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/essence_storage.png b/src/main/resources/assets/overdrive_that_matters/textures/block/essence_storage.png new file mode 100644 index 000000000..bf0d32920 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/essence_storage.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/fluid_tank.png b/src/main/resources/assets/overdrive_that_matters/textures/block/fluid_tank.png new file mode 100644 index 000000000..f68041c2f Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/fluid_tank.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_em.png deleted file mode 100644 index 336799c4d..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_idle_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_idle_em.png deleted file mode 100644 index 7f7c95c4a..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/gravitational_stabilizer_idle_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/holo_sign.png b/src/main/resources/assets/overdrive_that_matters/textures/block/holo_sign.png new file mode 100644 index 000000000..8e3d643a8 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/holo_sign.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace.png b/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace.png new file mode 100644 index 000000000..3eb4d807d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace_offline.png b/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace_offline.png new file mode 100644 index 000000000..3ec6e5b07 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/induction_furnace_offline.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/item_monitor_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/item_monitor_em.png deleted file mode 100644 index e5c42a6fe..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/item_monitor_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler.png index e440f0e48..a3e712419 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler_em.png deleted file mode 100644 index ab1db4e56..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_bottler_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_cable.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_cable.png index 8a8dcfa90..544079c53 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_cable.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_cable.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_decomposer_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_decomposer_em.png deleted file mode 100644 index 8ad97db8e..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_decomposer_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_panel_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_panel_em.png deleted file mode 100644 index 28fafef25..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_panel_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_reconstructor.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_reconstructor.png new file mode 100644 index 000000000..854ab7991 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_reconstructor.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_recycler_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_recycler_em.png deleted file mode 100644 index 6b48c2cb8..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_recycler_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator.png index bb8aec884..3c6117992 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_base.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_base.png new file mode 100644 index 000000000..fcadddf02 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_em.png deleted file mode 100644 index 09c99d763..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted.png index 2804aa7a1..40c734cf7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted_em.png deleted file mode 100644 index a83f9bd04..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_halted_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline.png index 5044a04ce..572b79b06 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline_em.png deleted file mode 100644 index 2b21edc16..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_replicator_offline_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_scanner_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/matter_scanner_em.png deleted file mode 100644 index 0a7494157..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/matter_scanner_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png b/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png new file mode 100644 index 000000000..227dde8cb Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png.mcmeta b/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png.mcmeta new file mode 100644 index 000000000..55c29419e --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/textures/block/piston_offset.png.mcmeta @@ -0,0 +1 @@ +{ "animation": { "frametime": 2 } } \ No newline at end of file diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press2.png b/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press2.png new file mode 100644 index 000000000..dbe92085b Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press2.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_em.png deleted file mode 100644 index 2340c4106..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_piston.png b/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_piston.png index 2cf5fac10..ea816cbe7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_piston.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/plate_press_piston.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_1.png b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_1.png new file mode 100644 index 000000000..7dc22ae16 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_1.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_2.png b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_2.png new file mode 100644 index 000000000..14d4e1fb8 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_2.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_top.png b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_top.png new file mode 100644 index 000000000..d0cd682da Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/roflite_alloy_top.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/resource/tritanium_ingot_block.png b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/tritanium_ingot_block.png index b69ebb1df..a0667b061 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/resource/tritanium_ingot_block.png and b/src/main/resources/assets/overdrive_that_matters/textures/block/resource/tritanium_ingot_block.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/ship_engine.png b/src/main/resources/assets/overdrive_that_matters/textures/block/ship_engine.png new file mode 100644 index 000000000..fd3cdf6e8 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/ship_engine.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/storage_power_supplier_em.png b/src/main/resources/assets/overdrive_that_matters/textures/block/storage_power_supplier_em.png deleted file mode 100644 index 6c73a3755..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/block/storage_power_supplier_em.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil.png b/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil.png new file mode 100644 index 000000000..30c8ad6d6 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil_top.png b/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil_top.png new file mode 100644 index 000000000..cf2c2d668 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/block/tritanium_anvil_top.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/android_upgrades.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/android_upgrades.png index 8deae1d89..ce32f5677 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/android_upgrades.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/android_upgrades.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png new file mode 100644 index 000000000..e98895aac Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf new file mode 100644 index 000000000..e5f6ecdae Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/essence_storage.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png new file mode 100644 index 000000000..2a39ad8d4 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png new file mode 100644 index 000000000..cb1905189 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png new file mode 100644 index 000000000..0c21edb22 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars.png index 4c867588e..bd9894af7 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars_health.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars_health.png new file mode 100644 index 000000000..912697873 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/player_bars_health.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/checkbox.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/checkbox.png index 740496b5a..55b1deff2 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/checkbox.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/checkbox.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png new file mode 100644 index 000000000..509d6d5f4 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf new file mode 100644 index 000000000..46606d3f7 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/fluid_level.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/horizontal_gauges.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/horizontal_gauges.png index 83caab387..699161318 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/horizontal_gauges.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/horizontal_gauges.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.png new file mode 100644 index 000000000..dbacdc975 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.xcf new file mode 100644 index 000000000..c8ec61762 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/large_button.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png index 8923a02d7..be39738d5 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.png new file mode 100644 index 000000000..e8e91ae60 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.xcf new file mode 100644 index 000000000..f42db9b99 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc18.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/pattern_panel_tabs.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/pattern_panel_tabs.png index 6988d0fc4..14b5b1ad1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/pattern_panel_tabs.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/pattern_panel_tabs.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/progress_arrows.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/progress_arrows.png index 97e068022..dc0c2a9a8 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/progress_arrows.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/progress_arrows.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar.png new file mode 100644 index 000000000..b8ac70a14 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/background.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/background.png deleted file mode 100644 index 187422eb7..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/background.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/disabled.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/disabled.png deleted file mode 100644 index 767ab2867..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/disabled.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/hovered.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/hovered.png deleted file mode 100644 index 60457f250..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/hovered.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/idle.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/idle.png deleted file mode 100644 index d68f984a7..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/idle.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/pressed.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/pressed.png deleted file mode 100644 index d1b456e8a..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar/pressed.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim.png new file mode 100644 index 000000000..5554e2366 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/background.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/background.png deleted file mode 100644 index b0a6eae96..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/background.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/disabled.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/disabled.png deleted file mode 100644 index d7cf35e98..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/disabled.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/hovered.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/hovered.png deleted file mode 100644 index 98f996d4d..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/hovered.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/idle.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/idle.png deleted file mode 100644 index 603961851..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/idle.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/pressed.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/pressed.png deleted file mode 100644 index 7b8b5dd0a..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/scrollbar_slim/pressed.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png new file mode 100644 index 000000000..9199ba51e Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf new file mode 100644 index 000000000..c11ce84bb Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.png new file mode 100644 index 000000000..38f3005c6 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.xcf new file mode 100644 index 000000000..df69ada02 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/slot_backgrounds.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.png new file mode 100644 index 000000000..b5dc14305 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.xcf new file mode 100644 index 000000000..65faca40d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/tabs.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/tabs.png new file mode 100644 index 000000000..660791b65 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/tabs.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/vertical_gauges.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/vertical_gauges.png index 677f59422..c981a8143 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/vertical_gauges.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/vertical_gauges.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.png deleted file mode 100644 index 765c4d40e..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.png and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.xcf deleted file mode 100644 index edfaa6351..000000000 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_18.xcf and /dev/null differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.png index 8144a8fa6..e39800d86 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf index 594d60564..cc53a4fea 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf and b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets_8.xcf differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/battery_procedural.png b/src/main/resources/assets/overdrive_that_matters/textures/item/battery_procedural.png new file mode 100644 index 000000000..afdf29101 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/battery_procedural.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/chest_upgrader.png b/src/main/resources/assets/overdrive_that_matters/textures/item/chest_upgrader.png new file mode 100644 index 000000000..d36635b4d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/chest_upgrader.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/carbon_mesh.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/carbon_mesh.png new file mode 100644 index 000000000..557cddc74 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/carbon_mesh.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/electromotor.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/electromotor.png new file mode 100644 index 000000000..d36635b4d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/electromotor.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/energy_bus.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/energy_bus.png index 7faf0d314..01ab3a204 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/energy_bus.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/energy_bus.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_limiter.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_limiter.png index 123b4d1e2..2e08bdfcf 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_limiter.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_limiter.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_sensor.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_sensor.png index 6e11df443..a887f7040 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_sensor.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/gravitation_field_sensor.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/machine_frame.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/machine_frame.png index e48cdc1ae..defb0539c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/machine_frame.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/machine_frame.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_capacitor_parts.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_capacitor_parts.png index de0bc920f..3fbd5030b 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_capacitor_parts.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_capacitor_parts.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_io_port.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_io_port.png index 48b538b1a..7aa8f5bd0 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_io_port.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_io_port.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_transform_matrix.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_transform_matrix.png index 36416c9eb..ae21d8294 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_transform_matrix.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/matter_transform_matrix.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror.png index a89a6dadf..66386cc52 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror_compound.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror_compound.png index 21122ec1d..52bb150ec 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror_compound.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/mirror_compound.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/quantum_transceiver.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/quantum_transceiver.png index 4f7134e34..725e474b1 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/component/quantum_transceiver.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/quantum_transceiver.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/reinforced_tritanium_plate.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/reinforced_tritanium_plate.png new file mode 100644 index 000000000..5e41fa125 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/reinforced_tritanium_plate.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/roflite_alloy_ingot.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/roflite_alloy_ingot.png new file mode 100644 index 000000000..27b68126b Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/roflite_alloy_ingot.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/component/tritanium_nugget.png b/src/main/resources/assets/overdrive_that_matters/textures/item/component/tritanium_nugget.png new file mode 100644 index 000000000..477a50993 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/component/tritanium_nugget.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/essence_capsule.png b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_capsule.png new file mode 100644 index 000000000..d1cb9fc85 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_capsule.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/essence_drive.png b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_drive.png new file mode 100644 index 000000000..8ed76f51b Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_drive.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/essence_servo.png b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_servo.png new file mode 100644 index 000000000..819311060 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/essence_servo.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_ender_upgrade.png b/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_ender_upgrade.png new file mode 100644 index 000000000..0712ac982 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_ender_upgrade.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_smelting_upgrade.png b/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_smelting_upgrade.png new file mode 100644 index 000000000..478a9479a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/exopack_smelting_upgrade.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_crafting_upgrade.png b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_crafting_upgrade.png index 14e1e9fe3..0712ac982 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_crafting_upgrade.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_crafting_upgrade.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade.png b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade.png index 193a436a5..4d127ba2c 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade_creative.png b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade_creative.png index 81826e77f..e81bd8a99 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade_creative.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/exosuit_inventory_upgrade_creative.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/explosive_hammer_item.png b/src/main/resources/assets/overdrive_that_matters/textures/item/explosive_hammer_item.png new file mode 100644 index 000000000..4da2c27a0 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/explosive_hammer_item.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule.png b/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule.png new file mode 100644 index 000000000..585679972 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule_liquid_mask.png b/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule_liquid_mask.png new file mode 100644 index 000000000..fa83ccdb8 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/fluid_capsule_liquid_mask.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/hammer.png b/src/main/resources/assets/overdrive_that_matters/textures/item/hammer.png new file mode 100644 index 000000000..d4277032f Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/hammer.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_creative.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_creative.png new file mode 100644 index 000000000..56a36411a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_creative.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_capacity.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_capacity.png new file mode 100644 index 000000000..6f53e2626 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_capacity.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_energy.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_energy.png new file mode 100644 index 000000000..461d774e8 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_energy.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_failure.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_failure.png new file mode 100644 index 000000000..6591efe7f Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_failure.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_matter.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_matter.png new file mode 100644 index 000000000..6f0d1d427 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_matter.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_processing.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_processing.png new file mode 100644 index 000000000..17b7ac2be Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_processing.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_speed.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_speed.png new file mode 100644 index 000000000..7ee2349f3 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_icon_speed.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier0.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier0.png new file mode 100644 index 000000000..c9cf18e41 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier0.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier1.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier1.png new file mode 100644 index 000000000..2922b8866 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier1.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier2.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier2.png new file mode 100644 index 000000000..760cf737e Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier2.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier3.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier3.png new file mode 100644 index 000000000..ffef41a20 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier3.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier4.png b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier4.png new file mode 100644 index 000000000..de42fdf38 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/machine_upgrade_tier4.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_boots.png b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_boots.png new file mode 100644 index 000000000..d8d29497d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_boots.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_chestplate.png b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_chestplate.png new file mode 100644 index 000000000..f31b9ba6a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_chestplate.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_helmet.png b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_helmet.png new file mode 100644 index 000000000..9e633a38d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_helmet.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_pants.png b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_pants.png new file mode 100644 index 000000000..be736f3a4 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/simple_tritanium_pants.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_axe.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_axe.png index f0e5ef407..32297995d 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_axe.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_axe.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_base.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_base.png new file mode 100644 index 000000000..e0d058160 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_overlay.png new file mode 100644 index 000000000..5da3bfded Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_boots_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_base.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_base.png new file mode 100644 index 000000000..c031ca6dd Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_overlay.png new file mode 100644 index 000000000..533643b65 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_chestplate_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_base.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_base.png new file mode 100644 index 000000000..bfa5490ba Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_overlay.png new file mode 100644 index 000000000..37f74f685 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_helmet_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_base.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_base.png new file mode 100644 index 000000000..7f2b2f482 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_overlay.png new file mode 100644 index 000000000..b0a799933 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pants_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pickaxe.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pickaxe.png index 3f664e89b..23d34a493 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pickaxe.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_pickaxe.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shears.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shears.png new file mode 100644 index 000000000..e8ac4f699 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shears.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shield.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shield.png new file mode 100644 index 000000000..2e7c3d385 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shield.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shovel.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shovel.png index 5e50c521b..e6f70f10a 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shovel.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_shovel.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_sword.png b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_sword.png index a157ddda6..5f9c6adef 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_sword.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/tritanium_sword.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/item/zpm_battery.png b/src/main/resources/assets/overdrive_that_matters/textures/item/zpm_battery.png index 619e2afa1..b4c9a0a1e 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/item/zpm_battery.png and b/src/main/resources/assets/overdrive_that_matters/textures/item/zpm_battery.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_base.png b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_base.png new file mode 100644 index 000000000..429e5e5a4 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_base.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_overlay.png b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_overlay.png new file mode 100644 index 000000000..2a5871d8d Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_armor_overlay.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_1.png b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_1.png new file mode 100644 index 000000000..b16dd66b0 Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_1.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_2.png b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_2.png new file mode 100644 index 000000000..ced36769a Binary files /dev/null and b/src/main/resources/assets/overdrive_that_matters/textures/models/armor/tritanium_simple_layer_2.png differ diff --git a/src/main/resources/coremods/chest_menus.js b/src/main/resources/coremods/chest_menus.js new file mode 100644 index 000000000..588fd0068 --- /dev/null +++ b/src/main/resources/coremods/chest_menus.js @@ -0,0 +1,104 @@ + +var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI') +var Opcodes = Java.type('org.objectweb.asm.Opcodes') +var MethodNode = Java.type('org.objectweb.asm.tree.MethodNode') +var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode') +var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode') +var InsnNode = Java.type('org.objectweb.asm.tree.InsnNode') +var TypeInsnNode = Java.type('org.objectweb.asm.tree.TypeInsnNode') + +var classesToPatch = [ + 'net.minecraft.world.entity.vehicle.ChestBoat', + 'net.minecraft.world.level.block.entity.BarrelBlockEntity', + 'net.minecraft.world.level.block.entity.ChestBlockEntity', + 'net.minecraft.world.level.block.EnderChestBlock', + 'net.minecraft.world.entity.vehicle.MinecartChest', + 'net.minecraft.world.level.block.ChestBlock$2$1' +] + +var isOwnPatches = [ + 'net.minecraft.world.level.block.entity.BarrelBlockEntity$1', + 'net.minecraft.world.level.block.entity.ChestBlockEntity$1', +] + +function patchMethod(node) { + if (node.desc.endsWith('AbstractContainerMenu;')) { + var threeRows = ASMAPI.mapMethod('m_39237_') + var sixRows = ASMAPI.mapMethod('m_39246_') + + for (var i = 0; i < node.instructions.size(); i++) { + var instr = node.instructions.get(i) + + if (instr.getOpcode() == Opcodes.INVOKESTATIC && instr.name == threeRows && instr.owner == 'net/minecraft/world/inventory/ChestMenu') { + node.instructions.set(instr, new MethodInsnNode( + Opcodes.INVOKESTATIC, + 'ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu', + 'c9x3', + '(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/Container;)Lru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu;' + )) + + break + } else if (instr.getOpcode() == Opcodes.INVOKESTATIC && instr.name == sixRows && instr.owner == 'net/minecraft/world/inventory/ChestMenu') { + node.instructions.set(instr, new MethodInsnNode( + Opcodes.INVOKESTATIC, + 'ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu', + 'c9x6', + '(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/Container;)Lru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu;' + )) + + break + } + } + } +} + +function initializeCoreMod() { + var result = {} + + for (i in classesToPatch) { + var clazz = classesToPatch[i] + + result[clazz] = { + 'target': { + 'type': 'CLASS', + 'name': clazz + }, + 'transformer': function(classNode) { + for (var i = 0; i < classNode.methods.size(); i++) { + patchMethod(classNode.methods.get(i)) + } + + return classNode + } + } + } + + for (i in isOwnPatches) { + var clazz = isOwnPatches[i] + + result[clazz] = { + 'target': { + 'type': 'METHOD', + 'class': clazz, + 'methodName': ASMAPI.mapMethod('m_142718_'), + 'methodDesc': '(Lnet/minecraft/world/entity/player/Player;)Z' + }, + 'transformer': function(node) { + for (var i = 0; i < node.instructions.size(); i++) { + var instr = node.instructions.get(i) + + if ((instr.getOpcode() == Opcodes.INSTANCEOF || instr.getOpcode() == Opcodes.CHECKCAST) && instr.desc == 'net/minecraft/world/inventory/ChestMenu') { + instr.desc = 'ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu' + } else if (instr.getOpcode() == Opcodes.INVOKEVIRTUAL && instr.owner == 'net/minecraft/world/inventory/ChestMenu' && instr.name == ASMAPI.mapMethod('m_39261_')) { + instr.owner = 'ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu' + instr.name = 'getContainer' + } + } + + return node + } + } + } + + return result +} diff --git a/src/main/resources/coremods/code_injector.js b/src/main/resources/coremods/code_injector.js index e474e4095..d3ab6ff84 100644 --- a/src/main/resources/coremods/code_injector.js +++ b/src/main/resources/coremods/code_injector.js @@ -2,6 +2,7 @@ var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI') var AbstractInsnNode = Java.type('org.objectweb.asm.tree.AbstractInsnNode') var Opcodes = Java.type('org.objectweb.asm.Opcodes') +var FieldInsnNode = Java.type('org.objectweb.asm.tree.FieldInsnNode') var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode') var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode') var JumpInsnNode = Java.type('org.objectweb.asm.tree.JumpInsnNode') @@ -533,46 +534,6 @@ function injectAtTail(path, instructions) { }) } -function injectInventoryInsertHook( - instructions, - determinedOffset, - hookName, - hookDesc, - pre -) { - var last = instructions.get(determinedOffset) - - // print('Patching Inventory#add at instruction ' + determinedOffset + ' to add ' + hookName + hookDesc) - - // loading this (Inventory) to stack - - if (pre != undefined) { - instructions.insert(last, pre) - last = pre - } - - var next = new VarInsnNode(opcodesRemapped.aload, 0) - instructions.insert(last, next) - - last = next - // loading itemstack to stack - next = new VarInsnNode(opcodesRemapped.aload, 2) - - instructions.insert(last, next) - - last = next - // dispatching hook method - next = new MethodInsnNode( - opcodesRemapped.invokestatic, - 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', - hookName, - hookDesc, - false - ) - - instructions.insert(last, next) -} - function patchBlendFunc(node) { var last = new MethodInsnNode( opcodesRemapped.invokestatic, @@ -616,29 +577,50 @@ function backtrack(instructions, from, opcode, skipAmount) { function initializeCoreMod() { return { - 'Inventory#dropAll': injectAtTail( - 'net.minecraft.world.entity.player.Inventory.m_36071_()V', - [ - new VarInsnNode(opcodesRemapped.aload, 0), - new MethodInsnNode(opcodesRemapped.invokestatic, 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', 'inventoryDropAll', '(Lnet/minecraft/world/entity/player/Inventory;)V', false) - ] - ), + 'PhantomSpawner block spawns as android': method('net.minecraft.world.level.levelgen.PhantomSpawner.m_7995_(Lnet/minecraft/server/level/ServerLevel;ZZ)I', function(node) { + var players = ASMAPI.mapMethod('m_6907_') - 'Inventory#clearContent': injectAtTail( - 'net.minecraft.world.entity.player.Inventory.m_6211_()V', - [ - new VarInsnNode(opcodesRemapped.aload, 0), - new MethodInsnNode(opcodesRemapped.invokestatic, 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', 'inventoryClearContent', '(Lnet/minecraft/world/entity/player/Inventory;)V', false) - ] - ), + for (var i = 0; i < node.instructions.size(); i++) { + var instr = node.instructions.get(i) - 'Player#destroyVanishingCursedItems': injectAtTail( - 'net.minecraft.world.entity.player.Player.m_36345_()V', - [ - new VarInsnNode(opcodesRemapped.aload, 0), - new MethodInsnNode(opcodesRemapped.invokestatic, 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', 'playerDestroyVanishingCursedItems', '(Lnet/minecraft/world/entity/player/Player;)V', false) - ] - ), + if (instr.getOpcode() == opcodesRemapped.invokevirtual && instr.name == players) { + putInstructions(node, node.instructions.get(i + 1), [ + new MethodInsnNode( + opcodesRemapped.invokestatic, + 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', + 'phantomSpawnHook', + '(Ljava/util/Iterator;)Ljava/util/Iterator;' + ), + ]) + + return node + } + } + + return node + }), + + 'LivingEntity#addEatEffect patch for androids': method('net.minecraft.world.entity.LivingEntity.m_21063_(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;)V', function(node) { + for (var i = 0; i < node.instructions.size(); i++) { + var instr = node.instructions.get(i) + + if (instr.getOpcode() == opcodesRemapped.invokeinterface && instr.name == "iterator") { + putInstructions(node, node.instructions.get(i), [ + new VarInsnNode(opcodesRemapped.aload, 0), + new MethodInsnNode( + opcodesRemapped.invokestatic, + 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', + 'addEatEffectHook', + '(Ljava/util/Iterator;Lnet/minecraft/world/entity/LivingEntity;)Ljava/util/Iterator;' + ), + ]) + + return node + } + } + + return node + }), 'blend func lock 1': { 'target': { @@ -700,253 +682,12 @@ function initializeCoreMod() { 'transformer': patchBlendFunc }, - 'Minecraft#pickBlock patch for exosuit': - method('net.minecraft.client.Minecraft.m_91280_()V', function(node) { - // 275: invokevirtual #7672 // Method net/minecraft/world/entity/Entity.getType:()Lnet/minecraft/world/entity/EntityType; - // 278: invokevirtual #7667 // Method net/minecraft/core/DefaultedRegistry.getKey:(Ljava/lang/Object;)Lnet/minecraft/resources/ResourceLocation; - // 281: invokevirtual #4475 // Method net/minecraft/resources/ResourceLocation.toString:()Ljava/lang/String; - // 284: astore 5 - // 286: getstatic #5986 // Field LOGGER:Lorg/slf4j/Logger; - // 289: ldc_w #4484 // String Picking on: [{}] {} gave null item - // 292: aload_3 - // 293: aload 5 - // 295: invokeinterface #3145, 4 // InterfaceMethod org/slf4j/Logger.warn:(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V - // 300: goto 405 - // 303: aload_0 - // 304: getfield #6654 // Field player:Lnet/minecraft/client/player/LocalPlayer; - // 307: invokevirtual #7409 // Method net/minecraft/client/player/LocalPlayer.getInventory:()Lnet/minecraft/world/entity/player/Inventory; - // 310: astore 5 - // 312: aload_2 - // 313: ifnull 324 - // 316: aload_0 - // 317: aload 4 - // 319: aload_2 - // 320: invokevirtual #7675 // Method addCustomNbtData:(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/level/block/entity/BlockEntity;)Lnet/minecraft/world/item/ItemStack; - // 323: pop - // 324: aload 5 - // 326: aload 4 - // 328: invokevirtual #7678 // Method net/minecraft/world/entity/player/Inventory.findSlotMatchingItem:(Lnet/minecraft/world/item/ItemStack;)I - // 331: istore 6 - // <-- Our target - // 333: iload_1 - // 334: ifeq 372 - // 337: aload 5 - // 339: aload 4 - // 341: invokevirtual #7681 // Method net/minecraft/world/entity/player/Inventory.setPickedItem:(Lnet/minecraft/world/item/ItemStack;)V - // 344: aload_0 - - for (var i = 0; i < node.instructions.size(); i++) { - var determinedOffset = test([ - opcodesRemapped.aload, - opcodesRemapped.getfield, - opcodesRemapped.invokevirtual, - opcodesRemapped.astore, - opcodesRemapped.aload, - opcodesRemapped.ifnull, - opcodesRemapped.aload, - opcodesRemapped.aload, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - opcodesRemapped.pop, - opcodesRemapped.aload, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - opcodesRemapped.istore, - ], node.instructions, i) - - if (determinedOffset != -1) { - putInstructions(node, node.instructions.get(determinedOffset), [ - new VarInsnNode(opcodesRemapped.iload, backtrack(node.instructions, determinedOffset, opcodesRemapped.istore, 0)['var']), - new VarInsnNode(opcodesRemapped.aload, backtrack(node.instructions, determinedOffset, opcodesRemapped.aload, 0)['var']), - new MethodInsnNode(opcodesRemapped.invokestatic, 'ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability', 'pickBlockHook', '(ILnet/minecraft/world/item/ItemStack;)V', false) - ]) - - break - } - } - - return node - }), - - 'Inventory#add patch': { - 'target': { - 'type': 'METHOD', - 'class': 'net.minecraft.world.entity.player.Inventory', - 'methodName': ASMAPI.mapMethod('m_36040_'), // add - 'methodDesc': '(ILnet/minecraft/world/item/ItemStack;)Z' - }, - - 'transformer': function(node) { - // If item is not "damaged": - // 113: invokevirtual #144 // Method addResource:(ILnet/minecraft/world/item/ItemStack;)I - // 116: invokevirtual #158 // Method net/minecraft/world/item/ItemStack.setCount:(I)V - // 119: aload_2 - // 120: invokevirtual #57 // Method net/minecraft/world/item/ItemStack.isEmpty:()Z - // 123: ifne 134 - // 126: aload_2 - // 127: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I - // 130: iload_3 - // 131: if_icmplt 87 - // <-- our target - // 134: aload_2 - // 135: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I - // 138: iload_3 - // 139: if_icmpne 162 - // 142: aload_0 - - // If item returned that it is damaged: - // 10: invokevirtual #97 // Method net/minecraft/world/item/ItemStack.isDamaged:()Z - // 13: ifeq 87 - // 16: iload_1 - // 17: iconst_m1 - // 18: if_icmpne 26 - // 21: aload_0 - // 22: invokevirtual #86 // Method getFreeSlot:()I - // 25: istore_1 - // <-- our target - // 26: iload_1 - // 27: iflt 65 - // 30: aload_0 - // 31: getfield #19 // Field items:Lnet/minecraft/core/NonNullList; - // 34: iload_1 - // 35: aload_2 - - var i - - // 83: iconst_1 - // 84: ireturn - // 85: iconst_0 - // 86: ireturn - // 87: aload_2 - // 88: invokevirtual #68 // Method net/minecraft/world/item/ItemStack.getCount:()I - - // patch "not damaged" before loop - for (i = 0; i < node.instructions.size(); i++) { - var determinedOffset = test([ - opcodesRemapped.iconst_1, - opcodesRemapped.ireturn, - opcodesRemapped.iconst_0, - opcodesRemapped.ireturn, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - ], node.instructions, i) - - if (determinedOffset != -1) { - injectInventoryInsertHook(node.instructions, determinedOffset - 1, - 'inventoryAddItemHookPre', - '(Lnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V') - - break - } - } - - // patch "not damaged" after loop - for (i = 0; i < node.instructions.size(); i++) { - var determinedOffset = test([ - opcodesRemapped.invokevirtual, - opcodesRemapped.invokevirtual, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - opcodesRemapped.ifne, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - opcodesRemapped.iload, - opcodesRemapped.if_icmplt, - ], node.instructions, i) - - if (determinedOffset != -1) { - injectInventoryInsertHook(node.instructions, determinedOffset, - 'inventoryAddItemHook', - '(Lnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V') - - break - } - } - - // patch "damaged" - for (i = 0; i < node.instructions.size(); i++) { - var determinedOffset = test([ - opcodesRemapped.invokevirtual, - opcodesRemapped.ifeq, - opcodesRemapped.iload, - opcodesRemapped.iconst_m1, - opcodesRemapped.if_icmpne, - opcodesRemapped.aload, - opcodesRemapped.invokevirtual, - opcodesRemapped.istore, - ], node.instructions, i) - - if (determinedOffset != -1) { - injectInventoryInsertHook(node.instructions, determinedOffset, - 'inventoryAddDamagedItemHook', - '(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/item/ItemStack;)V', - new VarInsnNode(opcodesRemapped.iload, node.instructions.get(determinedOffset - 1)['var'])) - - break - } - } - - return node - } - }, - - 'GameRenderer#render hook': { - 'target': { - 'type': 'METHOD', - 'class': 'net.minecraft.client.renderer.GameRenderer', - 'methodName': ASMAPI.mapMethod('m_109093_'), // render - 'methodDesc': '(FJZ)V' - }, - - 'transformer': function(node) { - node.instructions.insert(new MethodInsnNode( - opcodesRemapped.invokestatic, - 'ru/dbotthepony/mc/otm/client/render/WidgetAtlasHolder', - 'renderGameHook', - '()V', - false - )) - - // 288: invokevirtual #3558 // Method net/minecraft/client/renderer/PostChain.process:(F)V - // 291: aload_0 - // 292: getfield #2773 // Field minecraft:Lnet/minecraft/client/Minecraft; - // 295: invokevirtual #2818 // Method net/minecraft/client/Minecraft.getMainRenderTarget:()Lcom/mojang/blaze3d/pipeline/RenderTarget; - // 298: iconst_1 - // 299: invokevirtual #3561 // Method com/mojang/blaze3d/pipeline/RenderTarget.bindWrite:(Z)V - // <-- our target - // 302: aload_0 - // 303: getfield #2773 // Field minecraft:Lnet/minecraft/client/Minecraft; - // 306: invokevirtual #2821 // Method net/minecraft/client/Minecraft.getWindow:()Lcom/mojang/blaze3d/platform/Window; - // 309: astore 7 - - var ourmethod = ASMAPI.mapMethod('m_83947_') // bindWrite - - for (var i = 0; i < node.instructions.size(); i++) { - var instruction = node.instructions.get(i) - - if (instruction.getOpcode() == opcodesRemapped.invokevirtual && instruction.name == ourmethod && instruction.desc == '(Z)V') { - node.instructions.insert(instruction, new MethodInsnNode( - opcodesRemapped.invokestatic, - 'ru/dbotthepony/mc/otm/client/render/GlitchRenderer', - 'render', - '()V', - false - )) - - break - } - } - - return node - } - }, - 'LevelRenderer DynamicBufferSource callback': { 'target': { 'type': 'METHOD', 'class': 'net.minecraft.client.renderer.LevelRenderer', 'methodName': ASMAPI.mapMethod('m_109599_'), // renderLevel - 'methodDesc': '(Lcom/mojang/blaze3d/vertex/PoseStack;FJZLnet/minecraft/client/Camera;Lnet/minecraft/client/renderer/GameRenderer;Lnet/minecraft/client/renderer/LightTexture;Lcom/mojang/math/Matrix4f;)V' + 'methodDesc': '(Lcom/mojang/blaze3d/vertex/PoseStack;FJZLnet/minecraft/client/Camera;Lnet/minecraft/client/renderer/GameRenderer;Lnet/minecraft/client/renderer/LightTexture;Lorg/joml/Matrix4f;)V' }, 'transformer': function(node) { @@ -987,80 +728,6 @@ function initializeCoreMod() { } } - return node - } - }, - - 'EnchantmentHelper#getSweepingDamageRatio patch for energy sword': { - 'target': { - 'type': 'METHOD', - 'class': 'net.minecraft.world.item.enchantment.EnchantmentHelper', - 'methodName': ASMAPI.mapMethod('m_44821_'), // getSweepingDamageRatio - 'methodDesc': '(Lnet/minecraft/world/entity/LivingEntity;)F' - }, - - 'transformer': function(node) { - // 0: getstatic #237 // Field net/minecraft/world/item/enchantment/Enchantments.SWEEPING_EDGE:Lnet/minecraft/world/item/enchantment/Enchantment; - // 3: aload_0 - // 4: invokestatic #243 // Method getEnchantmentLevel:(Lnet/minecraft/world/item/enchantment/Enchantment;Lnet/minecraft/world/entity/LivingEntity;)I - // 7: istore_1 - // 8: iload_1 - // 9: ifle 19 - // 12: iload_1 - // 13: invokestatic #246 // Method net/minecraft/world/item/enchantment/SweepingEdgeEnchantment.getSweepingDamageRatio:(I)F - // 16: goto 20 - // 19: fconst_0 - // 20: freturn - - var instructions = node.instructions - - var last = new VarInsnNode(opcodesRemapped.aload, 0) - instructions.insert(last) // load player onto stack - - var next = new MethodInsnNode( - opcodesRemapped.invokestatic, - 'ru/dbotthepony/mc/otm/item/EnergySwordItemKt', - 'getSweepingDamageRatioHook', - '(Lnet/minecraft/world/entity/LivingEntity;)Ljava/lang/Float;', - false - ) // call hook - - instructions.insert(last, next) - last = next - - var label = new Label() - var labelNode = new LabelNode(label) - - // add label to jump to if our hook returns null - instructions.insert(last, labelNode) - - // duplicate our value, so `ifnull` can safely pop it from stack - next = new InsnNode(opcodesRemapped.dup) - instructions.insert(last, next) - last = next - - // jump to original code if we returned null - next = new JumpInsnNode(opcodesRemapped.ifnull, labelNode) - instructions.insert(last, next) - last = next - - // unbox float - next = new MethodInsnNode( - opcodesRemapped.invokevirtual, - 'java/lang/Float', - 'floatValue', - '()F', - false - ) - - instructions.insert(last, next) - last = next - - // return float - next = new InsnNode(opcodesRemapped.freturn) - instructions.insert(last, next) - last = next - return node } } diff --git a/src/main/resources/coremods/limb_brush_overclock.js b/src/main/resources/coremods/limb_brush_overclock.js new file mode 100644 index 000000000..344fa1a7d --- /dev/null +++ b/src/main/resources/coremods/limb_brush_overclock.js @@ -0,0 +1,76 @@ +var Opcodes = Java.type('org.objectweb.asm.Opcodes') +var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode') +var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode') + +function initializeCoreMod() { + return { + 'Patch brush item': { + 'target': { + "type":"METHOD", + "class":"net.minecraft.world.item.BrushItem", + "methodName":"m_5929_", + "methodDesc":"(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;I)V" + }, + 'transformer': function(node) { + for (i = 0; i < node.instructions.size(); i++) { + var insn = node.instructions.get(i) + + if (insn.getOpcode() == Opcodes.BIPUSH) { + node.instructions.insert(insn, new MethodInsnNode( + Opcodes.INVOKESTATIC, + 'ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature', + 'getBrushCooldown', + '(Lnet/minecraft/world/entity/LivingEntity;)I' + )) + node.instructions.set(insn, new VarInsnNode(Opcodes.ALOAD, 2)) + + i += 1 + continue + } + + if (insn.getOpcode() == Opcodes.ICONST_5) { + node.instructions.insert(insn, new MethodInsnNode( + Opcodes.INVOKESTATIC, + 'ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature', + 'getBrushTick', + '(Lnet/minecraft/world/entity/LivingEntity;)I' + )) + node.instructions.set(insn, new VarInsnNode(Opcodes.ALOAD, 2)) + + i += 1 + continue + } + } + + return node + } + }, + 'Patch brushable block': { + 'target': { + "type":"METHOD", + "class":"net.minecraft.world.level.block.entity.BrushableBlockEntity", + "methodName":"m_276923_", + "methodDesc":"(JLnet/minecraft/world/entity/player/Player;Lnet/minecraft/core/Direction;)Z" + }, + 'transformer': function(node) { + for (i = 0; i < node.instructions.size(); i++) { + var insn = node.instructions.get(i) + + if (insn.getOpcode() == Opcodes.LDC && insn.cst == 10) { + node.instructions.insert(insn, new MethodInsnNode( + Opcodes.INVOKESTATIC, + 'ru/dbotthepony/mc/otm/android/feature/LimbOverclockingFeature', + 'getBrushableBlockCooldown', + '(Lnet/minecraft/world/entity/player/Player;)J' + )) + node.instructions.set(insn, new VarInsnNode(Opcodes.ALOAD, 3)) + + break + } + } + + return node + } + }, + } +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/forge/biome_modifier/overdrive_that_matters/tritanium_ore.json b/src/main/resources/data/overdrive_that_matters/forge/biome_modifier/overdrive_that_matters/tritanium_ore.json index f09c405e1..2624ac791 100644 --- a/src/main/resources/data/overdrive_that_matters/forge/biome_modifier/overdrive_that_matters/tritanium_ore.json +++ b/src/main/resources/data/overdrive_that_matters/forge/biome_modifier/overdrive_that_matters/tritanium_ore.json @@ -2,8 +2,8 @@ "type": "forge:add_features", "biomes": "#minecraft:is_overworld", "features": [ - "overdrive_that_matters:ore_tritanium_normal", - "overdrive_that_matters:ore_tritanium_deep" + "overdrive_that_matters:deep_tritanium", + "overdrive_that_matters:normal_tritanium" ], "step": "underground_ores" } diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json index b24f469cb..2e0353a3a 100644 --- a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json +++ b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json @@ -1,5 +1,5 @@ { "recipe_type": "minecraft:crafting", - "type": "overdrive_that_matters:simple", + "type": "overdrive_that_matters:crafting", "is_critical": true } \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json index 00593b9d0..59297e8b1 100644 --- a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json +++ b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/smithing.json @@ -1,6 +1,5 @@ { "recipe_type": "minecraft:smithing", - "type": "overdrive_that_matters:simple", - "is_critical": false, - "ignore_damageables": true + "type": "overdrive_that_matters:smithing", + "is_critical": false } \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/recipes/machines/matter_panel.json b/src/main/resources/data/overdrive_that_matters/recipes/machines/matter_panel.json index abe33da3b..167cba500 100644 --- a/src/main/resources/data/overdrive_that_matters/recipes/machines/matter_panel.json +++ b/src/main/resources/data/overdrive_that_matters/recipes/machines/matter_panel.json @@ -10,7 +10,7 @@ "key": { "P": {"tag": "forge:glass_panes"}, "G": {"tag": "forge:dusts/glowstone"}, - "T": {"tag": "forge:plates/iron"}, + "T": {"tag": "forge:plates/tritanium"}, "C": {"tag": "forge:circuits/advanced"}, "E": {"item": "overdrive_that_matters:electric_parts"}, "-": {"item": "overdrive_that_matters:matter_cable"} diff --git a/src/main/resources/data/overdrive_that_matters/recipes/plates/circuit_plating.json b/src/main/resources/data/overdrive_that_matters/recipes/plates/circuit_plating.json deleted file mode 100644 index afe37fe11..000000000 --- a/src/main/resources/data/overdrive_that_matters/recipes/plates/circuit_plating.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "overdrive_that_matters:plate_press", - "input": { - "tag": "forge:sand" - }, - "result": { - "item": "overdrive_that_matters:circuit_plating" - }, - "work_time": 120 -} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_body.nbt b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_body.nbt new file mode 100644 index 000000000..691ce2bf7 Binary files /dev/null and b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_body.nbt differ diff --git a/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_front.nbt b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_front.nbt new file mode 100644 index 000000000..e56d01ec0 Binary files /dev/null and b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_front.nbt differ diff --git a/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_tail.nbt b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_tail.nbt new file mode 100644 index 000000000..d11685ff2 Binary files /dev/null and b/src/main/resources/data/overdrive_that_matters/structures/wreckage/wreckage_tail.nbt differ diff --git a/src/main/resources/data/overdrive_that_matters/tags/worldgen/biome/wreckage.json b/src/main/resources/data/overdrive_that_matters/tags/worldgen/biome/wreckage.json new file mode 100644 index 000000000..b342d23e7 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/tags/worldgen/biome/wreckage.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "#forge:is_desert", + "minecraft:desert" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/weapon_attributes/energy_sword.json b/src/main/resources/data/overdrive_that_matters/weapon_attributes/energy_sword.json new file mode 100644 index 000000000..c8bb11ed1 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/weapon_attributes/energy_sword.json @@ -0,0 +1,3 @@ +{ + "parent": "bettercombat:sword" +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/weapon_attributes/explosive_hammer.json b/src/main/resources/data/overdrive_that_matters/weapon_attributes/explosive_hammer.json new file mode 100644 index 000000000..6db48eab5 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/weapon_attributes/explosive_hammer.json @@ -0,0 +1,3 @@ +{ + "parent": "bettercombat:hammer" +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/structure/laboratory.json b/src/main/resources/data/overdrive_that_matters/worldgen/structure/laboratory.json index 3f457fd5f..b669b3896 100644 --- a/src/main/resources/data/overdrive_that_matters/worldgen/structure/laboratory.json +++ b/src/main/resources/data/overdrive_that_matters/worldgen/structure/laboratory.json @@ -1,7 +1,7 @@ { "type": "minecraft:jigsaw", "biomes": "#overdrive_that_matters:laboratory", - "max_distance_from_center": 80, + "max_distance_from_center": 100, "size": 7, "project_start_to_heightmap": "WORLD_SURFACE_WG", "start_height": { diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/structure/wreckage.json b/src/main/resources/data/overdrive_that_matters/worldgen/structure/wreckage.json new file mode 100644 index 000000000..7ddbd2358 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/worldgen/structure/wreckage.json @@ -0,0 +1,28 @@ +{ + "type": "minecraft:jigsaw", + "biomes": "#overdrive_that_matters:wreckage", + "max_distance_from_center": 100, + "size": 1, + "project_start_to_heightmap": "WORLD_SURFACE_WG", + "start_height": { + "absolute": 0 + }, + "spawn_overrides": { + "monster": { + "bounding_box": "piece", + "spawns": [ + { + "type": "minecraft:pillager", + "weight": 1, + "minCount": 1, + "maxCount": 2 + } + ] + } + }, + "start_jigsaw_name": "overdrive_that_matters:wreckage_anchor", + "start_pool": "overdrive_that_matters:wreckage/wreckage_body", + "step": "surface_structures", + "terrain_adaption": "beard_box", + "use_expansion_hack": false +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/structure_set/wreckage.json b/src/main/resources/data/overdrive_that_matters/worldgen/structure_set/wreckage.json new file mode 100644 index 000000000..ad8f6613a --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/worldgen/structure_set/wreckage.json @@ -0,0 +1,14 @@ +{ + "placement": { + "type": "minecraft:random_spread", + "salt": 20383242, + "separation": 8, + "spacing": 1200 + }, + "structures": [ + { + "structure": "overdrive_that_matters:wreckage", + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_body.json b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_body.json new file mode 100644 index 000000000..819e73030 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_body.json @@ -0,0 +1,15 @@ +{ + "name": "overdrive_that_matters:wreckage_body", + "fallback": "minecraft:empty", + "elements": [ + { + "element": { + "element_type": "minecraft:single_pool_element", + "location": "overdrive_that_matters:wreckage/wreckage_body", + "processors": "minecraft:empty", + "projection": "rigid" + }, + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_front.json b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_front.json new file mode 100644 index 000000000..e8ad4eb44 --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_front.json @@ -0,0 +1,15 @@ +{ + "name": "overdrive_that_matters:wreckage_front", + "fallback": "minecraft:empty", + "elements": [ + { + "element": { + "element_type": "minecraft:single_pool_element", + "location": "overdrive_that_matters:wreckage/wreckage_front", + "processors": "minecraft:empty", + "projection": "rigid" + }, + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_tail.json b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_tail.json new file mode 100644 index 000000000..ed70193be --- /dev/null +++ b/src/main/resources/data/overdrive_that_matters/worldgen/template_pool/wreckage/wreckage_tail.json @@ -0,0 +1,15 @@ +{ + "name": "overdrive_that_matters:wreckage_tail", + "fallback": "minecraft:empty", + "elements": [ + { + "element": { + "element_type": "minecraft:single_pool_element", + "location": "overdrive_that_matters:wreckage/wreckage_tail", + "processors": "minecraft:empty", + "projection": "rigid" + }, + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/src/main/resources/overdrive_that_matters.ad_astra.mixins.json b/src/main/resources/overdrive_that_matters.ad_astra.mixins.json new file mode 100644 index 000000000..2ee1936df --- /dev/null +++ b/src/main/resources/overdrive_that_matters.ad_astra.mixins.json @@ -0,0 +1,13 @@ + +{ + "required": false, + "package": "ru.dbotthepony.mc.otm.mixin.compat.ad_astra", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "refmap": "overdrive_that_matters.refmap.json", + "mixins": [ + "EntityOxygenSystemMixin", + "EntityTemperatureSystemMixin", + "OxygenUtilsMixin" + ] +} diff --git a/src/main/resources/overdrive_that_matters.mixins.json b/src/main/resources/overdrive_that_matters.mixins.json index dcbc2ffc0..b812f9335 100644 --- a/src/main/resources/overdrive_that_matters.mixins.json +++ b/src/main/resources/overdrive_that_matters.mixins.json @@ -1,3 +1,4 @@ + { "required": true, "package": "ru.dbotthepony.mc.otm.mixin", @@ -5,6 +6,21 @@ "minVersion": "0.8", "refmap": "overdrive_that_matters.refmap.json", "mixins": [ - "MixinPatchProjectileFinder" + "EnchantmentHelperMixin", + "FoodDataMixin", + "MixinPatchProjectileFinder", + "MixinLivingEntity", + "MixinAnvilBlock", + "MixinInventory", + "MixinAbstractHurtingProjectile", + "SimpleCriterionTriggerMixin", + "InventoryChangeTriggerMixin", + "MixinPlayer", + "HopperBlockEntityMixin", + "DispenserBlockEntityMixin" + ], + "client": [ + "MixinGameRenderer", + "MixinMinecraft" ] -} \ No newline at end of file +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index cb47551b4..07bda9007 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,6 @@ { "pack": { - "description": "Overdrive That Matters Resources", - "pack_format": 6, - "_comment": "A pack_format of 6 requires json lang files and some texture changes from 1.16.2. Note: we require v6 pack meta for all mods." + "description": "${mod_id} resources", + "pack_format": 18 } } diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt new file mode 100644 index 000000000..1a943112e --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt @@ -0,0 +1,34 @@ +package ru.dbotthepony.mc.otm.tests + +import it.unimi.dsi.fastutil.ints.IntComparators +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.mc.otm.core.addSorted +import java.util.Random + +object ComparatorTests { + @Test + @DisplayName("Comparator tests") + fun test() { + val sortedList = mutableListOf(1, 4, 6) + sortedList.addSorted(2, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(3, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(7, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(-1, IntComparators.NATURAL_COMPARATOR) + + assertEquals(mutableListOf(-1, 1, 2, 3, 4, 6, 7), sortedList) + + val rand = Random() + val sorted2 = ArrayList() + + for (i in 0 .. 100) { + sorted2.addSorted(rand.nextInt(-100, 100), IntComparators.NATURAL_COMPARATOR) + } + + val sorted22 = ArrayList(sorted2) + sorted22.sort() + + assertEquals(sorted22, sorted2) + } +} diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/DecimalTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/DecimalTests.kt index 52b31fcaf..366fd3dc3 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/DecimalTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/DecimalTests.kt @@ -5,52 +5,141 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.core.Decimal +import ru.dbotthepony.mc.otm.core.math.Decimal import java.io.ObjectInputStream import java.io.ObjectOutputStream object DecimalTests { @Test - @DisplayName("ImpreciseFraction comparison") + @DisplayName("Decimal comparison") fun comparison() { - check(Decimal(642, 0.43774) > Decimal(641, 0.43774)) { "must be bigger" } - check(Decimal(642, 0.43774) > Decimal(-641, 0.43774)) { "must be bigger" } + check(Decimal(642.43774) > Decimal(641.43774)) { "must be bigger" } + check(Decimal(642.43774) > Decimal(-641.43774)) { "must be bigger" } check(Decimal(0) == Decimal(0)) { "integer notation" } check(Decimal(0.1) > Decimal(0)) { "must be bigger" } check(Decimal(-0.1) < Decimal(0)) { "must be lesser" } - check(Decimal(-1, -0.1) < Decimal(-1)) { "must be lesser" } + check(Decimal(-1.1) < Decimal(-1)) { "must be lesser" } check(Decimal(0.1) == Decimal(0.1)) { "double notation" } check(Decimal("0.1") == Decimal("0.1")) { "string notation" } } @Test - @DisplayName("ImpreciseFraction mathematical operations") + @DisplayName("Decimal mathematical operations") fun math() { - check((Decimal(1) + Decimal(2)) == Decimal(3)) { "1 + 2 != 3" } - check((Decimal(0, 0.5) + Decimal(0, 0.5)) == Decimal(1)) { "0.5 + 0.5 != 1" } - check((Decimal(0, 0.5) + Decimal(0, -0.5)) == Decimal(0)) { "0.5 + -0.5 != 1" } - check((Decimal(0, 0.5) - Decimal(0, -0.5)) == Decimal(1)) { "0.5 - -0.5 != 1" } - check((Decimal(0, 0.3) - Decimal(1, 0.2)) == Decimal(0, -0.9)) { "0.3 - 1.2 != -0.9" } - check((Decimal(0, 0.3) * Decimal(0, 0.3)) == Decimal(0, 0.09)) { "0.3 * 0.3 != 0.9 ${Decimal(0, 0.3) * Decimal(0, 0.3)}" } - check((Decimal(2, 0.3) * Decimal(2, 0.3)) == Decimal(5, 0.29)) { "2.3 * 2.3 != 5.29 ${Decimal(2, 0.3) * Decimal(2, 0.3)}" } - check((Decimal(4) / Decimal(2)) == Decimal(2)) { "4 / 2 != 2" } - check((Decimal(6) / Decimal(2)) == Decimal(3)) { "6 / 2 != 2" } - check((Decimal(1) / Decimal(0, 0.25)) == Decimal(4)) { "1 / 0.25 != 4" } + assertEquals(Decimal(3), Decimal(1) + Decimal(2)) + assertEquals(Decimal(5), Decimal(2) + Decimal(3)) + assertEquals(Decimal(-1), Decimal(6) + Decimal(-7)) + assertEquals(Decimal(-1), Decimal(6) - Decimal(7)) + assertEquals(Decimal(2), Decimal(1) * Decimal(2)) + assertEquals(Decimal(4), Decimal(2) * Decimal(2)) + assertEquals(Decimal(16), Decimal(4) * Decimal(4)) + assertEquals(Decimal(100_000), Decimal(100) * Decimal(1_000)) + assertEquals(Decimal(4), Decimal(16) / Decimal(4)) + assertEquals(Decimal(3), Decimal(12) / Decimal(4)) + assertEquals(Decimal(1), Decimal(15) / Decimal(15)) + + assertEquals(Decimal("0.1"), Decimal(1) / Decimal(10)) + assertEquals(Decimal("-0.1"), Decimal(1) / Decimal(-10)) + assertEquals(Decimal("-0.1"), Decimal(-1) / Decimal(10)) + assertEquals(Decimal("0.1"), Decimal(-1) / Decimal(-10)) + assertEquals(Decimal("0.5"), Decimal("0.25") + Decimal("0.25")) + assertEquals(Decimal("0.5"), Decimal("0.25") * Decimal(2)) + assertEquals(Decimal("-0.5"), Decimal("0.25") * Decimal(-2)) + assertEquals(Decimal("-0.5"), Decimal("-0.25") * Decimal(2)) + assertEquals(Decimal("0.3"), Decimal(0.2) + Decimal(0.1)) + assertEquals(Decimal("0.3"), Decimal("0.2") + Decimal("0.1")) } @Test - @DisplayName("ImpreciseFraction store/load") + @DisplayName("Decimal precision test") + fun precisionTest() { + assertEquals(0.0, Decimal(4).fractionalDouble) + assertEquals(0.2, Decimal(4.2).fractionalDouble) + assertEquals(0.2, Decimal("4.2").fractionalDouble) + assertEquals(0.25, Decimal("4.25").fractionalDouble) + assertEquals(-0.25, Decimal("-1.25").fractionalDouble) + assertEquals(-0.2, Decimal("-1.2").fractionalDouble) + assertEquals(0.0, Decimal("-1").fractionalDouble) + + assertEquals(0.0f, Decimal(4).fractionalFloat) + assertEquals(0.2f, Decimal(4.2).fractionalFloat) + assertEquals(0.2f, Decimal("4.2").fractionalFloat) + assertEquals(0.25f, Decimal("4.25").fractionalFloat) + assertEquals(-0.25f, Decimal("-1.25").fractionalFloat) + assertEquals(-0.2f, Decimal("-1.2").fractionalFloat) + assertEquals(0.0f, Decimal("-1").fractionalFloat) + } + + @Test + @DisplayName("Decimal toString() tests") + fun toStringTest() { + assertEquals("0.0", Decimal(0).toString()) + assertEquals("0.0", Decimal("0.000").toString()) + + assertEquals("0.001", Decimal("0.001").toString()) + assertEquals("0.1", Decimal("0.1").toString()) + assertEquals("1.0", Decimal("1.0").toString()) + assertEquals("4.0", Decimal("4.0").toString()) + assertEquals("4.0", Decimal("4.000").toString()) + assertEquals("4.0001", Decimal("4.0001").toString()) + + assertEquals("-0.001", Decimal("-0.001").toString()) + assertEquals("-0.1", Decimal("-0.1").toString()) + assertEquals("-1.0", Decimal("-1.0").toString()) + assertEquals("-4.0", Decimal("-4.0").toString()) + assertEquals("-4.0", Decimal("-4.000").toString()) + assertEquals("-4.0001", Decimal("-4.0001").toString()) + + assertEquals("0.0", Decimal("0.000").toString(1)) + assertEquals("0.00", Decimal("0.000").toString(2)) + assertEquals("0.000", Decimal("0.000").toString(3)) + assertEquals("0.001", Decimal("0.001").toString(3)) + assertEquals("0.00", Decimal("0.001").toString(2)) + assertEquals("0.0", Decimal("0.001").toString(1)) + assertEquals("1.0", Decimal("1.001").toString(1)) + assertEquals("1.1", Decimal("1.101").toString(1)) + assertEquals("1.10", Decimal("1.101").toString(2)) + assertEquals("1.101", Decimal("1.101").toString(3)) + assertEquals("1.1010", Decimal("1.101").toString(4)) + } + + @Test + @DisplayName("Decimal store/load") fun storeLoad() { run { - val f = Decimal(4, 0.28) + val f = Decimal(4.28) val loaded = Decimal.fromByteArray(f.toByteArray()) - check(f == loaded) { "$f != $loaded" } + assertEquals(f, loaded) } run { - val f = Decimal(32748293658335L, 0.3472302174) + val f = Decimal(1) val loaded = Decimal.fromByteArray(f.toByteArray()) - check(f == loaded) { "$f != $loaded" } + assertEquals(f, loaded) + } + + run { + val f = Decimal(0) + val loaded = Decimal.fromByteArray(f.toByteArray()) + assertEquals(f, loaded) + } + + run { + val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475") + val loaded = Decimal.fromByteArray(f.toByteArray()) + assertEquals(f, loaded) + } + + run { + val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475.25") + val loaded = Decimal.fromByteArray(f.toByteArray()) + assertEquals(f, loaded) + } + + run { + val f = Decimal(32748293658335.3472302174) + val loaded = Decimal.fromByteArray(f.toByteArray()) + assertEquals(f, loaded) } } @@ -67,7 +156,7 @@ object DecimalTests { } @Test - @DisplayName("ImpreciseFraction serialization") + @DisplayName("Decimal serialization") fun serialization() { val output = FastByteArrayOutputStream() val outputStream = ObjectOutputStream(output) diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt new file mode 100644 index 000000000..cf617fc77 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ExperienceUtilsTest.kt @@ -0,0 +1,124 @@ +package ru.dbotthepony.mc.otm.tests + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.mc.otm.core.util.getLevelFromXp +import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel +import ru.dbotthepony.mc.otm.core.util.getXpRequiredForLevelUp + +object ExperienceUtilsTest { + @Test + @DisplayName("Experience utils xp required for level up") + fun levelUp() { + var i = 0 + assertEquals(7L, getXpRequiredForLevelUp(i++)) + assertEquals(9L, getXpRequiredForLevelUp(i++)) + assertEquals(11L, getXpRequiredForLevelUp(i++)) + assertEquals(13L, getXpRequiredForLevelUp(i++)) + assertEquals(15L, getXpRequiredForLevelUp(i++)) + assertEquals(17L, getXpRequiredForLevelUp(i++)) + assertEquals(19L, getXpRequiredForLevelUp(i++)) + assertEquals(21L, getXpRequiredForLevelUp(i++)) + assertEquals(23L, getXpRequiredForLevelUp(i++)) + assertEquals(25L, getXpRequiredForLevelUp(i++)) + assertEquals(27L, getXpRequiredForLevelUp(i++)) + assertEquals(29L, getXpRequiredForLevelUp(i++)) + assertEquals(31L, getXpRequiredForLevelUp(i++)) + assertEquals(33L, getXpRequiredForLevelUp(i++)) + assertEquals(35L, getXpRequiredForLevelUp(i++)) + assertEquals(37L, getXpRequiredForLevelUp(i++)) + assertEquals(42L, getXpRequiredForLevelUp(i++)) + assertEquals(47L, getXpRequiredForLevelUp(i++)) + assertEquals(52L, getXpRequiredForLevelUp(i++)) + assertEquals(57L, getXpRequiredForLevelUp(i++)) + assertEquals(62L, getXpRequiredForLevelUp(i++)) + assertEquals(67L, getXpRequiredForLevelUp(i++)) + assertEquals(72L, getXpRequiredForLevelUp(i++)) + assertEquals(77L, getXpRequiredForLevelUp(i++)) + assertEquals(82L, getXpRequiredForLevelUp(i++)) + assertEquals(87L, getXpRequiredForLevelUp(i++)) + assertEquals(92L, getXpRequiredForLevelUp(i++)) + assertEquals(97L, getXpRequiredForLevelUp(i++)) + assertEquals(102L, getXpRequiredForLevelUp(i++)) + assertEquals(107L, getXpRequiredForLevelUp(i++)) + assertEquals(112L, getXpRequiredForLevelUp(i++)) + assertEquals(121L, getXpRequiredForLevelUp(i++)) + assertEquals(130L, getXpRequiredForLevelUp(i++)) + assertEquals(139L, getXpRequiredForLevelUp(i++)) + assertEquals(148L, getXpRequiredForLevelUp(i++)) + assertEquals(157L, getXpRequiredForLevelUp(i++)) + assertEquals(166L, getXpRequiredForLevelUp(i++)) + assertEquals(175L, getXpRequiredForLevelUp(i++)) + assertEquals(184L, getXpRequiredForLevelUp(i++)) + assertEquals(193L, getXpRequiredForLevelUp(i++)) + } + + @Test + @DisplayName("Experience utils total xp required for level") + fun totalXp() { + var i = 1 + assertEquals(7L, getTotalXpRequiredForLevel(i++)) // 1 + assertEquals(16L, getTotalXpRequiredForLevel(i++)) // 2 + assertEquals(27L, getTotalXpRequiredForLevel(i++)) // 3 + assertEquals(40L, getTotalXpRequiredForLevel(i++)) // 4 + assertEquals(55L, getTotalXpRequiredForLevel(i++)) // 5 + assertEquals(72L, getTotalXpRequiredForLevel(i++)) // 6 + assertEquals(91L, getTotalXpRequiredForLevel(i++)) // 7 + assertEquals(112L, getTotalXpRequiredForLevel(i++)) // 8 + assertEquals(135L, getTotalXpRequiredForLevel(i++)) // 9 + assertEquals(160L, getTotalXpRequiredForLevel(i++)) // 10 + assertEquals(187L, getTotalXpRequiredForLevel(i++)) // 11 + assertEquals(216L, getTotalXpRequiredForLevel(i++)) // 12 + assertEquals(247L, getTotalXpRequiredForLevel(i++)) // 13 + assertEquals(280L, getTotalXpRequiredForLevel(i++)) // 14 + assertEquals(315L, getTotalXpRequiredForLevel(i++)) // 15 + assertEquals(352L, getTotalXpRequiredForLevel(i++)) // 16 + assertEquals(394L, getTotalXpRequiredForLevel(i++)) // 17 + assertEquals(441L, getTotalXpRequiredForLevel(i++)) // 18 + assertEquals(493L, getTotalXpRequiredForLevel(i++)) // 19 + assertEquals(550L, getTotalXpRequiredForLevel(i++)) // 20 + assertEquals(612L, getTotalXpRequiredForLevel(i++)) // 21 + assertEquals(679L, getTotalXpRequiredForLevel(i++)) // 22 + assertEquals(751L, getTotalXpRequiredForLevel(i++)) // 23 + assertEquals(828L, getTotalXpRequiredForLevel(i++)) // 24 + assertEquals(910L, getTotalXpRequiredForLevel(i++)) // 25 + assertEquals(997L, getTotalXpRequiredForLevel(i++)) // 26 + assertEquals(1089L, getTotalXpRequiredForLevel(i++)) // 27 + assertEquals(1186L, getTotalXpRequiredForLevel(i++)) // 28 + assertEquals(1288L, getTotalXpRequiredForLevel(i++)) // 29 + assertEquals(1395L, getTotalXpRequiredForLevel(i++)) // 30 + assertEquals(1507L, getTotalXpRequiredForLevel(i++)) // 31 + assertEquals(1628L, getTotalXpRequiredForLevel(i++)) // 32 + assertEquals(1758L, getTotalXpRequiredForLevel(i++)) // 33 + assertEquals(1897L, getTotalXpRequiredForLevel(i++)) // 34 + assertEquals(2045L, getTotalXpRequiredForLevel(i++)) // 35 + assertEquals(2202L, getTotalXpRequiredForLevel(i++)) // 36 + assertEquals(2368L, getTotalXpRequiredForLevel(i++)) // 37 + assertEquals(2543L, getTotalXpRequiredForLevel(i++)) // 38 + assertEquals(2727L, getTotalXpRequiredForLevel(i++)) // 39 + assertEquals(2920L, getTotalXpRequiredForLevel(i++)) // 40 + } + + @Test + @DisplayName("Experience utils reverse search xp -> level") + fun reverseSearch() { + assertEquals(0, getLevelFromXp(4L)) + assertEquals(0, getLevelFromXp(6L)) + assertEquals(1, getLevelFromXp(7L)) + assertEquals(1, getLevelFromXp(8L)) + + assertEquals(19, getLevelFromXp(540L)) + assertEquals(19, getLevelFromXp(545L)) + assertEquals(20, getLevelFromXp(550L)) + assertEquals(21, getLevelFromXp(551L)) + assertEquals(21, getLevelFromXp(554L)) + assertEquals(21, getLevelFromXp(556L)) + + for (i in 50 .. 100) { + assertEquals(i, getLevelFromXp(getTotalXpRequiredForLevel(i))) + assertEquals(i - 1, getLevelFromXp(getTotalXpRequiredForLevel(i) - 1L)) + assertEquals(i, getLevelFromXp(getTotalXpRequiredForLevel(i) + 1L)) + } + } +} diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FieldSynchronizerTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FieldSynchronizerTests.kt index 1d70b17a4..001768a4e 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FieldSynchronizerTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FieldSynchronizerTests.kt @@ -3,8 +3,8 @@ package ru.dbotthepony.mc.otm.tests import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.network.FieldSynchronizer -import ru.dbotthepony.mc.otm.network.IMutableField +import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer +import ru.dbotthepony.mc.otm.network.synchronizer.IMutableField import java.io.ByteArrayInputStream object FieldSynchronizerTests { @@ -31,7 +31,7 @@ object FieldSynchronizerTests { intA2.value = 348488 intA3.value = -4 - b.applyNetworkPayload(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) + b.read(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) assertEquals(boolA.value, boolB.value) assertEquals(intA.value, intB.value) @@ -62,7 +62,7 @@ object FieldSynchronizerTests { //intA2.setValue(348488) intA3.value = -4 - b.applyNetworkPayload(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) + b.read(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) assertEquals(boolA.value, boolB.value) assertEquals(intA.value, intB.value) @@ -95,8 +95,8 @@ object FieldSynchronizerTests { f4.value = 80L - b.applyNetworkPayload(bEndpoint.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) - c.applyNetworkPayload(cEndpoint.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) + b.read(bEndpoint.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) + c.read(cEndpoint.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) for ((i, field) in bFields.withIndex()) { assertEquals(aFields[i].value, field.value) @@ -125,35 +125,7 @@ object FieldSynchronizerTests { fieldsa[i].value = i } - b.applyNetworkPayload(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) - - for (i in 0 .. 900) { - assertEquals(fieldsa[i].value, fieldsb[i].value) - } - } - - @Test - @DisplayName("Field Synchronizer field order definition mismatch") - fun orderMismatch() { - val a = FieldSynchronizer() - val b = FieldSynchronizer() - - val fieldsa = ArrayList>() - val fieldsb = ArrayList>() - - for (i in 0 .. 900) { - fieldsa.add(a.int(name = "int$i")) - } - - for (i in 900 downTo 0) { - fieldsb.add(0, b.int(name = "int$i")) - } - - for (i in 0 .. 900) { - fieldsa[i].value = i - } - - b.applyNetworkPayload(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) + b.read(a.collectNetworkPayload()!!.let { ByteArrayInputStream(it.array, 0, it.length) }) for (i in 0 .. 900) { assertEquals(fieldsa[i].value, fieldsb[i].value) diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FormattingTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FormattingTests.kt deleted file mode 100644 index b278c12e9..000000000 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FormattingTests.kt +++ /dev/null @@ -1,69 +0,0 @@ -package ru.dbotthepony.mc.otm.tests - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.core.Decimal -import ru.dbotthepony.mc.otm.core.formatReadableNumber -import ru.dbotthepony.mc.otm.core.formatSi -import java.math.BigInteger - -object FormattingTests { - @Test - @DisplayName("BigInteger formatting as readable number") - fun biginteger() { - assertEquals("0", BigInteger("0").formatReadableNumber()) - assertEquals("45", BigInteger("45").formatReadableNumber()) - assertEquals("-45", BigInteger("-45").formatReadableNumber()) - assertEquals("0", BigInteger("-0").formatReadableNumber()) - - assertEquals("100", BigInteger("100").formatReadableNumber()) - assertEquals("-100", BigInteger("-100").formatReadableNumber()) - assertEquals("999", BigInteger("999").formatReadableNumber()) - assertEquals("1 999", BigInteger("1999").formatReadableNumber()) - assertEquals("8 992", BigInteger("8992").formatReadableNumber()) - assertEquals("-8 992", BigInteger("-8992").formatReadableNumber()) - assertEquals("100 200", BigInteger("100200").formatReadableNumber()) - assertEquals("-100 200", BigInteger("-100200").formatReadableNumber()) - - assertEquals("-1 100 200", BigInteger("-1100200").formatReadableNumber()) - assertEquals("1 100 200", BigInteger("1100200").formatReadableNumber()) - assertEquals("2 730 250 200", BigInteger("2730250200").formatReadableNumber()) - assertEquals("1 222 730 250 200", BigInteger("1222730250200").formatReadableNumber()) - } - - @Test - @DisplayName("BigInteger formatting as si number") - fun bigintegerSi() { - assertEquals("0", BigInteger("0").formatSi()) - assertEquals("420", BigInteger("420").formatSi()) - assertEquals("-420", BigInteger("-420").formatSi()) - assertEquals("555", BigInteger("555").formatSi()) - assertEquals("55", BigInteger("55").formatSi()) - assertEquals("1.20k", BigInteger("1205").formatSi()) - assertEquals("-1.20k", BigInteger("-1205").formatSi()) - assertEquals("1.21k", BigInteger("1215").formatSi()) - assertEquals("4.50M", BigInteger("4501204").formatSi()) - assertEquals("4.00M", BigInteger("4000111").formatSi()) - assertEquals("4.0011M", BigInteger("4001111").formatSi(4)) - assertEquals("-4.0011M", BigInteger("-4001111").formatSi(4)) - } - - @Test - @DisplayName("ImpreciseFraction formatting as si number") - fun impreciseFractionSi() { - assertEquals("0.00", Decimal("0").formatSi(2)) - assertEquals("14.62", Decimal("14.62").formatSi(2)) - assertEquals("1.00k", Decimal("1000").formatSi(2)) - assertEquals("1.00k", Decimal("1000.1").formatSi(2)) - assertEquals("1.00k", Decimal("1004.2").formatSi(2)) - assertEquals("1.01k", Decimal("1014.5").formatSi(2)) - assertEquals("1.014k", Decimal("1014.5").formatSi(3)) - assertEquals("1.01k", Decimal("1014.256").formatSi(2)) - assertEquals("12.73k", Decimal("12734.256").formatSi(2)) - assertEquals("127.34k", Decimal("127342.256").formatSi(2)) - assertEquals("1.27M", Decimal("1273421.256").formatSi(2)) - assertEquals("1.273M", Decimal("1273421.256").formatSi(3)) - assertEquals("1.2734M", Decimal("1273421.256").formatSi(4)) - } -} diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt deleted file mode 100644 index e51fa3924..000000000 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt +++ /dev/null @@ -1,345 +0,0 @@ -package ru.dbotthepony.mc.otm.tests - -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import ru.dbotthepony.mc.otm.core.Fraction -import ru.dbotthepony.mc.otm.core.Decimal -import java.math.BigDecimal -import java.math.BigInteger - -object FractionTests { - @Test - @DisplayName("Fraction declaration") - fun declaration() { - println("BigInteger 1/1 == ${Fraction(BigInteger.valueOf(1), BigInteger.valueOf(1))}") - println("BigDecimal 1 == ${Fraction(BigDecimal("1"))}") - - var one = BigDecimal("1.00") - - println("Unscaled ${one.unscaledValue()} with scale ${one.scale()} of $one") - println("BigDecimal $one == ${Fraction(one)}") - assert(Fraction(one).compareTo(Fraction.ONE) == 0) - - one = BigDecimal("1.0000") - - println("Unscaled ${one.unscaledValue()} with scale ${one.scale()} of $one") - println("BigDecimal $one == ${Fraction(one)}") - assert(Fraction(one).compareTo(Fraction.ONE) == 0) - - println("1/2 == ${Fraction(1, 2)}") - println("-1/2 == ${Fraction(-1, 2)}") - println("1/-2 == ${Fraction(1, -2)}") - println("-1/-2 == ${Fraction(-1, -2)}") - - println("canonical 1/2 == ${Fraction(1, 2).canonize()}") - println("canonical -1/2 == ${Fraction(-1, 2).canonize()}") - println("canonical 1/-2 == ${Fraction(1, -2).canonize()}") - println("canonical -1/-2 == ${Fraction(-1, -2).canonize()}") - } - - @Test - @DisplayName("Fraction NaN behavior") - fun nan() { - assert((Fraction.NaN + Fraction.ONE).isNaN()) - assert((Fraction.NaN - Fraction.ONE).isNaN()) - assert((Fraction.NaN / Fraction.ONE).isNaN()) - assert((Fraction.NaN * Fraction.ONE).isNaN()) - - assert((Fraction.ONE + Fraction.NaN).isNaN()) - assert((Fraction.ONE - Fraction.NaN).isNaN()) - assert((Fraction.ONE / Fraction.NaN).isNaN()) - assert((Fraction.ONE * Fraction.NaN).isNaN()) - - assert((Fraction.ONE / Fraction.ZERO).isNaN()) - assert((Fraction.ONE * Fraction(0, 0)).isNaN()) - assert((Fraction.ONE * Fraction(1, 0)).isNaN()) - - assert(Fraction.NaN.toFloat().isNaN()) - assert(Fraction.NaN.toDouble().isNaN()) - - assertThrows {Fraction.NaN.toInt()} - assertThrows {Fraction.NaN.toLong()} - assertThrows {Fraction.NaN.wholePart()} - - assert(Fraction.fromByteArray(Fraction.NaN.toByteArray()).isNaN()) - assert(Fraction.NaN.percentage(Fraction.ONE).isNaN()) - } - - @Test - @DisplayName("Fraction comparison") - fun equality() { - assert(Fraction(1).compareTo(Fraction.ONE) == 0) - assert(Fraction(1.0).compareTo(Fraction.ONE) == 0) - assert(Fraction(1.0F).compareTo(Fraction.ONE) == 0) - assert(Fraction(1, 1).compareTo(Fraction.ONE) == 0) - assert(Fraction(1.0, 1.0).compareTo(Fraction.ONE) == 0) - assert(Fraction(1.0F, 1.0F).compareTo(Fraction.ONE) == 0) - - assert(Fraction(1, 2).compareTo(Fraction.HALF) == 0) - assert(Fraction(1.0, 2.0).compareTo(Fraction.HALF) == 0) - assert(Fraction(1.0F, 2.0F).compareTo(Fraction.HALF) == 0) - - assert(Fraction(-1, -2).compareTo(Fraction.HALF) == 0) - assert(Fraction(-1.0, -2.0).compareTo(Fraction.HALF) == 0) - assert(Fraction(-1.0F, -2.0F).compareTo(Fraction.HALF) == 0) - - assert(Fraction(-1, 2).compareTo(Fraction.HALF) != 0) - assert(Fraction(-1.0, 2.0).compareTo(Fraction.HALF) != 0) - assert(Fraction(-1.0F, 2.0F).compareTo(Fraction.HALF) != 0) - - assert(Fraction(1, 2).compareTo(Fraction.ONE) != 0) - assert(Fraction(1.0, 2.0).compareTo(Fraction.ONE) != 0) - assert(Fraction(1.0F, 2.0F).compareTo(Fraction.ONE) != 0) - - assert(Fraction(2).compareTo(Fraction.TWO) == 0) - assert(Fraction(2.0).compareTo(Fraction.TWO) == 0) - assert(Fraction(2.0F).compareTo(Fraction.TWO) == 0) - assert(Fraction(2, 1).compareTo(Fraction.TWO) == 0) - assert(Fraction(2.0, 1.0).compareTo(Fraction.TWO) == 0) - assert(Fraction(2.0F, 1.0F).compareTo(Fraction.TWO) == 0) - - assert(Fraction(4, 3) > Fraction(5, 4)) - assert(Fraction(4, 4) < Fraction(5, 4)) - - assert(Fraction(-15, 2) < Fraction(2, 4)) - assert(Fraction(-1, 2) < Fraction(2, 4)) - assert(Fraction(-15, -2) > Fraction(2, 4)) - assert(Fraction(-15, -2) > Fraction(-2, -4)) - assert(Fraction(-15, 2) < Fraction(-2, -4)) - } - - @Test - @DisplayName("Fraction math") - fun math() { - assert((Fraction(1) / Fraction(1)) == Fraction(1)) - assert((Fraction(2) / Fraction(1)) == Fraction(2, 1)) - assert((Fraction(2) / Fraction(2)) == Fraction(1)) { Fraction(2) / Fraction(2) } - - assert((Fraction(4, 3) / Fraction(5, 4)) == (Fraction(4, 3) * Fraction(4, 5))) - assert((Fraction(4, 3) + Fraction(5, 4)) == Fraction(31, 12)) - } - - @Test - @DisplayName("Fraction compacting") - fun compacting() { - val frac = Fraction("2721280000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "25600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - - println("Compacting $frac => ${frac + Fraction.ZERO}") - } - - private val samples = arrayOf( - Fraction(9475, 4729), - Fraction(23535, 58723), - Fraction(-4852, 6859), - Fraction(-45623, -76849), - Fraction(38494, -76849), - Fraction(1043, -648), - ) - - private val samplesIM = arrayOf( - Decimal(9475.0 / 4729), - Decimal(23535.0 / 58723), - Decimal(-4852.0 / 6859), - Decimal(-45623.0 / -76849), - Decimal(38494.0 / -76849), - Decimal(1043.0 / -648), - ) - - private val samples2 = arrayOf( - (9475.0 / 4729), - (23535.0 / 58723), - (-4852.0 / 6859), - (-45623.0 / -76849), - (38494.0 / -76849), - (1043.0 / -648), - ) - - private val samples3 = arrayOf( - BigDecimal((9475.0 / 4729).toString()), - BigDecimal((23535.0 / 58723).toString()), - BigDecimal((-4852.0 / 6859).toString()), - BigDecimal((-45623.0 / -76849).toString()), - BigDecimal((38494.0 / -76849).toString()), - BigDecimal((1043.0 / -648).toString()), - ) - - @Test - @DisplayName("Fraction performance measurement") - fun performance() { - val rand = java.util.Random() - val blackHole = arrayOfNulls(100_000) - val blackHoleIM = arrayOfNulls(100_000) - val size = samples.size - var time = System.currentTimeMillis() - - for (i in 0 until 100_000) { - when (rand.nextInt(3)) { - 0 -> blackHole[i] = samples[rand.nextInt(size)] + samples[rand.nextInt(size)] - 1 -> blackHole[i] = samples[rand.nextInt(size)] - samples[rand.nextInt(size)] - 2 -> blackHole[i] = samples[rand.nextInt(size)] * samples[rand.nextInt(size)] - // 3 -> blackHole[i] = samples[rand.nextInt(size)] / samples[rand.nextInt(size)] - } - } - - println("Mean time for Fraction operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") - - time = System.currentTimeMillis() - - for (i in 0 until 100_000) { - when (rand.nextInt(3)) { - 0 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] + samplesIM[rand.nextInt(size)] - 1 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] - samplesIM[rand.nextInt(size)] - 2 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] * samplesIM[rand.nextInt(size)] - // 3 -> blackHole[i] = samples[rand.nextInt(size)] / samples[rand.nextInt(size)] - } - } - - println("Mean time for ImpreciseFraction operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") - - var sum = Fraction.ZERO - - // перемешаем чтоб оптимизатор не отбросил - for (i in 0 until size) { - sum += blackHole[i]!! - blackHole[i] = blackHole[rand.nextInt(size)] - } - - val blackHole2 = DoubleArray(100_000) - time = System.currentTimeMillis() - - for (i in 0 until 100_000) { - when (rand.nextInt(3)) { - 0 -> blackHole2[i] = samples2[rand.nextInt(size)] + samples2[rand.nextInt(size)] - 1 -> blackHole2[i] = samples2[rand.nextInt(size)] - samples2[rand.nextInt(size)] - 2 -> blackHole2[i] = samples2[rand.nextInt(size)] * samples2[rand.nextInt(size)] - // 3 -> blackHole2[i] = samples2[rand.nextInt(size)] / samples2[rand.nextInt(size)] - } - } - - println("Mean time for Double operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") - - val blackHole3 = arrayOfNulls(100_000) - time = System.currentTimeMillis() - - for (i in 0 until 100_000) { - when (rand.nextInt(3)) { - 0 -> blackHole3[i] = samples3[rand.nextInt(size)] + samples3[rand.nextInt(size)] - 1 -> blackHole3[i] = samples3[rand.nextInt(size)] - samples3[rand.nextInt(size)] - 2 -> blackHole3[i] = samples3[rand.nextInt(size)] * samples3[rand.nextInt(size)] - // 3 -> blackHole2[i] = samples2[rand.nextInt(size)] / samples2[rand.nextInt(size)] - } - } - - println("Mean time for BigDecimal operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") - - var sum2 = 0.0 - - // перемешаем чтоб оптимизатор не отбросил - for (i in 0 until size) { - sum2 += blackHole2[i] - blackHole2[i] = blackHole2[rand.nextInt(size)] - } - - var sum3 = 0.0 - - // перемешаем чтоб оптимизатор не отбросил - for (i in 0 until size) { - sum3 += blackHole2[i] - blackHole3[i] = blackHole3[rand.nextInt(size)] - } - - var sum4 = 0.0 - - // перемешаем чтоб оптимизатор не отбросил - for (i in 0 until size) { - sum4 += blackHoleIM[i]!!.toDouble() - blackHoleIM[i] = blackHoleIM[rand.nextInt(size)] - } - - println("$sum $sum2 $sum3") - } - - @Test - @DisplayName("Fraction serialization test") - fun serialization() { - var value = Fraction(1) - var serialized = value.serializeNBT() - - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction(4, 2) - serialized = value.serializeNBT() - - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction(-4, 2) - serialized = value.serializeNBT() - - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction(-4, -18) - serialized = value.serializeNBT() - - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("3407203485237459085739045724837543569234750927348902374590872345", "-57777772398450982374590230984532984") - serialized = value.serializeNBT() - - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("320", "100") - serialized = value.serializeNBT() - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("324", "100") - serialized = value.serializeNBT() - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("328", "100") - serialized = value.serializeNBT() - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("332", "100") - serialized = value.serializeNBT() - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - - value = Fraction("336", "100") - serialized = value.serializeNBT() - assert(value.compareTo(Fraction.deserializeNBT(serialized)) == 0) - } - - @Test - @DisplayName("Fraction inaccurate representation") - fun inaccurate() { - var value = 1.1 - var frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - - value = 1.45 - frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - - value = -1.0 - frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - - value = -254.66 - frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - - value = 94.949494 - frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - - value = 1.0 / 3 - frac = Fraction(value) - - assert(frac.toDouble() == value) { "$value != $frac as (${frac.toDouble()})" } - } -} diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FriendlyStreams.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FriendlyStreams.kt index 371cfb050..b54aa47aa 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FriendlyStreams.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FriendlyStreams.kt @@ -5,12 +5,12 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.core.readInt -import ru.dbotthepony.mc.otm.core.readVarIntLE -import ru.dbotthepony.mc.otm.core.readVarLongLE -import ru.dbotthepony.mc.otm.core.writeInt -import ru.dbotthepony.mc.otm.core.writeVarIntLE -import ru.dbotthepony.mc.otm.core.writeVarLongLE +import ru.dbotthepony.mc.otm.core.util.readInt +import ru.dbotthepony.mc.otm.core.util.readVarIntLE +import ru.dbotthepony.mc.otm.core.util.readVarLongLE +import ru.dbotthepony.mc.otm.core.util.writeInt +import ru.dbotthepony.mc.otm.core.util.writeVarIntLE +import ru.dbotthepony.mc.otm.core.util.writeVarLongLE object FriendlyStreams { @Test @@ -18,104 +18,54 @@ object FriendlyStreams { fun test() { val output = FastByteArrayOutputStream() - output.writeInt(4) - output.writeInt(16) - output.writeInt(-1) - output.writeInt(1000000) + for (i in 0 .. 65565) { + output.writeInt(i) + } - output.writeVarIntLE(0) - output.writeVarIntLE(1) - output.writeVarIntLE(4) - output.writeVarIntLE(15) - output.writeVarIntLE(16) - output.writeVarIntLE(127) - output.writeVarIntLE(128) - output.writeVarIntLE(129) - output.writeVarIntLE(10023) - output.writeVarIntLE(100000) + for (i in 0 .. 65565) { + output.writeVarIntLE(i) + } - output.writeVarIntLE(-0) - output.writeVarIntLE(-1) - output.writeVarIntLE(-4) - output.writeVarIntLE(-15) - output.writeVarIntLE(-16) - output.writeVarIntLE(-127) - output.writeVarIntLE(-128) - output.writeVarIntLE(-129) - output.writeVarIntLE(-10023) - output.writeVarIntLE(-100000) + for (i in 0 .. 65565) { + output.writeVarLongLE(i.toLong()) + } - output.writeVarLongLE(0) - output.writeVarLongLE(1) - output.writeVarLongLE(4) - output.writeVarLongLE(15) - output.writeVarLongLE(16) - output.writeVarLongLE(127) - output.writeVarLongLE(128) - output.writeVarLongLE(129) - output.writeVarLongLE(10023) - output.writeVarLongLE(100000) + for (i in 0 .. 65565) { + output.writeInt(-i) + } - output.writeVarLongLE(-0) - output.writeVarLongLE(-1) - output.writeVarLongLE(-4) - output.writeVarLongLE(-15) - output.writeVarLongLE(-16) - output.writeVarLongLE(-127) - output.writeVarLongLE(-128) - output.writeVarLongLE(-129) - output.writeVarLongLE(-10023) - output.writeVarLongLE(-100000) + for (i in 0 .. 65565) { + output.writeVarIntLE(-i) + } + + for (i in 0 .. 65565) { + output.writeVarLongLE(-i.toLong()) + } val input = FastByteArrayInputStream(output.array, 0, output.length) - assertEquals(4, input.readInt()) - assertEquals(16, input.readInt()) - assertEquals(-1, input.readInt()) - assertEquals(1000000, input.readInt()) + for (i in 0 .. 65565) { + assertEquals(i, input.readInt()) + } - assertEquals(0, input.readVarIntLE()) - assertEquals(1, input.readVarIntLE()) - assertEquals(4, input.readVarIntLE()) - assertEquals(15, input.readVarIntLE()) - assertEquals(16, input.readVarIntLE()) - assertEquals(127, input.readVarIntLE()) - assertEquals(128, input.readVarIntLE()) - assertEquals(129, input.readVarIntLE()) - assertEquals(10023, input.readVarIntLE()) - assertEquals(100000, input.readVarIntLE()) + for (i in 0 .. 65565) { + assertEquals(i, input.readVarIntLE()) + } - assertEquals(-0, input.readVarIntLE()) - assertEquals(-1, input.readVarIntLE()) - assertEquals(-4, input.readVarIntLE()) - assertEquals(-15, input.readVarIntLE()) - assertEquals(-16, input.readVarIntLE()) - assertEquals(-127, input.readVarIntLE()) - assertEquals(-128, input.readVarIntLE()) - assertEquals(-129, input.readVarIntLE()) - assertEquals(-10023, input.readVarIntLE()) - assertEquals(-100000, input.readVarIntLE()) + for (i in 0 .. 65565) { + assertEquals(i.toLong(), input.readVarLongLE()) + } - assertEquals(0, input.readVarLongLE()) - assertEquals(1, input.readVarLongLE()) - assertEquals(4, input.readVarLongLE()) - assertEquals(15, input.readVarLongLE()) - assertEquals(16, input.readVarLongLE()) - assertEquals(127, input.readVarLongLE()) - assertEquals(128, input.readVarLongLE()) - assertEquals(129, input.readVarLongLE()) - assertEquals(10023, input.readVarLongLE()) - assertEquals(100000, input.readVarLongLE()) + for (i in 0 .. 65565) { + assertEquals(-i, input.readInt()) + } - assertEquals(-0, input.readVarLongLE()) - assertEquals(-1, input.readVarLongLE()) - assertEquals(-4, input.readVarLongLE()) - assertEquals(-15, input.readVarLongLE()) - assertEquals(-16, input.readVarLongLE()) - assertEquals(-127, input.readVarLongLE()) - assertEquals(-128, input.readVarLongLE()) - assertEquals(-129, input.readVarLongLE()) - assertEquals(-10023, input.readVarLongLE()) - assertEquals(-100000, input.readVarLongLE()) + for (i in 0 .. 65565) { + assertEquals(-i, input.readVarIntLE()) + } + + for (i in 0 .. 65565) { + assertEquals(-i.toLong(), input.readVarLongLE()) + } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/MathTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/MathTests.kt index 4e96b646d..43b2b8a6e 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/MathTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/MathTests.kt @@ -3,8 +3,8 @@ package ru.dbotthepony.mc.otm.tests import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.core.integerDivisionDown -import ru.dbotthepony.mc.otm.core.integerDivisionUp +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.core.math.integerDivisionUp object MathTests { @Test diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TickListTests.kt similarity index 60% rename from src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt rename to src/test/kotlin/ru/dbotthepony/mc/otm/tests/TickListTests.kt index 0a2d1edae..b489731ef 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TickListTests.kt @@ -3,13 +3,13 @@ package ru.dbotthepony.mc.otm.tests import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.core.TimerQueue +import ru.dbotthepony.mc.otm.core.util.TickList -object TimerQueueTests { +object TickListTests { @Test - @DisplayName("TimerQueue test") - fun test() { - val queue = TimerQueue() + @DisplayName("TickList.Timer test") + fun timers() { + val queue = TickList() var state = 0 @@ -37,4 +37,27 @@ object TimerQueueTests { queue.tick() // 8 assertEquals(7, state) } + + @Test + @DisplayName("TickList test") + fun ticks() { + val list = TickList() + + var a = false + var b = 0 + var c = 0 + + list.once { a = true } + list.add { ++b < 4 } + list.always { c++ } + + for (i in 0 .. 10) { + list.tick() + } + + assertEquals(11, list.ticks) + assertEquals(11, c) + assertEquals(true, a) + assertEquals(4, b) + } } diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/WeakHashSetTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/WeakHashSetTests.kt new file mode 100644 index 000000000..38bf57250 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/WeakHashSetTests.kt @@ -0,0 +1,57 @@ +package ru.dbotthepony.mc.otm.tests + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.mc.otm.core.collect.WeakHashSet +import ru.dbotthepony.mc.otm.core.collect.forEach + +object WeakHashSetTests { + @Test + @DisplayName("WeakHashSet additions") + fun add() { + val set = WeakHashSet() + + check(set.add(1)) + check(set.add(2)) + check(set.add(3)) + check(!set.add(3)) + check(set.add(10)) + check(set.add(15)) + check(!set.add(15)) + check(!set.add(1)) + } + + @Test + @DisplayName("WeakHashSet removals") + fun remove() { + val set = WeakHashSet() + + check(set.add(1)) + check(set.add(2)) + check(set.add(3)) + + check(set.remove(1)) + check(set.remove(2)) + check(set.remove(3)) + + check(!set.remove(4)) + } + + @Test + @DisplayName("WeakHashSet traversal") + fun traversal() { + val set = WeakHashSet() + + check(set.add(1)) + check(set.add(2)) + check(set.add(3)) + check(set.add(5)) + check(set.add(6)) + check(set.remove(1)) + + val set2 = mutableSetOf(2, 3, 5, 6) + check(set.containsAll(set2)) + + set.iterator().forEach { check(set2.remove(it)) } + } +}