diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f811f6a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Disable autocrlf on generated files, they always generate with LF +# Add any extra files or paths here to make git stop saying they +# are changed when only line endings change. +src/generated/**/.cache/cache text eol=lf +src/generated/**/*.json text eol=lf diff --git a/.gitignore b/.gitignore index ba41971..5d129d0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,8 @@ build # other eclipse -run_client -run_server -run_test -*.txt +run +runs +run-data +repo diff --git a/build.gradle b/build.gradle index a00f795..df5ed2e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,77 +1,260 @@ plugins { + id 'java-library' id 'eclipse' + id 'idea' id 'maven-publish' - id 'net.minecraftforge.gradle' version '6.0.+' - id 'com.github.johnrengelman.shadow' version '8.1.+' + id 'net.neoforged.moddev' version '1.0.14' } -apply from: 'gradle/teacon-forge.gradle' +// Added by TeaCon +abstract class TeaConDumpPathToGitHub extends DefaultTask { + @Input + abstract Property getPublishName() + @InputFile + abstract RegularFileProperty getTargetFile() + @TaskAction + void dump() { + if (System.env.GITHUB_ACTIONS) { + File theFile = targetFile.getAsFile().get() -java.toolchain.languageVersion = JavaLanguageVersion.of(17) + def outputFile = new File(System.env.GITHUB_OUTPUT) + // Use the env-specific line separator for maximally possible compatibility + def newLine = System.getProperty('line.separator') -// definitions at gradle/teacon-forge.gradle -teacon { - modId = 'slide_show' - modVersion = '0.8.4' - modLicense = 'LGPL-3.0-only' - modGitHubRepo = 'teaconmc/SlideShow' - modAuthors = ['BloCamLimb', '3TUSK', 'ustc-zzzz'] - modDescription = 'Minecraft mod, adding a projector that can display online images.' + // Write out new env variable for later usage + outputFile << newLine << "artifact_name=${theFile.getName()}" + outputFile << newLine << "artifact_publish_name=${publishName.get()}" + outputFile << newLine << "artifact_path=${theFile.absolutePath}" + } + } +} - platform = 'forge-1.20-46.0.1' - // parchment = '2022.03.13' +tasks.named('wrapper', Wrapper).configure { + // Define wrapper values here so as to not have to always do so when updating gradlew.properties. + // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with + // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased + // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards. + // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`) + distributionType = Wrapper.DistributionType.BIN +} - // uncomment these lines if you need - modName = 'Slide Show' // default to repo name - // modGitHubBranch = 1.18-forge // for referring the license - modifyMemberAccess = true // for access transformer - // useDataGeneration = true // for data generation - publishTask = shadowJar // for shadow jar or other usages - lazyTokens = ['minecraft_classpath': { (project.configurations.shadow - project.configurations.minecraft).asPath }] // for runtime tokens +version = mod_version +group = mod_group_id - // use './gradlew -q printModMeta > src/main/resources/META-INF/mods.toml' to generate mod meta +repositories { + mavenLocal() + // Added by TeaCon + maven { + name "JitPack" + url 'https://jitpack.io' + } + // Added by TeaCon + maven { + name "Modrinth" + url "https://api.modrinth.com/maven" + } } -configurations { - compileClasspath.extendsFrom(shadow) - runtimeClasspath.extendsFrom(shadow) +base { + // Modified by TeaCon + archivesName = "$mod_github_repo-NeoForge-$minecraft_version" } -repositories { - maven { url 'https://jitpack.io' } - maven { - name = "Modrinth" - url = "https://api.modrinth.com/maven" +// Mojang ships Java 21 to end users starting in 1.20.5, so mods should target Java 21. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +neoForge { + // Specify the version of NeoForge to use. + version = project.neo_version + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.parchment_minecraft_version + } + + // This line is optional. Access Transformers are automatically detected + // accessTransformers = project.files('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + client() + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + server { + server() + programArgument '--nogui' + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + type = "gameTestServer" + systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id + } + + data { + data() + + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // gameDirectory = project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + + // applies to all the run configs above + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + logLevel = org.slf4j.event.Level.DEBUG + } + } + + mods { + // define mod <-> source bindings + // these are used to tell the game which sources are for which mod + // mostly optional in a single mod project + // but multi mod projects should define one per mod + "${mod_id}" { + sourceSet(sourceSets.main) + } } } +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + +// Sets up a dependency configuration called 'localRuntime'. +// This configuration should be used instead of 'runtimeOnly' to declare +// a dependency that will be present for runtime testing but that is +// "optional", meaning it will not be pulled by dependents of this mod. +configurations { + runtimeClasspath.extendsFrom localRuntime +} + dependencies { - shadow 'org.teacon:urlpattern:1.0.1' - shadow 'net.objecthunter:exp4j:0.4.8' - shadow 'org.apache.httpcomponents:httpclient-cache:4.5.13' + // Modified by TeaCon + jarJar implementation('org.teacon:urlpattern') { version { strictly '1.0.1' } } + jarJar implementation('net.objecthunter:exp4j') { version { strictly '0.4.8' } } + jarJar implementation('org.apache.httpcomponents:httpclient-cache') { version { strictly '4.5.13' } } +} - runtimeOnly fg.deobf("maven.modrinth:oculus:HxUtDCCe") // 1.20-1.6.4 +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +publishing { + publications { + // Modified by TeaCon + register('release', MavenPublication) { + // noinspection GroovyAssignabilityCheck + from components.java + version = mod_version + groupId = mod_group_id + artifactId = "$mod_github_repo-NeoForge-$minecraft_version" + pom { + name = mod_github_repo + url = "https://github.com/$mod_github_owner/$mod_github_repo" + licenses { + license { + name = mod_license + url = "https://github.com/$mod_github_owner/$mod_github_repo/blob/HEAD/LICENSE" + } + } + organization { + name = 'TeaConMC' + url = 'https://github.com/teaconmc' + } + developers { + for (mod_author in "$mod_authors".split(',')) { + developer { id = mod_author.trim(); name = mod_author.trim() } + } + } + issueManagement { + system = 'GitHub Issues' + url = "https://github.com/$mod_github_owner/$mod_github_repo/issues" + } + scm { + url = "https://github.com/$mod_github_owner/$mod_github_repo" + connection = "scm:git:git://github.com/$mod_github_owner/${mod_github_repo}.git" + developerConnection = "scm:git:git@github.com:$mod_github_owner/${mod_github_repo}.git" + } + } + } + } + repositories { + // Modified by TeaCon + maven { + name "teacon" + url "s3://maven/" + credentials(AwsCredentials) { + accessKey = System.env.ARCHIVE_ACCESS_KEY + secretKey = System.env.ARCHIVE_SECRET_KEY + } + } + } } -shadowJar { - archiveClassifier.set(null) - configurations = [project.configurations.shadow] - from(file('LICENSE')) { into 'META-INF' rename { "$it-slide-show" } } - dependencies { - it.exclude it.dependency('org.apache.httpcomponents:httpclient:.*') - it.exclude it.dependency('org.apache.httpcomponents:httpcore:.*') - it.exclude it.dependency('commons-logging:commons-logging:.*') - it.exclude it.dependency('commons-codec:commons-codec:.*') - it.exclude it.dependency('org.slf4j:slf4j-api:.*') +// Added by TeaCon +tasks.withType(PublishToMavenRepository).configureEach { + if (repository && repository.name == "archive") { + it.onlyIf { + System.env.MAVEN_USERNAME && System.env.MAVEN_PASSWORD + } } - relocate 'net.objecthunter.exp4j', 'org.teacon.slides.exp4j' - relocate 'org.teacon.urlpattern', 'org.teacon.slides.urlpattern' - relocate 'org.apache.http.client.cache', 'org.teacon.slides.http.client.cache' - relocate 'org.apache.http.impl.client.cache', 'org.teacon.slides.http.impl.client.cache' } -jar { it.enabled false } +// Added by TeaCon +tasks.register("githubActionOutput", TeaConDumpPathToGitHub) { task -> + task.onlyIf { System.env.GITHUB_ACTIONS } + task.getTargetFile().set(jar.archiveFile) + task.getPublishName().set("${jar.archiveBaseName.get()}-${version}.jar") +} -reobf { - jar { it.enabled false } +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } } diff --git a/gradle.properties b/gradle.properties index c4f1c9e..caf8f34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,49 @@ -# Ensure enough heap space during deployment -org.gradle.jvmargs=-Xmx3G -# It is said that daemon often brings more issues, so we disable it. -org.gradle.daemon=false +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +org.gradle.jvmargs=-Xmx1G +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true + +#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment +# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started +parchment_minecraft_version=1.21 +parchment_mappings_version=2024.07.07 +# Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.21,1.21.1) +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=21.0.142-beta +# The Neo version range can use any version of Neo as bounds +neo_version_range=[21.0.0-beta,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[4,) + +## Mod Properties + +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=slide_show +# The human-readable display name for the mod. +mod_name=Slide Show +# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. +mod_license=LGPL-3.0-only +# The mod version. See https://semver.org/ +mod_version=0.9.0 +# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. +# This should match the base package used for the mod sources. +# See https://maven.apache.org/guides/mini/guide-naming-conventions.html +mod_group_id=org.teacon +# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. +mod_authors=3TUSK, BloCamLimb, ustc-zzzz +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=Minecraft mod, adding a projector that can display online images. +# Added by TeaCon: gh owner +mod_github_owner=TeaCon +# Added by TeaCon: gh repo name +mod_github_repo=SlideShow diff --git a/gradle/teacon-forge.gradle b/gradle/teacon-forge.gradle deleted file mode 100644 index 6b4242b..0000000 --- a/gradle/teacon-forge.gradle +++ /dev/null @@ -1,351 +0,0 @@ -interface TeaConExtension { - // get the mod id - Property getModId() - // get the mod name - Property getModName() - // get the mod version - Property getModVersion() - // get the mod license - Property getModLicense() - // get the github repo - Property getModGitHubRepo() - // get the github repo - ListProperty getModAuthors() - // get the github branch - Property getModGitHubBranch() - // get the mod description - Property getModDescription() - // get the platform which should be 'forge-1.1x.x-xx.x.x' - Property getPlatform() - // get the parchment mapping version which should be either '20xx.xx.xx' or null - Property getParchment() - // check if access transformer is used - Property getModifyMemberAccess() - // check if data generation is used - Property getUseDataGeneration() - // get publish jar task (default task is jar) - Property getPublishTask() - // get lazy tokens used in generating runs - MapProperty getLazyTokens() -} - -gradle.afterProject { Project current -> - if (current != project) return - - def teacon = current.extensions.teacon as TeaConExtension - - def (platformName, gameVersion, platformVersion) = teacon.getPlatform().get().split('-', 3) - - // check if it is forge - assert platformName == 'forge' - - def modId = teacon.modId.get() - def artifactVersion = teacon.modVersion.get() - def (repoAuthor, repoName) = teacon.modGitHubRepo.get().split('/', 2) - - // check mod id - assert modId ==~ /[a-z_\d]+/ - - // check if it is from teacon - if (repoAuthor != 'teaconmc') { - logger.log(LogLevel.WARN, - 'An project whose repo not under https://github.com/teaconmc is configured. Use at your own risk.') - } - - current.group = 'org.teacon' - current.version = artifactVersion - current.archivesBaseName = repoName - - def useDataGeneration = teacon.useDataGeneration.getOrElse(false) - - // check if generated source is included - if (useDataGeneration) { - current.sourceSets.main.resources { it.srcDir 'src/generated/resources' } - } - - // configure minecraft - current.minecraft { /* net.minecraftforge.gradle.common.util.MinecraftExtension */ it -> - def parchment = teacon.getParchment().getOrElse(null) - def channel = parchment != null ? 'parchment' : 'official' - - it.mappings 'channel': channel, 'version': (parchment != null ? parchment + '-' : '') + gameVersion - - def modifyMemberAccess = teacon.modifyMemberAccess.getOrElse(false) - - if (modifyMemberAccess) { - it.accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - } - - def runtimeLazyTokens = teacon.lazyTokens.getOrElse([:]) - - it.runs { - client { - workingDirectory current.file('run_client') - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' - property 'forge.enabledGameTestNamespaces', modId - mods { - // noinspection GroovyAssignabilityCheck - create(modId) { it.source sourceSets.main } - } - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - // noinspection GroovyAssignabilityCheck - runtimeLazyTokens.each { k, v -> lazyToken(k, v) } - } - - server { - workingDirectory current.file('run_server') - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' - property 'forge.enabledGameTestNamespaces', modId - mods { - // noinspection GroovyAssignabilityCheck - create(modId) { it.source sourceSets.main } - } - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - // noinspection GroovyAssignabilityCheck - runtimeLazyTokens.each { k, v -> lazyToken(k, v) } - } - - gameTestServer { - workingDirectory current.file('run_test') - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' - property 'forge.enabledGameTestNamespaces', modId - mods { - // noinspection GroovyAssignabilityCheck - create(modId) { it.source sourceSets.main } - } - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - // noinspection GroovyAssignabilityCheck - runtimeLazyTokens.each { k, v -> lazyToken(k, v) } - } - - if (useDataGeneration) { - data { - workingDirectory current.file('run_data') - property 'forge.logging.markers', 'REGISTRIES' - property 'forge.logging.console.level', 'debug' - // noinspection GroovyAssignabilityCheck - args '--mod', modId, '--all', '--output', - file('src/generated/resources/'), '--existing', file('src/main/resources/') - mods { - // noinspection GroovyAssignabilityCheck - create(modId) { it.source sourceSets.main } - } - // noinspection GroovyAssignabilityCheck - runtimeLazyTokens.each { k, v -> lazyToken(k, v) } - } - } - } - } - - // configure dependencies - current.dependencies { DependencyHandler it -> - it.minecraft 'net.minecraftforge:forge:' + gameVersion + '-' + platformVersion - } - - // noinspection GroovyAssignabilityCheck - def generateArtifactAttributes = { String name, String specVersion, String implVersion -> - return [ - "Specification-Title" : name, - "Specification-Vendor" : "TeaConMC", - "Specification-Version" : specVersion, - "Implementation-Title" : name, - "Implementation-Version" : implVersion, - "Implementation-Vendor" : "TeaConMC", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ] - } - - def gameVersions = gameVersion.split('\\.') - def identifier = System.env.VERSION_IDENTIFIER - - def publishJarTask = teacon.publishTask.getOrElse(current.tasks.jar) - def jarArchiveBaseName = "${repoName}-Forge-${gameVersions[0]}.${gameVersions[1]}" as String - - // configure jar and re-obf - publishJarTask.configure { Jar it -> - it.archiveBaseName.set(jarArchiveBaseName) - it.archiveVersion.set("${artifactVersion}${identifier ? '-' + identifier : ''}") - it.manifest.attributes(generateArtifactAttributes(repoName, '1', it.archiveVersion.get()) as Map) - } - - current.reobf { - it.create(publishJarTask.name) { - current.publish.dependsOn it - publishJarTask.finalizedBy it - } - } - - def modAuthors = teacon.modAuthors.getOrElse([]) - // noinspection GroovyAssignabilityCheck - def modName = teacon.modName.getOrElse(repoName) - - def publishBranchName = teacon.modGitHubBranch.getOrElse("${gameVersions[0]}.${gameVersions[1]}-forge") - def publishPomName = "${repoName} for Minecraft ${gameVersions[0]}.${gameVersions[1]}" as String - def publishDescription = teacon.modDescription.getOrElse(publishPomName) - def publishLicense = teacon.modLicense.get() - - // configure maven publishing - current.publishing { PublishingExtension it -> - it.publications { - // noinspection GroovyAssignabilityCheck - release(MavenPublication) { - // noinspection GroovyAssignabilityCheck - groupId = "org.teacon" - // noinspection GroovyAssignabilityCheck - artifactId = jarArchiveBaseName - // noinspection GroovyAssignabilityCheck - version = artifactVersion - pom { - // noinspection GroovyAssignabilityCheck - name = publishPomName - // noinspection GroovyAssignabilityCheck - description = publishDescription != null ? publishDescription : publishPomName - // noinspection GroovyAssignabilityCheck - url = "https://github.com/${repoAuthor}/${repoName}" - licenses { - license { - // noinspection GroovyAssignabilityCheck - name = publishLicense - // noinspection GroovyAssignabilityCheck - url = "https://github.com/${repoAuthor}/${repoName}/blob/${publishBranchName}/LICENSE" - } - } - organization { - // noinspection GroovyAssignabilityCheck - name = 'TeaConMC' - // noinspection GroovyAssignabilityCheck - url = 'https://github.com/teaconmc' - } - developers { - for (def modAuthor : modAuthors) { - assert modAuthor == (modAuthor =~ /^\s+|\s+$/).replaceAll('') - - def matcher = modAuthor =~ /([^(\s]+)\s*\(([^)]+)\)/ - def modAuthorName = modAuthor - - if (matcher.matches()) { - modAuthor = matcher.group(1) - modAuthorName = matcher.group(2) - } - - developer { - // noinspection GroovyAssignabilityCheck - id = modAuthor - // noinspection GroovyAssignabilityCheck - name = modAuthorName - } - } - } - issueManagement { - // noinspection GroovyAssignabilityCheck - system = 'GitHub Issues' - // noinspection GroovyAssignabilityCheck - url = "https://github.com/${repoAuthor}/${repoName}/issues" - } - scm { - // noinspection GroovyAssignabilityCheck - url = "https://github.com/${repoAuthor}/${repoName}" - // noinspection GroovyAssignabilityCheck - connection = "scm:git:git://github.com/${repoAuthor}/${repoName}.git" - // noinspection GroovyAssignabilityCheck - developerConnection = "scm:git:git@github.com:${repoAuthor}/${repoName}.git" - } - } - // noinspection GroovyAssignabilityCheck - artifact publishJarTask - } - } - it.repositories { - maven { - name = "teacon" - url = System.env.ARCHIVE_URL - credentials(AwsCredentials) { - accessKey = System.env.ARCHIVE_ACCESS_KEY - secretKey = System.env.ARCHIVE_SECRET_KEY - } - } - } - } - - current.tasks.withType(PublishToMavenRepository) { - if (repository && repository.name == "archive") { - it.onlyIf { - System.env.MAVEN_USERNAME && System.env.MAVEN_PASSWORD - } - } - } - - // A simple task to pass down the artifact name and path to other GitHub actions - current.tasks.register("githubActionOutput") { - it.onlyIf { - System.env.GITHUB_ACTIONS - } - it.doLast { - println "::set-output name=artifact_name::${publishJarTask.archiveFileName.get()}" - println "::set-output name=artifact_publish_name::${jarArchiveBaseName}-${artifactVersion}.jar" - println "::set-output name=artifact_path::${publishJarTask.archiveFile.get().asFile.absolutePath}" - } - } - - // A task for generating mods.toml - current.tasks.register("printModMeta") { - it.doLast { - // noinspection GroovyAssignabilityCheck - def escaped = { String input -> - input = input.replace('\"', '\\\"') - input = input.replace('\b', '\\\b') - input = input.replace('\f', '\\\f') - input = input.replace('\n', '\\\n') - input = input.replace('\r', '\\\r') - input = input.replace('\t', '\\\t') - return input - } - - def lines = [] - - // loaders - lines += "modLoader=\"javafml\"" - lines += "loaderVersion=\"[${escaped(platformVersion.split('\\.')[0])},)\"" - lines += "license=\"${escaped(publishLicense)}\"" - lines += "" - - // mods - lines += "[[mods]]" - lines += "modId=\"${escaped(modId)}\"" - lines += "version=\"\${file.jarVersion}\"" - lines += "displayName=\"${escaped(modName)}\"" - lines += "authors=\"TeaConMC${modAuthors.collect { ', ' + escaped(it) }.join()}\"" - lines += "description=\"${escaped(publishDescription)}\"" - lines += "" - - // forge dependency - lines += "[[dependencies.${modId}]]" - lines += "modId=\"forge\"" - lines += "mandatory=true" - lines += "versionRange=\"[${escaped(platformVersion.split('\\.')[0])},)\"" - lines += "ordering=\"NONE\"" - lines += "side=\"BOTH\"" - lines += "" - - // minecraft dependency - lines += "[[dependencies.${modId}]]" - lines += "modId=\"minecraft\"" - lines += "mandatory=true" - lines += "versionRange=\"[${escaped("${gameVersions[0]}.${gameVersions[1]}")},)\"" - lines += "ordering=\"NONE\"" - lines += "side=\"BOTH\"" - lines += "" - - // print lines - lines.each { println it } - } - } -} - -project.extensions.create('teacon', TeaConExtension) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..2c35211 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 1f017e4..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or 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. @@ -15,69 +15,104 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# 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». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# 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"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +122,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - 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=SC2039,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=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + 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. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,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,13 +43,13 @@ 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. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +78,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 b/settings.gradle index bc6d0c3..ada876e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,11 @@ -pluginManagement.repositories { - gradlePluginPortal() - maven { url = 'https://maven.minecraftforge.net/' } +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + } } -rootProject.name = 'SlideShow' +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} diff --git a/src/main/java/org/teacon/slides/ModClientRegistries.java b/src/main/java/org/teacon/slides/ModClientRegistries.java index dee778a..949ac94 100644 --- a/src/main/java/org/teacon/slides/ModClientRegistries.java +++ b/src/main/java/org/teacon/slides/ModClientRegistries.java @@ -2,21 +2,22 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.client.gui.screens.MenuScreens; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.EntityRenderersEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import org.teacon.slides.screen.ProjectorScreen; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.neoforge.client.event.EntityRenderersEvent; +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import org.teacon.slides.renderer.ProjectorRenderer; +import org.teacon.slides.renderer.SlideState; +import org.teacon.slides.screen.ProjectorScreen; import javax.annotation.ParametersAreNonnullByDefault; @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public final class ModClientRegistries { public static final boolean IS_OPTIFINE_LOADED = isOptifineLoaded(); @@ -30,9 +31,15 @@ private static boolean isOptifineLoaded() { } @SubscribeEvent - public static void setupClient(final FMLClientSetupEvent event) { + public static void onRegisterMenuScreen(final RegisterMenuScreensEvent event) { SlideShow.LOGGER.info("OptiFine loaded: {}", IS_OPTIFINE_LOADED); - MenuScreens.register(ModRegistries.MENU.get(), ProjectorScreen::new); + event.register(ModRegistries.MENU.get(), ProjectorScreen::new); + } + + @SubscribeEvent + public static void onClientSetup(final FMLClientSetupEvent event) { + SlideShow.setRequestUrlPrefetch(SlideState::prefetch); + SlideShow.setApplyPrefetch(SlideState::applyPrefetch); } @SubscribeEvent @@ -44,7 +51,7 @@ public static void registerRenders(EntityRenderersEvent.RegisterRenderers event) public static void registerShaders(RegisterShadersEvent event) { try { event.registerShader(new ShaderInstance(event.getResourceProvider(), - new ResourceLocation(SlideShow.ID, "rendertype_palette_slide"), + ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "rendertype_palette_slide"), DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP), SlideRenderType::setPaletteSlideShader); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/java/org/teacon/slides/ModRegistries.java b/src/main/java/org/teacon/slides/ModRegistries.java index 75f8c13..951bd20 100644 --- a/src/main/java/org/teacon/slides/ModRegistries.java +++ b/src/main/java/org/teacon/slides/ModRegistries.java @@ -2,24 +2,22 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraftforge.common.CreativeModeTabRegistry; -import net.minecraftforge.event.BuildCreativeModeTabContentsEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.network.NetworkDirection; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.RegisterEvent; -import net.minecraftforge.registries.RegistryObject; -import org.teacon.slides.network.ProjectorURLPrefetchPacket; -import org.teacon.slides.network.ProjectorURLRequestPacket; -import org.teacon.slides.network.ProjectorURLSummaryPacket; -import org.teacon.slides.network.ProjectorUpdatePacket; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.RegisterEvent; +import org.teacon.slides.network.SlideURLPrefetchPacket; +import org.teacon.slides.network.SlideURLRequestPacket; +import org.teacon.slides.network.SlideURLSummaryPacket; +import org.teacon.slides.network.SlideUpdatePacket; import org.teacon.slides.projector.ProjectorBlock; import org.teacon.slides.projector.ProjectorBlockEntity; import org.teacon.slides.projector.ProjectorContainerMenu; @@ -28,12 +26,11 @@ import org.teacon.slides.url.ProjectorURLPatternArgument; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.Optional; @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = SlideShow.ID) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = SlideShow.ID) public final class ModRegistries { /** * The networking channel version. Since we follow SemVer, this is @@ -43,77 +40,52 @@ public final class ModRegistries { // Last Update: Thu, 17 Dec 2020 15:00:00 +0800 (0 => 1) // Last Update: Tue, 18 Jan 2022 20:00:00 +0800 (1 => 2) // Last Update: Sun, 26 Mar 2023 22:00:00 +0800 (2 => 3) - public static final String NETWORK_VERSION = "3"; + // Last Update: Mon, 29 Jul 2024 21:00:00 +0800 (3 => 4) + public static final String NETWORK_VERSION = "4"; - public static final ResourceLocation PROJECTOR_URL_PATTERN_ID = new ResourceLocation(SlideShow.ID, "projector_url_pattern"); + public static final ResourceLocation PROJECTOR_URL_PATTERN_ID = ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "projector_url_pattern"); - public static final ResourceLocation PROJECTOR_URL_ID = new ResourceLocation(SlideShow.ID, "projector_url"); + public static final ResourceLocation PROJECTOR_URL_ID = ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "projector_url"); - public static final ResourceLocation PROJECTOR_ID = new ResourceLocation(SlideShow.ID, "projector"); + public static final ResourceLocation PROJECTOR_ID = ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "projector"); - public static final ResourceLocation CHANNEL_ID = new ResourceLocation(SlideShow.ID, "network"); + public static final DeferredHolder PROJECTOR; - public static final SimpleChannel CHANNEL; + public static final DeferredHolder, BlockEntityType> BLOCK_ENTITY; - public static final RegistryObject PROJECTOR; - - public static final RegistryObject> BLOCK_ENTITY; - - public static final RegistryObject> MENU; + public static final DeferredHolder, MenuType> MENU; static { - CHANNEL = NetworkRegistry.newSimpleChannel(CHANNEL_ID, - () -> NETWORK_VERSION, NETWORK_VERSION::equals, NETWORK_VERSION::equals); - PROJECTOR = RegistryObject.create(PROJECTOR_ID, ForgeRegistries.BLOCKS); - BLOCK_ENTITY = RegistryObject.create(PROJECTOR_ID, ForgeRegistries.BLOCK_ENTITY_TYPES); - MENU = RegistryObject.create(PROJECTOR_ID, ForgeRegistries.MENU_TYPES); + PROJECTOR = DeferredHolder.create(BuiltInRegistries.BLOCK.key(), PROJECTOR_ID); + BLOCK_ENTITY = DeferredHolder.create(BuiltInRegistries.BLOCK_ENTITY_TYPE.key(), PROJECTOR_ID); + MENU = DeferredHolder.create(BuiltInRegistries.MENU.key(), PROJECTOR_ID); } @SubscribeEvent public static void register(final RegisterEvent event) { - event.register(ForgeRegistries.BLOCKS.getRegistryKey(), PROJECTOR_ID, ProjectorBlock::new); - event.register(ForgeRegistries.ITEMS.getRegistryKey(), PROJECTOR_ID, ProjectorItem::new); - event.register(ForgeRegistries.BLOCK_ENTITY_TYPES.getRegistryKey(), PROJECTOR_ID, ProjectorBlockEntity::create); - event.register(ForgeRegistries.MENU_TYPES.getRegistryKey(), PROJECTOR_ID, ProjectorContainerMenu::create); - event.register(ForgeRegistries.COMMAND_ARGUMENT_TYPES.getRegistryKey(), PROJECTOR_URL_ID, ProjectorURLArgument::create); - event.register(ForgeRegistries.COMMAND_ARGUMENT_TYPES.getRegistryKey(), PROJECTOR_URL_PATTERN_ID, ProjectorURLPatternArgument::create); + event.register(BuiltInRegistries.BLOCK.key(), PROJECTOR_ID, ProjectorBlock::new); + event.register(BuiltInRegistries.ITEM.key(), PROJECTOR_ID, ProjectorItem::new); + event.register(BuiltInRegistries.BLOCK_ENTITY_TYPE.key(), PROJECTOR_ID, ProjectorBlockEntity::create); + event.register(BuiltInRegistries.MENU.key(), PROJECTOR_ID, ProjectorContainerMenu::create); + event.register(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.key(), PROJECTOR_URL_ID, ProjectorURLArgument::create); + event.register(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.key(), PROJECTOR_URL_PATTERN_ID, ProjectorURLPatternArgument::create); } @SubscribeEvent - public static void setupCommon(final FMLCommonSetupEvent event) { - var index = 0; - CHANNEL.registerMessage(index++, - ProjectorUpdatePacket.class, - ProjectorUpdatePacket::write, - ProjectorUpdatePacket::new, - ProjectorUpdatePacket::handle, - Optional.of(NetworkDirection.PLAY_TO_SERVER)); - CHANNEL.registerMessage(index++, - ProjectorURLPrefetchPacket.class, - ProjectorURLPrefetchPacket::write, - ProjectorURLPrefetchPacket::new, - ProjectorURLPrefetchPacket::handle, - Optional.of(NetworkDirection.PLAY_TO_CLIENT)); - CHANNEL.registerMessage(index++, - ProjectorURLRequestPacket.class, - ProjectorURLRequestPacket::write, - ProjectorURLRequestPacket::new, - ProjectorURLRequestPacket::handle, - Optional.of(NetworkDirection.PLAY_TO_SERVER)); - CHANNEL.registerMessage(index++, - ProjectorURLSummaryPacket.class, - ProjectorURLSummaryPacket::write, - ProjectorURLSummaryPacket::new, - ProjectorURLSummaryPacket::handle, - Optional.of(NetworkDirection.PLAY_TO_CLIENT)); - SlideShow.LOGGER.info("Registered {} network packages (version {})", index, NETWORK_VERSION); + public static void onPayloadRegister(final RegisterPayloadHandlersEvent event) { + var pr = event.registrar(NETWORK_VERSION); + pr.playToServer(SlideUpdatePacket.TYPE, SlideUpdatePacket.CODEC, SlideUpdatePacket::handle); + pr.playToClient(SlideURLPrefetchPacket.TYPE, SlideURLPrefetchPacket.CODEC, SlideURLPrefetchPacket::handle); + pr.playToServer(SlideURLRequestPacket.TYPE, SlideURLRequestPacket.CODEC, SlideURLRequestPacket::handle); + pr.commonToClient(SlideURLSummaryPacket.TYPE, SlideURLSummaryPacket.CODEC, SlideURLSummaryPacket::handle); + SlideShow.LOGGER.info("Registered related network packages (version {})", NETWORK_VERSION); } @SubscribeEvent public static void onBuildContents(BuildCreativeModeTabContentsEvent event) { - var tabName = CreativeModeTabRegistry.getName(event.getTab()); - if (new ResourceLocation("minecraft", "tools_and_utilities").equals(tabName)) { - event.accept(PROJECTOR); + var tabKey = BuiltInRegistries.CREATIVE_MODE_TAB.getResourceKey(event.getTab()); + if (tabKey.isPresent() && CreativeModeTabs.TOOLS_AND_UTILITIES.equals(tabKey.get())) { + event.accept(PROJECTOR.get()); } } } diff --git a/src/main/java/org/teacon/slides/SlideShow.java b/src/main/java/org/teacon/slides/SlideShow.java index 0944858..d856472 100644 --- a/src/main/java/org/teacon/slides/SlideShow.java +++ b/src/main/java/org/teacon/slides/SlideShow.java @@ -2,11 +2,20 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraftforge.fml.common.Mod; +import net.neoforged.fml.common.Mod; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.teacon.slides.projector.ProjectorBlockEntity; +import org.teacon.slides.url.ProjectorURL; import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; @Mod(SlideShow.ID) @FieldsAreNonnullByDefault @@ -14,6 +23,33 @@ @ParametersAreNonnullByDefault public final class SlideShow { public static final String ID = "slide_show"; // as well as the namespace - public static final Logger LOGGER = LogManager.getLogger("SlideShow"); + + private static volatile Consumer requestUrlPrefetch = Objects::hash; + private static volatile BiConsumer, Map> applyPrefetch = Objects::hash; + private static volatile Function checkBlock = url -> ProjectorURL.Status.UNKNOWN; + + public static void setRequestUrlPrefetch(Consumer requestUrlPrefetch) { + SlideShow.requestUrlPrefetch = requestUrlPrefetch; + } + + public static void requestUrlPrefetch(ProjectorBlockEntity projector) { + requestUrlPrefetch.accept(projector); + } + + public static void setApplyPrefetch(BiConsumer, Map> applyPrefetch) { + SlideShow.applyPrefetch = applyPrefetch; + } + + public static void applyPrefetch(Set nonExistent, Map existent) { + applyPrefetch.accept(nonExistent, existent); + } + + public static void setCheckBlock(Function checkBlock) { + SlideShow.checkBlock = checkBlock; + } + + public static ProjectorURL.Status checkBlock(ProjectorURL url) { + return checkBlock.apply(url); + } } diff --git a/src/main/java/org/teacon/slides/admin/SlideCommand.java b/src/main/java/org/teacon/slides/admin/SlideCommand.java index 5f6372d..317db34 100644 --- a/src/main/java/org/teacon/slides/admin/SlideCommand.java +++ b/src/main/java/org/teacon/slides/admin/SlideCommand.java @@ -14,12 +14,13 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentUtils; import net.minecraft.network.chat.HoverEvent; -import net.minecraftforge.event.RegisterCommandsEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.network.PacketDistributor; import org.apache.commons.lang3.StringUtils; import org.teacon.slides.SlideShow; -import org.teacon.slides.network.ProjectorURLPrefetchPacket; +import org.teacon.slides.network.SlideURLPrefetchPacket; import org.teacon.slides.url.ProjectorURL; import org.teacon.slides.url.ProjectorURLArgument; import org.teacon.slides.url.ProjectorURLPatternArgument; @@ -36,7 +37,7 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME) public final class SlideCommand { private static final DynamicCommandExceptionType URL_NOT_EXIST = new DynamicCommandExceptionType(v -> Component.translatable("command.slide_show.failed.url_not_exist", v)); @@ -54,25 +55,25 @@ private static LiteralArgumentBuilder command(String name) { .then(argument("pattern", new ProjectorURLPatternArgument()) .executes(context -> list(context.getSource(), ProjectorURLPatternArgument.getUrl(context, "pattern"), - ProjectorURLSavedData.get(context.getSource().getLevel())))) + ProjectorURLSavedData.get(context.getSource().getServer())))) .executes(context -> list(context.getSource(), new URLPattern(Map.of(URLPattern.ComponentType.PROTOCOL, "http(s?)")), - ProjectorURLSavedData.get(context.getSource().getLevel())))) + ProjectorURLSavedData.get(context.getSource().getServer())))) .then(literal("prefetch") .then(argument("url", new ProjectorURLArgument()) .executes(context -> prefetch(context.getSource(), ProjectorURLArgument.getUrl(context, "url"), - ProjectorURLSavedData.get(context.getSource().getLevel()))))) + ProjectorURLSavedData.get(context.getSource().getServer()))))) .then(literal("block") .then(argument("url", new ProjectorURLArgument()) .executes(context -> block(context.getSource(), ProjectorURLArgument.getUrl(context, "url"), - ProjectorURLSavedData.get(context.getSource().getLevel()))))) + ProjectorURLSavedData.get(context.getSource().getServer()))))) .then(literal("unblock") .then(argument("url", new ProjectorURLArgument()) .executes(context -> unblock(context.getSource(), ProjectorURLArgument.getUrl(context, "url"), - ProjectorURLSavedData.get(context.getSource().getLevel()))))); + ProjectorURLSavedData.get(context.getSource().getServer()))))); } private static int prefetch(CommandSourceStack source, @@ -84,7 +85,7 @@ private static int prefetch(CommandSourceStack source, var uuidOptional = data.getIdByUrl(url); if (uuidOptional.isPresent() || SlidePermission.canInteractCreateUrl(source.source)) { var uuid = uuidOptional.orElseGet(() -> data.getOrCreateIdByCommand(url, source)); - new ProjectorURLPrefetchPacket(Set.of(uuid), data).sendToAll(); + PacketDistributor.sendToAllPlayers(new SlideURLPrefetchPacket(Set.of(uuid), data)); var msg = Component.translatable("command.slide_show.prefetch_projector_url.success", toText(uuid, url)); source.sendSuccess(() -> msg.withStyle(ChatFormatting.GREEN), true); return Command.SINGLE_SUCCESS; diff --git a/src/main/java/org/teacon/slides/admin/SlidePermission.java b/src/main/java/org/teacon/slides/admin/SlidePermission.java index b77c3be..af7f0a5 100644 --- a/src/main/java/org/teacon/slides/admin/SlidePermission.java +++ b/src/main/java/org/teacon/slides/admin/SlidePermission.java @@ -6,13 +6,13 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.rcon.RconConsoleSource; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.server.permission.PermissionAPI; -import net.minecraftforge.server.permission.events.PermissionGatherEvent; -import net.minecraftforge.server.permission.nodes.PermissionDynamicContext; -import net.minecraftforge.server.permission.nodes.PermissionNode; -import net.minecraftforge.server.permission.nodes.PermissionTypes; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.server.permission.PermissionAPI; +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContext; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; import org.teacon.slides.SlideShow; import javax.annotation.Nullable; @@ -23,7 +23,7 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME) public final class SlidePermission { private static @Nullable PermissionNode INTERACT_CREATE_PERM; private static @Nullable PermissionNode INTERACT_PERM; diff --git a/src/main/java/org/teacon/slides/cache/LegacyStorage.java b/src/main/java/org/teacon/slides/cache/LegacyStorage.java index 2ac4e85..1331f33 100644 --- a/src/main/java/org/teacon/slides/cache/LegacyStorage.java +++ b/src/main/java/org/teacon/slides/cache/LegacyStorage.java @@ -73,6 +73,7 @@ private static HttpCacheEntry createDummyCacheEntry(Path entryPath, Resource res return new HttpCacheEntry(dummyDate, dummyDate, dummyStatus, headers, resource, Collections.emptyMap()); } + @SuppressWarnings("deprecation") private static String normalizeUri(String uriString) { try { URI uri = URI.create(uriString); diff --git a/src/main/java/org/teacon/slides/network/ProjectorURLRequestPacket.java b/src/main/java/org/teacon/slides/network/ProjectorURLRequestPacket.java deleted file mode 100644 index 6f2d7a8..0000000 --- a/src/main/java/org/teacon/slides/network/ProjectorURLRequestPacket.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.teacon.slides.network; - -import com.google.common.collect.ImmutableSet; -import net.minecraft.FieldsAreNonnullByDefault; -import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.core.BlockPos; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; -import org.teacon.slides.ModRegistries; -import org.teacon.slides.projector.ProjectorBlockEntity; -import org.teacon.slides.url.ProjectorURLSavedData; - -import javax.annotation.ParametersAreNonnullByDefault; -import java.util.LinkedHashSet; -import java.util.UUID; -import java.util.function.Supplier; - -@FieldsAreNonnullByDefault -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -public final class ProjectorURLRequestPacket { - private final ImmutableSet requestedPosSet; - - public ProjectorURLRequestPacket(Iterable requested) { - this.requestedPosSet = ImmutableSet.copyOf(requested); - } - - public ProjectorURLRequestPacket(FriendlyByteBuf buf) { - var builder = ImmutableSet.builder(); - for (var hasNext = buf.readBoolean(); hasNext; hasNext = buf.readBoolean()) { - builder.add(new BlockPos(buf.readVarInt(), buf.readVarInt(), buf.readVarInt())); - } - this.requestedPosSet = builder.build(); - } - - public void write(FriendlyByteBuf buf) { - for (var pos : this.requestedPosSet) { - buf.writeBoolean(true); - buf.writeVarInt(pos.getX()); - buf.writeVarInt(pos.getY()); - buf.writeVarInt(pos.getZ()); - } - buf.writeBoolean(false); - } - - public void sendToServer() { - ModRegistries.CHANNEL.sendToServer(this); - } - - public void handle(Supplier context) { - context.get().enqueueWork(() -> { - var player = context.get().getSender(); - if (player != null) { - var level = player.serverLevel(); - var imageLocations = new LinkedHashSet(this.requestedPosSet.size()); - for (var pos : this.requestedPosSet) { - // prevent remote chunk loading - if (level.isLoaded(pos) && level.getBlockEntity(pos) instanceof ProjectorBlockEntity tile) { - imageLocations.add(tile.getImageLocation()); - } - } - var data = ProjectorURLSavedData.get(level); - new ProjectorURLPrefetchPacket(imageLocations, data).sendToClient(player); - } - }); - context.get().setPacketHandled(true); - } -} diff --git a/src/main/java/org/teacon/slides/network/ProjectorURLPrefetchPacket.java b/src/main/java/org/teacon/slides/network/SlideURLPrefetchPacket.java similarity index 60% rename from src/main/java/org/teacon/slides/network/ProjectorURLPrefetchPacket.java rename to src/main/java/org/teacon/slides/network/SlideURLPrefetchPacket.java index 03a4e7b..d08ca71 100644 --- a/src/main/java/org/teacon/slides/network/ProjectorURLPrefetchPacket.java +++ b/src/main/java/org/teacon/slides/network/SlideURLPrefetchPacket.java @@ -4,25 +4,31 @@ import com.google.common.collect.ImmutableSet; import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.network.NetworkEvent; -import net.minecraftforge.network.PacketDistributor; -import org.teacon.slides.ModRegistries; -import org.teacon.slides.renderer.SlideState; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.teacon.slides.SlideShow; import org.teacon.slides.url.ProjectorURL; import org.teacon.slides.url.ProjectorURLSavedData; import javax.annotation.ParametersAreNonnullByDefault; -import java.util.*; -import java.util.function.Supplier; +import java.util.Set; +import java.util.UUID; @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -public final class ProjectorURLPrefetchPacket { +public final class SlideURLPrefetchPacket implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE; + public static final StreamCodec CODEC; + + static { + TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "url_prefetch")); + CODEC = StreamCodec.ofMember(SlideURLPrefetchPacket::write, SlideURLPrefetchPacket::new); + } + private final ImmutableSet nonExistentIdSet; private final ImmutableMap existentIdMap; @@ -31,7 +37,7 @@ private enum Status { END, EXISTENT, NON_EXISTENT } - public ProjectorURLPrefetchPacket(Set idSet, ProjectorURLSavedData data) { + public SlideURLPrefetchPacket(Set idSet, ProjectorURLSavedData data) { var nonExistentBuilder = ImmutableSet.builder(); var existentBuilder = ImmutableMap.builder(); for (var id : idSet) { @@ -41,7 +47,7 @@ public ProjectorURLPrefetchPacket(Set idSet, ProjectorURLSavedData data) { this.existentIdMap = existentBuilder.build(); } - public ProjectorURLPrefetchPacket(FriendlyByteBuf buf) { + public SlideURLPrefetchPacket(RegistryFriendlyByteBuf buf) { var nonExistentBuilder = ImmutableSet.builder(); var existentBuilder = ImmutableMap.builder(); while (true) { @@ -57,25 +63,18 @@ public ProjectorURLPrefetchPacket(FriendlyByteBuf buf) { } } - public void write(FriendlyByteBuf buf) { + public void write(RegistryFriendlyByteBuf buf) { this.existentIdMap.forEach((uuid, url) -> buf.writeEnum(Status.EXISTENT).writeUUID(uuid).writeUtf(url.toUrl().toString())); this.nonExistentIdSet.forEach(uuid -> buf.writeEnum(Status.NON_EXISTENT).writeUUID(uuid)); buf.writeEnum(Status.END); } - public void sendToAll() { - ModRegistries.CHANNEL.send(PacketDistributor.ALL.noArg(), this); - } - - public void sendToClient(ServerPlayer player) { - ModRegistries.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), this); + public void handle(IPayloadContext context) { + context.enqueueWork(() -> SlideShow.applyPrefetch(this.nonExistentIdSet, this.existentIdMap)); } - public void handle(Supplier context) { - context.get().enqueueWork(() -> { - var applyPrefetch = DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> SlideState::getApplyPrefetch); - Objects.requireNonNull(applyPrefetch).accept(this.nonExistentIdSet, this.existentIdMap); - }); - context.get().setPacketHandled(true); + @Override + public Type type() { + return TYPE; } } diff --git a/src/main/java/org/teacon/slides/network/SlideURLRequestPacket.java b/src/main/java/org/teacon/slides/network/SlideURLRequestPacket.java new file mode 100644 index 0000000..8440198 --- /dev/null +++ b/src/main/java/org/teacon/slides/network/SlideURLRequestPacket.java @@ -0,0 +1,81 @@ +package org.teacon.slides.network; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.teacon.slides.SlideShow; +import org.teacon.slides.projector.ProjectorBlockEntity; +import org.teacon.slides.url.ProjectorURLSavedData; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.LinkedHashSet; +import java.util.UUID; + +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public final class SlideURLRequestPacket implements CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE; + public static final StreamCodec CODEC; + + static { + TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "url_request")); + CODEC = StreamCodec.ofMember(SlideURLRequestPacket::write, SlideURLRequestPacket::new); + } + + private final ImmutableSet requestedPosSet; + + public SlideURLRequestPacket(Iterable requested) { + this.requestedPosSet = ImmutableSet.copyOf(requested); + } + + public SlideURLRequestPacket(RegistryFriendlyByteBuf buf) { + var builder = ImmutableSet.builder(); + for (var hasNext = buf.readBoolean(); hasNext; hasNext = buf.readBoolean()) { + builder.add(new BlockPos(buf.readVarInt(), buf.readVarInt(), buf.readVarInt())); + } + this.requestedPosSet = builder.build(); + } + + public void write(RegistryFriendlyByteBuf buf) { + for (var pos : this.requestedPosSet) { + buf.writeBoolean(true); + buf.writeVarInt(pos.getX()); + buf.writeVarInt(pos.getY()); + buf.writeVarInt(pos.getZ()); + } + buf.writeBoolean(false); + } + + public void handle(IPayloadContext context) { + context.enqueueWork(() -> { + var player = context.player(); + if (player instanceof ServerPlayer serverPlayer) { + // noinspection resource + var level = serverPlayer.serverLevel(); + var imageLocations = new LinkedHashSet(this.requestedPosSet.size()); + for (var pos : this.requestedPosSet) { + // prevent remote chunk loading + if (level.isLoaded(pos) && level.getBlockEntity(pos) instanceof ProjectorBlockEntity tile) { + imageLocations.add(tile.getImageLocation()); + } + } + var data = ProjectorURLSavedData.get(serverPlayer.getServer()); + PacketDistributor.sendToPlayer(serverPlayer, new SlideURLPrefetchPacket(imageLocations, data)); + } + }); + } + + @Override + public CustomPacketPayload.Type type() { + return TYPE; + } +} diff --git a/src/main/java/org/teacon/slides/network/ProjectorURLSummaryPacket.java b/src/main/java/org/teacon/slides/network/SlideURLSummaryPacket.java similarity index 81% rename from src/main/java/org/teacon/slides/network/ProjectorURLSummaryPacket.java rename to src/main/java/org/teacon/slides/network/SlideURLSummaryPacket.java index 83df768..3e2dfff 100644 --- a/src/main/java/org/teacon/slides/network/ProjectorURLSummaryPacket.java +++ b/src/main/java/org/teacon/slides/network/SlideURLSummaryPacket.java @@ -9,37 +9,41 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Crypt; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.network.NetworkEvent; -import net.minecraftforge.network.PacketDistributor; -import org.teacon.slides.ModRegistries; -import org.teacon.slides.renderer.SlideState; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.teacon.slides.SlideShow; import org.teacon.slides.url.ProjectorURL; import javax.annotation.ParametersAreNonnullByDefault; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; -import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.function.Function; -import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -public final class ProjectorURLSummaryPacket implements Function { +public final class SlideURLSummaryPacket implements Function, CustomPacketPayload { + public static final CustomPacketPayload.Type TYPE; + public static final StreamCodec CODEC; + + static { + TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "url_summary")); + CODEC = StreamCodec.ofMember(SlideURLSummaryPacket::write, SlideURLSummaryPacket::new); + } + private final Bits256 hmacNonce; private final HashFunction hmacNonceFunction; private final Object2BooleanMap hmacUrlToBlockStatus; - public ProjectorURLSummaryPacket(BiMap idToUrl, Set blockedIdSet) { + public SlideURLSummaryPacket(BiMap idToUrl, Set blockedIdSet) { var hmacNonce = Bits256.random(); var hmacNonceFunction = Hashing.hmacSha256(hmacNonce.toBytes()); var hmacUrlToBlockStatus = new Object2BooleanArrayMap(idToUrl.size()); @@ -52,7 +56,7 @@ public ProjectorURLSummaryPacket(BiMap idToUrl, Set bl this.hmacUrlToBlockStatus = Object2BooleanMaps.unmodifiable(hmacUrlToBlockStatus); } - public ProjectorURLSummaryPacket(FriendlyByteBuf buf) { + public SlideURLSummaryPacket(FriendlyByteBuf buf) { var defaultCapacity = 16; var nonce = Bits256.read(buf); var hmacUrlToBlockStatus = new Object2BooleanArrayMap(defaultCapacity); @@ -70,14 +74,6 @@ public ProjectorURLSummaryPacket(FriendlyByteBuf buf) { } } - public void sendToAll() { - ModRegistries.CHANNEL.send(PacketDistributor.ALL.noArg(), this); - } - - public void sendToClient(ServerPlayer player) { - ModRegistries.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), this); - } - public void write(FriendlyByteBuf buf) { this.hmacNonce.write(buf); for (var entry : this.hmacUrlToBlockStatus.object2BooleanEntrySet()) { @@ -97,12 +93,14 @@ public ProjectorURL.Status apply(ProjectorURL url) { return ProjectorURL.Status.UNKNOWN; } - public void handle(Supplier context) { - context.get().enqueueWork(() -> { - var applySummary = DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> SlideState::getApplySummary); - Objects.requireNonNull(applySummary).accept(this); - }); - context.get().setPacketHandled(true); + public void handle(IPayloadContext context) { + // thread safe + SlideShow.setCheckBlock(this); + } + + @Override + public CustomPacketPayload.Type type() { + return TYPE; } public record Bits256(long bytesLE1, long bytesLE2, diff --git a/src/main/java/org/teacon/slides/network/ProjectorUpdatePacket.java b/src/main/java/org/teacon/slides/network/SlideUpdatePacket.java similarity index 82% rename from src/main/java/org/teacon/slides/network/ProjectorUpdatePacket.java rename to src/main/java/org/teacon/slides/network/SlideUpdatePacket.java index 216bc03..5d83987 100644 --- a/src/main/java/org/teacon/slides/network/ProjectorUpdatePacket.java +++ b/src/main/java/org/teacon/slides/network/SlideUpdatePacket.java @@ -4,16 +4,17 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; -import net.minecraftforge.fml.loading.FMLEnvironment; -import net.minecraftforge.network.NetworkEvent; +import net.neoforged.neoforge.network.handling.IPayloadContext; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.joml.Vector2f; import org.joml.Vector3f; -import org.teacon.slides.ModRegistries; import org.teacon.slides.SlideShow; import org.teacon.slides.admin.SlidePermission; import org.teacon.slides.projector.ProjectorBlock; @@ -29,16 +30,21 @@ import java.util.Set; import java.util.UUID; import java.util.function.Function; -import java.util.function.Supplier; - -import static com.google.common.base.Preconditions.checkArgument; @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -public final class ProjectorUpdatePacket { +public final class SlideUpdatePacket implements CustomPacketPayload { private static final Marker MARKER = MarkerManager.getMarker("Network"); + public static final CustomPacketPayload.Type TYPE; + public static final StreamCodec CODEC; + + static { + TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "update")); + CODEC = StreamCodec.ofMember(SlideUpdatePacket::write, SlideUpdatePacket::new); + } + public final BlockPos pos; public final UUID imgId; public final ProjectorBlock.InternalRotation rotation; @@ -54,9 +60,9 @@ public final class ProjectorUpdatePacket { public final @Nullable ProjectorURL imgUrl; public final @Nullable Log lastOperationLog; - public ProjectorUpdatePacket(ProjectorBlockEntity entity, - boolean canCreateNewProjectorUrl, - Function> uuidToUrl) { + public SlideUpdatePacket(ProjectorBlockEntity entity, + boolean canCreateNewProjectorUrl, + Function> uuidToUrl) { this.pos = entity.getBlockPos(); var imgLocation = entity.getImageLocation(); var dimension = entity.getDimension(); @@ -64,7 +70,7 @@ public ProjectorUpdatePacket(ProjectorBlockEntity entity, var imgUrlOptional = uuidToUrl.apply(imgLocation); var lastOperationOptional = imgUrlOptional.flatMap(uuid -> { if (entity.getLevel() instanceof ServerLevel serverLevel) { - var data = ProjectorURLSavedData.get(serverLevel); + var data = ProjectorURLSavedData.get(serverLevel.getServer()); var globalPos = GlobalPos.of(serverLevel.dimension(), this.pos); return data.getLatestLog(uuid, globalPos, Set.of(LogType.BLOCK, LogType.UNBLOCK)) .or(() -> data.getLatestLog(uuid, globalPos, Set.of(LogType.values()))); @@ -86,7 +92,7 @@ public ProjectorUpdatePacket(ProjectorBlockEntity entity, this.lastOperationLog = lastOperationOptional.orElse(null); } - public ProjectorUpdatePacket(FriendlyByteBuf buf) { + public SlideUpdatePacket(RegistryFriendlyByteBuf buf) { this.pos = buf.readBlockPos(); this.imgId = buf.readUUID(); this.rotation = buf.readEnum(ProjectorBlock.InternalRotation.class); @@ -103,7 +109,7 @@ public ProjectorUpdatePacket(FriendlyByteBuf buf) { this.lastOperationLog = Optional.ofNullable(buf.readNbt()).map(c -> Log.readTag(c).getValue()).orElse(null); } - public void write(FriendlyByteBuf buf) { + public void write(RegistryFriendlyByteBuf buf) { buf.writeBlockPos(this.pos).writeUUID(this.imgId); buf.writeEnum(this.rotation).writeInt(this.color).writeFloat(this.dimensionX).writeFloat(this.dimensionY); buf.writeFloat(this.slideOffsetX).writeFloat(this.slideOffsetY).writeFloat(this.slideOffsetZ); @@ -112,22 +118,17 @@ public void write(FriendlyByteBuf buf) { buf.writeNbt(this.lastOperationLog == null ? null : this.lastOperationLog.writeTag()); } - public void sendToServer() { - checkArgument(FMLEnvironment.dist.isClient()); - ModRegistries.CHANNEL.sendToServer(this); - } - - public void handle(Supplier context) { - context.get().enqueueWork(() -> { - var player = context.get().getSender(); - if (SlidePermission.canInteract(player)) { - var level = player.serverLevel(); + public void handle(IPayloadContext context) { + context.enqueueWork(() -> { + var player = context.player(); + // noinspection resource + if (SlidePermission.canInteract(player) && player.level() instanceof ServerLevel level) { var globalPos = GlobalPos.of(level.dimension(), this.pos); // prevent remote chunk loading if (level.isLoaded(this.pos) && level.getBlockEntity(this.pos) instanceof ProjectorBlockEntity tile) { // update image locations var oldId = tile.getImageLocation(); - var data = ProjectorURLSavedData.get(level); + var data = ProjectorURLSavedData.get(level.getServer()); var newId = data.getUrlById(this.imgId).map(u -> this.imgId).orElseGet(() -> { if (this.imgUrl != null) { if (this.hasCreatePermission) { @@ -165,6 +166,10 @@ public void handle(Supplier context) { SlideShow.LOGGER.debug(MARKER, "Received illegal packet: player = {}, pos = {}", profile, globalPos); } }); - context.get().setPacketHandled(true); + } + + @Override + public Type type() { + return TYPE; } } diff --git a/src/main/java/org/teacon/slides/projector/ProjectorBlock.java b/src/main/java/org/teacon/slides/projector/ProjectorBlock.java index ccd72ae..947d059 100644 --- a/src/main/java/org/teacon/slides/projector/ProjectorBlock.java +++ b/src/main/java/org/teacon/slides/projector/ProjectorBlock.java @@ -6,8 +6,9 @@ import net.minecraft.core.Direction; import net.minecraft.util.StringRepresentable; import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; +import net.minecraft.world.ItemInteractionResult; 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.BlockGetter; import net.minecraft.world.level.Level; @@ -139,15 +140,16 @@ public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { } @Override - public InteractionResult use(BlockState state, Level level, - BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, + Level level, BlockPos pos, Player player, + InteractionHand hand, BlockHitResult hitResult) { if (level.isClientSide) { - return InteractionResult.SUCCESS; + return ItemInteractionResult.SUCCESS; } if (level.getBlockEntity(pos) instanceof ProjectorBlockEntity tile) { ProjectorContainerMenu.openGui(player, tile); } - return InteractionResult.CONSUME; + return ItemInteractionResult.CONSUME; } public enum InternalRotation implements StringRepresentable { diff --git a/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java b/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java index 029d08f..68513cf 100644 --- a/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java +++ b/src/main/java/org/teacon/slides/projector/ProjectorBlockEntity.java @@ -5,6 +5,7 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.StringTag; @@ -23,20 +24,17 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.phys.AABB; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; import org.joml.*; import org.teacon.slides.ModRegistries; +import org.teacon.slides.SlideShow; import org.teacon.slides.admin.SlidePermission; -import org.teacon.slides.network.ProjectorUpdatePacket; -import org.teacon.slides.renderer.SlideState; +import org.teacon.slides.network.SlideUpdatePacket; import org.teacon.slides.url.ProjectorURL; import org.teacon.slides.url.ProjectorURLSavedData; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -110,7 +108,7 @@ public UUID getImageLocation() { public void setImageLocation(UUID imageLocation) { mImageLocation = Either.left(imageLocation); - this.requestUrlPrefetch(); + SlideShow.requestUrlPrefetch(this); } public boolean getKeepAspectRatio() { @@ -137,12 +135,8 @@ public boolean getHideLoadingSlideIcon() { return mHideLoadingSlideIcon; } - private void requestUrlPrefetch() { - var prefetch = DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> SlideState::getPrefetch); - Optional.ofNullable(prefetch).ifPresent(consumer -> consumer.accept(this.getBlockPos())); - } - - private void readAdditional(CompoundTag tag) { + @Override + protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { mColor = tag.getInt("Color"); mWidth = tag.getFloat("Width"); mHeight = tag.getFloat("Height"); @@ -157,14 +151,14 @@ private void readAdditional(CompoundTag tag) { mHideLoadingSlideIcon = tag.getBoolean("HideLoadingSlide"); if (tag.hasUUID("ImageLocation")) { mImageLocation = Either.left(tag.getUUID("ImageLocation")); - this.requestUrlPrefetch(); + SlideShow.requestUrlPrefetch(this); } else { mImageLocation = Either.right(tag.getString("ImageLocation")); } } @Override - protected void saveAdditional(CompoundTag tag) { + protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { tag.putInt("Color", mColor); tag.putFloat("Width", mWidth); tag.putFloat("Height", mHeight); @@ -188,7 +182,7 @@ public void setLevel(Level level) { if (level instanceof ServerLevel serverLevel) { try { var url = new ProjectorURL(urlString); - var data = ProjectorURLSavedData.get(serverLevel); + var data = ProjectorURLSavedData.get(serverLevel.getServer()); var css = level.getServer().createCommandSourceStack(); mImageLocation = Either.left(data.getOrCreateIdByCommand(url, css)); } catch (IllegalArgumentException e) { @@ -204,20 +198,14 @@ public void setLevel(Level level) { }); } - @Override - public void load(CompoundTag tag) { - super.load(tag); - this.readAdditional(tag); - } - @Override public ClientboundBlockEntityDataPacket getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); } @Override - public CompoundTag getUpdateTag() { - return this.saveWithoutMetadata(); + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + return this.saveWithoutMetadata(registries); } @Override @@ -225,9 +213,9 @@ public CompoundTag getUpdateTag() { if (currentPlayer instanceof ServerPlayer player) { var canInteract = SlidePermission.canInteract(player); if (canInteract) { - var data = ProjectorURLSavedData.get(player.serverLevel()); + var data = ProjectorURLSavedData.get(player.getServer()); var canCreate = SlidePermission.canInteractCreateUrl(currentPlayer); - return new ProjectorContainerMenu(id, new ProjectorUpdatePacket(this, canCreate, data::getUrlById)); + return new ProjectorContainerMenu(id, new SlideUpdatePacket(this, canCreate, data::getUrlById)); } } return null; @@ -244,7 +232,6 @@ public boolean hasCustomOutlineRendering(Player player) { return handItems.contains(ModRegistries.PROJECTOR.get().asItem()); } - @Override public AABB getRenderBoundingBox() { var pose = new Matrix4f(); var normal = new Matrix3f(); diff --git a/src/main/java/org/teacon/slides/projector/ProjectorContainerMenu.java b/src/main/java/org/teacon/slides/projector/ProjectorContainerMenu.java index ecb0008..86a3a8c 100644 --- a/src/main/java/org/teacon/slides/projector/ProjectorContainerMenu.java +++ b/src/main/java/org/teacon/slides/projector/ProjectorContainerMenu.java @@ -2,18 +2,17 @@ import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; 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.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.common.extensions.IForgeMenuType; -import net.minecraftforge.network.NetworkHooks; +import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; import org.teacon.slides.ModRegistries; import org.teacon.slides.admin.SlidePermission; -import org.teacon.slides.network.ProjectorUpdatePacket; +import org.teacon.slides.network.SlideUpdatePacket; import org.teacon.slides.url.ProjectorURLSavedData; import javax.annotation.ParametersAreNonnullByDefault; @@ -22,27 +21,28 @@ @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault public final class ProjectorContainerMenu extends AbstractContainerMenu { - public final ProjectorUpdatePacket updatePacket; + public final SlideUpdatePacket slideUpdatePacket; - public ProjectorContainerMenu(int containerId, ProjectorUpdatePacket updatePacket) { + public ProjectorContainerMenu(int containerId, SlideUpdatePacket slideUpdatePacket) { super(ModRegistries.MENU.get(), containerId); - this.updatePacket = updatePacket; + this.slideUpdatePacket = slideUpdatePacket; } - public ProjectorContainerMenu(int containerId, Inventory inventory, FriendlyByteBuf buf) { + public ProjectorContainerMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf buf) { super(ModRegistries.MENU.get(), containerId); - this.updatePacket = new ProjectorUpdatePacket(buf); + this.slideUpdatePacket = new SlideUpdatePacket(buf); } public static void openGui(Player currentPlayer, ProjectorBlockEntity tile) { - var player = (ServerPlayer) currentPlayer; - var data = ProjectorURLSavedData.get(player.serverLevel()); - var canCreate = SlidePermission.canInteractCreateUrl(currentPlayer); - NetworkHooks.openScreen(player, tile, new ProjectorUpdatePacket(tile, canCreate, data::getUrlById)::write); + if (currentPlayer instanceof ServerPlayer player) { + var data = ProjectorURLSavedData.get(player.getServer()); + var canCreate = SlidePermission.canInteractCreateUrl(currentPlayer); + currentPlayer.openMenu(tile, new SlideUpdatePacket(tile, canCreate, data::getUrlById)::write); + } } public static MenuType create() { - return IForgeMenuType.create(ProjectorContainerMenu::new); + return IMenuTypeExtension.create(ProjectorContainerMenu::new); } @Override @@ -52,11 +52,12 @@ public ItemStack quickMoveStack(Player p_38941_, int p_38942_) { @Override public boolean stillValid(Player player) { + // noinspection resource var level = player.level(); - if (!level.isLoaded(this.updatePacket.pos)) { + if (!level.isLoaded(this.slideUpdatePacket.pos)) { return false; } - if (!(level.getBlockEntity(this.updatePacket.pos) instanceof ProjectorBlockEntity)) { + if (!(level.getBlockEntity(this.slideUpdatePacket.pos) instanceof ProjectorBlockEntity)) { return false; } return SlidePermission.canInteract(player); diff --git a/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java b/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java index 0dff895..0eb6fbb 100644 --- a/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java +++ b/src/main/java/org/teacon/slides/renderer/ProjectorRenderer.java @@ -14,7 +14,8 @@ import net.minecraft.world.inventory.InventoryMenu; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraftforge.client.model.data.ModelData; +import net.minecraft.world.phys.AABB; +import net.neoforged.neoforge.client.model.data.ModelData; import org.joml.Matrix3f; import org.joml.Matrix4f; import org.teacon.slides.ModRegistries; @@ -58,7 +59,7 @@ public void render(ProjectorBlockEntity tile, float partialTick, PoseStack pStac var tileNormal = new Matrix3f(last.normal()); tile.transformToSlideSpace(tilePose, tileNormal); var flipped = tileState.getValue(ProjectorBlock.ROTATION).isFlipped(); - slide.render(source, tilePose, tileNormal, tile.getDimension(), + slide.render(source, last, tile.getDimension(), tileColorARGB, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, flipped || tile.getDoubleSided(), !flipped || tile.getDoubleSided(), SlideState.getAnimationTick(), partialTick); @@ -80,6 +81,11 @@ public void render(ProjectorBlockEntity tile, float partialTick, PoseStack pStac pStack.popPose(); } + @Override + public AABB getRenderBoundingBox(ProjectorBlockEntity blockEntity) { + return blockEntity.getRenderBoundingBox(); + } + @Override public boolean shouldRenderOffScreen(ProjectorBlockEntity tile) { // global rendering diff --git a/src/main/java/org/teacon/slides/renderer/SlideRenderType.java b/src/main/java/org/teacon/slides/renderer/SlideRenderType.java index fce3758..9b29978 100644 --- a/src/main/java/org/teacon/slides/renderer/SlideRenderType.java +++ b/src/main/java/org/teacon/slides/renderer/SlideRenderType.java @@ -49,6 +49,8 @@ public final class SlideRenderType extends RenderType.CompositeRenderType { ); } + private final Runnable additionalSetupState; + public SlideRenderType(int texture) { super(SlideShow.ID, DefaultVertexFormat.BLOCK, VertexFormat.Mode.QUADS, 256, false, true, @@ -66,16 +68,7 @@ public SlideRenderType(int texture) { .setLineState(DEFAULT_LINE) .createCompositeState(true) ); - var baseSetup = this.setupState; - this.setupState = () -> { - baseSetup.run(); - RenderSystem.setShaderTexture(0, texture); - }; -// () -> { -// GENERAL_STATES.forEach(RenderStateShard::setupRenderState); -// RenderSystem.setShaderTexture(0, texture); -// }, -// () -> GENERAL_STATES.forEach(RenderStateShard::clearRenderState)); + this.additionalSetupState = () -> RenderSystem.setShaderTexture(0, texture); } public SlideRenderType(int imageTexture, int paletteTexture) { @@ -95,17 +88,10 @@ public SlideRenderType(int imageTexture, int paletteTexture) { .setLineState(DEFAULT_LINE) .createCompositeState(true)); var baseSetup = this.setupState; - this.setupState = () -> { - baseSetup.run(); + this.additionalSetupState = () -> { RenderSystem.setShaderTexture(0, imageTexture); RenderSystem.setShaderTexture(3, paletteTexture); }; -// () -> { -// PALETTE_STATES.forEach(RenderStateShard::setupRenderState); -// RenderSystem.setShaderTexture(0, imageTexture); -// RenderSystem.setShaderTexture(3, paletteTexture); -// }, -// () -> GENERAL_STATES.forEach(RenderStateShard::clearRenderState)); } public SlideRenderType(ResourceLocation texture) { @@ -125,15 +111,13 @@ public SlideRenderType(ResourceLocation texture) { .setLineState(DEFAULT_LINE) .createCompositeState(true)); var baseSetup = this.setupState; - this.setupState = () -> { - baseSetup.run(); - RenderSystem.setShaderTexture(0, texture); - }; -// () -> { -// GENERAL_STATES.forEach(RenderStateShard::setupRenderState); -// RenderSystem.setShaderTexture(0, texture); -// }, -// () -> GENERAL_STATES.forEach(RenderStateShard::clearRenderState)); + this.additionalSetupState = () -> RenderSystem.setShaderTexture(0, texture); + } + + @Override + public void setupRenderState() { + super.setupRenderState(); + this.additionalSetupState.run(); } @Override diff --git a/src/main/java/org/teacon/slides/renderer/SlideState.java b/src/main/java/org/teacon/slides/renderer/SlideState.java index fc6a4de..ee1d47a 100644 --- a/src/main/java/org/teacon/slides/renderer/SlideState.java +++ b/src/main/java/org/teacon/slides/renderer/SlideState.java @@ -10,15 +10,17 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.ClientPlayerNetworkEvent; -import net.minecraftforge.client.event.CustomizeGuiOverlayEvent; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.CustomizeGuiOverlayEvent; +import net.neoforged.neoforge.network.PacketDistributor; import org.teacon.slides.SlideShow; import org.teacon.slides.cache.ImageCache; -import org.teacon.slides.network.ProjectorURLRequestPacket; +import org.teacon.slides.network.SlideURLRequestPacket; +import org.teacon.slides.projector.ProjectorBlockEntity; import org.teacon.slides.slide.Slide; import org.teacon.slides.texture.AnimatedTextureProvider; import org.teacon.slides.texture.GIFDecoder; @@ -35,8 +37,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; /** * @author BloCamLimb @@ -44,14 +44,13 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT) +@EventBusSubscriber(bus = EventBusSubscriber.Bus.GAME, value = Dist.CLIENT) public final class SlideState { private static final Executor RENDER_EXECUTOR = r -> RenderSystem.recordRenderCall(r::run); private static final int PENDING_TIMEOUT_SECONDS = 360; // 6min private static final Object2IntMap sBlockPending = new Object2IntLinkedOpenHashMap<>(); private static final Object2ObjectMap sIdWithImage = new Object2ObjectOpenHashMap<>(); - private static Function sBlockedCheck = url -> ProjectorURL.Status.UNKNOWN; private static final int RECYCLE_SECONDS = 120; // 2min private static final int RETRY_INTERVAL_SECONDS = 30; // 30s @@ -67,12 +66,10 @@ public final class SlideState { } @SubscribeEvent - public static void onTick(TickEvent.ClientTickEvent event) { - if (event.phase == TickEvent.Phase.START) { - var minecraft = Minecraft.getInstance(); - if (minecraft.player != null) { - SlideState.tick(minecraft.isPaused()); - } + public static void onTick(ClientTickEvent.Pre event) { + var minecraft = Minecraft.getInstance(); + if (minecraft.player != null) { + SlideState.tick(minecraft.isPaused()); } } @@ -83,13 +80,12 @@ public static void onPlayerLeft(ClientPlayerNetworkEvent.LoggingOut event) { @SubscribeEvent public static void onDebugTextCollection(CustomizeGuiOverlayEvent.DebugText event) { - if (Minecraft.getInstance().options.renderDebug) { + if (!Minecraft.getInstance().options.reducedDebugInfo().get()) { event.getLeft().add(SlideState.getDebugText()); } } private static void tick(boolean paused) { - // noinspection UnstableApiUsage var blockPosBuilder = ImmutableSet.builderWithExpectedSize(sBlockPending.size()); // pending request and timeout (which should not have been occurred) sBlockPending.object2IntEntrySet().removeIf(e -> { @@ -106,7 +102,7 @@ private static void tick(boolean paused) { var blockPosSet = blockPosBuilder.build(); if (!blockPosSet.isEmpty()) { SlideShow.LOGGER.debug("Requesting project urls for {} block position(s)", blockPosSet.size()); - new ProjectorURLRequestPacket(blockPosSet).sendToServer(); + PacketDistributor.sendToServer(new SlideURLRequestPacket(blockPosSet)); } // update cache if (!paused && ++sAnimationTick % 20 == 0) { @@ -151,38 +147,32 @@ public static long getAnimationTick() { } public static boolean getImgBlocked(ProjectorURL imgUrl) { - return sBlockedCheck.apply(imgUrl).isBlocked(); + return SlideShow.checkBlock(imgUrl).isBlocked(); } public static boolean getImgAllowed(ProjectorURL imgUrl) { - return sBlockedCheck.apply(imgUrl).isAllowed(); - } - - public static Consumer> getApplySummary() { - return summaryPredicate -> sBlockedCheck = summaryPredicate; + return SlideShow.checkBlock(imgUrl).isAllowed(); } - public static BiConsumer, Map> getApplyPrefetch() { - return (nonExistent, existent) -> { - // pending - sBlockPending.clear(); - // existent - sIdWithImage.putAll(existent); - // non-existent - sIdWithImage.keySet().removeAll(nonExistent); - // prefetch - existent.values().forEach(v -> sCache.getAcquire().computeIfAbsent(v, SlideState::new)); - }; + public static void applyPrefetch(Set nonExistent, Map existent) { + // pending + sBlockPending.clear(); + // existent + sIdWithImage.putAll(existent); + // non-existent + sIdWithImage.keySet().removeAll(nonExistent); + // prefetch + existent.values().forEach(v -> sCache.getAcquire().computeIfAbsent(v, SlideState::new)); } - public static Consumer getPrefetch() { - return pos -> sBlockPending.putIfAbsent(pos, PENDING_TIMEOUT_SECONDS * 20); + public static void prefetch(ProjectorBlockEntity blockEntity) { + sBlockPending.putIfAbsent(blockEntity.getBlockPos(), PENDING_TIMEOUT_SECONDS * 20); } public static @Nullable Slide getSlide(UUID id) { var imageUrl = sIdWithImage.get(id); if (imageUrl != null) { - var blockTestResult = sBlockedCheck.apply(imageUrl); + var blockTestResult = SlideShow.checkBlock(imageUrl); if (blockTestResult.isAllowed()) { return sCache.getAcquire().computeIfAbsent(sIdWithImage.get(id), SlideState::new).fetch(); } diff --git a/src/main/java/org/teacon/slides/screen/LazyWidget.java b/src/main/java/org/teacon/slides/screen/LazyWidget.java index 01514ff..181154d 100644 --- a/src/main/java/org/teacon/slides/screen/LazyWidget.java +++ b/src/main/java/org/teacon/slides/screen/LazyWidget.java @@ -5,11 +5,10 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; -import net.minecraftforge.common.util.NonNullFunction; -import net.minecraftforge.common.util.NonNullSupplier; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; +import java.util.function.Function; import java.util.function.Supplier; @FieldsAreNonnullByDefault @@ -17,17 +16,17 @@ @ParametersAreNonnullByDefault public final class LazyWidget implements Supplier { private @Nullable T cached; - private final NonNullSupplier initializer; - private final NonNullFunction refresher; + private final Supplier initializer; + private final Function refresher; - private LazyWidget(U init, NonNullFunction refresher, NonNullFunction supplier) { + private LazyWidget(U init, Function refresher, Function supplier) { this.refresher = old -> supplier.apply(refresher.apply(old)); this.initializer = () -> supplier.apply(init); this.cached = null; } public static LazyWidget of( - U init, NonNullFunction refresher, NonNullFunction supplier) { + U init, Function refresher, Function supplier) { return new LazyWidget<>(init, refresher, supplier); } diff --git a/src/main/java/org/teacon/slides/screen/ProjectorScreen.java b/src/main/java/org/teacon/slides/screen/ProjectorScreen.java index f100d86..f08242a 100644 --- a/src/main/java/org/teacon/slides/screen/ProjectorScreen.java +++ b/src/main/java/org/teacon/slides/screen/ProjectorScreen.java @@ -14,11 +14,11 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Rotation; +import net.neoforged.neoforge.network.PacketDistributor; import net.objecthunter.exp4j.ExpressionBuilder; import org.apache.commons.lang3.StringUtils; import org.joml.Matrix4f; @@ -27,7 +27,7 @@ import org.joml.Vector4f; import org.lwjgl.glfw.GLFW; import org.teacon.slides.SlideShow; -import org.teacon.slides.network.ProjectorUpdatePacket; +import org.teacon.slides.network.SlideUpdatePacket; import org.teacon.slides.projector.ProjectorBlock; import org.teacon.slides.projector.ProjectorBlockEntity; import org.teacon.slides.projector.ProjectorContainerMenu; @@ -45,7 +45,7 @@ @ParametersAreNonnullByDefault public final class ProjectorScreen extends AbstractContainerScreen { private static final ResourceLocation - GUI_TEXTURE = new ResourceLocation(SlideShow.ID, "textures/gui/projector.png"); + GUI_TEXTURE = ResourceLocation.fromNamespaceAndPath(SlideShow.ID, "textures/gui/projector.png"); private static final Component IMAGE_TEXT = Component.translatable("gui.slide_show.section.image"), @@ -83,7 +83,7 @@ public final class ProjectorScreen extends AbstractContainerScreen mKeepAspectChecked; private final LazyWidget