diff --git a/README.md b/README.md index 0dfee53a7..d737f152a 100644 --- a/README.md +++ b/README.md @@ -5,32 +5,6 @@ AzureLib represents a branch derived from Geckolib 4.x, serving as an animation engine tailored for Minecraft Mods. It boasts various features, including support for intricate 3D keyframe-driven animations, over 30 different easing functions, concurrent animation capabilities, sound and particle keyframes, event-based keyframes, and numerous other functionalities. Currently, I'll focus on maintaining and supporting AzureLib; no help will be given to Geckolib. -
-Are you a developer and want to use this library in your mod? Add the following to your build.gradle -

- - -``` -repositories { - // The Maven with the mods source - maven {url 'https://maven.azuredoom.com/mods'} -} - -dependencies { - - //Common 1.20.1+ Latest Only - compileOnly "mod.azure.azurelib:azurelib-common-MCVERSION:MODVERSION" - - //Fabric or Quilt - modImplementation "mod.azure.azurelib:azurelib-fabric-MCVERSION:MODVERSION" - - //NeoForge or Forge 1.20.1 - implementation fg.deobf("mod.azure.azurelib:azurelib-neo-MCVERSION:MODVERSION") -} -``` - -
-

Wiki

You can find the AzureLib Wiki here: https://wiki.azuredoom.com/ diff --git a/build.gradle b/build.gradle index f4bd4d6e5..db7ceb094 100644 --- a/build.gradle +++ b/build.gradle @@ -1,119 +1,5 @@ -import groovy.json.JsonOutput -import groovy.json.JsonSlurper - plugins { - id 'fabric-loom' version '1.2-SNAPSHOT' apply(false) - id 'net.minecraftforge.gradle' version '[6.0,6.2)' apply(false) - id 'org.spongepowered.gradle.vanilla' version '0.2.1-SNAPSHOT' apply(false) - id "org.spongepowered.mixin" version "0.7-SNAPSHOT" apply(false) -} - -version = version -group = maven_group - -subprojects { - apply plugin: 'java' - - java.toolchain.languageVersion = JavaLanguageVersion.of(17) - java.withSourcesJar() - java.withJavadocJar() - - jar { - from(rootProject.file("LICENSE")) { - rename { "${it}_${mod_name}" } - } - manifest { - attributes([ - "Specification-Title" : "AzureLib", - "Specification-Vendor" : "AzureDoom", - "Specification-Version" : "1", // We are version 1 of ourselves - "Implementation-Title" : project.name, - "Implementation-Version" : project.version, - "Implementation-Vendor" : "AzureDoom", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) - } - } - - sourcesJar { - from(rootProject.file("LICENSE")) { - rename { "${it}_${mod_name}" } - } - } - - repositories { - mavenCentral() - maven { - name = 'Sponge / Mixin' - url = 'https://repo.spongepowered.org/repository/maven-public/' - } - maven { - name = 'BlameJared Maven (JEI / CraftTweaker / Bookshelf)' - url = 'https://maven.blamejared.com' - } - } - - tasks.withType(JavaCompile).configureEach { - - it.options.encoding = 'UTF-8' - it.options.getRelease().set(17) - } - - processResources { - def expandProps = [ - "version" : project.version, - "group" : project.group, //Else we target the task's group. - "minecraft_version" : project.minecraft_version, - "neo_version" : project.neo_version, - "loader_version_range" : project.loader_version_range, - "neo_version_range" : project.neo_version_range, - "minecraft_version_range" : project.minecraft_version_range, - "fabric_version" : project.fabric_version, - "fabric_loader_version" : project.fabric_loader_version, - "mod_name" : project.mod_name, - "mod_author" : project.mod_author, - "mod_id" : project.mod_id, - "mod_license" : project.mod_license, - "mod_description" : project.mod_description, - "mod_credits" : project.mod_credits, - "mod_logo" : project.mod_logo, - "mod_url" : project.mod_url, - "mod_issues" : project.mod_issues, - "mod_sources" : project.mod_sources, - ] - - filesMatching(['pack.mcmeta', 'fabric.mod.json', '*.mixins.json', 'META-INF/mods.toml']) { - expand expandProps - } - inputs.properties(expandProps) - doLast { - def jsonMinifyStart = System.currentTimeMillis() - def jsonMinified = 0 - def jsonBytesSaved = 0 - fileTree(dir: outputs.files.asPath, include: '**/*.json').each { - File file = it - jsonMinified++ - def oldLength = file.length() - file.text = JsonOutput.toJson(new JsonSlurper().parse(file)) - jsonBytesSaved += oldLength - file.length() - } - println('Minified ' + jsonMinified + ' json files. Saved ' + jsonBytesSaved + ' bytes. Took ' + (System.currentTimeMillis() - jsonMinifyStart) + 'ms.') - } - } - - // Disables Gradle's custom module metadata from being published to maven. The - // metadata includes mapped dependencies which are not reasonably consumable by - // other mod developers. - tasks.withType(GenerateModuleMetadata).configureEach { - - enabled = false - } - - // Tells gradle to show 1000 errors instead of the default count of 100. - // See: https://stackoverflow.com/a/31905248 - gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xmaxerrs" << "1000" - } - } + id 'fabric-loom' version '1.9-SNAPSHOT' apply(false) + id 'net.neoforged.moddev.legacyforge' version '2.0.88' apply(false) + id 'me.modmuss50.mod-publish-plugin' version "${modmuss50_mod_publish_version}" apply(false) } \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 000000000..678405245 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'groovy-gradle-plugin' +} diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle new file mode 100644 index 000000000..15b726215 --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -0,0 +1,185 @@ +plugins { + id 'com.diffplug.spotless' + id 'java-library' + id 'maven-publish' + id 'idea' +} + +base { + archivesName = "${mod_id}-${project.name}-${minecraft_version}" +} + +java { + toolchain.languageVersion = JavaLanguageVersion.of(java_version) + withSourcesJar() + withJavadocJar() +} + +repositories { + mavenCentral() + // https://docs.gradle.org/current/userguide/declaring_repositories.html#declaring_content_exclusively_found_in_one_repository + exclusiveContent { + forRepository { + maven { + name = 'Sponge' + url = 'https://repo.spongepowered.org/repository/maven-public' + } + } + filter { includeGroupAndSubgroups('org.spongepowered') } + } + exclusiveContent { + forRepositories( + maven { + name = 'ParchmentMC' + url = 'https://maven.parchmentmc.org/' + }, + maven { + name = "NeoForge" + url = 'https://maven.neoforged.net/releases' + } + ) + filter { includeGroup('org.parchmentmc.data') } + } + maven { + name = 'BlameJared' + url = 'https://maven.blamejared.com' + } + maven { url "https://api.modrinth.com/maven" } + maven { url "https://cfa2.cursemaven.com" } + maven { url "https://maven.terraformersmc.com/" } + maven { url "https://maven.terraformersmc.com/releases" } // modmenu +} + +dependencies { + implementation 'org.jetbrains:annotations:24.1.0' +} + +['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant -> + configurations."$variant".outgoing { + capability("$group:${project.name}:$version") + capability("$group:${base.archivesName.get()}:$version") + capability("$group:$mod_id-${project.name}-${minecraft_version}:$version") + capability("$group:$mod_id:$version") + } + publishing.publications.configureEach { + suppressPomMetadataWarningsFor(variant) + } +} + +sourcesJar { + from(rootProject.file('LICENSE')) { + rename { "${it}_${mod_name}" } + } +} + +jar { + from(rootProject.file('LICENSE')) { + rename { "${it}_${mod_name}" } + } + + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version': project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Built-On-Minecraft' : minecraft_version + ]) + } +} + +processResources { + var expandProps = [ + "version" : project.version, + "group" : project.group, //Else we target the task's group. + "minecraft_version" : project.minecraft_version, + "neo_version" : project.neo_version, + "loader_version_range" : project.loader_version_range, + "neo_version_range" : project.neo_version_range, + "minecraft_version_range" : project.minecraft_version_range, + "fabric_version" : project.fabric_version, + "fabric_loader_version" : project.fabric_loader_version, + "mod_name" : project.mod_name, + "mod_author" : project.mod_author, + "mod_id" : project.mod_id, + "mod_license" : project.mod_license, + "mod_description" : project.mod_description, + "mod_credits" : project.mod_credits, + "mod_logo" : project.mod_logo, + "mod_url" : project.mod_url, + "mod_issues" : project.mod_issues, + "mod_sources" : project.mod_sources, + ] + + var jsonExpandProps = expandProps.collectEntries { + key, value -> [(key): value instanceof String ? value.replace("\n", "\\\\n") : value] + } + + filesMatching(['META-INF/mods.toml']) { + expand expandProps + } + + filesMatching(['pack.mcmeta', 'fabric.mod.json', '*.mixins.json']) { + expand jsonExpandProps + } + + inputs.properties(expandProps) +} + +publishing { + repositories { + maven { + name = project.mod_id + url = project.maven_url + credentials(PasswordCredentials) + authentication { + basic(BasicAuthentication) + } + } + } + publications { + maven(MavenPublication) { + artifactId base.archivesName.get() + from components.java + pom.withXml { + asNode().dependencies.dependency.each { dep -> + if (dep.groupId.text() == 'com.terraformersmc' && dep.artifactId.text() == 'modmenu') { + dep.parent().remove(dep) + } + } + } + } + } +} + + +// Disables Gradle's custom module metadata from being published to maven. The +// metadata includes mapped dependencies which are not reasonably consumable by +// other mod developers. +tasks.withType(GenerateModuleMetadata).configureEach { + enabled = false +} + +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} + +spotless { + java { + eclipse().configFile("$rootDir/eclipse-formatter.xml") + endWithNewline() + importOrder("", "java", group.toString(), "\\#") + indentWithSpaces(4) + removeUnusedImports() + trimTrailingWhitespace() + } +} + +tasks.build { + dependsOn("spotlessApply") +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle new file mode 100644 index 000000000..92e23251e --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-loader.gradle @@ -0,0 +1,44 @@ +plugins { + id 'multiloader-common' +} + +configurations { + commonJava{ + canBeResolved = true + } + commonResources{ + canBeResolved = true + } +} + +dependencies { + compileOnly(project(':common')) { + capabilities { + requireCapability "$group:$mod_id" + } + } + commonJava project(path: ':common', configuration: 'commonJava') + commonResources project(path: ':common', configuration: 'commonResources') +} + +tasks.named('compileJava', JavaCompile) { + dependsOn(configurations.commonJava) + source(configurations.commonJava) +} + +processResources { + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} + +tasks.named('javadoc', Javadoc).configure { + dependsOn(configurations.commonJava) + source(configurations.commonJava) +} + +tasks.named('sourcesJar', Jar) { + dependsOn(configurations.commonJava) + from(configurations.commonJava) + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} diff --git a/changelog.md b/changelog.md new file mode 100644 index 000000000..c4f548f8b --- /dev/null +++ b/changelog.md @@ -0,0 +1,3 @@ +v3.0.0 + +- Complete backport of AzureLib 3.x, please see: https://moddedmc.wiki/en/project/azurelib/docs Updating Guide for updating to this version. \ No newline at end of file diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index cd8206488..000000000 --- a/changelog.txt +++ /dev/null @@ -1,3 +0,0 @@ -v2.0.41 - -- Revert GeoGlowingTextureMeta change. \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index 8216986b1..b5f367c8c 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,39 +1,39 @@ plugins { - id 'idea' - id 'java' - id 'maven-publish' - id 'org.spongepowered.gradle.vanilla' -} -base { - archivesName = "${mod_id}-common-${minecraft_version}" + id 'multiloader-common' + id 'net.neoforged.moddev.legacyforge' + id 'com.diffplug.spotless' version "7.0.0.BETA3" } -minecraft { - version(minecraft_version) - if (file("src/main/resources/${mod_id}.aw").exists()) { - accessWideners(file("src/main/resources/${mod_id}.aw")) +legacyForge { + mcpVersion = minecraft_version + def at = file('src/main/resources/META-INF/accesstransformer.cfg') + if (at.exists()) { + accessTransformers.from(at.absolutePath) + } + parchment { + minecraftVersion = parchment_minecraft + mappingsVersion = parchment_version } } dependencies { compileOnly group:'org.spongepowered', name:'mixin', version:'0.8.5' + compileOnly group: 'io.github.llamalad7', name: 'mixinextras-common', version: '0.3.5' + annotationProcessor group: 'io.github.llamalad7', name: 'mixinextras-common', version: '0.3.5' } -publishing { - repositories { - maven { - name = "azurelib" - url = "https://maven.azuredoom.com/mods" - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } - } +configurations { + commonJava { + canBeResolved = false + canBeConsumed = true } - publications { - maven(MavenPublication) { - artifactId base.archivesName.get() - from components.java - } + commonResources { + canBeResolved = false + canBeConsumed = true } +} + +artifacts { + commonJava sourceSets.main.java.sourceDirectories.singleFile + commonResources sourceSets.main.resources.sourceDirectories.singleFile } \ No newline at end of file diff --git a/common/src/main/java/mod/azure/azurelib/AzureLib.java b/common/src/main/java/mod/azure/azurelib/AzureLib.java index 51d207d90..46e0247b1 100644 --- a/common/src/main/java/mod/azure/azurelib/AzureLib.java +++ b/common/src/main/java/mod/azure/azurelib/AzureLib.java @@ -18,6 +18,7 @@ public class AzureLib { public static final Logger LOGGER = LogManager.getLogger("azurelib"); public static final Marker MAIN_MARKER = MarkerManager.getMarker("main"); public static final String MOD_ID = "azurelib"; + public static final String ITEM_UUID_TAG = "az_id"; public static boolean hasInitialized; public static void initialize() { @@ -27,7 +28,7 @@ public static void initialize() { hasInitialized = true; } - public static final ResourceLocation modResource(String name) { + public static ResourceLocation modResource(String name) { return new ResourceLocation(MOD_ID, name); } } diff --git a/common/src/main/java/mod/azure/azurelib/ai/pathing/AzureNavigation.java b/common/src/main/java/mod/azure/azurelib/ai/pathing/AzureNavigation.java index 96119c668..ff06c9692 100644 --- a/common/src/main/java/mod/azure/azurelib/ai/pathing/AzureNavigation.java +++ b/common/src/main/java/mod/azure/azurelib/ai/pathing/AzureNavigation.java @@ -112,12 +112,31 @@ public boolean moveTo(Entity entity, double d) { return true; } - @Override - public void tick() { - super.tick(); + /** + * Ensures a minimum width of 1.0 for entities, addressing an issue + * where smaller entities (less than 0.8 units in width) encounter pathfinding + * failures. This resolves bugs such as MC-226637, where small entities + * end up "spinning" due to improper navigation logic. + *

+ * By enforcing a minimum calculated width of 1.0, this method prevents + * the pathfinding system from failing on smaller entities, while leaving + * the behavior of larger entities unchanged. It may also reduce + * performance overhead by preventing frequent hitbox and AI updates + * caused by entity spinning. + * + * @author Modrome + * @return the maximum of the entity's actual width and 1.0, ensuring a minimum width for correct pathing. + */ + public float getMinimumWidth() { + return Math.max(this.mob.getBbWidth(),1.0F); //Return whichever value is greater, for small entities, this returns 1.0 no matter what, fixing our spinning entities. + } + + @Override + public void tick() { + super.tick(); if (this.isDone()) { if (this.pathToPosition != null) { - if (this.pathToPosition.closerToCenterThan(this.mob.position(), this.mob.getBbWidth()) || this.mob.getY() > (double)this.pathToPosition.getY() && BlockPos.containing(this.pathToPosition.getX(), this.mob.getY(), this.pathToPosition.getZ()).closerToCenterThan(this.mob.position(), this.mob.getBbWidth())) { + if (this.pathToPosition.closerToCenterThan(this.mob.position(), getMinimumWidth()) || this.mob.getY() > this.pathToPosition.getY() && BlockPos.containing(this.pathToPosition.getX(), this.mob.getY(), this.pathToPosition.getZ()).closerToCenterThan(this.mob.position(), getMinimumWidth())) { this.pathToPosition = null; } else { this.mob.getMoveControl().setWantedPosition(this.pathToPosition.getX(), this.pathToPosition.getY(), this.pathToPosition.getZ(), this.speedModifier); @@ -125,9 +144,9 @@ public void tick() { } return; } - if (this.getTargetPos() != null) - this.mob.getLookControl().setLookAt(this.getTargetPos().getX(), this.getTargetPos().getY(), this.getTargetPos().getZ()); - } + if (this.getTargetPos() != null) + this.mob.getLookControl().setLookAt(this.getTargetPos().getX(), this.getTargetPos().getY(), this.getTargetPos().getZ()); + } private boolean isAt(Path path, float threshold) { final Vec3 pathPos = path.getNextEntityPos(this.mob); diff --git a/common/src/main/java/mod/azure/azurelib/animatable/GeoBlockEntity.java b/common/src/main/java/mod/azure/azurelib/animatable/GeoBlockEntity.java index 9257e1f1a..a40677591 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/GeoBlockEntity.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/GeoBlockEntity.java @@ -25,6 +25,7 @@ /** * The {@link GeoAnimatable} interface specific to {@link BlockEntity BlockEntities} */ +@Deprecated(forRemoval = true) public interface GeoBlockEntity extends GeoAnimatable { /** * Get server-synced animation data via its relevant {@link SerializableDataTicket}.
diff --git a/common/src/main/java/mod/azure/azurelib/animatable/GeoEntity.java b/common/src/main/java/mod/azure/azurelib/animatable/GeoEntity.java index b11ded72e..0f6415a20 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/GeoEntity.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/GeoEntity.java @@ -22,6 +22,7 @@ * The {@link GeoAnimatable} interface specific to {@link net.minecraft.world.entity.Entity Entities}. This also applies to Projectiles and other Entity subclasses.
* NOTE: This cannot be used for entities using the {@link mod.azure.azurelib.renderer.GeoReplacedEntityRenderer} as you aren't extending {@code Entity}. Use {@link GeoReplacedEntity} instead. */ +@Deprecated(forRemoval = true) public interface GeoEntity extends GeoAnimatable { /** * Get server-synced animation data via its relevant {@link SerializableDataTicket}.
diff --git a/common/src/main/java/mod/azure/azurelib/animatable/GeoItem.java b/common/src/main/java/mod/azure/azurelib/animatable/GeoItem.java index 48bb23e8d..fd498bee5 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/GeoItem.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/GeoItem.java @@ -34,6 +34,7 @@ /** * The {@link mod.azure.azurelib.core.animatable.GeoAnimatable GeoAnimatable} interface specific to {@link net.minecraft.world.item.Item Items}. This also applies to armor, as they are just items too. */ +@Deprecated(forRemoval = true) public interface GeoItem extends SingletonGeoAnimatable { String ID_NBT_KEY = "AzureLibID"; diff --git a/common/src/main/java/mod/azure/azurelib/animatable/GeoReplacedEntity.java b/common/src/main/java/mod/azure/azurelib/animatable/GeoReplacedEntity.java index 0295db2ef..cf53ce87f 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/GeoReplacedEntity.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/GeoReplacedEntity.java @@ -24,6 +24,7 @@ /** * The {@link GeoAnimatable} interface specific to {@link Entity Entities}. This interface is specifically for entities replacing the rendering of other, existing entities. */ +@Deprecated(forRemoval = true) public interface GeoReplacedEntity extends SingletonGeoAnimatable { /** * Returns the {@link EntityType} this entity is intending to replace.
diff --git a/common/src/main/java/mod/azure/azurelib/animatable/SingletonGeoAnimatable.java b/common/src/main/java/mod/azure/azurelib/animatable/SingletonGeoAnimatable.java index d5453b5be..e0dd918e0 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/SingletonGeoAnimatable.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/SingletonGeoAnimatable.java @@ -26,6 +26,7 @@ /** * The {@link GeoAnimatable} interface specific to singleton objects. This primarily applies to armor and items */ +@Deprecated(forRemoval = true) public interface SingletonGeoAnimatable extends GeoAnimatable { /** * Register this as a synched {@code GeoAnimatable} instance with AzureLib's networking functions.
diff --git a/common/src/main/java/mod/azure/azurelib/animatable/client/RenderProvider.java b/common/src/main/java/mod/azure/azurelib/animatable/client/RenderProvider.java index fa96652fd..e82f1769c 100644 --- a/common/src/main/java/mod/azure/azurelib/animatable/client/RenderProvider.java +++ b/common/src/main/java/mod/azure/azurelib/animatable/client/RenderProvider.java @@ -15,6 +15,7 @@ * Internal interface for safely providing a custom renderer instances at runtime.
* This can be safely instantiated as a new anonymous class inside your {@link Item} class */ +@Deprecated(forRemoval = true) public interface RenderProvider { RenderProvider DEFAULT = new RenderProvider() {}; diff --git a/common/src/main/java/mod/azure/azurelib/builders/AzureGunProperties.java b/common/src/main/java/mod/azure/azurelib/builders/AzureGunProperties.java index d7dc2e2dc..aa727039f 100644 --- a/common/src/main/java/mod/azure/azurelib/builders/AzureGunProperties.java +++ b/common/src/main/java/mod/azure/azurelib/builders/AzureGunProperties.java @@ -10,6 +10,7 @@ * * @author AzureDoom/Boston Vanseghi */ +@Deprecated(forRemoval = true) public class AzureGunProperties { private AzureGunProperties properties; private int ammoCount; diff --git a/common/src/main/java/mod/azure/azurelib/cache/AzureLibCache.java b/common/src/main/java/mod/azure/azurelib/cache/AzureLibCache.java index 600307242..9f0550184 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/AzureLibCache.java +++ b/common/src/main/java/mod/azure/azurelib/cache/AzureLibCache.java @@ -7,11 +7,12 @@ import mod.azure.azurelib.cache.object.BakedGeoModel; import mod.azure.azurelib.core.animatable.model.CoreGeoModel; import mod.azure.azurelib.loading.FileLoader; -import mod.azure.azurelib.loading.json.FormatVersion; import mod.azure.azurelib.loading.json.raw.Model; import mod.azure.azurelib.loading.object.BakedAnimations; import mod.azure.azurelib.loading.object.BakedModelFactory; import mod.azure.azurelib.loading.object.GeometryTree; +import mod.azure.azurelib.rewrite.animation.cache.AzBakedAnimationCache; +import mod.azure.azurelib.rewrite.model.cache.AzBakedModelCache; import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener.PreparationBarrier; @@ -34,6 +35,7 @@ * {@link mod.azure.azurelib.core.animation.Animation Animations} and * {@link CoreGeoModel Models} */ +@Deprecated(forRemoval = true) public final class AzureLibCache { private static final Set EXCLUDED_NAMESPACES = ObjectOpenHashSet.of("moreplayermodels", "customnpcs", "gunsrpg", "born_in_chaos_v1"); @@ -55,7 +57,7 @@ public static Map getBakedModels() { } public static void registerReloadListener() { - Minecraft mc = Minecraft.getInstance(); + var mc = Minecraft.getInstance(); if (mc == null) return; @@ -72,20 +74,33 @@ public static CompletableFuture reload(PreparationBarrier stage, ResourceM Map models = new Object2ObjectOpenHashMap<>(); return CompletableFuture - .allOf(loadAnimations(backgroundExecutor, resourceManager, animations::put), - loadModels(backgroundExecutor, resourceManager, models::put)) - .thenCompose(stage::wait).thenAcceptAsync(empty -> { + .allOf( + // TODO: Remove these. + loadAnimations(backgroundExecutor, resourceManager, animations::put), + loadModels(backgroundExecutor, resourceManager, models::put), + // Forward-support for new cache components + AzBakedAnimationCache.getInstance().loadAnimations(backgroundExecutor, resourceManager), + AzBakedModelCache.getInstance().loadModels(backgroundExecutor, resourceManager) + ) + .thenCompose(stage::wait) + .thenAcceptAsync(empty -> { AzureLibCache.ANIMATIONS = animations; AzureLibCache.MODELS = models; }, gameExecutor); } + /** + * @deprecated + */ private static CompletableFuture loadAnimations(Executor backgroundExecutor, ResourceManager resourceManager, BiConsumer elementConsumer) { return loadResources(backgroundExecutor, resourceManager, "animations", resource -> FileLoader.loadAnimationsFile(resource, resourceManager), elementConsumer); } + /** + * @deprecated + */ private static CompletableFuture loadModels(Executor backgroundExecutor, ResourceManager resourceManager, BiConsumer elementConsumer) { return loadResources(backgroundExecutor, resourceManager, "geo", resource -> { diff --git a/common/src/main/java/mod/azure/azurelib/cache/object/BakedGeoModel.java b/common/src/main/java/mod/azure/azurelib/cache/object/BakedGeoModel.java index c5dfce06d..e800c2021 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/object/BakedGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/cache/object/BakedGeoModel.java @@ -17,6 +17,7 @@ /** * Baked model object for AzureLib models. */ +@Deprecated(forRemoval = true) public record BakedGeoModel(List topLevelBones, ModelProperties properties) implements CoreBakedGeoModel { /** * Gets the list of top-level bones for this model. diff --git a/common/src/main/java/mod/azure/azurelib/cache/object/GeoBone.java b/common/src/main/java/mod/azure/azurelib/cache/object/GeoBone.java index 245ae593b..c8b4c5f8e 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/object/GeoBone.java +++ b/common/src/main/java/mod/azure/azurelib/cache/object/GeoBone.java @@ -24,6 +24,7 @@ * Mutable bone object representing a set of cubes, as well as child bones.
* This is the object that is directly modified by animations to handle movement */ +@Deprecated(forRemoval = true) public class GeoBone implements CoreGeoBone { private final GeoBone parent; private final String name; diff --git a/common/src/main/java/mod/azure/azurelib/cache/texture/AnimatableTexture.java b/common/src/main/java/mod/azure/azurelib/cache/texture/AnimatableTexture.java index 347d038b1..910a8fdba 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/texture/AnimatableTexture.java +++ b/common/src/main/java/mod/azure/azurelib/cache/texture/AnimatableTexture.java @@ -15,6 +15,7 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.util.RenderUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.SimpleTexture; @@ -37,8 +38,9 @@ * Wrapper for {@link SimpleTexture SimpleTexture} implementation allowing for casual use of animated non-atlas textures */ public class AnimatableTexture extends SimpleTexture { - private AnimationContents animationContents = null; - private boolean isAnimated = false; + protected AnimationContents animationContents = null; + + protected boolean isAnimated = false; public AnimatableTexture(final ResourceLocation location) { super(location); @@ -47,33 +49,29 @@ public AnimatableTexture(final ResourceLocation location) { @Override public void load(ResourceManager manager) throws IOException { Resource resource = manager.getResourceOrThrow(this.location); + AnimationMetadataSection animMeta = resource.metadata().getSection(AnimationMetadataSection.SERIALIZER).orElse(null); - try { + if (animMeta != null) { NativeImage nativeImage; try (InputStream inputstream = resource.open()) { nativeImage = NativeImage.read(inputstream); } - this.animationContents = resource.metadata().getSection(AnimationMetadataSection.SERIALIZER).map(animMeta -> new AnimationContents(nativeImage, animMeta)).orElse(null); + this.animationContents = new AnimationContents(nativeImage, animMeta); - if (this.animationContents != null) { - if (!this.animationContents.isValid()) { - nativeImage.close(); + if (!this.animationContents.isValid()) { + nativeImage.close(); - return; - } + return; + } - this.isAnimated = true; + this.isAnimated = true; - onRenderThread(() -> { - TextureUtil.prepareImage(getId(), 0, this.animationContents.frameSize.width(), this.animationContents.frameSize.height()); - nativeImage.upload(0, 0, 0, 0, 0, this.animationContents.frameSize.width(), this.animationContents.frameSize.height(), false, false); - }); - } - } - catch (RuntimeException exception) { - AzureLib.LOGGER.warn("Failed reading metadata of: {}", this.location, exception); + onRenderThread(() -> { + TextureUtil.prepareImage(getId(), 0, this.animationContents.frameSize.width(), this.animationContents.frameSize.height()); + nativeImage.upload(0, 0, 0, 0, 0, this.animationContents.frameSize.width(), this.animationContents.frameSize.height(), false, false); + }); } } @@ -86,6 +84,10 @@ public boolean isAnimated() { return this.isAnimated; } + public static void setAndUpdate(ResourceLocation texturePath) { + setAndUpdate(texturePath, (int) RenderUtils.getCurrentTick()); + } + public static void setAndUpdate(ResourceLocation texturePath, int frameTick) { AbstractTexture texture = Minecraft.getInstance().getTextureManager().getTexture(texturePath); @@ -108,9 +110,9 @@ private static void onRenderThread(RenderCall renderCall) { } } - private class AnimationContents { - private final FrameSize frameSize; - private final Texture animatedTexture; + protected class AnimationContents { + protected final FrameSize frameSize; + protected final Texture animatedTexture; private AnimationContents(NativeImage image, AnimationMetadataSection animMeta) { this.frameSize = animMeta.calculateFrameSize(image.getWidth(), image.getHeight()); @@ -172,7 +174,7 @@ private Texture generateAnimatedTexture(NativeImage image, AnimationMetadataSect private record Frame(int index, int time) { } - private class Texture implements AutoCloseable { + public class Texture implements AutoCloseable { private final NativeImage baseImage; private final Frame[] frames; private final int framePanelSize; @@ -180,6 +182,10 @@ private class Texture implements AutoCloseable { private final NativeImage interpolatedFrame; private final int totalFrameTime; + protected int glowMaskTextureId = -1; + protected NativeImage glowmaskImage = null; + protected NativeImage glowmaskInterpolatedFrame = null; + private int currentFrame; private int currentSubframe; @@ -207,6 +213,13 @@ private int getFrameY(int frameIndex) { return frameIndex / this.framePanelSize; } + public void setGlowMaskTexture(AutoGlowingTexture texture, NativeImage baseImage, NativeImage glowMask) { + this.glowMaskTextureId = texture.getId(); + this.glowmaskImage = glowMask; + this.glowmaskInterpolatedFrame = this.interpolating ? new NativeImage(AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height(), false) : null; + this.baseImage.copyFrom(baseImage); + } + public void setCurrentFrame(int ticks) { ticks %= this.totalFrameTime; @@ -237,43 +250,49 @@ public void setCurrentFrame(int ticks) { getFrameY(this.currentFrame) * AnimationContents.this.frameSize.height(), AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height(), false, false); + + if (this.glowmaskImage != null) { + TextureUtil.prepareImage(this.glowMaskTextureId, 0, AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height()); + this.glowmaskImage.upload(0, 0, 0, getFrameX(this.currentFrame) * AnimationContents.this.frameSize.width(), getFrameY(this.currentFrame) * AnimationContents.this.frameSize.height(), AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height(), false, false); + } }); } else if (this.currentSubframe != lastSubframe && this.interpolating) { - onRenderThread(this::generateInterpolatedFrame); + onRenderThread(() -> { + generateInterpolatedFrame(getId(), this.baseImage, this.interpolatedFrame); + + if (this.glowmaskImage != null) { + generateInterpolatedFrame(this.glowMaskTextureId, this.glowmaskImage, + this.glowmaskInterpolatedFrame); + } + }); } } - private void generateInterpolatedFrame() { + private void generateInterpolatedFrame(int textureId, NativeImage image, NativeImage interpolatedFrame) { Frame frame = this.frames[this.currentFrame]; - double frameProgress = 1 - (double) this.currentSubframe / (double) frame.time; - int nextFrameIndex = this.frames[(this.currentFrame + 1) % this.frames.length].index; - - if (frame.index != nextFrameIndex) { - for (int y = 0; y < this.interpolatedFrame.getHeight(); ++y) { - for (int x = 0; x < this.interpolatedFrame.getWidth(); ++x) { - int prevFramePixel = getPixel(frame.index, x, y); - int nextFramePixel = getPixel(nextFrameIndex, x, y); - int blendedRed = interpolate(frameProgress, prevFramePixel >> 16 & 255, - nextFramePixel >> 16 & 255); - int blendedGreen = interpolate(frameProgress, prevFramePixel >> 8 & 255, - nextFramePixel >> 8 & 255); + double frameProgress = 1 - (double)this.currentSubframe / (double)frame.time(); + int nextFrameIndex = this.frames[(this.currentFrame + 1) % this.frames.length].index(); + + if (frame.index() != nextFrameIndex) { + for (int y = 0; y < interpolatedFrame.getHeight(); ++y) { + for (int x = 0; x < interpolatedFrame.getWidth(); ++x) { + int prevFramePixel = getPixel(image, frame.index(), x, y); + int nextFramePixel = getPixel(image, nextFrameIndex, x, y); + int blendedRed = interpolate(frameProgress, prevFramePixel >> 16 & 255, nextFramePixel >> 16 & 255); + int blendedGreen = interpolate(frameProgress, prevFramePixel >> 8 & 255, nextFramePixel >> 8 & 255); int blendedBlue = interpolate(frameProgress, prevFramePixel & 255, nextFramePixel & 255); - this.interpolatedFrame.setPixelRGBA(x, y, - prevFramePixel & -16777216 | blendedRed << 16 | blendedGreen << 8 | blendedBlue); + interpolatedFrame.setPixelRGBA(x, y, prevFramePixel & -16777216 | blendedRed << 16 | blendedGreen << 8 | blendedBlue); } } - TextureUtil.prepareImage(AnimatableTexture.this.getId(), 0, - AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height()); - this.interpolatedFrame.upload(0, 0, 0, 0, 0, AnimationContents.this.frameSize.width(), - AnimationContents.this.frameSize.height(), false, false); + TextureUtil.prepareImage(textureId, 0, AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height()); + interpolatedFrame.upload(0, 0, 0, 0, 0, AnimationContents.this.frameSize.width(), AnimationContents.this.frameSize.height(), false, false); } } - private int getPixel(int frameIndex, int x, int y) { - return this.baseImage.getPixelRGBA(x + getFrameX(frameIndex) * AnimationContents.this.frameSize.width(), - y + getFrameY(frameIndex) * AnimationContents.this.frameSize.height()); + private int getPixel(NativeImage image, int frameIndex, int x, int y) { + return image.getPixelRGBA(x + getFrameX(frameIndex) * AnimationContents.this.frameSize.width(), y + getFrameY(frameIndex) * AnimationContents.this.frameSize.height()); } private int interpolate(double frameProgress, double prevColour, double nextColour) { @@ -284,8 +303,17 @@ private int interpolate(double frameProgress, double prevColour, double nextColo public void close() { this.baseImage.close(); - if (this.interpolatedFrame != null) + if (this.interpolatedFrame != null) { this.interpolatedFrame.close(); + } + + if (this.glowmaskImage != null) { + this.glowmaskImage.close(); + } + + if (this.glowmaskInterpolatedFrame != null) { + this.glowmaskInterpolatedFrame.close(); + } } } } diff --git a/common/src/main/java/mod/azure/azurelib/cache/texture/AutoGlowingTexture.java b/common/src/main/java/mod/azure/azurelib/cache/texture/AutoGlowingTexture.java index 012f561d7..92818b59c 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/texture/AutoGlowingTexture.java +++ b/common/src/main/java/mod/azure/azurelib/cache/texture/AutoGlowingTexture.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -47,11 +48,31 @@ public class AutoGlowingTexture extends GeoAbstractTexture { RenderSystem.defaultBlendFunc(); }); private static final RenderStateShard.WriteMaskStateShard WRITE_MASK = new RenderStateShard.WriteMaskStateShard(true, true); - private static final Function RENDER_TYPE_FUNCTION = Util.memoize(texture -> { - RenderStateShard.TextureStateShard textureState = new RenderStateShard.TextureStateShard(texture, false, false); - return RenderType.create("geo_glowing_layer", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, false, true, RenderType.CompositeState.builder().setShaderState(SHADER_STATE).setTextureState(textureState).setTransparencyState(TRANSPARENCY_STATE).setWriteMaskState(WRITE_MASK).createCompositeState(false)); - }); + protected static final BiFunction GLOWING_RENDER_TYPE = Util.memoize( + (texture, isGlowing) -> { + RenderStateShard.TextureStateShard textureState = new RenderStateShard.TextureStateShard( + texture, + false, + false + ); + + return RenderType.create( + "az_glowing_layer", + DefaultVertexFormat.NEW_ENTITY, + VertexFormat.Mode.QUADS, + 256, + false, + true, + RenderType.CompositeState.builder() + .setShaderState(SHADER_STATE) + .setTextureState(textureState) + .setTransparencyState(TRANSPARENCY_STATE) + .setWriteMaskState(WRITE_MASK) + .createCompositeState(isGlowing) + ); + } + ); private static final String APPENDIX = "_glowmask"; @@ -128,11 +149,20 @@ protected RenderCall loadTexture(ResourceManager resourceManager, Minecraft mc) NativeImage mask = glowImage; - if (mask == null) + if (mask == null) { + String expectedGlowmask = this.textureBase.toString().replace(".png", "_glowmask.png"); + AzureLib.LOGGER.warn("Missing glowmask texture. Base texture: {}, Expected glowmask: {}", this.textureBase, expectedGlowmask); return null; + } + + boolean animated = originalTexture instanceof AnimatableTexture animatableTexture && animatableTexture.isAnimated(); + + if (animated) + ((AnimatableTexture)originalTexture).animationContents.animatedTexture.setGlowMaskTexture(this, baseImage, mask); return () -> { - uploadSimple(getId(), mask, blur, clamp); + if (!animated) + uploadSimple(getId(), mask, blur, clamp); if (originalTexture instanceof DynamicTexture dynamicTexture) { dynamicTexture.upload(); @@ -148,6 +178,16 @@ protected RenderCall loadTexture(ResourceManager resourceManager, Minecraft mc) * @param texture The texture of the resource to apply a glow layer to */ public static RenderType getRenderType(ResourceLocation texture) { - return RENDER_TYPE_FUNCTION.apply(getEmissiveResource(texture)); + return GLOWING_RENDER_TYPE.apply(getEmissiveResource(texture), false); + } + + /** + * Return a cached instance of the RenderType for the given texture for AutoGlowingGeoLayer rendering, while the + * entity has an outline + * + * @param texture The texture of the resource to apply a glow layer to + */ + public static RenderType getOutlineRenderType(ResourceLocation texture) { + return GLOWING_RENDER_TYPE.apply(getEmissiveResource(texture), true); } } diff --git a/common/src/main/java/mod/azure/azurelib/cache/texture/GeoAbstractTexture.java b/common/src/main/java/mod/azure/azurelib/cache/texture/GeoAbstractTexture.java index 28605b174..c7a43845f 100644 --- a/common/src/main/java/mod/azure/azurelib/cache/texture/GeoAbstractTexture.java +++ b/common/src/main/java/mod/azure/azurelib/cache/texture/GeoAbstractTexture.java @@ -30,6 +30,7 @@ * Abstract texture wrapper for AzureLib textures.
* Mostly just handles boilerplate */ +@Deprecated(forRemoval = true) public abstract class GeoAbstractTexture extends AbstractTexture { /** * Generates the texture instance for the given path with the given appendix if it hasn't already been generated diff --git a/common/src/main/java/mod/azure/azurelib/client/screen/OptifineWarningScreen.java b/common/src/main/java/mod/azure/azurelib/client/screen/OptifineWarningScreen.java deleted file mode 100644 index 702f31f07..000000000 --- a/common/src/main/java/mod/azure/azurelib/client/screen/OptifineWarningScreen.java +++ /dev/null @@ -1,62 +0,0 @@ -package mod.azure.azurelib.client.screen; - -import mod.azure.azurelib.mixins.AccessorWarningScreen; -import mod.azure.azurelib.platform.Services; -import net.minecraft.ChatFormatting; -import net.minecraft.Util; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.screens.multiplayer.WarningScreen; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; - -public class OptifineWarningScreen extends WarningScreen { - public OptifineWarningScreen() { - super(HEADER, MESSAGE, CHECK_MESSAGE, NARRATED_TEXT); - } - - @Override - protected void initButtons(int yOffset) { - addRenderableWidget( - Button.builder(OPEN_MODS_FOLDER, buttonWidget -> Util.getPlatform().openFile(Services.PLATFORM.modsDir().toFile())) - .bounds(width / 2 - 155, 100 + yOffset, 150, 20) - .build() - ); - - addRenderableWidget( - Button.builder(OPTIFINE_ALTERNATIVES, buttonWidget -> Util.getPlatform().openUri( - "https://prismlauncher.org/wiki/getting-started/install-of-alternatives/" - )) - .bounds(width / 2 - 155 + 160, 100 + yOffset, 150, 20) - .build() - ); - - addRenderableWidget( - Button.builder(QUIT_GAME, buttonWidget -> this.minecraft.stop()) - .bounds(width / 2 - 75, 130 + yOffset, 150, 20) - .build() - ); - } - - @Override - protected void init() { - ((AccessorWarningScreen) this).setMessageText(MultiLineLabel.create(font, MESSAGE, width - 50)); - int yOffset = (((AccessorWarningScreen) this).getMessageText().getLineCount() + 1) * font.lineHeight * 2 - 20; - initButtons(yOffset); - } - - - @Override - public boolean shouldCloseOnEsc() { - return false; - } - - private static final MutableComponent HEADER = Component.translatable("header.azurelib.optifine").withStyle(ChatFormatting.DARK_RED, ChatFormatting.BOLD); - private static final Component MESSAGE = Component.translatable("message.azurelib.optifine"); - private static final Component CHECK_MESSAGE = Component.translatable("multiplayerWarning.check"); - private static final Component NARRATED_TEXT = HEADER.copy().append("\n").append(MESSAGE); - - private static final Component OPEN_MODS_FOLDER = Component.translatable("label.azurelib.open_mods_folder"); - private static final Component OPTIFINE_ALTERNATIVES = Component.translatable("label.azurelib.optifine_alternatives"); - private static final Component QUIT_GAME = Component.translatable("menu.quit"); -} diff --git a/common/src/main/java/mod/azure/azurelib/constant/DataTickets.java b/common/src/main/java/mod/azure/azurelib/constant/DataTickets.java index 85b098c10..f2d472c19 100644 --- a/common/src/main/java/mod/azure/azurelib/constant/DataTickets.java +++ b/common/src/main/java/mod/azure/azurelib/constant/DataTickets.java @@ -29,6 +29,7 @@ * Stores the default (builtin) {@link DataTicket DataTickets} used in AzureLib.
* Additionally handles registration of {@link mod.azure.azurelib.network.SerializableDataTicket SerializableDataTickets} */ +@Deprecated(forRemoval = true) public final class DataTickets { private static final Map> SERIALIZABLE_TICKETS = new ConcurrentHashMap<>(); diff --git a/common/src/main/java/mod/azure/azurelib/constant/DefaultAnimations.java b/common/src/main/java/mod/azure/azurelib/constant/DefaultAnimations.java index ea56e7697..d78684faf 100644 --- a/common/src/main/java/mod/azure/azurelib/constant/DefaultAnimations.java +++ b/common/src/main/java/mod/azure/azurelib/constant/DefaultAnimations.java @@ -24,6 +24,7 @@ * Using these won't affect much, but it may help keep some consistency in animation namings.
* Additionally, it encourages use of cached {@link mod.azure.azurelib.core.animation.RawAnimation RawAnimations}, to reduce overheads. */ +@Deprecated(forRemoval = true) public final class DefaultAnimations { public static final RawAnimation ITEM_ON_USE = RawAnimation.begin().thenPlay("item.use"); diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/GeoAnimatable.java b/common/src/main/java/mod/azure/azurelib/core/animatable/GeoAnimatable.java index 3f7dacc2a..38ca97058 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/GeoAnimatable.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/GeoAnimatable.java @@ -28,6 +28,7 @@ *

  • {@code GeoItem}
  • * */ +@Deprecated(forRemoval = true) public interface GeoAnimatable { /** * Register your {@link AnimationController AnimationControllers} and their respective animations and conditions. diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/AnimatableInstanceCache.java b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/AnimatableInstanceCache.java index 5a12d9137..b9136e1b6 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/AnimatableInstanceCache.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/AnimatableInstanceCache.java @@ -15,6 +15,7 @@ * The base cache class responsible for returning the {@link AnimatableManager} for a given instanceof of a {@link GeoAnimatable}. * This class is abstracted and not intended for direct use. See either {@link SingletonAnimatableInstanceCache} or {@link InstancedAnimatableInstanceCache} */ +@Deprecated(forRemoval = true) public abstract class AnimatableInstanceCache { protected final GeoAnimatable animatable; diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/InstancedAnimatableInstanceCache.java b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/InstancedAnimatableInstanceCache.java index 6ce4b735c..fe9a1fff8 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/InstancedAnimatableInstanceCache.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/InstancedAnimatableInstanceCache.java @@ -13,6 +13,7 @@ /** * AnimatableInstanceCache implementation for instantiated objects such as Entities or BlockEntities. Returns a single {@link AnimatableManager} instance per cache. */ +@Deprecated(forRemoval = true) public class InstancedAnimatableInstanceCache extends AnimatableInstanceCache { protected AnimatableManager manager; diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/SingletonAnimatableInstanceCache.java b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/SingletonAnimatableInstanceCache.java index e9cea59f0..716d12650 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/instance/SingletonAnimatableInstanceCache.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/instance/SingletonAnimatableInstanceCache.java @@ -15,6 +15,7 @@ /** * AnimatableInstanceCache implementation for singleton/flyweight objects such as Items. Utilises a keyed map to differentiate different instances of the object. */ +@Deprecated(forRemoval = true) public class SingletonAnimatableInstanceCache extends AnimatableInstanceCache { protected final Long2ObjectMap> managers = new Long2ObjectOpenHashMap<>(); diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreBakedGeoModel.java b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreBakedGeoModel.java index 6664b8c81..7e253b4b8 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreBakedGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreBakedGeoModel.java @@ -14,6 +14,7 @@ * Baked model object for AzureLib models.
    * Mostly an internal placeholder to allow for splitting up core (non-Minecraft) libraries */ +@Deprecated(forRemoval = true) public interface CoreBakedGeoModel { List getBones(); /** diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoBone.java b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoBone.java index c1c4d249e..95cfa23d2 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoBone.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoBone.java @@ -15,6 +15,7 @@ * Base class for AzureLib {@link CoreGeoModel model} bones.
    * Mostly a placeholder to allow for splitting up core (non-Minecraft) libraries */ +@Deprecated(forRemoval = true) public interface CoreGeoBone { String getName(); diff --git a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoModel.java b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoModel.java index cb05f9d03..81171d9e1 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/core/animatable/model/CoreGeoModel.java @@ -18,6 +18,7 @@ * Base class for AzureLib models.
    * Mostly an internal placeholder to allow for splitting up core (non-Minecraft) libraries */ +@Deprecated(forRemoval = true) public interface CoreGeoModel { /** * Get the baked model data for this model based on the provided string location diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/AnimatableManager.java b/common/src/main/java/mod/azure/azurelib/core/animation/AnimatableManager.java index a3e382bf0..45933dee6 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/AnimatableManager.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/AnimatableManager.java @@ -29,6 +29,7 @@ * will have a single instance of {@code AnimatableManager} associated with it.
    * */ +@Deprecated(forRemoval = true) public class AnimatableManager { private final Map boneSnapshotCollection = new Object2ObjectOpenHashMap<>(); private final Map> animationControllers; diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationController.java b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationController.java index df788df5c..1d168b415 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationController.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationController.java @@ -44,6 +44,7 @@ * Each controller can only play a single animation at a time - for example you may have one controller to animate walking, * one to control attacks, one to control size, etc. */ +@Deprecated(forRemoval = true) public class AnimationController { protected final T animatable; protected final String name; diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationProcessor.java b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationProcessor.java index fee3ff5f3..fedb1898c 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationProcessor.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationProcessor.java @@ -22,6 +22,7 @@ import mod.azure.azurelib.core.state.BoneSnapshot; import mod.azure.azurelib.core.utils.Interpolations; +@Deprecated(forRemoval = true) public class AnimationProcessor { private final Map bones = new Object2ObjectOpenHashMap<>(); private final CoreGeoModel model; diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationState.java b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationState.java index 99781c56e..6f6e7bd41 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/AnimationState.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/AnimationState.java @@ -20,6 +20,7 @@ * This is where users would set their selected animation to play, * stop the controller, or any number of other animation-related actions. */ +@Deprecated(forRemoval = true) public class AnimationState { private final T animatable; private final float limbSwing; diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/ContextAwareAnimatableManager.java b/common/src/main/java/mod/azure/azurelib/core/animation/ContextAwareAnimatableManager.java index ed374d424..0fb62707e 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/ContextAwareAnimatableManager.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/ContextAwareAnimatableManager.java @@ -20,6 +20,7 @@ * This can be used for things like perspective-dependent animation handling and other similar functionality.
    * This relies entirely on data present in {@link AnimatableManager#extraData} saved to this manager to determine context */ +@Deprecated(forRemoval = true) public abstract class ContextAwareAnimatableManager extends AnimatableManager { private final Map> managers; diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/EasingType.java b/common/src/main/java/mod/azure/azurelib/core/animation/EasingType.java index 732b04d67..d55ade474 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/EasingType.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/EasingType.java @@ -27,6 +27,7 @@ * Easings.net
    * Cubic-Bezier.com
    */ +@Deprecated(forRemoval = true) @FunctionalInterface public interface EasingType { Map EASING_TYPES = new ConcurrentHashMap<>(64); diff --git a/common/src/main/java/mod/azure/azurelib/core/animation/RawAnimation.java b/common/src/main/java/mod/azure/azurelib/core/animation/RawAnimation.java index c19c0199d..dab66ee5f 100644 --- a/common/src/main/java/mod/azure/azurelib/core/animation/RawAnimation.java +++ b/common/src/main/java/mod/azure/azurelib/core/animation/RawAnimation.java @@ -27,6 +27,7 @@ * Example usage:
    *
    {@code RawAnimation.begin().thenPlay("action.open_box").thenLoop("state.stay_open")}
    */ +@Deprecated(forRemoval = true) public final class RawAnimation { private final List animationList = new ObjectArrayList<>(); diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/AnimationPointQueue.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/AnimationPointQueue.java index 713e9b2e8..0aa2af659 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/AnimationPointQueue.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/AnimationPointQueue.java @@ -19,6 +19,7 @@ * An {@link AnimationPoint} queue holds a queue of {@code AnimationPoints} which are used in * the {@link mod.azure.azurelib.core.animation.AnimationController} to lerp between values */ +@Deprecated(forRemoval = true) public final class AnimationPointQueue extends LinkedList { @Serial private static final long serialVersionUID = 5472797438476621193L; diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/BoneAnimationQueue.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/BoneAnimationQueue.java index 5bd33545c..c6b756cea 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/BoneAnimationQueue.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/BoneAnimationQueue.java @@ -19,6 +19,7 @@ * A bone pseudo-stack for bone animation positions, scales, and rotations. * Animation points are calculated then pushed onto their respective queues to be used for transformations in rendering */ +@Deprecated(forRemoval = true) public record BoneAnimationQueue(CoreGeoBone bone, AnimationPointQueue rotationXQueue, AnimationPointQueue rotationYQueue, AnimationPointQueue rotationZQueue, AnimationPointQueue positionXQueue, AnimationPointQueue positionYQueue, AnimationPointQueue positionZQueue, AnimationPointQueue scaleXQueue, AnimationPointQueue scaleYQueue, diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/KeyframeLocation.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/KeyframeLocation.java index 24047210c..82f594804 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/KeyframeLocation.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/KeyframeLocation.java @@ -12,8 +12,6 @@ package mod.azure.azurelib.core.keyframe; -import mod.azure.azurelib.core.math.IValue; - /** * A named pair object that stores a {@link Keyframe} and a double representing a temporally placed {@code Keyframe} * @param keyframe The {@code Keyframe} at the tick time diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/CustomInstructionKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/CustomInstructionKeyframeEvent.java index 6a54b6f2b..b30757c41 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/CustomInstructionKeyframeEvent.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/CustomInstructionKeyframeEvent.java @@ -20,6 +20,7 @@ * The {@link KeyFrameEvent} specific to the {@link AnimationController#customKeyframeHandler}.
    * Called when a custom instruction keyframe is encountered */ +@Deprecated(forRemoval = true) public class CustomInstructionKeyframeEvent extends KeyFrameEvent { public CustomInstructionKeyframeEvent(T entity, double animationTick, AnimationController controller, CustomInstructionKeyframeData customInstructionKeyframeData) { diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/KeyFrameEvent.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/KeyFrameEvent.java index ce6b60373..083a115df 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/KeyFrameEvent.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/KeyFrameEvent.java @@ -24,6 +24,7 @@ * @see ParticleKeyframeEvent * @see SoundKeyframeEvent */ +@Deprecated(forRemoval = true) public abstract class KeyFrameEvent { private final T animatable; private final double animationTick; diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/ParticleKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/ParticleKeyframeEvent.java index 738e7a510..3abe01798 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/ParticleKeyframeEvent.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/ParticleKeyframeEvent.java @@ -15,6 +15,7 @@ * The {@link KeyFrameEvent} specific to the {@link AnimationController#particleKeyframeHandler}.
    * Called when a particle instruction keyframe is encountered */ +@Deprecated(forRemoval = true) public class ParticleKeyframeEvent extends KeyFrameEvent { public ParticleKeyframeEvent(T animatable, double animationTick, AnimationController controller, ParticleKeyframeData particleKeyFrameData) { super(animatable, animationTick, controller, particleKeyFrameData); diff --git a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/SoundKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/SoundKeyframeEvent.java index b2c98a3ba..2f5f1e035 100644 --- a/common/src/main/java/mod/azure/azurelib/core/keyframe/event/SoundKeyframeEvent.java +++ b/common/src/main/java/mod/azure/azurelib/core/keyframe/event/SoundKeyframeEvent.java @@ -20,6 +20,7 @@ * The {@link KeyFrameEvent} specific to the {@link AnimationController#soundKeyframeHandler}.
    * Called when a sound instruction keyframe is encountered */ +@Deprecated(forRemoval = true) public class SoundKeyframeEvent extends KeyFrameEvent { /** * This stores all the fields that are needed in the AnimationTestEvent diff --git a/common/src/main/java/mod/azure/azurelib/core/molang/MolangParser.java b/common/src/main/java/mod/azure/azurelib/core/molang/MolangParser.java index 65437bc5c..435c83902 100644 --- a/common/src/main/java/mod/azure/azurelib/core/molang/MolangParser.java +++ b/common/src/main/java/mod/azure/azurelib/core/molang/MolangParser.java @@ -39,7 +39,7 @@ public class MolangParser extends MathBuilder { public static final MolangParser INSTANCE = new MolangParser(); - private MolangParser() { + public MolangParser() { super(); // Remap functions to be intact with Molang specification diff --git a/common/src/main/java/mod/azure/azurelib/core/molang/MolangQueries.java b/common/src/main/java/mod/azure/azurelib/core/molang/MolangQueries.java index a0cd8e7f1..9e694e225 100644 --- a/common/src/main/java/mod/azure/azurelib/core/molang/MolangQueries.java +++ b/common/src/main/java/mod/azure/azurelib/core/molang/MolangQueries.java @@ -12,20 +12,48 @@ * These do not constitute a definitive list of queries; merely the default ones */ public final class MolangQueries { - public static final String ANIM_TIME = "query.anim_time"; - public static final String LIFE_TIME = "query.life_time"; - public static final String ACTOR_COUNT = "query.actor_count"; - public static final String TIME_OF_DAY = "query.time_of_day"; - public static final String MOON_PHASE = "query.moon_phase"; - public static final String DISTANCE_FROM_CAMERA = "query.distance_from_camera"; - public static final String IS_ON_GROUND = "query.is_on_ground"; - public static final String IS_IN_WATER = "query.is_in_water"; - public static final String IS_IN_WATER_OR_RAIN = "query.is_in_water_or_rain"; - public static final String HEALTH = "query.health"; - public static final String MAX_HEALTH = "query.max_health"; - public static final String IS_ON_FIRE = "query.is_on_fire"; - public static final String GROUND_SPEED = "query.ground_speed"; - public static final String YAW_SPEED = "query.yaw_speed"; + + private static final String QUERY_PREFIX = "query."; + + private static final String SHORT_PREFIX = "q."; + + public static final String ANIM_TIME = normalize("query.anim_time"); + + public static final String LIFE_TIME = normalize("query.life_time"); + + public static final String ACTOR_COUNT = normalize("query.actor_count"); + + public static final String TIME_OF_DAY = normalize("query.time_of_day"); + + public static final String MOON_PHASE = normalize("query.moon_phase"); + + public static final String DISTANCE_FROM_CAMERA = normalize("query.distance_from_camera"); + + public static final String IS_ON_GROUND = normalize("query.is_on_ground"); + + public static final String IS_IN_WATER = normalize("query.is_in_water"); + + public static final String IS_IN_WATER_OR_RAIN = normalize("query.is_in_water_or_rain"); + + public static final String HEALTH = normalize("query.health"); + + public static final String MAX_HEALTH = normalize("query.max_health"); + + public static final String IS_ON_FIRE = normalize("query.is_on_fire"); + + public static final String GROUND_SPEED = normalize("query.ground_speed"); + + public static final String YAW_SPEED = normalize("query.yaw_speed"); + + public static String normalize(String queryName) { + if (queryName.startsWith(QUERY_PREFIX)) { + return queryName; + } else if (queryName.startsWith(SHORT_PREFIX)) { + return QUERY_PREFIX + queryName.substring(SHORT_PREFIX.length()); + } else { + throw new IllegalArgumentException("Invalid query name: " + queryName); + } + } private MolangQueries() { throw new UnsupportedOperationException(); diff --git a/common/src/main/java/mod/azure/azurelib/core/object/DataTicket.java b/common/src/main/java/mod/azure/azurelib/core/object/DataTicket.java index 4f1a87f1c..b4233d86a 100644 --- a/common/src/main/java/mod/azure/azurelib/core/object/DataTicket.java +++ b/common/src/main/java/mod/azure/azurelib/core/object/DataTicket.java @@ -13,6 +13,7 @@ /** * Ticket object to define a typed data object */ +@Deprecated(forRemoval = true) public class DataTicket { private final String id; private final Class objectType; diff --git a/common/src/main/java/mod/azure/azurelib/core/object/PlayState.java b/common/src/main/java/mod/azure/azurelib/core/object/PlayState.java index 25f4f4736..6e4b39cd0 100644 --- a/common/src/main/java/mod/azure/azurelib/core/object/PlayState.java +++ b/common/src/main/java/mod/azure/azurelib/core/object/PlayState.java @@ -10,6 +10,7 @@ /** * State enum to define whether an {@link mod.azure.azurelib.core.animation.AnimationController} should continue or stop */ +@Deprecated(forRemoval = true) public enum PlayState { CONTINUE, STOP diff --git a/common/src/main/java/mod/azure/azurelib/loading/FileLoader.java b/common/src/main/java/mod/azure/azurelib/loading/FileLoader.java index 73671f50a..66f825ffe 100644 --- a/common/src/main/java/mod/azure/azurelib/loading/FileLoader.java +++ b/common/src/main/java/mod/azure/azurelib/loading/FileLoader.java @@ -13,6 +13,8 @@ import mod.azure.azurelib.cache.object.BakedGeoModel; import mod.azure.azurelib.loading.json.raw.Model; import mod.azure.azurelib.loading.object.BakedAnimations; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimation; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimations; import mod.azure.azurelib.util.JsonUtil; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; @@ -36,6 +38,16 @@ public static BakedAnimations loadAnimationsFile(ResourceLocation location, Reso return JsonUtil.GEO_GSON.fromJson(loadFile(location, manager), BakedAnimations.class); } + /** + * Load up and deserialize an animation json file to its respective {@link AzBakedAnimation} components + * + * @param location The resource path of the animations file + * @param manager The Minecraft {@code ResourceManager} responsible for maintaining in-memory resource access + */ + public static AzBakedAnimations loadAzAnimationsFile(ResourceLocation location, ResourceManager manager) { + return JsonUtil.GEO_GSON.fromJson(loadFile(location, manager), AzBakedAnimations.class); + } + /** * Load up and deserialize a geo model json file to its respective {@link BakedGeoModel} format * diff --git a/common/src/main/java/mod/azure/azurelib/loading/object/BakedModelFactory.java b/common/src/main/java/mod/azure/azurelib/loading/object/BakedModelFactory.java index 5e4a2fce2..d34d717f8 100644 --- a/common/src/main/java/mod/azure/azurelib/loading/object/BakedModelFactory.java +++ b/common/src/main/java/mod/azure/azurelib/loading/object/BakedModelFactory.java @@ -33,6 +33,7 @@ * Base interface for a factory of {@link BakedGeoModel} objects. * Handled by default by AzureLib, but custom implementations may be added by other mods for special needs */ +@Deprecated(forRemoval = true) public interface BakedModelFactory { final Map FACTORIES = new Object2ObjectOpenHashMap<>(1); final BakedModelFactory DEFAULT_FACTORY = new Builtin(); diff --git a/common/src/main/java/mod/azure/azurelib/mixins/AbstractContainerMenuMixin_AzItemIDFix.java b/common/src/main/java/mod/azure/azurelib/mixins/AbstractContainerMenuMixin_AzItemIDFix.java new file mode 100644 index 000000000..06012c8bb --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/mixins/AbstractContainerMenuMixin_AzItemIDFix.java @@ -0,0 +1,125 @@ +package mod.azure.azurelib.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.cache.AzIdentityRegistry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.UUID; + +/** + * A Mixin extension for the {@code AbstractContainerMenu} class that introduces support for AzureLib-specific {@code ItemStack} + * identity management (Az ID). This Mixin ensures the proper handling, synchronization, and comparison of + * AzureLib-registered item stacks with custom identifiers during container interactions. + */ +@Mixin(AbstractContainerMenu.class) +public abstract class AbstractContainerMenuMixin_AzItemIDFix { + + @Shadow public abstract void removed(Player player); + + @Unique + private static final int DEFAULT_AZ_ID = -1; + + /** + * Removes the AzureLib-specific ID (Az ID) from a copied `ItemStack` during a container click action. This is only + * performed if the original stack's item is registered with AzureLib's identity registry. + *

    + * Tooltip: Prevents the propagation of the Az ID when `ItemStack` objects are copied during container interactions, + * keeping custom IDs only for registered items. + */ + @Redirect( + method = "doClick", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/item/ItemStack;copyWithCount(I)Lnet/minecraft/world/item/ItemStack;", + ordinal = 1 + ) + ) + public ItemStack azurelib$syncAzureIDWithRemote(ItemStack itemStack, int count) { + var copyStack = itemStack.copyWithCount(count); + + if (AzIdentityRegistry.hasIdentity(itemStack.getItem()) && copyStack.hasTag() && copyStack.getTag().contains( + AzureLib.ITEM_UUID_TAG)) { + copyStack.getTag().putUUID(AzureLib.ITEM_UUID_TAG, UUID.randomUUID()); + } + + return copyStack; + } + + /** + * Validates AzureLib-specific IDs (Az ID) between two `ItemStack` objects during remote slot synchronization. This + * ensures that items with custom IDs remain synchronized correctly during slot operations. + *

    + * Tooltip: Compares two `ItemStack` objects, ensuring their Az IDs (if present) also match. + */ + @WrapOperation( + method = "synchronizeSlotToRemote", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/item/ItemStack;matches(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z" + ) + ) + public boolean azurelib$syncAzureIDWithRemote( + ItemStack itemStack, + ItemStack comparisonItemStack, + Operation original + ) { + return azurelib$compareStacksWithAzureID(itemStack, comparisonItemStack, original); + } + + /** + * Forces an Az ID comparison when listening for slot changes involving two `ItemStack` objects. + *

    + * Tooltip: Ensures that slot listeners detect changes in AzureLib-registered item stacks based not only on their + * normal properties but also their Az ID values, if applicable. + */ + @WrapOperation( + method = "triggerSlotListeners", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/item/ItemStack;matches(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z" + ) + ) + public boolean azurelib$detectSlotChangeWithAzureID( + ItemStack itemStack, + ItemStack comparisonItemStack, + Operation original + ) { + return azurelib$compareStacksWithAzureID(itemStack, comparisonItemStack, original); + } + + /** + * Compares two ItemStacks while considering AzureLib-specific IDs (Az ID) if the item is registered with AzureLib. + * + * @param itemStack The first ItemStack to compare. + * @param comparisonItemStack The second ItemStack to compare. + * @return True if the base comparison is true and the Az IDs (if present) match; false otherwise. + */ + @Unique + private boolean azurelib$compareStacksWithAzureID(ItemStack itemStack, ItemStack comparisonItemStack, Operation original) { + if (!AzIdentityRegistry.hasIdentity(itemStack.getItem())) { + return original.call(itemStack, comparisonItemStack); + } + + return original.call(itemStack, comparisonItemStack) && azurelib$checkAzIDMatch(itemStack.getTag(), comparisonItemStack.getTag()); + } + + /** + * Performs an exclusive-NOR (XNOR) operation on the Az ID tags of two ItemStacks to check for matching IDs. + * + * @param tag1 The CompoundTag of the first ItemStack. + * @param tag2 The CompoundTag of the second ItemStack. + * @return True if both tags either have matching Az IDs or are null, false otherwise. + */ + @Unique + private static boolean azurelib$checkAzIDMatch(CompoundTag tag1, CompoundTag tag2) { + return (tag1 == null ? DEFAULT_AZ_ID : tag1.getInt(AzureLib.ITEM_UUID_TAG)) == (tag2 == null ? DEFAULT_AZ_ID : tag2.getInt(AzureLib.ITEM_UUID_TAG)); + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/AccessorWarningScreen.java b/common/src/main/java/mod/azure/azurelib/mixins/AccessorWarningScreen.java deleted file mode 100644 index daa4f9adf..000000000 --- a/common/src/main/java/mod/azure/azurelib/mixins/AccessorWarningScreen.java +++ /dev/null @@ -1,15 +0,0 @@ -package mod.azure.azurelib.mixins; - -import net.minecraft.client.gui.components.MultiLineLabel; -import net.minecraft.client.gui.screens.multiplayer.WarningScreen; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(WarningScreen.class) -public interface AccessorWarningScreen { - @Accessor("message") - MultiLineLabel getMessageText(); - - @Accessor("message") - void setMessageText(MultiLineLabel messageText); -} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/BlockEntityMixin_AzBlockEntityAnimatorCache.java b/common/src/main/java/mod/azure/azurelib/mixins/BlockEntityMixin_AzBlockEntityAnimatorCache.java new file mode 100644 index 000000000..3de316e31 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/mixins/BlockEntityMixin_AzBlockEntityAnimatorCache.java @@ -0,0 +1,32 @@ +package mod.azure.azurelib.mixins; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +/** + * Mixin class that implements the {@code AzAnimatorAccessor} interface to enable managing and associating + * an {@link AzAnimator} instance with a {@link BlockEntity}. This allows for caching and retrieval of the animator + * associated with specific block entities. This mixin modifies the behavior of {@link BlockEntity} by adding an + * animator cache that can be used to store and retrieve {@link AzAnimator} instances for animation handling. + */ +@Mixin(BlockEntity.class) +public abstract class BlockEntityMixin_AzBlockEntityAnimatorCache implements AzAnimatorAccessor { + + @Unique + @Nullable + private AzAnimator animator; + + @Override + public void setAnimator(@Nullable AzAnimator animator) { + this.animator = animator; + } + + @Override + public @Nullable AzAnimator getAnimatorOrNull() { + return animator; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/EntityMixin_AzEntityAnimatorCache.java b/common/src/main/java/mod/azure/azurelib/mixins/EntityMixin_AzEntityAnimatorCache.java new file mode 100644 index 000000000..17a9c45b5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/mixins/EntityMixin_AzEntityAnimatorCache.java @@ -0,0 +1,32 @@ +package mod.azure.azurelib.mixins; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +/** + * A Mixin class designed to integrate an animation cache mechanism into the {@link Entity} class through the use of the + * {@link AzAnimatorAccessor} interface. This allows entities to store an instance of {@link AzAnimator} for managing + * animations. Implements methods to set and retrieve the {@link AzAnimator} instance, enabling animation control and + * association to the entity. + */ +@Mixin(Entity.class) +public abstract class EntityMixin_AzEntityAnimatorCache implements AzAnimatorAccessor { + + @Unique + @Nullable + private AzAnimator animator; + + @Override + public void setAnimator(@Nullable AzAnimator animator) { + this.animator = animator; + } + + @Override + public @Nullable AzAnimator getAnimatorOrNull() { + return animator; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/FabricMixinHumanoidArmorLayer.java b/common/src/main/java/mod/azure/azurelib/mixins/FabricMixinHumanoidArmorLayer.java index fee1e43f4..0bd9ea632 100644 --- a/common/src/main/java/mod/azure/azurelib/mixins/FabricMixinHumanoidArmorLayer.java +++ b/common/src/main/java/mod/azure/azurelib/mixins/FabricMixinHumanoidArmorLayer.java @@ -1,19 +1,28 @@ package mod.azure.azurelib.mixins; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; import com.mojang.blaze3d.vertex.PoseStack; import mod.azure.azurelib.animatable.GeoItem; import mod.azure.azurelib.animatable.client.RenderProvider; +import mod.azure.azurelib.renderer.GeoArmorRenderer; +import mod.azure.azurelib.rewrite.render.armor.AzArmorRendererRegistry; import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.PlayerModel; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; /** @@ -21,21 +30,89 @@ */ @Mixin(value = HumanoidArmorLayer.class, priority = 700) public abstract class FabricMixinHumanoidArmorLayer> { - @Unique - private LivingEntity gl_storedEntity; - @Unique - private EquipmentSlot gl_storedSlot; - @Unique - private ItemStack gl_storedItemStack; + @ModifyExpressionValue( + method = "renderArmorPiece", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;getItemBySlot(Lnet/minecraft/world/entity/EquipmentSlot;)Lnet/minecraft/world/item/ItemStack;" + ) + ) + private ItemStack azurelib$captureItemBySlot( + ItemStack original, + @Share("item_by_slot") LocalRef itemBySlotRef + ) { + itemBySlotRef.set(original); + return original; + } + + @Inject( + method = "renderArmorPiece", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/entity/layers/HumanoidArmorLayer;usesInnerModel(Lnet/minecraft/world/entity/EquipmentSlot;)Z" + ), cancellable = true + ) + public void azurelib$renderAzurelibModel( + PoseStack poseStack, + MultiBufferSource bufferSource, + T entity, + EquipmentSlot equipmentSlot, + int packedLight, + A baseModel, + CallbackInfo ci, + @Share("item_by_slot") LocalRef itemBySlotRef + ) { + var stack = itemBySlotRef.get(); + var renderProvider = RenderProvider.of(stack); + @SuppressWarnings("unchecked") + var humanoidModel = (HumanoidModel) baseModel; + var geckolibModel = renderProvider + .getGenericArmorModel(entity, stack, equipmentSlot, humanoidModel); + + if (geckolibModel != null && stack.getItem() instanceof GeoItem) { + if (geckolibModel instanceof GeoArmorRenderer geoArmorRenderer) { + geoArmorRenderer.prepForRender(entity, stack, equipmentSlot, baseModel); + } - @Inject(method = "renderArmorPiece", at = @At(value = "HEAD")) - public void armorModelHook(PoseStack poseStack, MultiBufferSource multiBufferSource, T livingEntity, EquipmentSlot equipmentSlot, int i, A humanoidModel, CallbackInfo ci) { - this.gl_storedEntity = livingEntity; - this.gl_storedSlot = equipmentSlot; - this.gl_storedItemStack = livingEntity.getItemBySlot(equipmentSlot); + baseModel.copyPropertiesTo((A) geckolibModel); + + geckolibModel.renderToBuffer(poseStack, null, packedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1); + ci.cancel(); + } + + var renderer = AzArmorRendererRegistry.getOrNull(stack.getItem()); + + if (renderer != null) { + var rendererPipeline = renderer.rendererPipeline(); + var armorModel = rendererPipeline.armorModel(); + @SuppressWarnings("unchecked") + var typedHumanoidModel = (HumanoidModel) armorModel; + + renderer.prepForRender(entity, stack, equipmentSlot, baseModel); + baseModel.copyPropertiesTo(typedHumanoidModel); + azurelib$testVisibility((A) typedHumanoidModel, entity, equipmentSlot); + armorModel.renderToBuffer(poseStack, null, packedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1); + ci.cancel(); + } } - @ModifyArg(method = "renderArmorPiece", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/layers/HumanoidArmorLayer;renderModel(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/item/ArmorItem;Lnet/minecraft/client/model/HumanoidModel;ZFFFLjava/lang/String;)V"), index = 4) - public A injectArmor(A humanoidModel) { - return this.gl_storedItemStack.getItem() instanceof GeoItem ? (A) RenderProvider.of(this.gl_storedItemStack).getGenericArmorModel(this.gl_storedEntity, this.gl_storedItemStack, this.gl_storedSlot, (HumanoidModel) humanoidModel) : humanoidModel; } + @Unique + private void azurelib$testVisibility(A model, @Nullable Entity entity, EquipmentSlot equipmentSlot) { + if (entity instanceof Player && model instanceof PlayerModel playerModel) { + switch (equipmentSlot) { + case HEAD -> { + playerModel.hat.visible = false; + playerModel.ear.visible = false; + } + case CHEST -> { + playerModel.jacket.visible = false; + playerModel.rightSleeve.visible = false; + playerModel.leftSleeve.visible = false; + } + case LEGS -> { + playerModel.leftPants.visible = false; + playerModel.rightPants.visible = false; + } + } + } + } } diff --git a/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemAnimatorCache.java b/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemAnimatorCache.java new file mode 100644 index 000000000..0d6d12e3d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemAnimatorCache.java @@ -0,0 +1,28 @@ +package mod.azure.azurelib.mixins; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import mod.azure.azurelib.rewrite.animation.cache.AzIdentifiableItemStackAnimatorCache; +import mod.azure.azurelib.rewrite.animation.impl.AzItemAnimator; +import mod.azure.azurelib.util.AzureLibUtil; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ItemStack.class) +public abstract class ItemStackMixin_AzItemAnimatorCache implements AzAnimatorAccessor { + + @Override + public void setAnimator(@Nullable AzAnimator animator) { + var itemStack = AzureLibUtil.self(this); + AzIdentifiableItemStackAnimatorCache.getInstance().add(itemStack, (AzItemAnimator) animator); + } + + @Override + public @Nullable AzAnimator getAnimatorOrNull() { + var self = AzureLibUtil.self(this); + var uuid = self.getOrCreateTag().getUUID(AzureLib.ITEM_UUID_TAG); + return AzIdentifiableItemStackAnimatorCache.getInstance().getOrNull(uuid); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemStackIdentityRegistry.java b/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemStackIdentityRegistry.java new file mode 100644 index 000000000..ace602c39 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/mixins/ItemStackMixin_AzItemStackIdentityRegistry.java @@ -0,0 +1,94 @@ +package mod.azure.azurelib.mixins; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.cache.AzIdentityRegistry; +import mod.azure.azurelib.util.AzureLibUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Optional; +import java.util.UUID; + +/** + * A mixin class for injecting additional functionality into the {@link ItemStack} + * constructor to handle identity registration via AzureLib. + * + * This mixin ensures that every {@link ItemStack} is assigned a unique identifier + * when its corresponding item has been registered in the {@link AzIdentityRegistry} and + * no existing UUID is present in the item's {@link CompoundTag}. + * + * The mixin method `az_addIdentityComponent` is invoked at the "TAIL" of the + * {@link ItemStack} constructor, which takes a {@link CompoundTag} as a parameter. + */ +@Mixin(ItemStack.class) +public class ItemStackMixin_AzItemStackIdentityRegistry { + + /** + * Injects into the constructor of the {@link ItemStack} that takes a {@link CompoundTag} parameter to initialize a unique AzureLib ID (Az ID) + * if the item is registered in the {@link AzIdentityRegistry}. + * + * @param compoundTag The {@link CompoundTag} associated with the {@link ItemStack}. + * @param ci The {@link CallbackInfo} for the mixin injection. + */ + + @Inject( + method = "(Lnet/minecraft/nbt/CompoundTag;)V", + at = @At("TAIL") + ) + public void azurelib$initializeAzIdFromCompoundTag(CompoundTag compoundTag, CallbackInfo ci) { + azureLib$initializeAzIdOnStack(this, compoundTag); + } + + /** + * Injects into the constructor of the {@link ItemStack} that takes an {@link ItemLike}, an integer item count, and an {@link Optional} for + * the compound tag. This ensures that a unique AzureLib ID (Az ID) is initialized if the item is registered in {@link AzIdentityRegistry}. + * + * @param ci The {@link CallbackInfo} for the mixin injection. + */ + @Inject(method = "Lnet/minecraft/world/item/ItemStack;(Lnet/minecraft/world/level/ItemLike;ILjava/util/Optional;)V", at = @At("TAIL")) + public void azurelib$initializeAzIdForConstructorWithOptional(CallbackInfo ci) { + azureLib$initializeAzIdOnStack(this, null); + } + + /** + * Injects into the constructor of the {@link ItemStack} that takes an {@link ItemLike} and an integer count. + * This ensures that a unique AzureLib ID (Az ID) is initialized if the item is registered in {@link AzIdentityRegistry}. + * + * @param ci The {@link CallbackInfo} for the mixin injection. + */ + @Inject(method = "Lnet/minecraft/world/item/ItemStack;(Lnet/minecraft/world/level/ItemLike;I)V", at = @At("TAIL")) + public void azurelib$initializeAzIdForConstructor(CallbackInfo ci) { + azureLib$initializeAzIdOnStack(this, null); + } + + /** + * Ensures that a unique AzureLib ID (Az ID) is initialized on the provided stack object if the item it represents + * is registered in the {@link AzIdentityRegistry} and does not already have a unique identifier. If necessary, + * assigns a new {@link CompoundTag} for the stack and generates a new UUID. + * + * @param stackObject The object representing the stack, expected to be an instance of {@link ItemStack}. + * @param tag The {@link CompoundTag} associated with the stack, used for storing or retrieving data. + */ + @Unique + private void azureLib$initializeAzIdOnStack(Object stackObject, CompoundTag tag) { + var self = AzureLibUtil.self(stackObject); + + if (!self.hasTag()) { + self.setTag(new CompoundTag()); + } + + var stackTag = self.getTag(); + + if (stackTag != null && AzIdentityRegistry.hasIdentity(self.getItem()) && !stackTag.hasUUID( + AzureLib.ITEM_UUID_TAG)) { + stackTag.putUUID(AzureLib.ITEM_UUID_TAG, UUID.randomUUID()); + } + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/mixins/MinecraftMixin.java b/common/src/main/java/mod/azure/azurelib/mixins/MinecraftMixin.java index 47edeb44b..87771d453 100644 --- a/common/src/main/java/mod/azure/azurelib/mixins/MinecraftMixin.java +++ b/common/src/main/java/mod/azure/azurelib/mixins/MinecraftMixin.java @@ -35,7 +35,7 @@ public MinecraftMixin(String p_i50401_1_) { } @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V")) - private void configuration_reloadClientConfigs(Screen screen, CallbackInfo ci) { + private void azurelib$reloadClientConfigs(Screen screen, CallbackInfo ci) { ConfigHolder.getSynchronizedConfigs().stream() .map(ConfigHolder::getConfig) .filter(Optional::isPresent) diff --git a/common/src/main/java/mod/azure/azurelib/mixins/MixinItemRenderer.java b/common/src/main/java/mod/azure/azurelib/mixins/MixinItemRenderer.java index 789ef5851..ac6c4fa69 100644 --- a/common/src/main/java/mod/azure/azurelib/mixins/MixinItemRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/mixins/MixinItemRenderer.java @@ -1,5 +1,6 @@ package mod.azure.azurelib.mixins; +import mod.azure.azurelib.rewrite.render.item.AzItemRendererRegistry; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -21,8 +22,22 @@ @Mixin(ItemRenderer.class) public class MixinItemRenderer { @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/BlockEntityWithoutLevelRenderer;renderByItem(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II)V"), cancellable = true) - public void itemModelHook(ItemStack itemStack, ItemDisplayContext transformType, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, CallbackInfo ci) { - if (itemStack.getItem() instanceof GeoItem) - RenderProvider.of(itemStack).getCustomRenderer().renderByItem(itemStack, transformType, poseStack, multiBufferSource, i, j); + public void azurelib$itemModelHook(ItemStack itemStack, ItemDisplayContext transformType, boolean bl, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, int j, BakedModel bakedModel, CallbackInfo ci) { + // TODO: Remove this along with Geo-code. + if (itemStack.getItem() instanceof GeoItem) { + RenderProvider.of(itemStack) + .getCustomRenderer() + .renderByItem(itemStack, transformType, poseStack, multiBufferSource, i, j); + } + + var item = itemStack.getItem(); + var renderer = AzItemRendererRegistry.getOrNull(item); + + if (renderer != null) { + switch (transformType) { + case GUI -> renderer.renderByGui(itemStack, poseStack, multiBufferSource, i); + default -> renderer.renderByItem(itemStack, poseStack, multiBufferSource, i); + } + } } } diff --git a/common/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java b/common/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java deleted file mode 100644 index fb5ade0ed..000000000 --- a/common/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java +++ /dev/null @@ -1,44 +0,0 @@ -package mod.azure.azurelib.mixins; - - -import com.mojang.blaze3d.vertex.PoseStack; -import mod.azure.azurelib.animatable.GeoItem; -import mod.azure.azurelib.animatable.client.RenderProvider; -import net.minecraft.client.model.HumanoidModel; -import net.minecraft.client.model.Model; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyArg; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * Render hook for injecting AzureLib's armor rendering functionalities - */ -@Mixin(value = HumanoidArmorLayer.class, priority = 700) -public class NeoMixinHumanoidArmorLayer> { - - @Unique - private LivingEntity gl_storedEntity; - @Unique - private EquipmentSlot gl_storedSlot; - @Unique - private ItemStack gl_storedItemStack; - - @Inject(method = "renderArmorPiece", at = @At(value = "HEAD")) - public void armorModelHook(PoseStack poseStack, MultiBufferSource source, T livingEntity, EquipmentSlot equipmentSlot, int i, A model, CallbackInfo ci) { - this.gl_storedEntity = livingEntity; - this.gl_storedSlot = equipmentSlot; - this.gl_storedItemStack = livingEntity.getItemBySlot(equipmentSlot); - } - - @ModifyArg(method = "renderArmorPiece", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/layers/HumanoidArmorLayer;renderModel(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/item/ArmorItem;Lnet/minecraft/client/model/Model;ZFFFLnet/minecraft/resources/ResourceLocation;)V", remap = false), index = 4) - public Model injectArmor(Model humanoidModel) { - return this.gl_storedItemStack.getItem() instanceof GeoItem ? (A) RenderProvider.of(this.gl_storedItemStack).getGenericArmorModel(this.gl_storedEntity, this.gl_storedItemStack, this.gl_storedSlot, (HumanoidModel) humanoidModel) : humanoidModel; } -} \ No newline at end of file diff --git a/common/src/main/java/mod/azure/azurelib/mixins/PlayerListMixin.java b/common/src/main/java/mod/azure/azurelib/mixins/PlayerListMixin.java index a29b579c9..f767c2f99 100644 --- a/common/src/main/java/mod/azure/azurelib/mixins/PlayerListMixin.java +++ b/common/src/main/java/mod/azure/azurelib/mixins/PlayerListMixin.java @@ -28,7 +28,7 @@ public abstract class PlayerListMixin { @Inject(method = "placeNewPlayer", at = @At("TAIL")) - private void configuration_sendServerConfigs(Connection connection, ServerPlayer player, CallbackInfo ci) { + private void azurelib$sendServerConfigs(Connection connection, ServerPlayer player, CallbackInfo ci) { Set set = ConfigHolder.getSynchronizedConfigs(); set.forEach(id -> Services.NETWORK.sendClientPacket(player, id)); } diff --git a/common/src/main/java/mod/azure/azurelib/mixins/TextureManagerMixin.java b/common/src/main/java/mod/azure/azurelib/mixins/TextureManagerMixin.java index 6904f5640..0242e1a0a 100644 --- a/common/src/main/java/mod/azure/azurelib/mixins/TextureManagerMixin.java +++ b/common/src/main/java/mod/azure/azurelib/mixins/TextureManagerMixin.java @@ -28,7 +28,7 @@ public abstract class TextureManagerMixin { @Shadow public abstract void register(ResourceLocation resourceLocation, AbstractTexture abstractTexture); @Inject(method = "getTexture(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/renderer/texture/AbstractTexture;", at = @At("HEAD")) - private void wrapAnimatableTexture(ResourceLocation path, CallbackInfoReturnable callback) { + private void azurelib$wrapAnimatableTexture(ResourceLocation path, CallbackInfoReturnable callback) { AbstractTexture existing = this.byPath.get(path); if (existing == null) { diff --git a/common/src/main/java/mod/azure/azurelib/model/DefaultedBlockGeoModel.java b/common/src/main/java/mod/azure/azurelib/model/DefaultedBlockGeoModel.java index a26ed806a..7f51b6d9f 100644 --- a/common/src/main/java/mod/azure/azurelib/model/DefaultedBlockGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/model/DefaultedBlockGeoModel.java @@ -14,6 +14,7 @@ * {@link DefaultedGeoModel} specific to {@link net.minecraft.world.level.block.Block Blocks}. * Using this class pre-sorts provided asset paths into the "block" subdirectory */ +@Deprecated(forRemoval = true) public class DefaultedBlockGeoModel extends DefaultedGeoModel { /** * Create a new instance of this model class.
    diff --git a/common/src/main/java/mod/azure/azurelib/model/DefaultedEntityGeoModel.java b/common/src/main/java/mod/azure/azurelib/model/DefaultedEntityGeoModel.java index 0b544435c..e5b0e0445 100644 --- a/common/src/main/java/mod/azure/azurelib/model/DefaultedEntityGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/model/DefaultedEntityGeoModel.java @@ -20,6 +20,7 @@ * Using this class pre-sorts provided asset paths into the "entity" subdirectory * Additionally it can automatically handle head-turning if the entity has a "head" bone */ +@Deprecated(forRemoval = true) public class DefaultedEntityGeoModel extends DefaultedGeoModel { private final boolean turnsHead; diff --git a/common/src/main/java/mod/azure/azurelib/model/DefaultedGeoModel.java b/common/src/main/java/mod/azure/azurelib/model/DefaultedGeoModel.java index 6b49f3eed..4924b9cb6 100644 --- a/common/src/main/java/mod/azure/azurelib/model/DefaultedGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/model/DefaultedGeoModel.java @@ -15,6 +15,7 @@ * This class allows for minimal boilerplate when implementing basic models, and saves on new classes.
    * Additionally, it encourages consistency and sorting of asset paths. */ +@Deprecated(forRemoval = true) public abstract class DefaultedGeoModel extends GeoModel { private ResourceLocation modelPath; private ResourceLocation texturePath; diff --git a/common/src/main/java/mod/azure/azurelib/model/DefaultedItemGeoModel.java b/common/src/main/java/mod/azure/azurelib/model/DefaultedItemGeoModel.java index 87b3069b7..5efc325fc 100644 --- a/common/src/main/java/mod/azure/azurelib/model/DefaultedItemGeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/model/DefaultedItemGeoModel.java @@ -14,6 +14,7 @@ * {@link DefaultedGeoModel} specific to {@link net.minecraft.world.item.Item Items}. * Using this class pre-sorts provided asset paths into the "item" subdirectory */ +@Deprecated(forRemoval = true) public class DefaultedItemGeoModel extends DefaultedGeoModel { /** * Create a new instance of this model class.
    diff --git a/common/src/main/java/mod/azure/azurelib/model/GeoModel.java b/common/src/main/java/mod/azure/azurelib/model/GeoModel.java index 97ec86927..af7c2aa7b 100644 --- a/common/src/main/java/mod/azure/azurelib/model/GeoModel.java +++ b/common/src/main/java/mod/azure/azurelib/model/GeoModel.java @@ -39,6 +39,7 @@ * Base class for all code-based model objects.
    * All models to registered to a {@link GeoRenderer} should be an instance of this or one of its subclasses. */ +@Deprecated(forRemoval = true) public abstract class GeoModel implements CoreGeoModel { private final AnimationProcessor processor = new AnimationProcessor<>(this); diff --git a/common/src/main/java/mod/azure/azurelib/model/data/EntityModelData.java b/common/src/main/java/mod/azure/azurelib/model/data/EntityModelData.java index a55600e3d..50e30294c 100644 --- a/common/src/main/java/mod/azure/azurelib/model/data/EntityModelData.java +++ b/common/src/main/java/mod/azure/azurelib/model/data/EntityModelData.java @@ -10,4 +10,5 @@ /** * Container class for various pieces of data relating to a model's current state. */ +@Deprecated(forRemoval = true) public record EntityModelData(boolean isSitting, boolean isChild, float netHeadYaw, float headPitch) {} diff --git a/common/src/main/java/mod/azure/azurelib/network/SerializableDataTicket.java b/common/src/main/java/mod/azure/azurelib/network/SerializableDataTicket.java index 50e6465d0..c3b159fa7 100644 --- a/common/src/main/java/mod/azure/azurelib/network/SerializableDataTicket.java +++ b/common/src/main/java/mod/azure/azurelib/network/SerializableDataTicket.java @@ -15,6 +15,7 @@ * Network-compatible {@link mod.azure.azurelib.core.object.DataTicket} implementation. * Used for sending data from server -> client in an easy manner */ +@Deprecated(forRemoval = true) public abstract class SerializableDataTicket extends DataTicket { protected SerializableDataTicket(String id, Class objectType) { super(id, objectType); diff --git a/common/src/main/java/mod/azure/azurelib/network/api/IPacket.java b/common/src/main/java/mod/azure/azurelib/network/api/IPacket.java index ced8116e5..05e04b940 100644 --- a/common/src/main/java/mod/azure/azurelib/network/api/IPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/api/IPacket.java @@ -14,6 +14,7 @@ import net.minecraft.resources.ResourceLocation; +@Deprecated(forRemoval = true) public interface IPacket { ResourceLocation getPacketId(); diff --git a/common/src/main/java/mod/azure/azurelib/network/api/IPacketDecoder.java b/common/src/main/java/mod/azure/azurelib/network/api/IPacketDecoder.java index cd89125d5..20a8ebe00 100644 --- a/common/src/main/java/mod/azure/azurelib/network/api/IPacketDecoder.java +++ b/common/src/main/java/mod/azure/azurelib/network/api/IPacketDecoder.java @@ -15,6 +15,7 @@ import net.minecraft.network.FriendlyByteBuf; @FunctionalInterface +@Deprecated(forRemoval = true) public interface IPacketDecoder { T decode(FriendlyByteBuf buffer); diff --git a/common/src/main/java/mod/azure/azurelib/network/api/IPacketEncoder.java b/common/src/main/java/mod/azure/azurelib/network/api/IPacketEncoder.java index b2b0296fc..f87f1fccd 100644 --- a/common/src/main/java/mod/azure/azurelib/network/api/IPacketEncoder.java +++ b/common/src/main/java/mod/azure/azurelib/network/api/IPacketEncoder.java @@ -15,6 +15,7 @@ import net.minecraft.network.FriendlyByteBuf; @FunctionalInterface +@Deprecated(forRemoval = true) public interface IPacketEncoder { void encode(T data, FriendlyByteBuf buffer); diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/AnimDataSyncPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/AnimDataSyncPacket.java index 0f1070d88..11a1fbea2 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/AnimDataSyncPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/AnimDataSyncPacket.java @@ -16,6 +16,7 @@ * Packet for syncing user-definable animation data for * {@link SingletonGeoAnimatable} instances */ +@Deprecated(forRemoval = true) public class AnimDataSyncPacket extends AbstractPacket { private final String syncableId; private final long instanceId; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/AnimTriggerPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/AnimTriggerPacket.java index 4c62c67f6..31eabc6fe 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/AnimTriggerPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/AnimTriggerPacket.java @@ -12,6 +12,7 @@ * Packet for syncing user-definable animations that can be triggered from the * server */ +@Deprecated(forRemoval = true) public class AnimTriggerPacket extends AbstractPacket { private final String syncableId; private final long instanceId; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/AzBlockEntityDispatchCommandPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/AzBlockEntityDispatchCommandPacket.java new file mode 100644 index 000000000..66f798cba --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/network/packet/AzBlockEntityDispatchCommandPacket.java @@ -0,0 +1,58 @@ +package mod.azure.azurelib.network.packet; + +import mod.azure.azurelib.network.AbstractPacket; +import mod.azure.azurelib.platform.services.AzureLibNetwork; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.AzCommand; +import mod.azure.azurelib.util.ClientUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +public class AzBlockEntityDispatchCommandPacket extends AbstractPacket { + + private final BlockPos blockPos; + + private final AzCommand dispatchCommand; + + public AzBlockEntityDispatchCommandPacket( + BlockPos blockPos, + AzCommand dispatchCommand + ) { + this.blockPos = blockPos; + this.dispatchCommand = dispatchCommand; + } + + @Override + public void encode(FriendlyByteBuf buf) { + buf.writeBlockPos(this.blockPos); + AzCommand.ENCODER.accept(buf, this.dispatchCommand); + } + + @Override + public ResourceLocation getPacketID() { + return AzureLibNetwork.AZ_BLOCKENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID; + } + + public static AzBlockEntityDispatchCommandPacket receive(FriendlyByteBuf buf) { + BlockPos blockPos = buf.readBlockPos(); // Decode block position + AzCommand dispatchCommand = AzCommand.DECODER.apply(buf); // Decode AzCommand + return new AzBlockEntityDispatchCommandPacket(blockPos, dispatchCommand); // Create a new packet instance + } + + @Override + public void handle() { + var blockEntity = ClientUtils.getLevel().getBlockEntity(blockPos); + + if (blockEntity == null) { + return; + } + + var animator = AzAnimatorAccessor.getOrNull(blockEntity); + + if (animator != null) { + dispatchCommand.actions().forEach(action -> action.handle(AzDispatchSide.SERVER, animator)); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/AzEntityDispatchCommandPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/AzEntityDispatchCommandPacket.java new file mode 100644 index 000000000..7509249d1 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/network/packet/AzEntityDispatchCommandPacket.java @@ -0,0 +1,55 @@ +package mod.azure.azurelib.network.packet; + +import mod.azure.azurelib.network.AbstractPacket; +import mod.azure.azurelib.platform.services.AzureLibNetwork; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.AzCommand; +import mod.azure.azurelib.util.ClientUtils; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +public class AzEntityDispatchCommandPacket extends AbstractPacket { + + private final int entityId; + private final AzCommand dispatchCommand; + + public AzEntityDispatchCommandPacket( + int entityId, + AzCommand dispatchCommand + ) { + this.entityId = entityId; + this.dispatchCommand = dispatchCommand; + } + + @Override + public void encode(FriendlyByteBuf buf) { + buf.writeInt(this.entityId); + AzCommand.ENCODER.accept(buf, this.dispatchCommand); + } + + @Override + public ResourceLocation getPacketID() { + return AzureLibNetwork.AZ_ENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID; + } + + public static AzEntityDispatchCommandPacket receive(FriendlyByteBuf buf) { + int entityId = buf.readInt(); // Decode integer entity ID + AzCommand dispatchCommand = AzCommand.DECODER.apply(buf); // Decode AzCommand + return new AzEntityDispatchCommandPacket(entityId, dispatchCommand); // Create and return the packet instance + } + + public void handle() { + var entity = ClientUtils.getLevel().getEntity(this.entityId); + + if (entity == null) { + return; + } + + var animator = AzAnimatorAccessor.getOrNull(entity); + + if (animator != null) { + dispatchCommand.actions().forEach(action -> action.handle(AzDispatchSide.SERVER, animator)); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/AzItemStackDispatchCommandPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/AzItemStackDispatchCommandPacket.java new file mode 100644 index 000000000..720b148c4 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/network/packet/AzItemStackDispatchCommandPacket.java @@ -0,0 +1,50 @@ +package mod.azure.azurelib.network.packet; + +import mod.azure.azurelib.network.AbstractPacket; +import mod.azure.azurelib.platform.services.AzureLibNetwork; +import mod.azure.azurelib.rewrite.animation.cache.AzIdentifiableItemStackAnimatorCache; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.AzCommand; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.UUID; + +public class AzItemStackDispatchCommandPacket extends AbstractPacket { + + private final UUID itemStackId; + private final AzCommand dispatchCommand; + + public AzItemStackDispatchCommandPacket( + UUID itemStackId, + AzCommand dispatchCommand + ) { + this.itemStackId = itemStackId; + this.dispatchCommand = dispatchCommand; + } + + @Override + public void encode(FriendlyByteBuf buf) { + buf.writeUUID(this.itemStackId); // Encode the UUID + AzCommand.ENCODER.accept(buf, this.dispatchCommand); // Encode AzCommand + } + + @Override + public ResourceLocation getPacketID() { + return AzureLibNetwork.AZ_ITEM_STACK_DISPATCH_COMMAND_SYNC_PACKET_ID; + } + + public static AzItemStackDispatchCommandPacket receive(FriendlyByteBuf buf) { + UUID itemStackId = buf.readUUID(); // Decode UUID + AzCommand dispatchCommand = AzCommand.DECODER.apply(buf); // Decode AzCommand + return new AzItemStackDispatchCommandPacket(itemStackId, dispatchCommand); // Create and return the packet instance + } + + public void handle() { + var animator = AzIdentifiableItemStackAnimatorCache.getInstance().getOrNull(itemStackId); + + if (animator != null) { + dispatchCommand.actions().forEach(action -> action.handle(AzDispatchSide.SERVER, animator)); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimDataSyncPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimDataSyncPacket.java index 4d4d69f5d..effa38cb0 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimDataSyncPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimDataSyncPacket.java @@ -17,6 +17,7 @@ * Packet for syncing user-definable animation data for {@link BlockEntity * BlockEntities} */ +@Deprecated(forRemoval = true) public class BlockEntityAnimDataSyncPacket extends AbstractPacket { private final BlockPos blockPos; private final SerializableDataTicket dataTicket; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimTriggerPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimTriggerPacket.java index ad3992d7e..b6caa4fa8 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimTriggerPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/BlockEntityAnimTriggerPacket.java @@ -23,6 +23,7 @@ * server for {@link net.minecraft.world.level.block.entity.BlockEntity * BlockEntities} */ +@Deprecated(forRemoval = true) public class BlockEntityAnimTriggerPacket extends AbstractPacket { private final BlockPos blockPos; private final String controllerName; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimDataSyncPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimDataSyncPacket.java index f523bf6cc..dcbdba4ad 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimDataSyncPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimDataSyncPacket.java @@ -14,6 +14,7 @@ * Packet for syncing user-definable animation data for * {@link net.minecraft.world.entity.Entity Entities} */ +@Deprecated(forRemoval = true) public class EntityAnimDataSyncPacket extends AbstractPacket { private final int entityId; private final SerializableDataTicket dataTicket; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimTriggerPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimTriggerPacket.java index 7c80cc8e4..38fdcf7ba 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimTriggerPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/EntityAnimTriggerPacket.java @@ -23,6 +23,7 @@ * Packet for syncing user-definable animations that can be triggered from the * server for {@link net.minecraft.world.entity.Entity Entities} */ +@Deprecated(forRemoval = true) public class EntityAnimTriggerPacket extends AbstractPacket { private final int entityId; private final boolean isReplacedEntity; diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacket.java b/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacket.java index add06ae69..133bbd26c 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacket.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacket.java @@ -5,6 +5,7 @@ import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.world.entity.Entity; +@Deprecated(forRemoval = true) public class EntityPacket { public static Packet createPacket(Entity entity) { return Services.NETWORK.createPacket(entity); diff --git a/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacketOnClient.java b/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacketOnClient.java index f6d0aca0f..fedab15f2 100644 --- a/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacketOnClient.java +++ b/common/src/main/java/mod/azure/azurelib/network/packet/EntityPacketOnClient.java @@ -9,6 +9,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +@Deprecated(forRemoval = true) public class EntityPacketOnClient { public static void onPacket(Minecraft context, FriendlyByteBuf byteBuf) { EntityType type = BuiltInRegistries.ENTITY_TYPE.byId(byteBuf.readVarInt()); diff --git a/common/src/main/java/mod/azure/azurelib/platform/services/AzureLibNetwork.java b/common/src/main/java/mod/azure/azurelib/platform/services/AzureLibNetwork.java index 981da9281..725978cce 100644 --- a/common/src/main/java/mod/azure/azurelib/platform/services/AzureLibNetwork.java +++ b/common/src/main/java/mod/azure/azurelib/platform/services/AzureLibNetwork.java @@ -20,23 +20,46 @@ class LockHolder { // Package private class } public interface AzureLibNetwork { + @Deprecated(forRemoval = true) ResourceLocation ANIM_DATA_SYNC_PACKET_ID = AzureLib.modResource("anim_data_sync"); + @Deprecated(forRemoval = true) ResourceLocation ANIM_TRIGGER_SYNC_PACKET_ID = AzureLib.modResource("anim_trigger_sync"); + @Deprecated(forRemoval = true) ResourceLocation ENTITY_ANIM_DATA_SYNC_PACKET_ID = AzureLib.modResource("entity_anim_data_sync"); + @Deprecated(forRemoval = true) ResourceLocation ENTITY_ANIM_TRIGGER_SYNC_PACKET_ID = AzureLib.modResource("entity_anim_trigger_sync"); + @Deprecated(forRemoval = true) ResourceLocation BLOCK_ENTITY_ANIM_DATA_SYNC_PACKET_ID = AzureLib.modResource("block_entity_anim_data_sync"); + @Deprecated(forRemoval = true) ResourceLocation BLOCK_ENTITY_ANIM_TRIGGER_SYNC_PACKET_ID = AzureLib.modResource("block_entity_anim_trigger_sync"); + @Deprecated(forRemoval = true) ResourceLocation CUSTOM_ENTITY_ID = AzureLib.modResource("spawn_entity"); + @Deprecated(forRemoval = true) Map SYNCED_ANIMATABLES = new Object2ObjectOpenHashMap<>(); + ResourceLocation AZ_BLOCKENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID = AzureLib.modResource( + "az_blockentity_dispatch_command_sync" + ); + + ResourceLocation AZ_ENTITY_ANIM_TRIGGER_SYNC_PACKET_ID = AzureLib.modResource("az_entity_anim_trigger_sync"); + + ResourceLocation AZ_ENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID = AzureLib.modResource( + "az_entity_dispatch_command_sync" + ); + + ResourceLocation AZ_ITEM_STACK_DISPATCH_COMMAND_SYNC_PACKET_ID = AzureLib.modResource( + "az_item_stack_dispatch_command_sync" + ); + /** * Registers a synced {@link GeoAnimatable} object for networking support.
    * It is recommended that you don't call this directly, instead implementing and calling {@link mod.azure.azurelib.animatable.SingletonGeoAnimatable#registerSyncedAnimatable} */ + @Deprecated(forRemoval = true) default void registerSyncedAnimatable(GeoAnimatable animatable) { synchronized (this) { GeoAnimatable existing = SYNCED_ANIMATABLES.put(animatable.getClass().toString(), animatable); @@ -46,11 +69,13 @@ default void registerSyncedAnimatable(GeoAnimatable animatable) { } } + @Deprecated(forRemoval = true) Packet createPacket(Entity entity); /** * Used to register packets that the server sends **/ + @Deprecated(forRemoval = true) void registerClientReceiverPackets(); void sendToTrackingEntityAndSelf(AbstractPacket packet, Entity entityToTrack); @@ -71,6 +96,7 @@ interface IPacketCallback { * @param className the className */ @Nullable + @Deprecated(forRemoval = true) static GeoAnimatable getSyncedAnimatable(String className) { GeoAnimatable animatable = SYNCED_ANIMATABLES.get(className); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/DyeableGeoArmorRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/DyeableGeoArmorRenderer.java index 7bf7e55c5..976a2ac43 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/DyeableGeoArmorRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/DyeableGeoArmorRenderer.java @@ -26,6 +26,7 @@ /** * A dyeable armour renderer for AzureLib armor models. */ +@Deprecated(forRemoval = true) public abstract class DyeableGeoArmorRenderer extends GeoArmorRenderer { protected final Set dyeableBones = new ObjectArraySet<>(); protected BakedGeoModel lastModel = null; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/DynamicGeoEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/DynamicGeoEntityRenderer.java index e401e5287..f4c26bc52 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/DynamicGeoEntityRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/DynamicGeoEntityRenderer.java @@ -38,6 +38,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, * and consider whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoEntityRenderer extends GeoEntityRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = new Object2ObjectOpenHashMap<>(); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRenderer.java index 228a0d1ca..4a011471c 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRenderer.java @@ -48,6 +48,7 @@ * @see GeoItem * @param */ +@Deprecated(forRemoval = true) public class GeoArmorRenderer extends HumanoidModel implements GeoRenderer { protected final GeoRenderLayersContainer renderLayers = new GeoRenderLayersContainer<>(this); protected final GeoModel model; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRendererConstants.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRendererConstants.java index 1b11a15f0..dff2461fb 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRendererConstants.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoArmorRendererConstants.java @@ -6,6 +6,7 @@ /** * @author Boston Vanseghi */ +@Deprecated(forRemoval = true) public class GeoArmorRendererConstants { public static final String BONE_ARMOR_BODY_NAME = "armorBody"; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoBlockRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoBlockRenderer.java index 639812ae7..e9889effa 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoBlockRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoBlockRenderer.java @@ -43,6 +43,7 @@ * Base {@link GeoRenderer} class for rendering {@link BlockEntity Blocks} specifically.
    * All blocks added to be rendered by AzureLib should use an instance of this class. */ +@Deprecated(forRemoval = true) public class GeoBlockRenderer implements GeoRenderer, BlockEntityRenderer { protected final GeoModel model; protected final GeoRenderLayersContainer renderLayers = new GeoRenderLayersContainer<>(this); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoEntityRenderer.java index c5364d92d..6dc0e42cc 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoEntityRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoEntityRenderer.java @@ -50,6 +50,7 @@ * All entities added to be rendered by AzureLib should use an instance of this class.
    * This also includes {@link net.minecraft.world.entity.projectile.Projectile Projectiles} */ +@Deprecated(forRemoval = true) public class GeoEntityRenderer extends EntityRenderer implements GeoRenderer { protected final List> renderLayers = new ObjectArrayList<>(); protected final GeoModel model; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoItemRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoItemRenderer.java index fbf6d944f..46b367217 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoItemRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoItemRenderer.java @@ -46,6 +46,7 @@ * Base {@link GeoRenderer} class for rendering {@link Item Items} specifically.
    * All items added to be rendered by AzureLib should use an instance of this class. */ +@Deprecated(forRemoval = true) public class GeoItemRenderer extends BlockEntityWithoutLevelRenderer implements GeoRenderer { protected final GeoRenderLayersContainer renderLayers = new GeoRenderLayersContainer<>(this); protected final GeoModel model; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoObjectRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoObjectRenderer.java index c3a63833e..7405c6558 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoObjectRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoObjectRenderer.java @@ -38,6 +38,7 @@ *
    * It is strongly recommended you override {@link GeoRenderer#getInstanceId} if using this renderer */ +@Deprecated(forRemoval = true) public class GeoObjectRenderer implements GeoRenderer { protected final GeoRenderLayersContainer renderLayers = new GeoRenderLayersContainer<>(this); protected final GeoModel model; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoRenderer.java index 93675ac24..e5396ea56 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoRenderer.java @@ -33,6 +33,7 @@ /** * Base interface for all AzureLib renderers.
    */ +@Deprecated(forRemoval = true) public interface GeoRenderer { /** * Gets the model instance for this renderer diff --git a/common/src/main/java/mod/azure/azurelib/renderer/GeoReplacedEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/GeoReplacedEntityRenderer.java index f1e964f0d..1b3022aa9 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/GeoReplacedEntityRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/GeoReplacedEntityRenderer.java @@ -49,6 +49,7 @@ /** * An alternate to {@link GeoEntityRenderer}, used specifically for replacing existing non-AzureLib entities with AzureLib rendering dynamically, without the need for an additional entity class */ +@Deprecated(forRemoval = true) public class GeoReplacedEntityRenderer extends EntityRenderer implements GeoRenderer { protected final GeoModel model; protected final List> renderLayers = new ObjectArrayList<>(); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoArmorRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoArmorRenderer.java index 033d8a48f..09f02c8fb 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoArmorRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoArmorRenderer.java @@ -33,6 +33,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, and consider * whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoArmorRenderer extends GeoArmorRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = diff --git a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoBlockRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoBlockRenderer.java index 3caab70e3..ea7ba3eba 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoBlockRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoBlockRenderer.java @@ -32,6 +32,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, and consider * whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoBlockRenderer extends GeoBlockRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = diff --git a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoItemRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoItemRenderer.java index c1536f279..71f9de7cf 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoItemRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoItemRenderer.java @@ -32,6 +32,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, and consider * whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoItemRenderer extends GeoItemRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = diff --git a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoObjectRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoObjectRenderer.java index 530fd8eb6..1db0ad195 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoObjectRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoObjectRenderer.java @@ -31,6 +31,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, and consider * whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoObjectRenderer extends GeoObjectRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = diff --git a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoReplacedEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoReplacedEntityRenderer.java index c46c2b2db..83de8e986 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoReplacedEntityRenderer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/dynamic/DynamicGeoReplacedEntityRenderer.java @@ -33,6 +33,7 @@ * Because of the extra performance cost of this renderer, it is advised to avoid using it unnecessarily, and consider * whether the benefits are worth the cost for your needs. */ +@Deprecated(forRemoval = true) public abstract class DynamicGeoReplacedEntityRenderer extends GeoReplacedEntityRenderer { protected static final Map TEXTURE_DIMENSIONS_CACHE = diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/AutoGlowingGeoLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/AutoGlowingGeoLayer.java index a2d7ca3ab..6303a0e04 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/AutoGlowingGeoLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/AutoGlowingGeoLayer.java @@ -22,6 +22,7 @@ /** * {@link GeoRenderLayer} for rendering the auto-generated glowlayer functionality implemented by AzureLib using the _glowing appendixed texture files. */ +@Deprecated(forRemoval = true) public class AutoGlowingGeoLayer extends GeoRenderLayer { public AutoGlowingGeoLayer(GeoRenderer renderer) { super(renderer); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/BlockAndItemGeoLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/BlockAndItemGeoLayer.java index 55ec94608..8b6bb0850 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/BlockAndItemGeoLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/BlockAndItemGeoLayer.java @@ -30,6 +30,7 @@ /** * {@link GeoRenderLayer} for rendering {@link net.minecraft.world.level.block.state.BlockState BlockStates} or {@link net.minecraft.world.item.ItemStack ItemStacks} on a given {@link GeoAnimatable} */ +@Deprecated(forRemoval = true) public class BlockAndItemGeoLayer extends GeoRenderLayer { protected final BiFunction stackForBone; protected final BiFunction blockForBone; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/BoneFilterGeoLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/BoneFilterGeoLayer.java index e55bf3b64..f5f3b2e38 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/BoneFilterGeoLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/BoneFilterGeoLayer.java @@ -24,6 +24,7 @@ *
    * NOTE: Despite this layer existing, it is much more efficient to use {@link FastBoneFilterGeoLayer} instead */ +@Deprecated(forRemoval = true) public class BoneFilterGeoLayer extends GeoRenderLayer { protected final TriConsumer checkAndApply; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/FastBoneFilterGeoLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/FastBoneFilterGeoLayer.java index b4adead5b..b01e37b23 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/FastBoneFilterGeoLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/FastBoneFilterGeoLayer.java @@ -26,6 +26,7 @@ * This version requires you provide the list of bones to filter up-front, * so that the bone hierarchy doesn't need to be traversed. */ +@Deprecated(forRemoval = true) public class FastBoneFilterGeoLayer extends BoneFilterGeoLayer { protected final Supplier> boneSupplier; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayer.java index 85f66b29a..042a2a816 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayer.java @@ -23,6 +23,7 @@ * Render layer base class for rendering additional layers of effects or textures over an existing model at runtime.
    * Contains the base boilerplate and helper code for various render layer features */ +@Deprecated(forRemoval = true) public abstract class GeoRenderLayer { protected final GeoRenderer renderer; diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayersContainer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayersContainer.java index a4888c7ca..23c247507 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayersContainer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/GeoRenderLayersContainer.java @@ -17,6 +17,7 @@ * Base interface for a container for {@link GeoRenderLayer GeoRenderLayers}
    * Each renderer should contain an instance of this, for holding its layers and handling events. */ +@Deprecated(forRemoval = true) public class GeoRenderLayersContainer { private final GeoRenderer renderer; private final List> layers = new ObjectArrayList<>(); diff --git a/common/src/main/java/mod/azure/azurelib/renderer/layer/ItemArmorGeoLayer.java b/common/src/main/java/mod/azure/azurelib/renderer/layer/ItemArmorGeoLayer.java index d5517d844..213c09fcf 100644 --- a/common/src/main/java/mod/azure/azurelib/renderer/layer/ItemArmorGeoLayer.java +++ b/common/src/main/java/mod/azure/azurelib/renderer/layer/ItemArmorGeoLayer.java @@ -9,7 +9,6 @@ import java.util.Map; -import mod.azure.azurelib.platform.Services; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -60,6 +59,7 @@ * Supports both {@link mod.azure.azurelib.animatable.GeoItem AzureLib} and {@link net.minecraft.world.item.ArmorItem Vanilla} armor models.
    * Unlike a traditional armor renderer, this renderer renders per-bone, giving much more flexible armor rendering. */ +@Deprecated(forRemoval = true) public class ItemArmorGeoLayer extends GeoRenderLayer { protected static final Map ARMOR_PATH_CACHE = new Object2ObjectOpenHashMap<>(); protected static final HumanoidModel INNER_ARMOR_MODEL = new HumanoidModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.PLAYER_INNER_ARMOR)); diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/AzResourceCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/AzResourceCache.java new file mode 100644 index 000000000..731f066cc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/AzResourceCache.java @@ -0,0 +1,59 @@ +package mod.azure.azurelib.rewrite; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * AzResourceCache is an abstract base class designed for managing and loading mod resources asynchronously. This class + * provides helper functions for loading and processing resource files of a specific type and storing them in a cache. + */ +public abstract class AzResourceCache { + + private static final Set EXCLUDED_NAMESPACES = ObjectOpenHashSet.of( + "moreplayermodels", + "customnpcs", + "creeperoverhaul", + "geckolib", + "gunsrpg", + "born_in_chaos_v1", + "neoforge" + ); + + protected final CompletableFuture loadResources( + Executor executor, + ResourceManager resourceManager, + String type, + Function loader, + BiConsumer map + ) { + return CompletableFuture.supplyAsync( + () -> resourceManager.listResources(type, fileName -> fileName.toString().endsWith(".json")), + executor + ) + .thenApplyAsync(resources -> { + var tasks = new Object2ObjectOpenHashMap>(); + + for (var resource : resources.keySet()) { + tasks.put(resource, CompletableFuture.supplyAsync(() -> loader.apply(resource), executor)); + } + + return tasks; + }, executor) + .thenAcceptAsync(tasks -> { + for (var entry : tasks.entrySet()) { + if (!EXCLUDED_NAMESPACES.contains(entry.getKey().getNamespace().toLowerCase(Locale.ROOT))) { + map.accept(entry.getKey(), entry.getValue().join()); + } + } + }, executor); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationContext.java new file mode 100644 index 000000000..1d22bacb5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationContext.java @@ -0,0 +1,71 @@ +package mod.azure.azurelib.rewrite.animation; + +import mod.azure.azurelib.rewrite.animation.cache.AzBoneCache; + +/** + * The {@code AzAnimationContext} class provides a context for managing animation-related state and behaviors for + * animatable objects of type {@code T}. It serves as the central point of interaction between the animation system, + * configuration settings, and the animated object itself. + * + * @param The type of the animatable object that this context operates on. + */ +public class AzAnimationContext { + + private final AzBoneCache boneCache; + + private final AzAnimatorConfig config; + + private final AzAnimationTimer timer; + + // Package-private for mutability purposes. + T animatable; + + public AzAnimationContext( + AzBoneCache boneCache, + AzAnimatorConfig config, + AzAnimationTimer timer + ) { + this.boneCache = boneCache; + this.config = config; + this.timer = timer; + } + + /** + * Returns the current animatable instance associated with this animation context. + * + * @return The animatable instance of type {@code T}. + */ + public T animatable() { + return animatable; + } + + /** + * Returns the bone cache associated with the animation context. The bone cache is responsible for storing and + * managing bone-related data and transformations used during animations. + * + * @return The {@link AzBoneCache} instance managing bone data and transformations for animations. + */ + public AzBoneCache boneCache() { + return boneCache; + } + + /** + * Returns the animation configuration associated with this animation context. The configuration defines behavior + * such as bone reset speed, handling of missing bones, and whether animations should play while the game is paused. + * + * @return The {@link AzAnimatorConfig} instance containing the animation settings for this context. + */ + public AzAnimatorConfig config() { + return config; + } + + /** + * Returns the animation timer associated with the animation context. The timer is used to track animation progress + * over time and manage timing adjustments based on game state, such as pausing and resuming. + * + * @return The {@link AzAnimationTimer} instance responsible for animation timing. + */ + public AzAnimationTimer timer() { + return timer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationTimer.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationTimer.java new file mode 100644 index 000000000..7a60686b7 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimationTimer.java @@ -0,0 +1,91 @@ +package mod.azure.azurelib.rewrite.animation; + +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.Minecraft; + +/** + * AzAnimationTimer is responsible for managing animation progression based on game events and time deltas. It keeps + * track of the current animation time and ensures smooth transitions during various game states, such as pausing and + * resuming.
    + * The class relies on the provided {@link AzAnimatorConfig} for configurable behaviors, such as whether animations + * continue during game pauses or specific error handling preferences. + */ +public class AzAnimationTimer { + + private final AzAnimatorConfig config; + + // Remnants from GeoModel. + private double animTime; + + private double lastGameTickTime; + + private boolean wasPausedLastFrame; + + /** + * Constructs a new instance of AzAnimationTimer with the given configuration. + * + * @param config The configuration settings used to configure the animation timer. It includes parameters such as + * bone reset time, behavior during game pause, and whether to crash if a bone is missing. + */ + public AzAnimationTimer(AzAnimatorConfig config) { + this.config = config; + } + + /** + * Updates the animation timer by calculating the time delta since the last frame and applying it to the internal + * animation time. This method handles game pause states and adjusts the time calculations accordingly.
    + * Behavior: If the game is paused: + *

      + *
    • Sets an internal flag to indicate the paused state.
    • + *
    • Returns immediately if animations should not play while paused.
    • + *
    + * If transitioning from paused to unpaused: + *
      + *
    • Resets the frame delta to prevent large time skips in animations.
    • + *
    + * Accumulates the computed time delta into the animation time tracker to control the progression of animations. + */ + public void tick() { + var minecraft = Minecraft.getInstance(); + var currentRenderTick = RenderUtils.getCurrentTick(); + + if (minecraft.isPaused()) { + if (!wasPausedLastFrame) { + // If this is the first frame of the game pause time, we need to set a flag. + this.wasPausedLastFrame = true; + } + + if (!config.shouldPlayAnimationsWhileGamePaused()) { + // If we cannot play animations while the game is paused, then return early. + return; + } + } + + // Compute the delta render tick for this frame. This calculation by default does not account for the game + // pause state, which means that the difference here could be massive by the time the player unpauses. + var deltaRenderTick = currentRenderTick - lastGameTickTime; + + if (wasPausedLastFrame && !minecraft.isPaused()) { + // If this is the first frame of the game play time, we need to set a flag and adjust the deltaRenderTick. + this.wasPausedLastFrame = false; + // To account for the deltaRenderTick being massive on exiting the game pause state, we simply set + // it to 0. This will result in no difference being added to animTime, allowing animations to + // continue right where it left off. + deltaRenderTick = 0; + } + + // Add the deltaRenderTick to animTime. animTime is what controls the progress of animations. + this.animTime += deltaRenderTick; + this.lastGameTickTime = currentRenderTick; + } + + /** + * Retrieves the current animation time. + * + * @return The current animation time as a double value, representing the accumulated time used for the progression + * of animations. + */ + public double getAnimTime() { + return animTime; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimator.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimator.java new file mode 100644 index 000000000..8f290d2d1 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimator.java @@ -0,0 +1,138 @@ +package mod.azure.azurelib.rewrite.animation; + +import mod.azure.azurelib.AzureLibException; +import mod.azure.azurelib.core.molang.MolangParser; +import mod.azure.azurelib.core.molang.MolangQueries; +import mod.azure.azurelib.rewrite.animation.cache.AzBakedAnimationCache; +import mod.azure.azurelib.rewrite.animation.cache.AzBoneCache; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationControllerContainer; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimation; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * The {@code AzAnimator} class is an abstract base class for managing animations for various types of objects such as + * entities, blocks, or items. It provides a reusable structure for animating objects, allowing the integration of a + * variety of animation controllers and custom animations. + * + * @param The type of object this animator will animate (e.g., an entity, block entity, or item stack). + */ +public abstract class AzAnimator { + + private final AzAnimationContext reusableContext; + + // Holds animation controllers. + private final AzAnimationControllerContainer animationControllerContainer; + + public boolean reloadAnimations; + + protected AzAnimator() { + this(AzAnimatorConfig.defaultConfig()); + } + + protected AzAnimator(AzAnimatorConfig config) { + this.animationControllerContainer = new AzAnimationControllerContainer<>(); + + var boneCache = new AzBoneCache(); + var timer = new AzAnimationTimer(config); + + this.reusableContext = new AzAnimationContext<>(boneCache, config, timer); + } + + public abstract void registerControllers(AzAnimationControllerContainer animationControllerContainer); + + public abstract @NotNull ResourceLocation getAnimationLocation(T animatable); + + public void animate(T animatable, float partialTicks) { + reusableContext.animatable = animatable; + + var boneCache = reusableContext.boneCache(); + var timer = reusableContext.timer(); + + timer.tick(); + + preAnimationSetup(animatable, timer.getAnimTime()); + + if (!boneCache.isEmpty()) { + + for (var controller : animationControllerContainer.getAll()) { + controller.update(); + } + + this.reloadAnimations = false; + + boneCache.update(reusableContext); + } + + setCustomAnimations(animatable, partialTicks); + } + + /** + * Apply transformations and settings prior to acting on any animation-related functionality + */ + protected void preAnimationSetup(T animatable, double animTime) { + applyMolangQueries(animatable, animTime); + } + + protected void applyMolangQueries(T animatable, double animTime) { + var level = Minecraft.getInstance().level; + var parser = MolangParser.INSTANCE; + + if (level == null) { + return; + } + + parser.setMemoizedValue(MolangQueries.LIFE_TIME, () -> animTime / 20d); + parser.setMemoizedValue(MolangQueries.ACTOR_COUNT, level::getEntityCount); + parser.setMemoizedValue(MolangQueries.TIME_OF_DAY, () -> level.getDayTime() / 24000f); + parser.setMemoizedValue(MolangQueries.MOON_PHASE, level::getMoonPhase); + } + + /** + * Sets custom animations for the given animatable object. This method is used to define and configure specific + * animations unique to the context of the animatable and the current render state. + * + * @param animatable The object for which custom animations are being set. + * @param partialTicks The partial tick time used for interpolating animations smoothly between frames. + */ + public void setCustomAnimations(T animatable, float partialTicks) {} + + public void setActiveModel(AzBakedModel model) { + var modelChanged = reusableContext.boneCache().setActiveModel(model); + + if (modelChanged) { + // If the model changed, we need to clear the bone animation queue cache for every controller. + // TODO: We shouldn't have to remember to do this. If the baked model changes, then the bone cache + // should be re-instantiated. If the bone cache is re-instantiated, then so should the bone animation + // queue caches. + animationControllerContainer.getAll() + .forEach(controller -> controller.boneAnimationQueueCache().clear()); + } + } + + /** + * Get the baked animation object used for rendering from the given resource path + */ + public AzBakedAnimation getAnimation(T animatable, String name) { + var location = getAnimationLocation(animatable); + var bakedAnimations = AzBakedAnimationCache.getInstance().getNullable(location); + + if (bakedAnimations == null) { + throw new AzureLibException(location, "Unable to find animation."); + } + + return bakedAnimations.getAnimation(name); + } + + public AzAnimationContext context() { + return reusableContext; + } + + public AzAnimationControllerContainer getAnimationControllerContainer() { + return animationControllerContainer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorAccessor.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorAccessor.java new file mode 100644 index 000000000..afa249885 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorAccessor.java @@ -0,0 +1,37 @@ +package mod.azure.azurelib.rewrite.animation; + +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +/** + * The {@code AzAnimatorAccessor} interface provides a mechanism to associate and manage an {@link AzAnimator} instance + * with a target object. This enables retrieval and manipulation of animator instances that are specific to the target + * object. + * + * @param The type of the target object that the animator applies to. + */ +public interface AzAnimatorAccessor { + + @Nullable + AzAnimator getAnimatorOrNull(); + + void setAnimator(AzAnimator animator); + + default Optional> getAnimator() { + return Optional.ofNullable(getAnimatorOrNull()); + } + + @SuppressWarnings("unchecked") + static AzAnimatorAccessor cast(T target) { + return (AzAnimatorAccessor) target; + } + + static AzAnimator getOrNull(T target) { + return cast(target).getAnimatorOrNull(); + } + + static Optional> get(T target) { + return Optional.ofNullable(getOrNull(target)); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorConfig.java new file mode 100644 index 000000000..1ab8fa9da --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzAnimatorConfig.java @@ -0,0 +1,108 @@ +package mod.azure.azurelib.rewrite.animation; + +/** + * The {@code AzAnimatorConfig} record encapsulates configuration settings related to the animation system of the + * AzureLib framework. It provides customizable options for controlling animation behavior and error handling. This + * configuration is used to determine runtime behaviors such as whether animations should continue while the game is + * paused, whether the system should throw an error if a bone in the animation structure is missing, and the duration it + * takes to reset bone transformations. + * + * @param boneResetTime The specified time duration (in ticks or seconds) for resetting bones to + * their default transformations when animations are interrupted. + * @param crashIfBoneMissing Specifies whether the system will throw an exception if an expected bone + * in the animation is not found during runtime. + * @param shouldPlayAnimationsWhileGamePaused Indicates whether animations should continue playing when the game is + * paused. + */ +public record AzAnimatorConfig( + double boneResetTime, + boolean crashIfBoneMissing, + boolean shouldPlayAnimationsWhileGamePaused +) { + + /** + * Creates a new instance of the {@link Builder} to configure and build an {@code AzAnimatorConfig}. + * + * @return A new {@code Builder} instance for constructing an {@code AzAnimatorConfig}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a default {@link AzAnimatorConfig} instance with predefined settings. The default configuration typically + * includes settings such as: + *
      + *
    • A bone reset time of 1 tick/second.
    • + *
    • Disabling the feature to crash if a bone is missing.
    • + *
    • Disabling animations while the game is paused.
    • + *
    + * + * @return The default configuration instance of {@code AzAnimatorConfig}, built with default values. + */ + public static AzAnimatorConfig defaultConfig() { + return builder().build(); + } + + public static class Builder { + + private double boneResetTime; + + private boolean crashIfBoneMissing; + + private boolean shouldPlayAnimationsWhileGamePaused; + + private Builder() { + this.boneResetTime = 1; + this.crashIfBoneMissing = false; + this.shouldPlayAnimationsWhileGamePaused = false; + } + + /** + * Configures the builder to crash if a required bone is missing during animation setup. + * + * @return The current Builder instance for method chaining. + */ + public Builder crashIfBoneMissing() { + this.crashIfBoneMissing = true; + return this; + } + + /** + * Configures the builder to enable animations to play even when the game is paused. + * + * @return The current Builder instance for method chaining. + */ + public Builder shouldPlayAnimationsWhileGamePaused() { + this.shouldPlayAnimationsWhileGamePaused = true; + return this; + } + + /** + * Sets the bone reset time duration. This value determines how long it takes to reset bones to their default + * state after an animation is completed. + * + * @param boneResetTime The duration (in seconds) for bone reset time. + * @return The current Builder instance to allow method chaining. + */ + public Builder withBoneResetTime(double boneResetTime) { + this.boneResetTime = boneResetTime; + return this; + } + + /** + * Constructs a new {@link AzAnimatorConfig} instance with the specified configuration parameters defined in the + * Builder. The configuration includes options for bone reset timing, error handling when bones are missing, and + * animation playback behavior during game pause state. + * + * @return A new {@code AzAnimatorConfig} instance containing the configured settings. + */ + public AzAnimatorConfig build() { + return new AzAnimatorConfig( + boneResetTime, + crashIfBoneMissing, + shouldPlayAnimationsWhileGamePaused + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzBoneAnimationUpdateUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzBoneAnimationUpdateUtil.java new file mode 100644 index 000000000..16b10fad2 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzBoneAnimationUpdateUtil.java @@ -0,0 +1,107 @@ +package mod.azure.azurelib.rewrite.animation; + +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzBoneAnimationQueue; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingUtil; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; + +public class AzBoneAnimationUpdateUtil { + + /** + * Updates the position of the given bone by interpolating the position values from the animation queue and applying + * the specified easing type. The method also updates the snapshot offsets and flags the bone's position as changed. + * + * @param boneAnimation The animation queue containing position data for the bone. + * @param bone The bone whose position is being updated. + * @param easingType The easing type used for interpolating the position values. + * @param snapshot The snapshot used to store the updated position offsets and start animations. + */ + public static void updatePositions( + AzBoneAnimationQueue boneAnimation, + AzBone bone, + AzEasingType easingType, + AzBoneSnapshot snapshot + ) { + var posXPoint = boneAnimation.positionXQueue().poll(); + var posYPoint = boneAnimation.positionYQueue().poll(); + var posZPoint = boneAnimation.positionZQueue().poll(); + + if (posXPoint != null && posYPoint != null && posZPoint != null) { + bone.setPosX((float) AzEasingUtil.lerpWithOverride(posXPoint, easingType)); + bone.setPosY((float) AzEasingUtil.lerpWithOverride(posYPoint, easingType)); + bone.setPosZ((float) AzEasingUtil.lerpWithOverride(posZPoint, easingType)); + snapshot.updateOffset(bone.getPosX(), bone.getPosY(), bone.getPosZ()); + snapshot.startPosAnim(); + bone.markPositionAsChanged(); + } + } + + /** + * Updates the rotation of the specified bone by interpolating the rotation values from the animation queue and + * applying the specified easing type. The method also updates the snapshot rotation values, starts the rotation + * animation, and marks the bone's rotation as changed. + * + * @param boneAnimation The animation queue containing rotation data for the bone. + * @param bone The bone whose rotation is being updated. + * @param easingType The easing type used for interpolating the rotation values. + * @param initialSnapshot The initial snapshot containing the original rotation offsets. + * @param snapshot The snapshot used to store the updated rotation values and start animations. + */ + public static void updateRotations( + AzBoneAnimationQueue boneAnimation, + AzBone bone, + AzEasingType easingType, + AzBoneSnapshot initialSnapshot, + AzBoneSnapshot snapshot + ) { + var rotXPoint = boneAnimation.rotationXQueue().poll(); + var rotYPoint = boneAnimation.rotationYQueue().poll(); + var rotZPoint = boneAnimation.rotationZQueue().poll(); + + if (rotXPoint != null && rotYPoint != null && rotZPoint != null) { + bone.setRotX( + (float) AzEasingUtil.lerpWithOverride(rotXPoint, easingType) + initialSnapshot.getRotX() + ); + bone.setRotY( + (float) AzEasingUtil.lerpWithOverride(rotYPoint, easingType) + initialSnapshot.getRotY() + ); + bone.setRotZ( + (float) AzEasingUtil.lerpWithOverride(rotZPoint, easingType) + initialSnapshot.getRotZ() + ); + snapshot.updateRotation(bone.getRotX(), bone.getRotY(), bone.getRotZ()); + snapshot.startRotAnim(); + bone.markRotationAsChanged(); + } + } + + /** + * Updates the scale of the specified bone by interpolating the scale values from the animation queue and applying + * the specified easing type. The method also updates the snapshot with the new scale values, starts the scale + * animation, and marks the bone's scale as changed. + * + * @param boneAnimation The animation queue containing scale data for the bone. + * @param bone The bone whose scale is being updated. + * @param easingType The easing type used for interpolating the scale values. + * @param snapshot The snapshot used to store the updated scale values and start animations. + */ + public static void updateScale( + AzBoneAnimationQueue boneAnimation, + AzBone bone, + AzEasingType easingType, + AzBoneSnapshot snapshot + ) { + var scaleXPoint = boneAnimation.scaleXQueue().poll(); + var scaleYPoint = boneAnimation.scaleYQueue().poll(); + var scaleZPoint = boneAnimation.scaleZQueue().poll(); + + if (scaleXPoint != null && scaleYPoint != null && scaleZPoint != null) { + bone.setScaleX((float) AzEasingUtil.lerpWithOverride(scaleXPoint, easingType)); + bone.setScaleY((float) AzEasingUtil.lerpWithOverride(scaleYPoint, easingType)); + bone.setScaleZ((float) AzEasingUtil.lerpWithOverride(scaleZPoint, easingType)); + snapshot.updateScale(bone.getScaleX(), bone.getScaleY(), bone.getScaleZ()); + snapshot.startScaleAnim(); + bone.markScaleAsChanged(); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzCachedBoneUpdateUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzCachedBoneUpdateUtil.java new file mode 100644 index 000000000..ea1906e4d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/AzCachedBoneUpdateUtil.java @@ -0,0 +1,160 @@ +package mod.azure.azurelib.rewrite.animation; + +import mod.azure.azurelib.core.utils.Interpolations; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; + +import java.util.Map; + +public class AzCachedBoneUpdateUtil { + + /** + * Updates the cached position of a given bone by interpolating its offsets towards its initial snapshot. Stops + * ongoing position animations if necessary and updates the bone's position based on the reset percentage. + * + * @param bone the bone whose position is to be updated + * @param boneSnapshots a map containing snapshots of bones by their names + * @param animTime the current animation time + * @param resetTickLength the duration over which the position reset occurs + */ + public static void updateCachedBonePosition( + AzBone bone, + Map boneSnapshots, + double animTime, + double resetTickLength + ) { + if (bone.hasPositionChanged()) { + return; + } + + var initialSnapshot = bone.getInitialAzSnapshot(); + var saveSnapshot = boneSnapshots.get(bone.getName()); + + if (saveSnapshot.isPosAnimInProgress()) { + saveSnapshot.stopPosAnim(animTime); + } + + var percentageReset = Math.min( + (animTime - saveSnapshot.getLastResetPositionTick()) / resetTickLength, + 1 + ); + + bone.setPosX( + (float) Interpolations.lerp( + saveSnapshot.getOffsetX(), + initialSnapshot.getOffsetX(), + percentageReset + ) + ); + bone.setPosY( + (float) Interpolations.lerp( + saveSnapshot.getOffsetY(), + initialSnapshot.getOffsetY(), + percentageReset + ) + ); + bone.setPosZ( + (float) Interpolations.lerp( + saveSnapshot.getOffsetZ(), + initialSnapshot.getOffsetZ(), + percentageReset + ) + ); + + if (percentageReset >= 1) { + saveSnapshot.updateOffset(bone.getPosX(), bone.getPosY(), bone.getPosZ()); + } + } + + /** + * Updates the cached rotation of a given bone by interpolating its rotation values towards its initial snapshot. + * Stops any ongoing rotation animations if necessary and updates the bone's rotation based on the reset percentage. + * + * @param bone the bone whose rotation is to be updated + * @param boneSnapshots a map containing snapshots of bones by their names + * @param animTime the current animation time + * @param resetTickLength the duration over which the rotation reset occurs + */ + public static void updateCachedBoneRotation( + AzBone bone, + Map boneSnapshots, + double animTime, + double resetTickLength + ) { + if (bone.hasRotationChanged()) { + return; + } + + var initialSnapshot = bone.getInitialAzSnapshot(); + var saveSnapshot = boneSnapshots.get(bone.getName()); + + if (saveSnapshot.isRotAnimInProgress()) { + saveSnapshot.stopRotAnim(animTime); + } + + double percentageReset = Math.min( + (animTime - saveSnapshot.getLastResetRotationTick()) / resetTickLength, + 1 + ); + + bone.setRotX( + (float) Interpolations.lerp(saveSnapshot.getRotX(), initialSnapshot.getRotX(), percentageReset) + ); + bone.setRotY( + (float) Interpolations.lerp(saveSnapshot.getRotY(), initialSnapshot.getRotY(), percentageReset) + ); + bone.setRotZ( + (float) Interpolations.lerp(saveSnapshot.getRotZ(), initialSnapshot.getRotZ(), percentageReset) + ); + + if (percentageReset >= 1) { + saveSnapshot.updateRotation(bone.getRotX(), bone.getRotY(), bone.getRotZ()); + } + } + + /** + * Updates the cached scale of a given bone by interpolating its scale values towards its initial snapshot. Stops + * any ongoing scale animations if necessary and updates the bone's scale based on the reset percentage. + * + * @param bone the bone whose scale is to be updated + * @param boneSnapshots a map containing snapshots of bones by their names + * @param animTime the current animation time + * @param resetTickLength the duration over which the scale reset occurs + */ + public static void updateCachedBoneScale( + AzBone bone, + Map boneSnapshots, + double animTime, + double resetTickLength + ) { + if (bone.hasScaleChanged()) { + return; + } + + var initialSnapshot = bone.getInitialAzSnapshot(); + var saveSnapshot = boneSnapshots.get(bone.getName()); + + if (saveSnapshot.isScaleAnimInProgress()) { + saveSnapshot.stopScaleAnim(animTime); + } + + double percentageReset = Math.min( + (animTime - saveSnapshot.getLastResetScaleTick()) / resetTickLength, + 1 + ); + + bone.setScaleX( + (float) Interpolations.lerp(saveSnapshot.getScaleX(), initialSnapshot.getScaleX(), percentageReset) + ); + bone.setScaleY( + (float) Interpolations.lerp(saveSnapshot.getScaleY(), initialSnapshot.getScaleY(), percentageReset) + ); + bone.setScaleZ( + (float) Interpolations.lerp(saveSnapshot.getScaleZ(), initialSnapshot.getScaleZ(), percentageReset) + ); + + if (percentageReset >= 1) { + saveSnapshot.updateScale(bone.getScaleX(), bone.getScaleY(), bone.getScaleZ()); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBakedAnimationCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBakedAnimationCache.java new file mode 100644 index 000000000..33702aee5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBakedAnimationCache.java @@ -0,0 +1,54 @@ +package mod.azure.azurelib.rewrite.animation.cache; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.loading.FileLoader; +import mod.azure.azurelib.rewrite.AzResourceCache; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimations; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +/** + * AzBakedAnimationCache is a singleton cache to manage and store preloaded animation data of type + * {@link AzBakedAnimations}. It is an extension of {@link AzResourceCache} and provides mechanisms for managing + * animation resources in Minecraft modding. Aimed at efficient storage and retrieval, as well as background processing + * of animation data.
    + * Features: + *
      + *
    • Supports asynchronous loading of animation resources from the in-memory {@code ResourceManager}. + *
    • Caches animation data keyed by {@link ResourceLocation}. + *
    • Provides access to the cached animations or null values for non-existent records.
    • + *
    + */ +public class AzBakedAnimationCache extends AzResourceCache { + + private static final AzBakedAnimationCache INSTANCE = new AzBakedAnimationCache(); + + public static AzBakedAnimationCache getInstance() { + return INSTANCE; + } + + private final Map bakedAnimations; + + private AzBakedAnimationCache() { + this.bakedAnimations = new Object2ObjectOpenHashMap<>(); + } + + public CompletableFuture loadAnimations(Executor backgroundExecutor, ResourceManager resourceManager) { + return loadResources( + backgroundExecutor, + resourceManager, + "animations", + resource -> FileLoader.loadAzAnimationsFile(resource, resourceManager), + bakedAnimations::put + ); + } + + public @Nullable AzBakedAnimations getNullable(ResourceLocation resourceLocation) { + return bakedAnimations.get(resourceLocation); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBoneCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBoneCache.java new file mode 100644 index 000000000..57fd970ec --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzBoneCache.java @@ -0,0 +1,87 @@ +package mod.azure.azurelib.rewrite.animation.cache; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.rewrite.animation.AzAnimationContext; +import mod.azure.azurelib.rewrite.animation.AzCachedBoneUpdateUtil; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; + +import java.util.Map; +import java.util.Objects; + +/** + * The AzBoneCache class is responsible for managing the state and cache of bones in a baked model. It provides + * functionality for updating animation contexts, managing snapshots of bone states, and resetting transformation + * markers in preparation for rendering. + */ +public class AzBoneCache { + + private AzBakedModel bakedModel; + + private final Map boneSnapshotsByName; + + public AzBoneCache() { + this.bakedModel = AzBakedModel.EMPTY; + this.boneSnapshotsByName = new Object2ObjectOpenHashMap<>(); + } + + public boolean setActiveModel(AzBakedModel model) { + var willModelChange = !Objects.equals(bakedModel, model); + this.bakedModel = model; + + if (willModelChange) { + snapshot(); + } + + return willModelChange; + } + + public void update(AzAnimationContext context) { + var config = context.config(); + var timer = context.timer(); + var animTime = timer.getAnimTime(); + var boneSnapshots = getBoneSnapshotsByName(); + var resetTickLength = config.boneResetTime(); + + // Updates the cached bone snapshots (only if they have changed). + for (var bone : bakedModel.getBonesByName().values()) { + AzCachedBoneUpdateUtil.updateCachedBoneRotation(bone, boneSnapshots, animTime, resetTickLength); + AzCachedBoneUpdateUtil.updateCachedBonePosition(bone, boneSnapshots, animTime, resetTickLength); + AzCachedBoneUpdateUtil.updateCachedBoneScale(bone, boneSnapshots, animTime, resetTickLength); + } + + resetBoneTransformationMarkers(); + } + + /** + * Reset the transformation markers applied to each {@link AzBone} ready for the next render frame + */ + private void resetBoneTransformationMarkers() { + bakedModel.getBonesByName().values().forEach(AzBone::resetStateChanges); + } + + /** + * Create new bone {@link AzBoneSnapshot} based on the bone's initial snapshot for the currently registered + * {@link AzBone AzBones}, filtered by the bones already present in the master snapshots map + */ + private void snapshot() { + boneSnapshotsByName.clear(); + + for (var bone : bakedModel.getBonesByName().values()) { + boneSnapshotsByName.put(bone.getName(), AzBoneSnapshot.copy(bone.getInitialAzSnapshot())); + } + } + + public AzBakedModel getBakedModel() { + return bakedModel; + } + + public Map getBoneSnapshotsByName() { + return boneSnapshotsByName; + } + + public boolean isEmpty() { + return bakedModel.getBonesByName().isEmpty(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentifiableItemStackAnimatorCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentifiableItemStackAnimatorCache.java new file mode 100644 index 000000000..3a16d6794 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentifiableItemStackAnimatorCache.java @@ -0,0 +1,47 @@ +package mod.azure.azurelib.rewrite.animation.cache; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.impl.AzItemAnimator; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * The AzIdentifiableItemStackAnimatorCache class is a singleton utility for managing a cache of {@link ItemStack} + * objects, each associated with a unique identifier (UUID). This class provides functionality to register and retrieve + * item animators that apply to specific {@link ItemStack}s using their respective UUIDs. + */ +public class AzIdentifiableItemStackAnimatorCache { + + private static final AzIdentifiableItemStackAnimatorCache INSTANCE = new AzIdentifiableItemStackAnimatorCache(); + + // TODO: Purge animators periodically. + private static final Map ANIMATORS_BY_UUID = new HashMap<>(); + + public static AzIdentifiableItemStackAnimatorCache getInstance() { + return INSTANCE; + } + + private AzIdentifiableItemStackAnimatorCache() {} + + public void add(ItemStack itemStack, AzItemAnimator animator) { + if (!itemStack.hasTag()) { + itemStack.setTag(new CompoundTag()); + } + + var tag = itemStack.getTag(); + var uuid = tag.getUUID(AzureLib.ITEM_UUID_TAG); + + if (uuid != null) { + ANIMATORS_BY_UUID.computeIfAbsent(uuid, ($) -> animator); + } + } + + public @Nullable AzItemAnimator getOrNull(UUID uuid) { + return uuid == null ? null : ANIMATORS_BY_UUID.get(uuid); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentityRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentityRegistry.java new file mode 100644 index 000000000..b89b3c15f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/cache/AzIdentityRegistry.java @@ -0,0 +1,39 @@ +package mod.azure.azurelib.rewrite.animation.cache; + +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * The AzIdentityRegistry class provides functionality to register and check the identity of items. This class maintains + * a static registry of unique items, allowing you to determine if a specific item has been registered. + */ +public class AzIdentityRegistry { + + private static final Set IDENTITY_OF_ITEMS = new HashSet<>(); + + /** + * Registers one or more items into a static identity set, ensuring that the items are stored for identity tracking. + * + * @param first The first non-null item to be registered. This parameter is mandatory. + * @param rest A varargs array of additional items to register. These items can be null, but null values will not + * be added to the identity set. + */ + public static void register(@NotNull Item first, Item... rest) { + IDENTITY_OF_ITEMS.add(first); + IDENTITY_OF_ITEMS.addAll(Arrays.asList(rest)); + } + + /** + * Checks if the specified item exists in the identity set. + * + * @param item The item to check for identity. Must not be null. + * @return true if the item exists in the identity set, false otherwise. + */ + public static boolean hasIdentity(Item item) { + return IDENTITY_OF_ITEMS.contains(item); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAbstractAnimationController.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAbstractAnimationController.java new file mode 100644 index 000000000..a3a3f85a4 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAbstractAnimationController.java @@ -0,0 +1,33 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.sequence.AzAnimationSequence; + +// TODO: This will eventually be usable in common-side code once animations are moved from assets to data. +public class AzAbstractAnimationController { + + private final String name; + + protected AzAnimationSequence currentSequence; + + protected AzDispatchSide currentSequenceOrigin; + + protected AzAbstractAnimationController(String name) { + this.name = name; + } + + public String name() { + return name; + } + + /** + * Checks whether the last animation that was playing on this controller has finished or not.
    + * This will return true if the controller has had an animation set previously, and it has finished playing and + * isn't going to loop or proceed to another animation.
    + * + * @return Whether the previous animation finished or not + */ + public boolean hasAnimationFinished() { + return currentSequence != null; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationController.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationController.java new file mode 100644 index 000000000..ea52d8459 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationController.java @@ -0,0 +1,210 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeCallbacks; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeManager; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationPauseState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationPlayState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationStopState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationTransitionState; +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.sequence.AzAnimationSequence; +import mod.azure.azurelib.rewrite.animation.primitive.AzQueuedAnimation; +import mod.azure.azurelib.rewrite.animation.property.AzAnimationProperties; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * The actual controller that handles the playing and usage of animations, including their various keyframes and + * instruction markers. Each controller can only play a single animation at a time - for example you may have one + * controller to animate walking, one to control attacks, one to control size, etc. + */ +public class AzAnimationController extends AzAbstractAnimationController { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AzAnimationController.class); + + public static AzAnimationControllerBuilder builder(AzAnimator animator, String name) { + return new AzAnimationControllerBuilder<>(animator, name); + } + + private final AzAnimationControllerTimer controllerTimer; + + private final AzAnimationQueue animationQueue; + + private final AzAnimationControllerStateMachine stateMachine; + + private final AzAnimator animator; + + private final AzBoneAnimationQueueCache boneAnimationQueueCache; + + private final AzBoneSnapshotCache boneSnapshotCache; + + private final AzKeyframeManager keyframeManager; + + protected AzQueuedAnimation currentAnimation; + + private AzAnimationProperties animationProperties; + + AzAnimationController( + String name, + AzAnimator animator, + AzAnimationProperties animationProperties, + AzKeyframeCallbacks keyframeCallbacks + ) { + super(name); + + this.animator = animator; + this.controllerTimer = new AzAnimationControllerTimer<>(this); + this.animationProperties = animationProperties; + + this.animationQueue = new AzAnimationQueue(); + this.boneAnimationQueueCache = new AzBoneAnimationQueueCache<>(animator.context().boneCache()); + this.boneSnapshotCache = new AzBoneSnapshotCache(); + this.keyframeManager = new AzKeyframeManager<>( + this, + boneAnimationQueueCache, + boneSnapshotCache, + keyframeCallbacks + ); + + var stateHolder = new AzAnimationControllerStateMachine.StateHolder( + new AzAnimationPlayState<>(), + new AzAnimationPauseState<>(), + new AzAnimationStopState<>(), + new AzAnimationTransitionState<>() + ); + + this.stateMachine = new AzAnimationControllerStateMachine<>(stateHolder, this, animator.context()); + } + + @Override + public boolean hasAnimationFinished() { + return super.hasAnimationFinished() && stateMachine.isStopped(); + } + + public List tryCreateAnimationQueue(T animatable, AzAnimationSequence sequence) { + var stages = sequence.stages(); + var animations = new ArrayList(); + + for (var stage : stages) { + var animation = animator.getAnimation(animatable, stage.name()); + + if (animation == null) { + LOGGER.warn( + "Unable to find animation: {} for {}", + stage.name(), + animatable.getClass().getSimpleName() + ); + return List.of(); + } else { + animations.add(new AzQueuedAnimation(animation, stage.properties().playBehavior())); + } + } + + return animations; + } + + /** + * This method is called every frame in order to populate the animation point queues, and process animation state + * logic. + */ + public void update() { + // Adjust the tick before making any updates. + controllerTimer.update(); + // Run state machine updates. + stateMachine.update(); + // Update bone animation queue cache. + boneAnimationQueueCache.update(animationProperties.easingType()); + } + + public void run(AzDispatchSide originSide, @NotNull AzAnimationSequence sequence) { + if (currentSequenceOrigin == AzDispatchSide.SERVER && originSide == AzDispatchSide.CLIENT) { + if (!hasAnimationFinished()) { + // If we're playing a server-side sequence, ignore client-side sequences. + return; + } + } + + this.currentSequenceOrigin = originSide; + + if (stateMachine.isStopped()) { + stateMachine.transition(); + } + + if (currentSequence == null || !currentSequence.equals(sequence)) { + this.currentAnimation = null; + } + + var animatable = animator.context().animatable(); + + if (sequence.stages().isEmpty()) { + stateMachine.stop(); + return; + } + + if (!sequence.equals(currentSequence)) { + var animations = tryCreateAnimationQueue(animatable, sequence); + + if (!animations.isEmpty()) { + animationQueue.clear(); + animationQueue.addAll(animations); + this.currentSequence = sequence; + stateMachine.transition(); + return; + } + + stateMachine.stop(); + } + } + + public AzAnimationProperties animationProperties() { + return animationProperties; + } + + public void setAnimationProperties(AzAnimationProperties animationProperties) { + this.animationProperties = animationProperties; + } + + public AzAnimationQueue animationQueue() { + return animationQueue; + } + + public AzBoneAnimationQueueCache boneAnimationQueueCache() { + return boneAnimationQueueCache; + } + + public AzBoneSnapshotCache boneSnapshotCache() { + return boneSnapshotCache; + } + + public AzAnimationControllerTimer controllerTimer() { + return controllerTimer; + } + + public @Nullable AzQueuedAnimation currentAnimation() { + return currentAnimation; + } + + public AzKeyframeManager keyframeManager() { + return keyframeManager; + } + + public AzAnimationControllerStateMachine stateMachine() { + return stateMachine; + } + + public void setCurrentAnimation(AzQueuedAnimation currentAnimation) { + this.currentAnimation = currentAnimation; + + if (currentAnimation == null) { + this.currentSequence = null; + this.currentSequenceOrigin = null; + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerBuilder.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerBuilder.java new file mode 100644 index 000000000..0adbee4da --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerBuilder.java @@ -0,0 +1,64 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeCallbacks; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.property.AzAnimationProperties; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A builder class to construct {@link AzAnimationController} instances for managing animations in {@link AzAnimator}. + * This provides a fluent API to configure properties such as animation speed, keyframe callbacks, easing type, + * transition length, and triggerable animations. + * + * @param The type of object that the animation controller will operate on. + */ +public class AzAnimationControllerBuilder { + + private final AzAnimator animator; + + private final String name; + + private AzAnimationProperties animationProperties; + + private AzKeyframeCallbacks keyframeCallbacks; + + public AzAnimationControllerBuilder(AzAnimator animator, String name) { + this.animator = animator; + this.name = name; + this.animationProperties = AzAnimationProperties.DEFAULT; + this.keyframeCallbacks = AzKeyframeCallbacks.noop(); + } + + public AzAnimationControllerBuilder setAnimationSpeed(double animationSpeed) { + animationProperties = animationProperties.withAnimationSpeed(animationSpeed); + return this; + } + + public AzAnimationControllerBuilder setKeyframeCallbacks(@NotNull AzKeyframeCallbacks keyframeCallbacks) { + Objects.requireNonNull(keyframeCallbacks); + this.keyframeCallbacks = keyframeCallbacks; + return this; + } + + public AzAnimationControllerBuilder setEasingType(AzEasingType easingType) { + animationProperties = animationProperties.withEasingType(easingType); + return this; + } + + public AzAnimationControllerBuilder setTransitionLength(int transitionLength) { + animationProperties = animationProperties.withTransitionLength(transitionLength); + return this; + } + + public AzAnimationController build() { + return new AzAnimationController<>( + name, + animator, + animationProperties, + keyframeCallbacks + ); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerContainer.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerContainer.java new file mode 100644 index 000000000..d62bad1b3 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerContainer.java @@ -0,0 +1,39 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; + +/** + * A container class for managing a collection of {@link AzAnimationController} instances. Provides methods to add, + * retrieve, and access animation controllers by their names. + * + * @param the type of the animation data or state managed by {@link AzAnimationController}. + */ +public class AzAnimationControllerContainer { + + private final Map> animationControllersByName; + + public AzAnimationControllerContainer() { + this.animationControllersByName = new Object2ObjectArrayMap<>(); + } + + @SafeVarargs + public final void add(AzAnimationController controller, AzAnimationController... controllers) { + animationControllersByName.put(controller.name(), controller); + + for (var extraController : controllers) { + animationControllersByName.put(extraController.name(), extraController); + } + } + + public @Nullable AzAnimationController getOrNull(String controllerName) { + return animationControllersByName.get(controllerName); + } + + public Collection> getAll() { + return animationControllersByName.values(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerTimer.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerTimer.java new file mode 100644 index 000000000..35bc94785 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationControllerTimer.java @@ -0,0 +1,48 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +/** + * A timer utility that integrates directly with an {@link AzAnimationController} to track and adjust tick values for + * animation playback control, based on the controller's state and animation speed modifiers. + * + * @param The type of the animatable entity being controlled by the animation controller. + */ +public class AzAnimationControllerTimer { + + private final AzAnimationController animationController; + + private double adjustedTick; + + private double tickOffset; + + public AzAnimationControllerTimer(AzAnimationController animationController) { + this.animationController = animationController; + } + + /** + * Adjust a tick value depending on the controller's current state and speed modifier.
    + * Is used when starting a new animation, transitioning, and a few other key areas + */ + public void update() { + var stateMachine = animationController.stateMachine(); + var animContext = stateMachine.getContext().animationContext(); + var animationSpeed = animationController.animationProperties().animationSpeed(); + var tick = animContext.timer().getAnimTime(); + + adjustedTick = animationSpeed * Math.max(tick - tickOffset, 0); + } + + public void reset() { + var stateMachine = animationController.stateMachine(); + var animContext = stateMachine.getContext().animationContext(); + this.tickOffset = animContext.timer().getAnimTime(); + this.adjustedTick = 0; + } + + public double getAdjustedTick() { + return adjustedTick; + } + + public void addToAdjustedTick(double adjustedTick) { + this.adjustedTick += adjustedTick; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationQueue.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationQueue.java new file mode 100644 index 000000000..e78b89f2d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzAnimationQueue.java @@ -0,0 +1,51 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import mod.azure.azurelib.rewrite.animation.primitive.AzQueuedAnimation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Represents a queue of animations to be processed in a sequential manner. This class manages a collection of + * {@link AzQueuedAnimation} objects, allowing animations to be queued, retrieved, and cleared efficiently. It ensures + * that animations are processed in the order they are added.
    + *
    + * The queue supports operations to inspect the next animation without removal, retrieve and remove the next animation, + * add individual or multiple animations, and clear the entire queue. Additionally, it provides a method to determine if + * the queue is empty. + */ +public class AzAnimationQueue { + + private final Queue animationQueue; + + public AzAnimationQueue() { + this.animationQueue = new LinkedList<>(); + } + + public void add(@NotNull AzQueuedAnimation queuedAnimation) { + animationQueue.add(queuedAnimation); + } + + public void addAll(@NotNull Collection queuedAnimations) { + animationQueue.addAll(queuedAnimations); + } + + public @Nullable AzQueuedAnimation peek() { + return animationQueue.peek(); + } + + public @Nullable AzQueuedAnimation next() { + return animationQueue.poll(); + } + + public void clear() { + animationQueue.clear(); + } + + public boolean isEmpty() { + return animationQueue.isEmpty(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneAnimationQueueCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneAnimationQueueCache.java new file mode 100644 index 000000000..5b8232fff --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneAnimationQueueCache.java @@ -0,0 +1,61 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.rewrite.animation.AzBoneAnimationUpdateUtil; +import mod.azure.azurelib.rewrite.animation.cache.AzBoneCache; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzBoneAnimationQueue; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; + +/** + * The AzBoneAnimationQueueCache class is responsible for managing and updating animation queues for bones. It acts as a + * cache that maps bone names to their respective animation queues, enabling efficient updates and access. + * + * @param the type of the animatable object used in the animation context + */ +public class AzBoneAnimationQueueCache { + + private final Map boneAnimationQueues; + + private final AzBoneCache boneCache; + + public AzBoneAnimationQueueCache(AzBoneCache boneCache) { + this.boneAnimationQueues = new Object2ObjectOpenHashMap<>(); + this.boneCache = boneCache; + } + + public void update(AzEasingType easingType) { + var boneSnapshots = boneCache.getBoneSnapshotsByName(); + + for (var boneAnimation : boneAnimationQueues.values()) { + var bone = boneAnimation.bone(); + var snapshot = boneSnapshots.get(bone.getName()); + var initialSnapshot = bone.getInitialAzSnapshot(); + + AzBoneAnimationUpdateUtil.updateRotations(boneAnimation, bone, easingType, initialSnapshot, snapshot); + AzBoneAnimationUpdateUtil.updatePositions(boneAnimation, bone, easingType, snapshot); + AzBoneAnimationUpdateUtil.updateScale(boneAnimation, bone, easingType, snapshot); + } + } + + public Collection values() { + return boneAnimationQueues.values(); + } + + public @Nullable AzBoneAnimationQueue getOrNull(String boneName) { + var bone = boneCache.getBakedModel().getBoneOrNull(boneName); + + if (bone == null) { + return null; + } + + return boneAnimationQueues.computeIfAbsent(boneName, $ -> new AzBoneAnimationQueue(bone)); + } + + public void clear() { + boneAnimationQueues.clear(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneSnapshotCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneSnapshotCache.java new file mode 100644 index 000000000..474db7337 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/AzBoneSnapshotCache.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.animation.controller; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.rewrite.animation.primitive.AzQueuedAnimation; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; + +/** + * A cache system for managing and storing {@link AzBoneSnapshot} objects related to specific animations and their + * corresponding bone animations. This class is designed to optimize the retrieval and utilization of bone snapshots + * during animation interpolation (lerping).
    + *
    + * The cache works by filtering and storing relevant {@code AzBoneSnapshot} instances for a given + * {@link AzQueuedAnimation}, based on the associated bone animations defined within the animation. It ensures that only + * the required snapshots are cached, reducing redundancy and improving performance during animation processing. + */ +public class AzBoneSnapshotCache { + + private final Map boneSnapshots; + + public AzBoneSnapshotCache() { + this.boneSnapshots = new Object2ObjectOpenHashMap<>(); + } + + /** + * Cache the relevant {@link AzBoneSnapshot AzBoneSnapshots} for the current {@link AzQueuedAnimation} for animation + * lerping + * + * @param animation The {@code QueuedAnimation} to filter {@code BoneSnapshots} for + * @param snapshots The master snapshot collection to pull filter from + */ + public void put(AzQueuedAnimation animation, Collection snapshots) { + if (animation.animation().boneAnimations() == null) { + return; + } + + for (var snapshot : snapshots) { + for (var boneAnimation : animation.animation().boneAnimations()) { + if (boneAnimation.boneName().equals(snapshot.getBone().getName())) { + boneSnapshots.put(boneAnimation.boneName(), AzBoneSnapshot.copy(snapshot)); + break; + } + } + } + } + + public @Nullable AzBoneSnapshot getOrNull(String name) { + return boneSnapshots.get(name); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAbstractKeyframeExecutor.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAbstractKeyframeExecutor.java new file mode 100644 index 000000000..b20b107c5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAbstractKeyframeExecutor.java @@ -0,0 +1,79 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.core.keyframe.AnimationPoint; +import mod.azure.azurelib.core.keyframe.Keyframe; +import mod.azure.azurelib.core.keyframe.KeyframeLocation; +import mod.azure.azurelib.core.math.Constant; +import mod.azure.azurelib.core.math.IValue; +import mod.azure.azurelib.core.object.Axis; + +import java.util.List; + +/** + * AzAbstractKeyframeExecutor is a base class designed to handle animations and transitions between keyframes in a + * generic and reusable fashion. It provides the foundational logic for determining the current state of an animation + * based on the tick time and computing the animation's required values. + */ +public class AzAbstractKeyframeExecutor { + + protected AzAbstractKeyframeExecutor() {} + + /** + * Convert a {@link KeyframeLocation} to an {@link AnimationPoint} + */ + protected AzAnimationPoint getAnimationPointAtTick( + List> frames, + double tick, + boolean isRotation, + Axis axis + ) { + var location = getCurrentKeyframeLocation(frames, tick); + var currentFrame = location.keyframe(); + var startValue = currentFrame.startValue().get(); + var endValue = currentFrame.endValue().get(); + + if (isRotation) { + if (!(currentFrame.startValue() instanceof Constant)) { + startValue = Math.toRadians(startValue); + + if (axis == Axis.X || axis == Axis.Y) { + startValue *= -1; + } + } + + if (!(currentFrame.endValue() instanceof Constant)) { + endValue = Math.toRadians(endValue); + + if (axis == Axis.X || axis == Axis.Y) { + endValue *= -1; + } + } + } + + return new AzAnimationPoint(currentFrame, location.startTick(), currentFrame.length(), startValue, endValue); + } + + /** + * Returns the {@link Keyframe} relevant to the current tick time + * + * @param frames The list of {@code Keyframes} to filter through + * @param ageInTicks The current tick time + * @return A new {@code KeyframeLocation} containing the current {@code Keyframe} and the tick time used to find it + */ + protected AzKeyframeLocation> getCurrentKeyframeLocation( + List> frames, + double ageInTicks + ) { + var totalFrameTime = 0.0; + + for (var frame : frames) { + totalFrameTime += frame.length(); + + if (totalFrameTime > ageInTicks) { + return new AzKeyframeLocation<>(frame, (ageInTicks - (totalFrameTime - frame.length()))); + } + } + + return new AzKeyframeLocation<>(frames.get(frames.size() - 1), ageInTicks); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAnimationPoint.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAnimationPoint.java new file mode 100644 index 000000000..094ea1cdc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzAnimationPoint.java @@ -0,0 +1,27 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +/** + * Animation state record that holds the state of an animation at a given point + * + * @param currentTick The lerped tick time (current tick + partial tick) of the point + * @param transitionLength The length of time (in ticks) that the point should take to transition + * @param animationStartValue The start value to provide to the animation handling system + * @param animationEndValue The end value to provide to the animation handling system + * @param keyframe The {@code Nullable} Keyframe + */ +public record AzAnimationPoint( + AzKeyframe keyframe, + double currentTick, + double transitionLength, + double animationStartValue, + double animationEndValue +) { + + @Override + public String toString() { + return "Tick: " + this.currentTick + + " | Transition Length: " + this.transitionLength + + " | Start Value: " + this.animationStartValue + + " | End Value: " + this.animationEndValue; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimation.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimation.java new file mode 100644 index 000000000..b76970f1d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimation.java @@ -0,0 +1,20 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.core.keyframe.Keyframe; +import mod.azure.azurelib.core.math.IValue; + +/** + * A record of a deserialized animation for a given bone.
    + * Responsible for holding the various {@link Keyframe Keyframes} for the bone's animation transformations + * + * @param boneName The name of the bone as listed in the {@code animation.json} + * @param rotationKeyframes The deserialized rotation {@code Keyframe} stack + * @param positionKeyframes The deserialized position {@code Keyframe} stack + * @param scaleKeyframes The deserialized scale {@code Keyframe} stack + */ +public record AzBoneAnimation( + String boneName, + AzKeyframeStack> rotationKeyframes, + AzKeyframeStack> positionKeyframes, + AzKeyframeStack> scaleKeyframes +) {} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimationQueue.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimationQueue.java new file mode 100644 index 000000000..4f636ee8e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzBoneAnimationQueue.java @@ -0,0 +1,400 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * A bone pseudo-stack for bone animation positions, scales, and rotations. Animation points are calculated then pushed + * onto their respective queues to be used for transformations in rendering + */ +public record AzBoneAnimationQueue( + AzBone bone, + Queue rotationXQueue, + Queue rotationYQueue, + Queue rotationZQueue, + Queue positionXQueue, + Queue positionYQueue, + Queue positionZQueue, + Queue scaleXQueue, + Queue scaleYQueue, + Queue scaleZQueue +) { + + public AzBoneAnimationQueue(AzBone bone) { + // TODO: Optimize + this( + bone, + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>() + ); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#positionXQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addPosXPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.positionXQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#positionYQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addPosYPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.positionYQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#positionZQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addPosZPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.positionZQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new X, Y, and Z position {@link AzAnimationPoint} to their respective queues + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (base on the {@link AzAnimationController} + * @param startSnapshot The {@link AzBoneSnapshot} that serves as the starting positions relevant to the keyframe + * provided + * @param nextXPoint The X {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextYPoint The Y {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextZPoint The Z {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + */ + public void addNextPosition( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + AzBoneSnapshot startSnapshot, + AzAnimationPoint nextXPoint, + AzAnimationPoint nextYPoint, + AzAnimationPoint nextZPoint + ) { + addPosXPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getOffsetX(), + nextXPoint.animationStartValue() + ); + addPosYPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getOffsetY(), + nextYPoint.animationStartValue() + ); + addPosZPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getOffsetZ(), + nextZPoint.animationStartValue() + ); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#scaleXQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addScaleXPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.scaleXQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#scaleYQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addScaleYPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.scaleYQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#scaleZQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addScaleZPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.scaleZQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new X, Y, and Z scale {@link AzAnimationPoint} to their respective queues + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (base on the {@link AzAnimationController} + * @param startSnapshot The {@link AzBoneSnapshot} that serves as the starting scales relevant to the keyframe + * provided + * @param nextXPoint The X {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextYPoint The Y {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextZPoint The Z {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + */ + public void addNextScale( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + AzBoneSnapshot startSnapshot, + AzAnimationPoint nextXPoint, + AzAnimationPoint nextYPoint, + AzAnimationPoint nextZPoint + ) { + addScaleXPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getScaleX(), + nextXPoint.animationStartValue() + ); + addScaleYPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getScaleY(), + nextYPoint.animationStartValue() + ); + addScaleZPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getScaleZ(), + nextZPoint.animationStartValue() + ); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#rotationXQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addRotationXPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.rotationXQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#rotationYQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addRotationYPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.rotationYQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new {@link AzAnimationPoint} to the {@link AzBoneAnimationQueue#rotationZQueue} + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (based on the {@link AzAnimationController}) + * @param startValue The value of the point at the start of its transition + * @param endValue The value of the point at the end of its transition + */ + public void addRotationZPoint( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + double startValue, + double endValue + ) { + this.rotationZQueue.add(new AzAnimationPoint(keyframe, lerpedTick, transitionLength, startValue, endValue)); + } + + /** + * Add a new X, Y, and Z scale {@link AzAnimationPoint} to their respective queues + * + * @param keyframe The {@code Nullable} Keyframe relevant to the animation point + * @param lerpedTick The lerped time (current tick + partial tick) that the point starts at + * @param transitionLength The length of the transition (base on the {@link AzAnimationController} + * @param startSnapshot The {@link AzBoneSnapshot} that serves as the starting rotations relevant to the keyframe + * provided + * @param initialSnapshot The {@link AzBoneSnapshot} that serves as the unmodified rotations of the bone + * @param nextXPoint The X {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextYPoint The Y {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + * @param nextZPoint The Z {@code AnimationPoint} that is next in the queue, to serve as the end value of the + * new point + */ + public void addNextRotation( + AzKeyframe keyframe, + double lerpedTick, + double transitionLength, + AzBoneSnapshot startSnapshot, + AzBoneSnapshot initialSnapshot, + AzAnimationPoint nextXPoint, + AzAnimationPoint nextYPoint, + AzAnimationPoint nextZPoint + ) { + addRotationXPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getRotX() - initialSnapshot.getRotX(), + nextXPoint.animationStartValue() + ); + addRotationYPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getRotY() - initialSnapshot.getRotY(), + nextYPoint.animationStartValue() + ); + addRotationZPoint( + keyframe, + lerpedTick, + transitionLength, + startSnapshot.getRotZ() - initialSnapshot.getRotZ(), + nextZPoint.animationStartValue() + ); + } + + /** + * Add an X, Y, and Z position {@link AzAnimationPoint} to their respective queues + * + * @param xPoint The x position {@code AnimationPoint} to add + * @param yPoint The y position {@code AnimationPoint} to add + * @param zPoint The z position {@code AnimationPoint} to add + */ + public void addPositions(AzAnimationPoint xPoint, AzAnimationPoint yPoint, AzAnimationPoint zPoint) { + this.positionXQueue.add(xPoint); + this.positionYQueue.add(yPoint); + this.positionZQueue.add(zPoint); + } + + /** + * Add an X, Y, and Z scale {@link AzAnimationPoint} to their respective queues + * + * @param xPoint The x scale {@code AnimationPoint} to add + * @param yPoint The y scale {@code AnimationPoint} to add + * @param zPoint The z scale {@code AnimationPoint} to add + */ + public void addScales(AzAnimationPoint xPoint, AzAnimationPoint yPoint, AzAnimationPoint zPoint) { + this.scaleXQueue.add(xPoint); + this.scaleYQueue.add(yPoint); + this.scaleZQueue.add(zPoint); + } + + /** + * Add an X, Y, and Z rotation {@link AzAnimationPoint} to their respective queues + * + * @param xPoint The x rotation {@code AnimationPoint} to add + * @param yPoint The y rotation {@code AnimationPoint} to add + * @param zPoint The z rotation {@code AnimationPoint} to add + */ + public void addRotations(AzAnimationPoint xPoint, AzAnimationPoint yPoint, AzAnimationPoint zPoint) { + this.rotationXQueue.add(xPoint); + this.rotationYQueue.add(yPoint); + this.rotationZQueue.add(zPoint); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframe.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframe.java new file mode 100644 index 000000000..e03b4943f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframe.java @@ -0,0 +1,56 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.core.math.IValue; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; + +import java.util.List; +import java.util.Objects; + +/** + * Animation keyframe data + * + * @param length The length (in ticks) the keyframe lasts for + * @param startValue The value to start the keyframe's transformation with + * @param endValue The value to end the keyframe's transformation with + * @param easingType The {@code EasingType} to use for transformations + * @param easingArgs The arguments to provide to the easing calculation + */ +public record AzKeyframe( + double length, + T startValue, + T endValue, + AzEasingType easingType, + List easingArgs +) { + + public AzKeyframe(double length, T startValue, T endValue) { + this(length, startValue, endValue, AzEasingTypes.LINEAR); + } + + public AzKeyframe(double length, T startValue, T endValue, AzEasingType easingType) { + this(length, startValue, endValue, easingType, new ObjectArrayList<>(0)); + } + + @Override + public int hashCode() { + return Objects.hash(this.length, this.startValue, this.endValue, this.easingType, this.easingArgs); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + return hashCode() == obj.hashCode(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbackHandler.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbackHandler.java new file mode 100644 index 000000000..70fb79e6f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbackHandler.java @@ -0,0 +1,126 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import mod.azure.azurelib.core.keyframe.event.data.KeyFrameData; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.event.AzCustomInstructionKeyframeEvent; +import mod.azure.azurelib.rewrite.animation.event.AzParticleKeyframeEvent; +import mod.azure.azurelib.rewrite.animation.event.AzSoundKeyframeEvent; +import mod.azure.azurelib.rewrite.animation.primitive.AzQueuedAnimation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +/** + * AzKeyframeCallbackHandler acts as a handler for managing animation keyframe events such as sound, particle, or custom + * events during a specific animation. It works in conjunction with an animation controller and a set of keyframe + * callbacks, executing them as appropriate based on the animation's progress.
    + * This class is generic and operates on a user-defined animatable type to handle various keyframe events related to + * animations. + * + * @param the type of the animatable object being handled + */ +// TODO: reduce the boilerplate of the specialized handle functions in this class. +public class AzKeyframeCallbackHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AzKeyframeCallbackHandler.class); + + private final AzAnimationController animationController; + + private final Set executedKeyframes; + + private final AzKeyframeCallbacks keyframeCallbacks; + + public AzKeyframeCallbackHandler( + AzAnimationController animationController, + AzKeyframeCallbacks keyframeCallbacks + ) { + this.animationController = animationController; + this.executedKeyframes = new ObjectOpenHashSet<>(); + this.keyframeCallbacks = keyframeCallbacks; + } + + public void handle(T animatable, double adjustedTick) { + handleSoundKeyframes(animatable, adjustedTick); + handleParticleKeyframes(animatable, adjustedTick); + handleCustomKeyframes(animatable, adjustedTick); + } + + private void handleCustomKeyframes(T animatable, double adjustedTick) { + var customKeyframeHandler = keyframeCallbacks.customKeyframeHandler(); + var customInstructions = currentAnimation().animation().keyframes().customInstructions(); + + for (var keyframeData : customInstructions) { + if (adjustedTick >= keyframeData.getStartTick() && executedKeyframes.add(keyframeData)) { + if (customKeyframeHandler == null) { + LOGGER.warn( + "Custom Instruction Keyframe found for {} -> {}, but no keyframe handler registered", + animatable.getClass().getSimpleName(), + animationController.name() + ); + break; + } + + customKeyframeHandler.handle( + new AzCustomInstructionKeyframeEvent<>(animatable, adjustedTick, animationController, keyframeData) + ); + } + } + } + + private void handleParticleKeyframes(T animatable, double adjustedTick) { + var particleKeyframeHandler = keyframeCallbacks.particleKeyframeHandler(); + var particleInstructions = currentAnimation().animation().keyframes().particles(); + + for (var keyframeData : particleInstructions) { + if (adjustedTick >= keyframeData.getStartTick() && executedKeyframes.add(keyframeData)) { + if (particleKeyframeHandler == null) { + LOGGER.warn( + "Particle Keyframe found for {} -> {}, but no keyframe handler registered", + animatable.getClass().getSimpleName(), + animationController.name() + ); + break; + } + + particleKeyframeHandler.handle( + new AzParticleKeyframeEvent<>(animatable, adjustedTick, animationController, keyframeData) + ); + } + } + } + + private void handleSoundKeyframes(T animatable, double adjustedTick) { + var soundKeyframeHandler = keyframeCallbacks.soundKeyframeHandler(); + var soundInstructions = currentAnimation().animation().keyframes().sounds(); + + for (var keyframeData : soundInstructions) { + if (adjustedTick >= keyframeData.getStartTick() && executedKeyframes.add(keyframeData)) { + if (soundKeyframeHandler == null) { + LOGGER.warn( + "Sound Keyframe found for {} -> {}, but no keyframe handler registered", + animatable.getClass().getSimpleName(), + animationController.name() + ); + break; + } + + soundKeyframeHandler.handle( + new AzSoundKeyframeEvent<>(animatable, adjustedTick, animationController, keyframeData) + ); + } + } + } + + /** + * Clear the {@link KeyFrameData} cache in preparation for the next animation + */ + public void reset() { + executedKeyframes.clear(); + } + + private AzQueuedAnimation currentAnimation() { + return animationController.currentAnimation(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbacks.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbacks.java new file mode 100644 index 000000000..342795f97 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeCallbacks.java @@ -0,0 +1,105 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.rewrite.animation.controller.keyframe.handler.AzCustomKeyframeHandler; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.handler.AzParticleKeyframeHandler; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.handler.AzSoundKeyframeHandler; +import mod.azure.azurelib.rewrite.animation.event.AzCustomInstructionKeyframeEvent; +import mod.azure.azurelib.rewrite.animation.event.AzParticleKeyframeEvent; +import mod.azure.azurelib.rewrite.animation.event.AzSoundKeyframeEvent; +import org.jetbrains.annotations.Nullable; + +/** + * The AzKeyframeCallbacks class manages callbacks for different types of keyframe events, enabling the handling of + * sound, particle, and custom-defined keyframe instructions during an animation sequence. + * + * @param The type of entity or object this keyframe callback interacts with. + */ +public class AzKeyframeCallbacks { + + private static final AzKeyframeCallbacks NO_OP = new AzKeyframeCallbacks<>(null, null, null); + + @SuppressWarnings("unchecked") + public static AzKeyframeCallbacks noop() { + return (AzKeyframeCallbacks) NO_OP; + } + + private final @Nullable AzCustomKeyframeHandler customKeyframeHandler; + + private final @Nullable AzParticleKeyframeHandler particleKeyframeHandler; + + private final @Nullable AzSoundKeyframeHandler soundKeyframeHandler; + + private AzKeyframeCallbacks( + @Nullable AzCustomKeyframeHandler customKeyframeHandler, + @Nullable AzParticleKeyframeHandler particleKeyframeHandler, + @Nullable AzSoundKeyframeHandler soundKeyframeHandler + ) { + this.customKeyframeHandler = customKeyframeHandler; + this.particleKeyframeHandler = particleKeyframeHandler; + this.soundKeyframeHandler = soundKeyframeHandler; + } + + public @Nullable AzCustomKeyframeHandler customKeyframeHandler() { + return customKeyframeHandler; + } + + public @Nullable AzParticleKeyframeHandler particleKeyframeHandler() { + return particleKeyframeHandler; + } + + public @Nullable AzSoundKeyframeHandler soundKeyframeHandler() { + return soundKeyframeHandler; + } + + public static Builder builder() { + return new Builder<>(); + } + + public static class Builder { + + private @Nullable AzCustomKeyframeHandler customKeyframeHandler; + + private @Nullable AzParticleKeyframeHandler particleKeyframeHandler; + + private @Nullable AzSoundKeyframeHandler soundKeyframeHandler; + + private Builder() {} + + /** + * Applies the given {@link AzSoundKeyframeHandler} to this controller, for handling {@link AzSoundKeyframeEvent + * sound keyframe instructions}. + * + * @return this + */ + public Builder setSoundKeyframeHandler(AzSoundKeyframeHandler soundHandler) { + this.soundKeyframeHandler = soundHandler; + return this; + } + + /** + * Applies the given {@link AzParticleKeyframeHandler} to this controller, for handling + * {@link AzParticleKeyframeEvent particle keyframe instructions}. + * + * @return this + */ + public Builder setParticleKeyframeHandler(AzParticleKeyframeHandler particleHandler) { + this.particleKeyframeHandler = particleHandler; + return this; + } + + /** + * Applies the given {@link AzCustomKeyframeHandler} to this controller, for handling + * {@link AzCustomInstructionKeyframeEvent sound keyframe instructions}. + * + * @return this + */ + public Builder setCustomInstructionKeyframeHandler(AzCustomKeyframeHandler customInstructionHandler) { + this.customKeyframeHandler = customInstructionHandler; + return this; + } + + public AzKeyframeCallbacks build() { + return new AzKeyframeCallbacks<>(customKeyframeHandler, particleKeyframeHandler, soundKeyframeHandler); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeExecutor.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeExecutor.java new file mode 100644 index 000000000..f597dc7ca --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeExecutor.java @@ -0,0 +1,127 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.core.math.IValue; +import mod.azure.azurelib.core.molang.MolangParser; +import mod.azure.azurelib.core.molang.MolangQueries; +import mod.azure.azurelib.core.object.Axis; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationControllerTimer; +import mod.azure.azurelib.rewrite.animation.controller.AzBoneAnimationQueueCache; +import mod.azure.azurelib.rewrite.animation.primitive.AzQueuedAnimation; +import org.jetbrains.annotations.NotNull; + +import java.util.NoSuchElementException; + +/** + * AzKeyframeExecutor is a specialized implementation of {@link AzAbstractKeyframeExecutor}, designed to handle + * keyframe-based animations for animatable objects. It delegates animation control to an {@link AzAnimationController} + * and manages bone animation queues through an {@link AzBoneAnimationQueueCache}.
    + * This class processes and applies transformations such as rotation, position, and scale to bone animations, based on + * the current tick time and the keyframes associated with each bone animation. + * + * @param The type of the animatable object to which the keyframe animations will be applied + */ +public class AzKeyframeExecutor extends AzAbstractKeyframeExecutor { + + private final AzAnimationController animationController; + + private final AzBoneAnimationQueueCache boneAnimationQueueCache; + + public AzKeyframeExecutor( + AzAnimationController animationController, + AzBoneAnimationQueueCache boneAnimationQueueCache + ) { + this.animationController = animationController; + this.boneAnimationQueueCache = boneAnimationQueueCache; + } + + /** + * Handle the current animation's state modifications and translations + * + * @param crashWhenCantFindBone Whether the controller should throw an exception when unable to find the required + * bone, or continue with the remaining bones + */ + public void execute(@NotNull AzQueuedAnimation currentAnimation, T animatable, boolean crashWhenCantFindBone) { + AzKeyframeCallbackHandler keyframeCallbackHandler = animationController.keyframeManager().keyframeCallbackHandler(); + AzAnimationControllerTimer controllerTimer = animationController.controllerTimer(); + float transitionLength = animationController.animationProperties().transitionLength(); + + final double finalAdjustedTick = controllerTimer.getAdjustedTick(); + + MolangParser.INSTANCE.setMemoizedValue(MolangQueries.ANIM_TIME, () -> finalAdjustedTick / 20d); + + for (AzBoneAnimation boneAnimation : currentAnimation.animation().boneAnimations()) { + AzBoneAnimationQueue boneAnimationQueue = boneAnimationQueueCache.getOrNull(boneAnimation.boneName()); + + if (boneAnimationQueue == null) { + if (crashWhenCantFindBone) { + throw new NoSuchElementException("Could not find bone: " + boneAnimation.boneName()); + } + + continue; + } + + AzKeyframeStack> rotationKeyframes = boneAnimation.rotationKeyframes(); + AzKeyframeStack> positionKeyframes = boneAnimation.positionKeyframes(); + AzKeyframeStack> scaleKeyframes = boneAnimation.scaleKeyframes(); + double adjustedTick = controllerTimer.getAdjustedTick(); + + updateRotation(rotationKeyframes, boneAnimationQueue, adjustedTick); + updatePosition(positionKeyframes, boneAnimationQueue, adjustedTick); + updateScale(scaleKeyframes, boneAnimationQueue, adjustedTick); + } + + // TODO: Is this correct??? + controllerTimer.addToAdjustedTick(transitionLength); + + keyframeCallbackHandler.handle(animatable, controllerTimer.getAdjustedTick()); + } + + private void updateRotation( + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double adjustedTick + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + AzAnimationPoint x = getAnimationPointAtTick(keyframes.xKeyframes(), adjustedTick, true, Axis.X); + AzAnimationPoint y = getAnimationPointAtTick(keyframes.yKeyframes(), adjustedTick, true, Axis.Y); + AzAnimationPoint z = getAnimationPointAtTick(keyframes.zKeyframes(), adjustedTick, true, Axis.Z); + + queue.addRotations(x, y, z); + } + + private void updatePosition( + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double adjustedTick + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + AzAnimationPoint x = getAnimationPointAtTick(keyframes.xKeyframes(), adjustedTick, false, Axis.X); + AzAnimationPoint y = getAnimationPointAtTick(keyframes.yKeyframes(), adjustedTick, false, Axis.Y); + AzAnimationPoint z = getAnimationPointAtTick(keyframes.zKeyframes(), adjustedTick, false, Axis.Z); + + queue.addPositions(x, y, z); + } + + private void updateScale( + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double adjustedTick + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + AzAnimationPoint x = getAnimationPointAtTick(keyframes.xKeyframes(), adjustedTick, false, Axis.X); + AzAnimationPoint y = getAnimationPointAtTick(keyframes.yKeyframes(), adjustedTick, false, Axis.Y); + AzAnimationPoint z = getAnimationPointAtTick(keyframes.zKeyframes(), adjustedTick, false, Axis.Z); + + queue.addScales(x, y, z); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeLocation.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeLocation.java new file mode 100644 index 000000000..13858caa0 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeLocation.java @@ -0,0 +1,14 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.core.keyframe.Keyframe; + +/** + * A named pair object that stores a {@link Keyframe} and a double representing a temporally placed {@code Keyframe} + * + * @param keyframe The {@code Keyframe} at the tick time + * @param startTick The animation tick time at the start of this {@code Keyframe} + */ +public record AzKeyframeLocation>( + T keyframe, + double startTick +) {} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeManager.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeManager.java new file mode 100644 index 000000000..3c1da1ae4 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeManager.java @@ -0,0 +1,47 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.AzBoneAnimationQueueCache; +import mod.azure.azurelib.rewrite.animation.controller.AzBoneSnapshotCache; + +/** + * AzKeyframeManager is responsible for managing the keyframe-related operations in an animation system. It coordinates + * the execution, transition, and callback handling of animation keyframes through its associated components. + * + * @param the type of the animatable object being handled + */ +public class AzKeyframeManager { + + private final AzKeyframeCallbackHandler keyframeCallbackHandler; + + private final AzKeyframeExecutor keyframeExecutor; + + private final AzKeyframeTransitioner keyframeTransitioner; + + public AzKeyframeManager( + AzAnimationController animationController, + AzBoneAnimationQueueCache boneAnimationQueueCache, + AzBoneSnapshotCache boneSnapshotCache, + AzKeyframeCallbacks keyframeCallbacks + ) { + this.keyframeCallbackHandler = new AzKeyframeCallbackHandler<>(animationController, keyframeCallbacks); + this.keyframeExecutor = new AzKeyframeExecutor<>(animationController, boneAnimationQueueCache); + this.keyframeTransitioner = new AzKeyframeTransitioner<>( + animationController, + boneAnimationQueueCache, + boneSnapshotCache + ); + } + + public AzKeyframeCallbackHandler keyframeCallbackHandler() { + return keyframeCallbackHandler; + } + + public AzKeyframeExecutor keyframeExecutor() { + return keyframeExecutor; + } + + public AzKeyframeTransitioner keyframeTransitioner() { + return keyframeTransitioner; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeStack.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeStack.java new file mode 100644 index 000000000..f1fba098f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeStack.java @@ -0,0 +1,43 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.List; + +/** + * Stores a triplet of {@link AzKeyframe Keyframes} in an ordered stack + */ +public record AzKeyframeStack>( + List xKeyframes, + List yKeyframes, + List zKeyframes +) { + + public AzKeyframeStack() { + this(new ObjectArrayList<>(), new ObjectArrayList<>(), new ObjectArrayList<>()); + } + + public static > AzKeyframeStack from(AzKeyframeStack otherStack) { + return new AzKeyframeStack<>(otherStack.xKeyframes, otherStack.yKeyframes, otherStack.zKeyframes); + } + + public double getLastKeyframeTime() { + double xTime = 0; + double yTime = 0; + double zTime = 0; + + for (T frame : xKeyframes()) { + xTime += frame.length(); + } + + for (T frame : yKeyframes()) { + yTime += frame.length(); + } + + for (T frame : zKeyframes()) { + zTime += frame.length(); + } + + return Math.max(xTime, Math.max(yTime, zTime)); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeTransitioner.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeTransitioner.java new file mode 100644 index 000000000..d8a6b2971 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/AzKeyframeTransitioner.java @@ -0,0 +1,125 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe; + +import mod.azure.azurelib.core.math.IValue; +import mod.azure.azurelib.core.molang.MolangParser; +import mod.azure.azurelib.core.molang.MolangQueries; +import mod.azure.azurelib.core.object.Axis; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.AzBoneAnimationQueueCache; +import mod.azure.azurelib.rewrite.animation.controller.AzBoneSnapshotCache; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneSnapshot; + +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * AzKeyframeTransitioner is a specialized class for executing smooth animations and transitions between keyframes for + * bones in an animation system. It utilizes animation controllers, bone animation queue caches, and bone snapshot + * caches to manage and apply transitions for rotation, position, and scale of bones. + * + * @param The type of the animation data handled by the associated animation controller. + */ +public class AzKeyframeTransitioner extends AzAbstractKeyframeExecutor { + + private final AzAnimationController animationController; + + private final AzBoneAnimationQueueCache boneAnimationQueueCache; + + private final AzBoneSnapshotCache boneSnapshotCache; + + public AzKeyframeTransitioner( + AzAnimationController animationController, + AzBoneAnimationQueueCache boneAnimationQueueCache, + AzBoneSnapshotCache boneSnapshotCache + ) { + this.animationController = animationController; + this.boneAnimationQueueCache = boneAnimationQueueCache; + this.boneSnapshotCache = boneSnapshotCache; + } + + public void transition(Map bones, boolean crashWhenCantFindBone, double adjustedTick) { + var currentAnimation = animationController.currentAnimation(); + var transitionLength = animationController.animationProperties().transitionLength(); + + MolangParser.INSTANCE.setValue(MolangQueries.ANIM_TIME, () -> 0); + + for (var boneAnimation : currentAnimation.animation().boneAnimations()) { + var bone = bones.get(boneAnimation.boneName()); + + if (bone == null) { + if (crashWhenCantFindBone) + throw new NoSuchElementException("Could not find bone: " + boneAnimation.boneName()); + + continue; + } + + var queue = boneAnimationQueueCache.getOrNull(boneAnimation.boneName()); + var snapshot = boneSnapshotCache.getOrNull(boneAnimation.boneName()); + + var rotationKeyframes = boneAnimation.rotationKeyframes(); + var positionKeyframes = boneAnimation.positionKeyframes(); + var scaleKeyframes = boneAnimation.scaleKeyframes(); + + transitionRotation(adjustedTick, rotationKeyframes, queue, transitionLength, snapshot, bone); + transitionPosition(adjustedTick, positionKeyframes, queue, transitionLength, snapshot); + transitionScale(adjustedTick, scaleKeyframes, queue, transitionLength, snapshot); + } + } + + private void transitionRotation( + double adjustedTick, + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double transitionLength, + AzBoneSnapshot snapshot, + AzBone bone + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + var initialSnapshot = bone.getInitialAzSnapshot(); + var x = getAnimationPointAtTick(keyframes.xKeyframes(), 0, true, Axis.X); + var y = getAnimationPointAtTick(keyframes.yKeyframes(), 0, true, Axis.Y); + var z = getAnimationPointAtTick(keyframes.zKeyframes(), 0, true, Axis.Z); + + queue.addNextRotation(null, adjustedTick, transitionLength, snapshot, initialSnapshot, x, y, z); + } + + private void transitionPosition( + double adjustedTick, + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double transitionLength, + AzBoneSnapshot snapshot + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + var x = getAnimationPointAtTick(keyframes.xKeyframes(), 0, false, Axis.X); + var y = getAnimationPointAtTick(keyframes.yKeyframes(), 0, false, Axis.Y); + var z = getAnimationPointAtTick(keyframes.zKeyframes(), 0, false, Axis.Z); + + queue.addNextPosition(null, adjustedTick, transitionLength, snapshot, x, y, z); + } + + private void transitionScale( + double adjustedTick, + AzKeyframeStack> keyframes, + AzBoneAnimationQueue queue, + double transitionLength, + AzBoneSnapshot snapshot + ) { + if (keyframes.xKeyframes().isEmpty()) { + return; + } + + var x = getAnimationPointAtTick(keyframes.xKeyframes(), 0, false, Axis.X); + var y = getAnimationPointAtTick(keyframes.yKeyframes(), 0, false, Axis.Y); + var z = getAnimationPointAtTick(keyframes.zKeyframes(), 0, false, Axis.Z); + + queue.addNextScale(null, adjustedTick, transitionLength, snapshot, x, y, z); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzCustomKeyframeHandler.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzCustomKeyframeHandler.java new file mode 100644 index 000000000..81041d535 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzCustomKeyframeHandler.java @@ -0,0 +1,14 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe.handler; + +import mod.azure.azurelib.rewrite.animation.event.AzCustomInstructionKeyframeEvent; + +/** + * A handler for pre-defined custom instruction keyframes. When the keyframe is encountered, the + * {@link AzCustomKeyframeHandler#handle(AzCustomInstructionKeyframeEvent)} method will be called. You can then take + * whatever action you want at this point. + */ +@FunctionalInterface +public interface AzCustomKeyframeHandler { + + void handle(AzCustomInstructionKeyframeEvent event); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzParticleKeyframeHandler.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzParticleKeyframeHandler.java new file mode 100644 index 000000000..5e3c27068 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzParticleKeyframeHandler.java @@ -0,0 +1,14 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe.handler; + +import mod.azure.azurelib.rewrite.animation.event.AzParticleKeyframeEvent; + +/** + * A handler for when a predefined particle keyframe is hit. When the keyframe is encountered, the + * {@link AzParticleKeyframeHandler#handle(AzParticleKeyframeEvent)} method will be called. Spawn the particles/effects + * of your choice at this time. + */ +@FunctionalInterface +public interface AzParticleKeyframeHandler { + + void handle(AzParticleKeyframeEvent event); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzSoundKeyframeHandler.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzSoundKeyframeHandler.java new file mode 100644 index 000000000..e6d751e0c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/keyframe/handler/AzSoundKeyframeHandler.java @@ -0,0 +1,14 @@ +package mod.azure.azurelib.rewrite.animation.controller.keyframe.handler; + +import mod.azure.azurelib.rewrite.animation.event.AzSoundKeyframeEvent; + +/** + * A handler for when a predefined sound keyframe is hit. When the keyframe is encountered, the + * {@link AzSoundKeyframeHandler#handle(AzSoundKeyframeEvent)} method will be called. Play the sound(s) of your choice + * at this time. + */ +@FunctionalInterface +public interface AzSoundKeyframeHandler { + + void handle(AzSoundKeyframeEvent event); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/AzAnimationState.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/AzAnimationState.java new file mode 100644 index 000000000..5630454fb --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/AzAnimationState.java @@ -0,0 +1,41 @@ +package mod.azure.azurelib.rewrite.animation.controller.state; + +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; +import mod.azure.azurelib.rewrite.util.state.State; + +/** + * Represents an abstract animation state within the {@link AzAnimationControllerStateMachine}. Each concrete + * implementation of this class defines specific behavior for managing animations during state transitions.
    + *
    + * The animation state lifecycle consists of three primary methods: + *
      + *
    • {@code onEnter}: Invoked when the state is entered. This method is used for initializing the state.
    • + *
    • {@code onUpdate}: Should be implemented by subclasses to define the behavior during the state's execution.
    • + *
    • {@code onExit}: Invoked when transitioning out of the state. This method is used for cleanup or + * finalization.
    • + *
    + * + * @param the type of the animation context associated with this state + */ +public abstract class AzAnimationState implements State> { + + private boolean isActive; + + protected AzAnimationState() { + this.isActive = false; + } + + @Override + public void onEnter(AzAnimationControllerStateMachine.Context context) { + this.isActive = true; + } + + public boolean isActive() { + return isActive; + } + + @Override + public void onExit(AzAnimationControllerStateMachine.Context context) { + this.isActive = false; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPauseState.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPauseState.java new file mode 100644 index 000000000..537acc79a --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPauseState.java @@ -0,0 +1,25 @@ +package mod.azure.azurelib.rewrite.animation.controller.state.impl; + +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +/** + * Represents the paused state in the animation state machine. This state ensures no updates are applied to the + * animation while it is paused, maintaining its current state until it is transitioned back to a play or stop state. + * + * @param the type of animation managed by the state + */ +public final class AzAnimationPauseState extends AzAnimationPlayState { + + public AzAnimationPauseState() {} + + @Override + public void onEnter(AzAnimationControllerStateMachine.Context context) { + // Do nothing, because the pause state shouldn't reset on enter. + } + + @Override + public void onUpdate(AzAnimationControllerStateMachine.Context context) { + super.onUpdate(context); + // Pause state does not need to do anything. + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPlayState.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPlayState.java new file mode 100644 index 000000000..b9eabc93c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationPlayState.java @@ -0,0 +1,88 @@ +package mod.azure.azurelib.rewrite.animation.controller.state.impl; + +import mod.azure.azurelib.rewrite.animation.controller.state.AzAnimationState; +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +/** + * Represents a "play" state in an animation state machine. This state is responsible for managing the playing of + * animations either by starting from the beginning or playing subsequent animations. It ensures that the animation + * progresses based on the controller's timer and handles transitions when animations complete.
    + *
    + * Inherits general animation state behavior such as lifecycle management from {@link AzAnimationState}. + * + * @param the type of animation being managed + */ +public class AzAnimationPlayState extends AzAnimationState { + + public AzAnimationPlayState() {} + + @Override + public void onEnter(AzAnimationControllerStateMachine.Context context) { + super.onEnter(context); + var controller = context.animationController(); + var controllerTimer = controller.controllerTimer(); + + controllerTimer.reset(); + } + + @Override + public void onUpdate(AzAnimationControllerStateMachine.Context context) { + var controller = context.animationController(); + var controllerTimer = controller.controllerTimer(); + var currentAnimation = controller.currentAnimation(); + + if (currentAnimation == null) { + // If the current animation is null, we should try to play the next animation. + tryPlayNextOrStop(context); + return; + } + + currentAnimation.playBehavior().onUpdate(context); + + // At this point we have an animation currently playing. We need to query if that animation has finished. + + var animContext = context.animationContext(); + var animatable = animContext.animatable(); + var hasAnimationFinished = controllerTimer.getAdjustedTick() >= currentAnimation.animation().length(); + + if (hasAnimationFinished) { + currentAnimation.playBehavior().onFinish(context); + } + + if (context.stateMachine().isStopped()) { + // Nothing more to do at this point since we can't play the animation again, so return. + return; + } + + // The animation is still running at this point, proceed with updating the bones according to keyframes. + + var keyframeManager = controller.keyframeManager(); + var keyframeExecutor = keyframeManager.keyframeExecutor(); + var crashWhenCantFindBone = animContext.config().crashIfBoneMissing(); + + keyframeExecutor.execute(currentAnimation, animatable, crashWhenCantFindBone); + } + + private void tryPlayNextOrStop(AzAnimationControllerStateMachine.Context context) { + var controller = context.animationController(); + var stateMachine = context.stateMachine(); + var keyframeManager = controller.keyframeManager(); + var keyframeCallbackHandler = keyframeManager.keyframeCallbackHandler(); + + keyframeCallbackHandler.reset(); + + var animationQueue = controller.animationQueue(); + var nextAnimation = animationQueue.peek(); + + if (nextAnimation == null) { + // If we can't play the next animation for some reason, then there's nothing to play. + // So we should put the state machine in the 'stop' state. + stateMachine.stop(); + return; + } + + // If we can play the next animation successfully, then let's do that. + stateMachine.transition(); + controller.setCurrentAnimation(nextAnimation); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationStopState.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationStopState.java new file mode 100644 index 000000000..6c7d4271a --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationStopState.java @@ -0,0 +1,25 @@ +package mod.azure.azurelib.rewrite.animation.controller.state.impl; + +import mod.azure.azurelib.rewrite.animation.controller.state.AzAnimationState; +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +/** + * Represents the "stop" state in an animation state machine. This state is responsible for halting any ongoing + * animations and putting the animation controller into a minimal responsibility state where no further updates or + * actions are performed until a new state transition occurs.
    + *
    + * This state is typically used when an animation sequence has fully completed and no
    + *
    + * Inherits the general animation state behavior and lifecycle from {@link AzAnimationState}. + * + * @param the type of animation context associated with the state machine + */ +public final class AzAnimationStopState extends AzAnimationState { + + public AzAnimationStopState() {} + + @Override + public void onUpdate(AzAnimationControllerStateMachine.Context context) { + // Stop state does not need to do anything. + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationTransitionState.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationTransitionState.java new file mode 100644 index 000000000..c7789825a --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/impl/AzAnimationTransitionState.java @@ -0,0 +1,74 @@ +package mod.azure.azurelib.rewrite.animation.controller.state.impl; + +import mod.azure.azurelib.rewrite.animation.controller.state.AzAnimationState; +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +/** + * Represents a transition state in an animation state machine. This state is responsible for managing the transition + * between animations, including handling setup, updates, and transitioning to the appropriate play state when the + * transition is complete. The `AzAnimationTransitionState` extends the functionality of `AzAnimationState` to implement + * the behavior specific to transitioning between animations. It resets timers, initializes animations, and updates + * keyframes to create smooth transitions. + * + * @param the type of the animation context associated with this state + */ +public final class AzAnimationTransitionState extends AzAnimationState { + + public AzAnimationTransitionState() {} + + @Override + public void onEnter(AzAnimationControllerStateMachine.Context context) { + super.onEnter(context); + prepareTransition(context); + } + + @Override + public void onUpdate(AzAnimationControllerStateMachine.Context context) { + var controller = context.animationController(); + var controllerTimer = controller.controllerTimer(); + var animContext = context.animationContext(); + + var stateMachine = context.stateMachine(); + var boneCache = animContext.boneCache(); + + var transitionLength = controller.animationProperties().transitionLength(); + var hasFinishedTransitioning = controllerTimer.getAdjustedTick() >= transitionLength; + + if (hasFinishedTransitioning) { + // If we've exceeded the amount of time we should be transitioning, then switch to play state. + stateMachine.play(); + return; + } + + if (controller.currentAnimation() != null) { + var bones = boneCache.getBakedModel().getBonesByName(); + var crashWhenCantFindBone = animContext.config().crashIfBoneMissing(); + var keyframeTransitioner = controller.keyframeManager().keyframeTransitioner(); + + keyframeTransitioner.transition(bones, crashWhenCantFindBone, controllerTimer.getAdjustedTick()); + } + } + + private void prepareTransition(AzAnimationControllerStateMachine.Context context) { + var animContext = context.animationContext(); + var boneCache = animContext.boneCache(); + var controller = context.animationController(); + var boneSnapshotCache = controller.boneSnapshotCache(); + var controllerTimer = controller.controllerTimer(); + + controllerTimer.reset(); + controller.keyframeManager().keyframeCallbackHandler().reset(); + + var nextAnimation = controller.animationQueue().next(); + + if (nextAnimation == null) { + return; + } + + controller.setCurrentAnimation(nextAnimation); + + var snapshots = boneCache.getBoneSnapshotsByName(); + + boneSnapshotCache.put(nextAnimation, snapshots.values()); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/machine/AzAnimationControllerStateMachine.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/machine/AzAnimationControllerStateMachine.java new file mode 100644 index 000000000..6e40eb0c8 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/controller/state/machine/AzAnimationControllerStateMachine.java @@ -0,0 +1,105 @@ +package mod.azure.azurelib.rewrite.animation.controller.state.machine; + +import mod.azure.azurelib.rewrite.animation.AzAnimationContext; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.state.AzAnimationState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationPauseState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationPlayState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationStopState; +import mod.azure.azurelib.rewrite.animation.controller.state.impl.AzAnimationTransitionState; +import mod.azure.azurelib.rewrite.util.state.StateMachine; +import mod.azure.azurelib.rewrite.util.state.StateMachineContext; + +/** + * A state machine for managing animation controller states, providing functionality to transition between play, pause, + * stop, and transition states. It is generic and supports handling context and states specific to animations. + * + * @param the type of the animation the state machine controls + */ +public class AzAnimationControllerStateMachine extends StateMachine, AzAnimationState> { + + private final StateHolder stateHolder; + + public AzAnimationControllerStateMachine( + StateHolder stateHolder, + AzAnimationController animationController, + AzAnimationContext animationContext + ) { + super(stateHolder.stopState()); + this.stateHolder = stateHolder; + getContext().stateMachine = this; + getContext().animationController = animationController; + getContext().animationContext = animationContext; + } + + @Override + public Context createContext() { + return new Context<>(); + } + + public void update() { + super.update(getContext()); + } + + public void pause() { + setState(stateHolder.pauseState); + } + + public void play() { + setState(stateHolder.playState); + } + + public void transition() { + setState(stateHolder.transitionState); + } + + public void stop() { + setState(stateHolder.stopState); + } + + public boolean isPlaying() { + return getState() == stateHolder.playState; + } + + public boolean isPaused() { + return getState() == stateHolder.pauseState; + } + + public boolean isStopped() { + return getState() == stateHolder.stopState; + } + + public boolean isTransitioning() { + return getState() == stateHolder.transitionState; + } + + public record StateHolder( + AzAnimationPlayState playState, + AzAnimationPauseState pauseState, + AzAnimationStopState stopState, + AzAnimationTransitionState transitionState + ) {} + + public static class Context implements StateMachineContext { + + private AzAnimationContext animationContext; + + private AzAnimationController animationController; + + private AzAnimationControllerStateMachine stateMachine; + + private Context() {} + + public AzAnimationContext animationContext() { + return animationContext; + } + + public AzAnimationController animationController() { + return animationController; + } + + public AzAnimationControllerStateMachine stateMachine() { + return stateMachine; + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/AzDispatchSide.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/AzDispatchSide.java new file mode 100644 index 000000000..5b7e94900 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/AzDispatchSide.java @@ -0,0 +1,52 @@ +package mod.azure.azurelib.rewrite.animation.dispatch; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * This enum represents the dispatch side for animation commands, which can either be client-side or server-side. It is + * used as part of the AzureLib animation system for identifying where an animation command originates from or should be + * executed.
    + * Each enum constant has an associated unique identifier for easy lookup and transmission across the network. This + * mapping is also used within codecs for serialization and deserialization purposes. + */ +public enum AzDispatchSide implements StringRepresentable { + + CLIENT(0), + SERVER(1); + + private static final Map ID_TO_ENUM_MAP = new Int2ObjectArrayMap<>(); + + static { + // Populate the map for quick lookup + for (AzDispatchSide side : values()) { + ID_TO_ENUM_MAP.put(side.id, side); + } + } + + private final int id; + + AzDispatchSide(int id) { + this.id = id; + } + + public static final Function DECODER = buf -> { + int id = buf.readByte(); // Read byte and convert to int + return ID_TO_ENUM_MAP.get(id); // Get enum from ID + }; + + public static final BiConsumer ENCODER = (buf, val) -> { + buf.writeByte(val.id); // Write the integer ID to the buffer + }; + + @Override + public @NotNull String getSerializedName() { + return name(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommand.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommand.java new file mode 100644 index 000000000..f20620172 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommand.java @@ -0,0 +1,168 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.network.packet.AzBlockEntityDispatchCommandPacket; +import mod.azure.azurelib.network.packet.AzEntityDispatchCommandPacket; +import mod.azure.azurelib.network.packet.AzItemStackDispatchCommandPacket; +import mod.azure.azurelib.platform.Services; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehavior; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehaviors; +import mod.azure.azurelib.rewrite.util.codec.AzListStreamCodec; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Represents a command structure used to dispatch a sequence of actions in the animation system. This class primarily + * serves as a container for a list of {@link AzAction} instances that define specific operations or behaviors to be + * executed.
    + * The class provides support for building complex dispatch commands by leveraging the hierarchical builder system, + * enabling customization of animation-related functionality. + */ +public record AzCommand(List actions) { + + public static final AzListStreamCodec ACTION_LIST_CODEC = + new AzListStreamCodec<>(AzAction::decode, (buf, action) -> action.encode(buf)); + + public static final Function DECODER = buf -> { + // Decode the list of actions using the AzListStreamCodec + List actions = ACTION_LIST_CODEC.decode(buf); + return new AzCommand(actions); + }; + + public static final BiConsumer ENCODER = (buf, command) -> { + // Encode the list of actions using the AzListStreamCodec + ACTION_LIST_CODEC.encode(buf, command.actions()); + }; + + public static AzRootCommandBuilder builder() { + return new AzRootCommandBuilder(); + } + + public static AzCommand compose(Collection commands) { + if (commands.isEmpty()) { + throw new IllegalArgumentException("Attempted to compose an empty collection of commands."); + } else if (commands.size() == 1) { + return commands.iterator().next(); + } + + return new AzCommand( + commands.stream() + .flatMap(command -> command.actions().stream()) + .toList() + ); + } + + public static AzCommand compose(AzCommand first, AzCommand second, AzCommand... others) { + var allCommands = new ArrayList(); + + allCommands.add(first); + allCommands.add(second); + Collections.addAll(allCommands, others); + + return compose(allCommands); + } + + public static AzCommand create(String controllerName, String animationName) { + return create(controllerName, animationName, AzPlayBehaviors.PLAY_ONCE); + } + + /** + * Creates a dispatch command to play a specified animation on a given controller. + * + * @param controllerName the name of the animation controller on which the animation should be played + * @param animationName the name of the animation to be played on the specified controller + * @param playBehavior the play behavior for the animation to use + * @return an instance of {@code AzCommand} representing the command to play the desired animation + */ + public static AzCommand create(String controllerName, String animationName, AzPlayBehavior playBehavior) { + return builder() + .playSequence( + controllerName, + sequenceBuilder -> sequenceBuilder.queue(animationName, props -> props.withPlayBehavior(playBehavior)) + ) + .build(); + } + + /** + * Sends animation commands for the specified entity based on the configured dispatch origin. The method determines + * whether the command should proceed, logs a warning if it cannot, and dispatches the animation commands either + * from the client or the server side. + * + * @param entity the target {@link Entity} for which the animation commands are dispatched. + */ + public void sendForEntity(Entity entity) { + if (entity.level().isClientSide()) { + dispatchFromClient(entity); + } else { + var entityId = entity.getId(); + var packet = new AzEntityDispatchCommandPacket(entityId, this); + Services.NETWORK.sendToTrackingEntityAndSelf(packet, entity); + } + } + + /** + * Sends animation commands for the specified block entity based on the configured dispatch origin. The method + * determines whether the command should proceed, logs a warning if it cannot, and dispatches the animation commands + * either from the client or the server side. + * + * @param entity the target {@link BlockEntity} for which the animation commands are dispatched. + */ + public void sendForBlockEntity(BlockEntity entity) { + if (entity.getLevel().isClientSide()) { + dispatchFromClient(entity); + } else { + var entityBlockPos = entity.getBlockPos(); + var packet = new AzBlockEntityDispatchCommandPacket(entityBlockPos, this); + Services.NETWORK.sendToEntitiesTrackingChunk(packet, (ServerLevel) entity.getLevel(), entityBlockPos); + } + } + + /** + * Sends animation commands for the specified item based on the configured dispatch origin. The method determines + * whether the command can proceed, assigns a unique identifier to the item if required, and dispatches the + * animation commands either from the client or the server side. + * + * @param entity the {@link Entity} associated with the {@link ItemStack}. + * @param itemStack the {@link ItemStack} on which the animation commands are dispatched. + */ + public void sendForItem(Entity entity, ItemStack itemStack) { + if (entity.level().isClientSide()) { + dispatchFromClient(entity); + } else { + var uuid = itemStack.getTag().getUUID(AzureLib.ITEM_UUID_TAG); + + if (uuid == null) { + AzureLib.LOGGER.warn( + "Could not find item stack UUID during dispatch. Did you forget to register an identity for the item? Item: {}, Item Stack: {}", + itemStack.getItem(), + itemStack + ); + return; + } + + var packet = new AzItemStackDispatchCommandPacket(uuid, this); + Services.NETWORK.sendToTrackingEntityAndSelf(packet, entity); + } + } + + private void dispatchFromClient(T animatable) { + var animator = AzAnimatorAccessor.getOrNull(animatable); + + if (animator != null) { + actions.forEach(action -> action.handle(AzDispatchSide.CLIENT, animator)); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommandBuilder.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommandBuilder.java new file mode 100644 index 000000000..e0aeb1a51 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzCommandBuilder.java @@ -0,0 +1,19 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command; + +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AzCommandBuilder { + + protected final List actions; + + protected AzCommandBuilder() { + this.actions = new ArrayList<>(); + } + + public AzCommand build() { + return new AzCommand(actions); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzRootCommandBuilder.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzRootCommandBuilder.java new file mode 100644 index 000000000..5d7fc9bd6 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/AzRootCommandBuilder.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command; + +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root.*; +import mod.azure.azurelib.rewrite.animation.dispatch.command.sequence.AzAnimationSequenceBuilder; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; + +import java.util.function.UnaryOperator; + +public class AzRootCommandBuilder extends AzCommandBuilder { + + public AzRootCommandBuilder append(AzCommand command) { + actions.addAll(command.actions()); + return this; + } + + public AzRootCommandBuilder cancelAll() { + actions.add(AzRootCancelAllAction.INSTANCE); + return this; + } + + public AzRootCommandBuilder setEasingType(AzEasingType easingType) { + actions.add(new AzRootSetEasingTypeAction(easingType)); + return this; + } + + public AzRootCommandBuilder setSpeed(float speed) { + actions.add(new AzRootSetAnimationSpeedAction(speed)); + return this; + } + + public AzRootCommandBuilder setTransitionSpeed(float transitionSpeed) { + actions.add(new AzRootSetTransitionSpeedAction(transitionSpeed)); + return this; + } + + public AzRootCommandBuilder cancel(String controllerName) { + actions.add(new AzRootCancelAction(controllerName)); + return this; + } + + public AzRootCommandBuilder play(String controllerName, String animationName) { + return playSequence(controllerName, builder -> builder.queue(animationName)); + } + + public AzRootCommandBuilder playSequence( + String controllerName, + UnaryOperator builderUnaryOperator + ) { + var sequence = builderUnaryOperator.apply(new AzAnimationSequenceBuilder()).build(); + actions.add(new AzRootPlayAnimationSequenceAction(controllerName, sequence)); + return this; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/AzAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/AzAction.java new file mode 100644 index 000000000..c58f211cc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/AzAction.java @@ -0,0 +1,36 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.codec.AzActionCodec; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +/** + * The AzAction interface serves as a base contract for defining actions that can be dispatched within the animation + * system. It provides methods for handling an action and retrieving its unique resource location identifier. + * Implementations of this interface encapsulate specific animation-related behaviors, allowing for the modification or + * control of animation states or properties within an {@link AzAnimator}. + */ +public interface AzAction { + + /** + * Decodes an AzAction from a {@link FriendlyByteBuf}. + * Delegates to {@link AzActionCodec#decode(FriendlyByteBuf)} for decoding logic. + */ + static AzAction decode(FriendlyByteBuf byteBuf) { + return new AzActionCodec().decode(byteBuf); + } + + /** + * Encodes this AzAction into a {@link FriendlyByteBuf}. + * Delegates to {@link AzActionCodec#encode(FriendlyByteBuf, AzAction)} for encoding logic. + */ + default void encode(FriendlyByteBuf byteBuf) { + new AzActionCodec().encode(byteBuf, this); + } + + void handle(AzDispatchSide originSide, AzAnimator animator); + + ResourceLocation getResourceLocation(); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/codec/AzActionCodec.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/codec/AzActionCodec.java new file mode 100644 index 000000000..f300f5fd0 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/codec/AzActionCodec.java @@ -0,0 +1,69 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.codec; + +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.registry.AzActionRegistry; +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; + +/** + * The AzActionCodec class serves as an implementation of the {@link StreamCodec} interface specifically designed for + * encoding and decoding {@link AzAction} objects. This codec encodes and decodes AzAction instances using their + * associated resource locations and registered codecs within the {@link AzActionRegistry}.
    + * This class provides the necessary functionality to serialize an AzAction to a {@link FriendlyByteBuf} and deserialize + * it back, ensuring proper handling of resource location and associated data. It relies on the AzActionRegistry to + * dynamically retrieve the appropriate codec and handle the serialization or deserialization process.
    + * Use this implementation in scenarios where AzAction objects need to be serialized or deserialized for efficient data + * transmission or storage. + */ +/** + * The AzActionCodec class is responsible for encoding and decoding {@link AzAction} instances in 1.20.1, + * using the {@link FriendlyByteBuf} and the {@link AzActionRegistry} for maintaining associations + * between resource locations and their respective action codecs. + */ +public class AzActionCodec { + + public @NotNull AzAction decode(@NotNull FriendlyByteBuf byteBuf) { + // Decode the ID for the corresponding AzAction + var id = byteBuf.readShort(); + // Retrieve the action's codec using its ID from the registry + var codec = AzActionRegistry.getDecoderOrNull(id); + + if (codec == null) { + throw new NullPointerException( + "Could not find action decoder for a given action ID while decoding data. ID: " + id + ); + } + + // Use the codec to decode the AzAction + return codec.apply(byteBuf); + } + + public void encode(@NotNull FriendlyByteBuf byteBuf, @NotNull AzAction action) { + // Get the resource location for the AzAction + var resourceLocation = action.getResourceLocation(); + // Retrieve the corresponding ID and codec for the resource location + var id = AzActionRegistry.getIdOrNull(resourceLocation); + var encoder = AzActionRegistry.getEncoderOrNull(resourceLocation); + + if (id == null) { + throw new NullPointerException( + "Could not find action ID for a given resource location while encoding data. Resource Location: " + + resourceLocation + ); + } + + // Write the ID to the buffer + byteBuf.writeShort(id); + + if (encoder == null) { + throw new NullPointerException( + "Could not find action encoder for a given resource location while encoding data. Resource Location: " + + resourceLocation + ", ID: " + id + ); + } + + // Use the encoder to encode the AzAction + encoder.accept(byteBuf, action); + } +} + diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAction.java new file mode 100644 index 000000000..67d6266e5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAction.java @@ -0,0 +1,85 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Represents an action that cancels the current animation of a specified animation controller in the animation system. + * This action is part of the root-level dispatch actions and interacts with a specific animation controller by name. + *
    + *
    + * An instance of this record is serialized and deserialized using the {@code CODEC}, and it is associated with a unique + * resource location defined by {@code RESOURCE_LOCATION}.
    + *
    + * When executed, the {@code handle} method ensures that the animation of the targeted controller is stopped by setting + * its current animation to {@code null}.
    + *
    + * This class is primarily used within the {@code AzAnimator} context where each animation controller is part of the + * animator's controller container.
    + *
    + * Implements: - {@link AzAction}: Allows the action to be dispatched within the animation system.
    + *
    + * Fields: + *
      + *
    • {@code controllerName}: The name of the animation controller which this action targets.
    • + *
    + *
    + *
    + * Constants: + *
      + *
    • {@code CODEC}: A codec for encoding and decoding this action during network communication.
    • + *
    • {@code RESOURCE_LOCATION}: A unique identifier for this action.
    • + *
    + *
    + *
    + * Methods: + *
      + *
    • {@code handle(AzAnimator animator)}: Stops the current animation of the specified controller within the + * animator's animation controller container.
    • + *
    • {@code getResourceLocation()}: Returns the unique resource location associated with this action.
    • + *
    + */ +public record AzRootCancelAction( + String controllerName +) implements AzAction { + + public static final Function DECODER = buf -> { + String controllerName = buf.readUtf(); // Read UTF-8 string for the controller's name + return new AzRootCancelAction(controllerName); + }; + + public static final BiConsumer ENCODER = (buf, action) -> { + buf.writeUtf(action.controllerName()); // Write UTF-8 string for the controller's name + }; + + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/cancel"); + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + var controller = animator.getAnimationControllerContainer().getOrNull(controllerName); + + if (controller != null) { + controller.setCurrentAnimation(null); + } + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootCancelAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); // Delegate to the DECODER functional interface + } + + public static void encode(FriendlyByteBuf buf, AzRootCancelAction action) { + ENCODER.accept(buf, action); // Delegate to the ENCODER functional interface + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAllAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAllAction.java new file mode 100644 index 000000000..abdbf64ee --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootCancelAllAction.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * The AzRootCancelAllAction class implements the AzAction interface and defines an action that cancels all ongoing + * animations within an animator by setting the current animation of all controllers to null.
    + * This class is designed to work within a system that manages animations for objects using animation controllers. Once + * this action is handled, all animation controllers associated with a specific animator will have their current + * animations cleared. + */ +public class AzRootCancelAllAction implements AzAction { + + public static final AzRootCancelAllAction INSTANCE = new AzRootCancelAllAction(); + + public static final Function DECODER = buf -> INSTANCE; + + public static final BiConsumer ENCODER = (buf, action) -> { + // No data to write since this is a singleton + }; + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/cancel_all"); + + private AzRootCancelAllAction() {} + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + var controllerContainer = animator.getAnimationControllerContainer(); + var controllers = controllerContainer.getAll(); + + controllers.forEach(controller -> controller.setCurrentAnimation(null)); + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootCancelAllAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); // Always returns the singleton instance + } + + public static void encode(FriendlyByteBuf buf, AzRootCancelAllAction action) { + ENCODER.accept(buf, action); // Does nothing since no data is encoded + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootPlayAnimationSequenceAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootPlayAnimationSequenceAction.java new file mode 100644 index 000000000..4c8658bef --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootPlayAnimationSequenceAction.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import mod.azure.azurelib.rewrite.animation.dispatch.command.sequence.AzAnimationSequence; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public record AzRootPlayAnimationSequenceAction( + String controllerName, + AzAnimationSequence sequence +) implements AzAction { + + public static final Function DECODER = buf -> { + String controllerName = buf.readUtf(); // Read controller name (UTF string) + AzAnimationSequence sequence = AzAnimationSequence.DECODER.apply(buf); // Decode AzAnimationSequence + return new AzRootPlayAnimationSequenceAction(controllerName, sequence); // Create new instance + }; + + public static final BiConsumer ENCODER = (buf, action) -> { + buf.writeUtf(action.controllerName()); // Write controller name (UTF string) + AzAnimationSequence.ENCODER.accept(buf, action.sequence()); // Encode AzAnimationSequence + }; + + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/play_animation_sequence"); + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + var controller = animator.getAnimationControllerContainer().getOrNull(controllerName); + + if (controller != null) { + controller.run(originSide, sequence); + } + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootPlayAnimationSequenceAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); // Delegate decoding to DECODER + } + + public static void encode(FriendlyByteBuf buf, AzRootPlayAnimationSequenceAction action) { + ENCODER.accept(buf, action); // Delegate encoding to ENCODER + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetAnimationSpeedAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetAnimationSpeedAction.java new file mode 100644 index 000000000..f819940b1 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetAnimationSpeedAction.java @@ -0,0 +1,51 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public record AzRootSetAnimationSpeedAction( + double animationSpeed +) implements AzAction { + + public static final Function DECODER = buf -> { + double animationSpeed = buf.readDouble(); // Read double from the buffer + return new AzRootSetAnimationSpeedAction(animationSpeed); // Create a new instance + }; + + public static final BiConsumer ENCODER = (buf, action) -> { + buf.writeDouble(action.animationSpeed()); // Write the animation speed to the buffer + }; + + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/set_animation_speed"); + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + animator.getAnimationControllerContainer() + .getAll() + .forEach( + controller -> controller.setAnimationProperties( + controller.animationProperties().withAnimationSpeed(animationSpeed) + ) + ); + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootSetAnimationSpeedAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); // Delegate decoding to DECODER + } + + public static void encode(FriendlyByteBuf buf, AzRootSetAnimationSpeedAction action) { + ENCODER.accept(buf, action); // Delegate encoding to ENCODER + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetEasingTypeAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetEasingTypeAction.java new file mode 100644 index 000000000..e8d163136 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetEasingTypeAction.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public record AzRootSetEasingTypeAction( + AzEasingType easingType +) implements AzAction { + + public static final Function DECODER = buf -> { + AzEasingType easingType = AzEasingType.DECODER.apply(buf); + return new AzRootSetEasingTypeAction(easingType); + }; + + public static final BiConsumer ENCODER = (buf, action) -> { + AzEasingType.ENCODER.accept(buf, action.easingType()); + }; + + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/set_easing_type"); + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + animator.getAnimationControllerContainer() + .getAll() + .forEach( + controller -> controller.setAnimationProperties( + controller.animationProperties().withEasingType(easingType) + ) + ); + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootSetEasingTypeAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); + } + + public static void encode(FriendlyByteBuf buf, AzRootSetEasingTypeAction action) { + ENCODER.accept(buf, action); + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetTransitionSpeedAction.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetTransitionSpeedAction.java new file mode 100644 index 000000000..98c245e63 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/impl/root/AzRootSetTransitionSpeedAction.java @@ -0,0 +1,59 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root; + +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.dispatch.AzDispatchSide; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Represents an action that sets the transition speed of animation controllers within an {@link AzAnimator}. + * This action is part of the AzureLib animation system and provides a means to modify the transition length + * property of all animation controllers contained in the target animator. + * + * The {@link AzRootSetTransitionSpeedAction} encapsulates a single `transitionSpeed` value, which determines + * the length of animation transition in seconds when applied during animation state changes. + */ +public record AzRootSetTransitionSpeedAction( + float transitionSpeed +) implements AzAction { + + public static final Function DECODER = buf -> { + float transitionSpeed = buf.readFloat(); // Read float from the buffer + return new AzRootSetTransitionSpeedAction(transitionSpeed); // Create a new instance + }; + + public static final BiConsumer ENCODER = (buf, action) -> { + buf.writeFloat(action.transitionSpeed()); // Write the transition speed to the buffer + }; + + public static final ResourceLocation RESOURCE_LOCATION = AzureLib.modResource("root/set_transition_speed"); + + @Override + public void handle(AzDispatchSide originSide, AzAnimator animator) { + animator.getAnimationControllerContainer() + .getAll() + .forEach( + controller -> controller.setAnimationProperties( + controller.animationProperties().withTransitionLength(transitionSpeed) + ) + ); + } + + @Override + public ResourceLocation getResourceLocation() { + return RESOURCE_LOCATION; + } + + public static AzRootSetTransitionSpeedAction decode(FriendlyByteBuf buf) { + return DECODER.apply(buf); // Delegate decoding to DECODER + } + + public static void encode(FriendlyByteBuf buf, AzRootSetTransitionSpeedAction action) { + ENCODER.accept(buf, action); // Delegate encoding to ENCODER + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/registry/AzActionRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/registry/AzActionRegistry.java new file mode 100644 index 000000000..1c09cfa64 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/action/registry/AzActionRegistry.java @@ -0,0 +1,121 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.action.registry; + +import it.unimi.dsi.fastutil.objects.Object2ShortArrayMap; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.AzAction; +import mod.azure.azurelib.rewrite.animation.dispatch.command.action.impl.root.*; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * The AzActionRegistry class serves as a centralized registry for mapping {@link AzAction} implementations to their + * associated {@link ResourceLocation} identifiers and corresponding encoders/decoders. This registry enables efficient + * encoding, decoding, and dispatching of animation-related actions within the animation system.
    + * Key Responsibilities: + *
      + *
    • Maintain a bidirectional mapping between {@link ResourceLocation} identifiers and short integer IDs for efficient + * serialization/deserialization.
    • + *
    • Register {@link AzAction} implementations and their corresponding encoders and decoders.
    • + *
    • Provide methods for retrieving encoders/decoders and IDs based on resource locations or integer IDs. + *
    + */ +public class AzActionRegistry { + + // Mappings for resource location to ID and encoders/decoders by ID + private static final Map RESOURCE_LOCATION_TO_ID = new Object2ShortArrayMap<>(); + + private static final Map> DECODERS_BY_ID = new HashMap<>(); + + private static final Map> ENCODERS_BY_ID = new HashMap<>(); + + private static short NEXT_FREE_ID = 0; + + static { + // Register root actions + register( + AzRootCancelAction.RESOURCE_LOCATION, + AzRootCancelAction::decode, // Decoder function + AzRootCancelAction::encode // Encoder function + ); + register( + AzRootCancelAllAction.RESOURCE_LOCATION, + AzRootCancelAllAction::decode, + AzRootCancelAllAction::encode + ); + register( + AzRootPlayAnimationSequenceAction.RESOURCE_LOCATION, + AzRootPlayAnimationSequenceAction::decode, + AzRootPlayAnimationSequenceAction::encode + ); + register( + AzRootSetAnimationSpeedAction.RESOURCE_LOCATION, + AzRootSetAnimationSpeedAction::decode, + AzRootSetAnimationSpeedAction::encode + ); + register( + AzRootSetEasingTypeAction.RESOURCE_LOCATION, + AzRootSetEasingTypeAction::decode, + AzRootSetEasingTypeAction::encode + ); + register( + AzRootSetTransitionSpeedAction.RESOURCE_LOCATION, + AzRootSetTransitionSpeedAction::decode, + AzRootSetTransitionSpeedAction::encode + ); + } + + /** + * Returns a decoder function for the given {@link ResourceLocation}. + */ + public static @Nullable Function getDecoderOrNull(ResourceLocation resourceLocation) { + var id = RESOURCE_LOCATION_TO_ID.get(resourceLocation); + return DECODERS_BY_ID.get(id); + } + + /** + * Returns a decoder function for the given ID. + */ + public static @Nullable Function getDecoderOrNull(short id) { + return DECODERS_BY_ID.get(id); + } + + /** + * Returns an encoder function for the given {@link ResourceLocation}. + */ + public static @Nullable BiConsumer getEncoderOrNull(ResourceLocation resourceLocation) { + var id = RESOURCE_LOCATION_TO_ID.get(resourceLocation); + return ENCODERS_BY_ID.get(id); + } + + /** + * Returns an encoder function for the given ID. + */ + public static @Nullable BiConsumer getEncoderOrNull(short id) { + return ENCODERS_BY_ID.get(id); + } + + /** + * Returns the ID associated with a given {@link ResourceLocation}. + */ + public static @Nullable Short getIdOrNull(ResourceLocation resourceLocation) { + return RESOURCE_LOCATION_TO_ID.get(resourceLocation); + } + + /** + * Registers a new action with its resource location, decoder, and encoder. + */ + private static
    void register( + ResourceLocation resourceLocation, + Function decoder, + BiConsumer encoder + ) { + var id = RESOURCE_LOCATION_TO_ID.computeIfAbsent(resourceLocation, ($) -> NEXT_FREE_ID++); + DECODERS_BY_ID.put(id, (Function) decoder); + ENCODERS_BY_ID.put(id, (BiConsumer) encoder); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequence.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequence.java new file mode 100644 index 000000000..18ee2a4af --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequence.java @@ -0,0 +1,27 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.sequence; + +import mod.azure.azurelib.rewrite.animation.dispatch.command.stage.AzAnimationStage; +import mod.azure.azurelib.rewrite.util.codec.AzListStreamCodec; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public record AzAnimationSequence( + List stages +) { + + private static final AzListStreamCodec STAGE_LIST_CODEC = + new AzListStreamCodec<>(AzAnimationStage.DECODER, AzAnimationStage.ENCODER); + + public static final Function DECODER = buf -> { + List stages = STAGE_LIST_CODEC.decode(buf); + return new AzAnimationSequence(stages); + }; + + public static final BiConsumer ENCODER = (buf, sequence) -> { + STAGE_LIST_CODEC.encode(buf, sequence.stages()); + }; + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequenceBuilder.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequenceBuilder.java new file mode 100644 index 000000000..da07e3901 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/sequence/AzAnimationSequenceBuilder.java @@ -0,0 +1,35 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.sequence; + +import mod.azure.azurelib.rewrite.animation.dispatch.command.stage.AzAnimationStage; +import mod.azure.azurelib.rewrite.animation.property.AzAnimationStageProperties; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.UnaryOperator; + +public class AzAnimationSequenceBuilder { + + private final List stages; + + public AzAnimationSequenceBuilder() { + this.stages = new ArrayList<>(); + } + + public AzAnimationSequenceBuilder queue(String animationName) { + stages.add(new AzAnimationStage(animationName, AzAnimationStageProperties.EMPTY)); + return this; + } + + public AzAnimationSequenceBuilder queue( + String animationName, + UnaryOperator builderUnaryOperator + ) { + var properties = builderUnaryOperator.apply(AzAnimationStageProperties.EMPTY); + stages.add(new AzAnimationStage(animationName, properties)); + return this; + } + + public AzAnimationSequence build() { + return new AzAnimationSequence(stages); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/stage/AzAnimationStage.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/stage/AzAnimationStage.java new file mode 100644 index 000000000..84886e91d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/dispatch/command/stage/AzAnimationStage.java @@ -0,0 +1,26 @@ +package mod.azure.azurelib.rewrite.animation.dispatch.command.stage; + +import mod.azure.azurelib.rewrite.animation.property.AzAnimationStageProperties; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public record AzAnimationStage( + String name, + AzAnimationStageProperties properties +) { + + public static final Function DECODER = buf -> { + String name = buf.readUtf(); + AzAnimationStageProperties properties = AzAnimationStageProperties.DECODER.apply(buf); + return new AzAnimationStage(name, properties); + }; + + public static final BiConsumer ENCODER = (buf, stage) -> { + buf.writeUtf(stage.name()); + AzAnimationStageProperties.ENCODER.accept(buf, stage.properties()); + }; + + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingType.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingType.java new file mode 100644 index 000000000..50176a2b8 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingType.java @@ -0,0 +1,42 @@ +package mod.azure.azurelib.rewrite.animation.easing; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import mod.azure.azurelib.core.utils.Interpolations; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzAnimationPoint; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public interface AzEasingType { + + String name(); + + Double2DoubleFunction buildTransformer(Double value); + + Function DECODER = buf -> + Objects.requireNonNull(AzEasingTypeRegistry.getOrNull(buf.readUtf())); + + BiConsumer ENCODER = (buf, val) -> buf.writeUtf(val.name()); + + default double apply(AzAnimationPoint animationPoint) { + Double easingVariable = null; + + if (animationPoint.keyframe() != null && animationPoint.keyframe().easingArgs().size() > 0) + easingVariable = animationPoint.keyframe().easingArgs().get(0).get(); + + return apply(animationPoint, easingVariable, animationPoint.currentTick() / animationPoint.transitionLength()); + } + + default double apply(AzAnimationPoint animationPoint, Double easingValue, double lerpValue) { + if (animationPoint.currentTick() >= animationPoint.transitionLength()) + return (float) animationPoint.animationEndValue(); + + return Interpolations.lerp( + animationPoint.animationStartValue(), + animationPoint.animationEndValue(), + buildTransformer(easingValue).apply(lerpValue) + ); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeLoader.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeLoader.java new file mode 100644 index 000000000..65963f051 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeLoader.java @@ -0,0 +1,33 @@ +package mod.azure.azurelib.rewrite.animation.easing; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import java.util.Locale; + +public class AzEasingTypeLoader { + + /** + * Retrieve an {@code EasingType} instance based on a {@link JsonElement}. Returns one of the default + * {@code EasingTypes} if the name matches, or any other registered {@code EasingType} with a matching name. + * + * @param json The {@code easing} {@link JsonElement} to attempt to parse. + * @return A usable {@code EasingType} instance + */ + public static AzEasingType fromJson(JsonElement json) { + if (!(json instanceof JsonPrimitive primitive) || !primitive.isString()) + return AzEasingTypes.LINEAR; + + return fromString(primitive.getAsString().toLowerCase(Locale.ROOT)); + } + + /** + * Get an existing {@code EasingType} from a given string, matching the string to its name. + * + * @param name The name of the easing function + * @return The relevant {@code EasingType}, or {@link AzEasingTypes#LINEAR} if none match + */ + public static AzEasingType fromString(String name) { + return AzEasingTypeRegistry.getOrDefault(name, AzEasingTypes.LINEAR); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeRegistry.java new file mode 100644 index 000000000..ff86f4ccd --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypeRegistry.java @@ -0,0 +1,56 @@ +package mod.azure.azurelib.rewrite.animation.easing; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class AzEasingTypeRegistry { + + private static final Map EASING_TYPES = new HashMap<>(); + + /** + * Register an {@code EasingType} with AzureLib for handling animation transitions and value curves.
    + * MUST be called during mod construct
    + * It is recommended you don't call this directly, and instead call it via {@code AzureLibUtil#addCustomEasingType} + * + * @param name The name of the easing type + * @param transformer The {@code Double2DoubleFunction} to associate with the given name + * @return The {@code EasingType} you registered + */ + public static AzEasingType register(String name, Function transformer) { + return EASING_TYPES.computeIfAbsent(name, ($) -> new AzEasingType() { + + @Override + public String name() { + return name; + } + + @Override + public Double2DoubleFunction buildTransformer(Double value) { + return transformer.apply(value); + } + }); + } + + public static AzEasingType register(String name, AzEasingType easingType) { + return register(name, easingType::buildTransformer); + } + + public static AzEasingType getOrDefault(String name, @NotNull AzEasingType defaultValue) { + return EASING_TYPES.getOrDefault(name, defaultValue); + } + + public static @Nullable AzEasingType getOrNull(String name) { + return EASING_TYPES.get(name); + } + + public static Collection getValues() { + return Collections.unmodifiableCollection(EASING_TYPES.values()); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypes.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypes.java new file mode 100644 index 000000000..3d82506c5 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingTypes.java @@ -0,0 +1,218 @@ +package mod.azure.azurelib.rewrite.animation.easing; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import mod.azure.azurelib.core.utils.Interpolations; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzAnimationPoint; + +public class AzEasingTypes { + + public static final AzEasingType NONE = AzEasingTypeRegistry.register( + "none", + value -> AzEasingUtil.easeIn(AzEasingUtil::linear) + ); + + public static final AzEasingType LINEAR = AzEasingTypeRegistry.register("linear", NONE); + + public static final AzEasingType STEP = AzEasingTypeRegistry.register( + "step", + value -> AzEasingUtil.easeIn(AzEasingUtil.step(value)) + ); + + public static final AzEasingType EASE_IN_SINE = AzEasingTypeRegistry.register( + "easeinsine", + value -> AzEasingUtil.easeIn(AzEasingUtil::sine) + ); + + public static final AzEasingType EASE_OUT_SINE = AzEasingTypeRegistry.register( + "easeoutsine", + value -> AzEasingUtil.easeOut(AzEasingUtil::sine) + ); + + public static final AzEasingType EASE_IN_OUT_SINE = AzEasingTypeRegistry.register( + "easeinoutsine", + value -> AzEasingUtil.easeInOut(AzEasingUtil::sine) + ); + + public static final AzEasingType EASE_IN_QUAD = AzEasingTypeRegistry.register( + "easeinquad", + value -> AzEasingUtil.easeIn(AzEasingUtil::quadratic) + ); + + public static final AzEasingType EASE_OUT_QUAD = AzEasingTypeRegistry.register( + "easeoutquad", + value -> AzEasingUtil.easeOut(AzEasingUtil::quadratic) + ); + + public static final AzEasingType EASE_IN_OUT_QUAD = AzEasingTypeRegistry.register( + "easeinoutquad", + value -> AzEasingUtil.easeInOut(AzEasingUtil::quadratic) + ); + + public static final AzEasingType EASE_IN_CUBIC = AzEasingTypeRegistry.register( + "easeincubic", + value -> AzEasingUtil.easeIn(AzEasingUtil::cubic) + ); + + public static final AzEasingType EASE_OUT_CUBIC = AzEasingTypeRegistry.register( + "easeoutcubic", + value -> AzEasingUtil.easeOut(AzEasingUtil::cubic) + ); + + public static final AzEasingType EASE_IN_OUT_CUBIC = AzEasingTypeRegistry.register( + "easeinoutcubic", + value -> AzEasingUtil.easeInOut(AzEasingUtil::cubic) + ); + + public static final AzEasingType EASE_IN_QUART = AzEasingTypeRegistry.register( + "easeinquart", + value -> AzEasingUtil.easeIn(AzEasingUtil.pow(4)) + ); + + public static final AzEasingType EASE_OUT_QUART = AzEasingTypeRegistry.register( + "easeoutquart", + value -> AzEasingUtil.easeOut(AzEasingUtil.pow(4)) + ); + + public static final AzEasingType EASE_IN_OUT_QUART = AzEasingTypeRegistry.register( + "easeinoutquart", + value -> AzEasingUtil.easeInOut(AzEasingUtil.pow(4)) + ); + + public static final AzEasingType EASE_IN_QUINT = AzEasingTypeRegistry.register( + "easeinquint", + value -> AzEasingUtil.easeIn(AzEasingUtil.pow(4)) + ); + + public static final AzEasingType EASE_OUT_QUINT = AzEasingTypeRegistry.register( + "easeoutquint", + value -> AzEasingUtil.easeOut(AzEasingUtil.pow(5)) + ); + + public static final AzEasingType EASE_IN_OUT_QUINT = AzEasingTypeRegistry.register( + "easeinoutquint", + value -> AzEasingUtil.easeInOut(AzEasingUtil.pow(5)) + ); + + public static final AzEasingType EASE_IN_EXPO = AzEasingTypeRegistry.register( + "easeinexpo", + value -> AzEasingUtil.easeIn(AzEasingUtil::exp) + ); + + public static final AzEasingType EASE_OUT_EXPO = AzEasingTypeRegistry.register( + "easeoutexpo", + value -> AzEasingUtil.easeOut(AzEasingUtil::exp) + ); + + public static final AzEasingType EASE_IN_OUT_EXPO = AzEasingTypeRegistry.register( + "easeinoutexpo", + value -> AzEasingUtil.easeInOut(AzEasingUtil::exp) + ); + + public static final AzEasingType EASE_IN_CIRC = AzEasingTypeRegistry.register( + "easeincirc", + value -> AzEasingUtil.easeIn(AzEasingUtil::circle) + ); + + public static final AzEasingType EASE_OUT_CIRC = AzEasingTypeRegistry.register( + "easeoutcirc", + value -> AzEasingUtil.easeOut(AzEasingUtil::circle) + ); + + public static final AzEasingType EASE_IN_OUT_CIRC = AzEasingTypeRegistry.register( + "easeinoutcirc", + value -> AzEasingUtil.easeInOut(AzEasingUtil::circle) + ); + + public static final AzEasingType EASE_IN_BACK = AzEasingTypeRegistry.register( + "easeinback", + value -> AzEasingUtil.easeIn(AzEasingUtil.back(value)) + ); + + public static final AzEasingType EASE_OUT_BACK = AzEasingTypeRegistry.register( + "easeoutback", + value -> AzEasingUtil.easeOut(AzEasingUtil.back(value)) + ); + + public static final AzEasingType EASE_IN_OUT_BACK = AzEasingTypeRegistry.register( + "easeinoutback", + value -> AzEasingUtil.easeInOut(AzEasingUtil.back(value)) + ); + + public static final AzEasingType EASE_IN_ELASTIC = AzEasingTypeRegistry.register( + "easeinelastic", + value -> AzEasingUtil.easeIn(AzEasingUtil.elastic(value)) + ); + + public static final AzEasingType EASE_OUT_ELASTIC = AzEasingTypeRegistry.register( + "easeoutelastic", + value -> AzEasingUtil.easeOut(AzEasingUtil.elastic(value)) + ); + + public static final AzEasingType EASE_IN_OUT_ELASTIC = AzEasingTypeRegistry.register( + "easeinoutelastic", + value -> AzEasingUtil.easeInOut(AzEasingUtil.elastic(value)) + ); + + public static final AzEasingType EASE_IN_BOUNCE = AzEasingTypeRegistry.register( + "easeinbounce", + value -> AzEasingUtil.easeIn(AzEasingUtil.bounce(value)) + ); + + public static final AzEasingType EASE_OUT_BOUNCE = AzEasingTypeRegistry.register( + "easeoutbounce", + value -> AzEasingUtil.easeOut(AzEasingUtil.bounce(value)) + ); + + public static final AzEasingType EASE_IN_OUT_BOUNCE = AzEasingTypeRegistry.register( + "easeinoutbounce", + value -> AzEasingUtil.easeInOut(AzEasingUtil.bounce(value)) + ); + + public static final AzEasingType CATMULLROM = AzEasingTypeRegistry.register( + "catmullrom", + new AzEasingType() { + @Override + public String name() { + return "Catmull-Rom"; + } + + @Override + public Double2DoubleFunction buildTransformer(Double value) { + return AzEasingUtil.easeInOut(AzEasingUtil::catmullRom); + } + + @Override + public double apply(AzAnimationPoint animationPoint, Double easingValue, double lerpValue) { + if (animationPoint.currentTick() >= animationPoint.transitionLength()) { + return animationPoint.animationEndValue(); + } + + var easingArgs = animationPoint.keyframe().easingArgs(); + + if (easingArgs.size() < 2) + return Interpolations.lerp( + buildTransformer(easingValue).apply(lerpValue), + animationPoint.animationStartValue(), + animationPoint.animationEndValue() + ); + + return AzEasingUtil.catmullRom( + lerpValue, + easingArgs.get(0).get(), + animationPoint.animationStartValue(), + animationPoint.animationEndValue(), + easingArgs.get(1).get() + ); + } + } + ); + + public static AzEasingType random() { + var collection = AzEasingTypeRegistry.getValues(); + + return collection.stream() + .skip((int) (collection.size() * Math.random())) + .findFirst() + .orElse(null); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingUtil.java new file mode 100644 index 000000000..209892aab --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/easing/AzEasingUtil.java @@ -0,0 +1,265 @@ +package mod.azure.azurelib.rewrite.animation.easing; + +import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzAnimationPoint; + +public class AzEasingUtil { + + /** + * Returns an easing function running linearly. Functionally equivalent to no easing + */ + public static Double2DoubleFunction linear(Double2DoubleFunction function) { + return function; + } + + /** + * Performs a Catmull-Rom interpolation, used to get smooth interpolated motion between keyframes.
    + *
    CatmullRom#position + * + * @param delta The interpolation parameter (between 0 and 1) + * @param p0 First control point (anchor) + * @param p1 Second control point (start point) + * @param p2 Third control point (end point) + * @param p3 Fourth control point (anchor) + * @return The interpolated value + */ + public static double catmullRom(double delta, double p0, double p1, double p2, double p3) { + return 0.5d * (2d * p1 + (p2 - p0) * delta + + (2d * p0 - 5d * p1 + 4d * p2 - p3) * delta * delta + + (3d * p1 - p0 - 3d * p2 + p3) * delta * delta * delta); + } + + /** + * Simplified Catmull-Rom interpolation for single parameter + * @param n The interpolation parameter + * @return The interpolated value + */ + public static double catmullRom(double n) {// Using default control points for simple interpolation + return catmullRom(n, 0, 0, 1, 1); + } + + /** + * Returns an easing function running forward in time + */ + public static Double2DoubleFunction easeIn(Double2DoubleFunction function) { + return function; + } + + // ---> Easing Transition Type Functions <--- // + + /** + * Returns an easing function running backwards in time + */ + public static Double2DoubleFunction easeOut(Double2DoubleFunction function) { + return time -> 1 - function.apply(1 - time); + } + + /** + * Returns an easing function that runs equally both forwards and backwards in time based on the halfway point, + * generating a symmetrical curve.
    + */ + public static Double2DoubleFunction easeInOut(Double2DoubleFunction function) { + return time -> { + if (time < 0.5d) + return function.apply(time * 2d) / 2d; + + return 1 - function.apply((1 - time) * 2d) / 2d; + }; + } + + /** + * Returns a stepping function that returns 1 for any input value greater than 0, or otherwise returning 0 + */ + public static Double2DoubleFunction stepPositive(Double2DoubleFunction function) { + return n -> n > 0 ? 1 : 0; + } + + /** + * Returns a stepping function that returns 1 for any input value greater than or equal to 0, or otherwise returning + * 0 + */ + public static Double2DoubleFunction stepNonNegative(Double2DoubleFunction function) { + return n -> n >= 0 ? 1 : 0; + } + + /** + * A linear function, equivalent to a null-operation.
    + * {@code f(n) = n} + */ + public static double linear(double n) { + return n; + } + + // ---> Stepping Functions <--- // + + /** + * A quadratic function, equivalent to the square (n^2) of elapsed time.
    + * {@code f(n) = n^2}
    + * Easings.net#easeInQuad + */ + public static double quadratic(double n) { + return n * n; + } + + /** + * A cubic function, equivalent to cube (n^3) of elapsed time.
    + * {@code f(n) = n^3}
    + * Easings.net#easeInCubic + */ + public static double cubic(double n) { + return n * n * n; + } + + // ---> Mathematical Functions <--- // + + /** + * A sinusoidal function, equivalent to a sine curve output.
    + * {@code f(n) = 1 - cos(n * π / 2)}
    + * Easings.net#easeInSine + */ + public static double sine(double n) { + return 1 - Math.cos(n * Math.PI / 2f); + } + + /** + * A circular function, equivalent to a normally symmetrical curve.
    + * {@code f(n) = 1 - sqrt(1 - n^2)}
    + * Easings.net#easeInCirc + */ + public static double circle(double n) { + return 1 - Math.sqrt(1 - n * n); + } + + /** + * An exponential function, equivalent to an exponential curve.
    + * {@code f(n) = 2^(10 * (n - 1))}
    + * Easings.net#easeInExpo + */ + public static double exp(double n) { + return Math.pow(2, 10 * (n - 1)); + } + + /** + * An elastic function, equivalent to an oscillating curve.
    + * n defines the elasticity of the output.
    + * {@code f(t) = 1 - (cos(t * π) / 2))^3 * cos(t * n * π)}
    + * Easings.net#easeInElastic + */ + public static Double2DoubleFunction elastic(Double n) { + double n2 = n == null ? 1 : n; + + return t -> 1 - Math.pow(Math.cos(t * Math.PI / 2f), 3) * Math.cos(t * n2 * Math.PI); + } + + /** + * A bouncing function, equivalent to a bouncing ball curve.
    + * n defines the bounciness of the output.
    + * Thanks to Waterded#6455 for making the bounce adjustable, and GiantLuigi4#6616 for additional + * cleanup.
    + * Easings.net#easeInBounce + */ + public static Double2DoubleFunction bounce(Double n) { + final double n2 = n == null ? 0.5d : n; + + Double2DoubleFunction one = x -> 121f / 16f * x * x; + Double2DoubleFunction two = x -> 121f / 4f * n2 * Math.pow(x - 6f / 11f, 2) + 1 - n2; + Double2DoubleFunction three = x -> 121 * n2 * n2 * Math.pow(x - 9f / 11f, 2) + 1 - n2 * n2; + Double2DoubleFunction four = x -> 484 * n2 * n2 * n2 * Math.pow(x - 10.5f / 11f, 2) + 1 - n2 * n2 * n2; + + return t -> Math.min(Math.min(one.apply(t), two.apply(t)), Math.min(three.apply(t), four.apply(t))); + } + + /** + * A negative elastic function, equivalent to inverting briefly before increasing.
    + * f(t) = t^2 * ((n * 1.70158 + 1) * t - n * 1.70158)
    + * Easings.net#easeInBack + */ + public static Double2DoubleFunction back(Double n) { + final double n2 = n == null ? 1.70158d : n * 1.70158d; + + return t -> t * t * ((n2 + 1) * t - n2); + } + + // ---> Easing Curve Functions <--- // + + /** + * An exponential function, equivalent to an exponential curve to the {@code n} root.
    + * f(t) = t^n + * + * @param n The exponent + */ + public static Double2DoubleFunction pow(double n) { + return t -> Math.pow(t, n); + } + + /** + * The MIT License (MIT)
    + *
    + * Copyright (c) 2015 Boris Chumichev
    + *
    + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    + *
    + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software.
    + *
    + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE.
    + *
    + * Returns a stepped value based on the nearest step to the input value.
    + * The size (grade) of the steps depends on the provided value of {@code n} + **/ + public static Double2DoubleFunction step(Double n) { + double n2 = n == null ? 2 : n; + + if (n2 < 2) + throw new IllegalArgumentException("Steps must be >= 2, got: " + n2); + + final int steps = (int) n2; + + return t -> { + double result = 0; + + if (t < 0) + return result; + + double stepLength = (1 / (double) steps); + + if (t > (result = (steps - 1) * stepLength)) + return result; + + int testIndex; + int leftBorderIndex = 0; + int rightBorderIndex = steps - 1; + + while (rightBorderIndex - leftBorderIndex != 1) { + testIndex = leftBorderIndex + (rightBorderIndex - leftBorderIndex) / 2; + + if (t >= testIndex * stepLength) { + leftBorderIndex = testIndex; + } else { + rightBorderIndex = testIndex; + } + } + + return leftBorderIndex * stepLength; + }; + } + + public static double lerpWithOverride(AzAnimationPoint animationPoint, AzEasingType override) { + var easingType = override; + + if (override == null) { + easingType = animationPoint.keyframe() == null + ? AzEasingTypes.LINEAR + : animationPoint.keyframe().easingType(); + } + + return easingType.apply(animationPoint); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzCustomInstructionKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzCustomInstructionKeyframeEvent.java new file mode 100644 index 000000000..2fc40463e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzCustomInstructionKeyframeEvent.java @@ -0,0 +1,29 @@ +package mod.azure.azurelib.rewrite.animation.event; + +import mod.azure.azurelib.core.keyframe.event.data.CustomInstructionKeyframeData; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeCallbacks; + +/** + * The {@link AzKeyframeEvent} specific to the {@link AzKeyframeCallbacks#customKeyframeHandler()}.
    + * Called when a custom instruction keyframe is encountered + */ +public class AzCustomInstructionKeyframeEvent extends AzKeyframeEvent { + + public AzCustomInstructionKeyframeEvent( + T entity, + double animationTick, + AzAnimationController controller, + CustomInstructionKeyframeData customInstructionKeyframeData + ) { + super(entity, animationTick, controller, customInstructionKeyframeData); + } + + /** + * Get the {@link CustomInstructionKeyframeData} relevant to this event call + */ + @Override + public CustomInstructionKeyframeData getKeyframeData() { + return super.getKeyframeData(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzKeyframeEvent.java new file mode 100644 index 000000000..96c56a53c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzKeyframeEvent.java @@ -0,0 +1,65 @@ +package mod.azure.azurelib.rewrite.animation.event; + +import mod.azure.azurelib.core.keyframe.Keyframe; +import mod.azure.azurelib.core.keyframe.event.data.KeyFrameData; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; + +/** + * The base class for {@link Keyframe} events.
    + * These will be passed to one of the controllers in {@link AzAnimationController} when encountered during animation. + * + * @see AzCustomInstructionKeyframeEvent + * @see AzParticleKeyframeEvent + * @see AzSoundKeyframeEvent + */ +public abstract class AzKeyframeEvent { + + private final T animatable; + + private final double animationTick; + + private final AzAnimationController controller; + + private final E eventKeyframe; + + protected AzKeyframeEvent( + T animatable, + double animationTick, + AzAnimationController controller, + E eventKeyframe + ) { + this.animatable = animatable; + this.animationTick = animationTick; + this.controller = controller; + this.eventKeyframe = eventKeyframe; + } + + /** + * Gets the amount of ticks that have passed in either the current transition or animation, depending on the + * controller's AnimationState. + */ + public double getAnimationTick() { + return animationTick; + } + + /** + * Gets the {@link T animatable} object being rendered + */ + public T getAnimatable() { + return animatable; + } + + /** + * Gets the {@link AzAnimationController} responsible for the currently playing animation + */ + public AzAnimationController getController() { + return controller; + } + + /** + * Returns the {@link KeyFrameData} relevant to the encountered {@link Keyframe} + */ + public E getKeyframeData() { + return this.eventKeyframe; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzParticleKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzParticleKeyframeEvent.java new file mode 100644 index 000000000..2c7ca81af --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzParticleKeyframeEvent.java @@ -0,0 +1,34 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.animation.event; + +import mod.azure.azurelib.core.keyframe.event.data.ParticleKeyframeData; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeCallbacks; + +/** + * The {@link AzKeyframeEvent} specific to the {@link AzKeyframeCallbacks#particleKeyframeHandler()}.
    + * Called when a particle instruction keyframe is encountered + */ +public class AzParticleKeyframeEvent extends AzKeyframeEvent { + + public AzParticleKeyframeEvent( + T animatable, + double animationTick, + AzAnimationController controller, + ParticleKeyframeData particleKeyframeData + ) { + super(animatable, animationTick, controller, particleKeyframeData); + } + + /** + * Get the {@link ParticleKeyframeData} relevant to this event call + */ + @Override + public ParticleKeyframeData getKeyframeData() { + return super.getKeyframeData(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzSoundKeyframeEvent.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzSoundKeyframeEvent.java new file mode 100644 index 000000000..0c27f74ba --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/event/AzSoundKeyframeEvent.java @@ -0,0 +1,34 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.animation.event; + +import mod.azure.azurelib.core.keyframe.event.data.SoundKeyframeData; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeCallbacks; + +/** + * The {@link AzKeyframeEvent} specific to the {@link AzKeyframeCallbacks#soundKeyframeHandler()}.
    + * Called when a sound instruction keyframe is encountered + */ +public class AzSoundKeyframeEvent extends AzKeyframeEvent { + + /** + * This stores all the fields that are needed in the AnimationTestEvent + * + * @param entity the entity + * @param animationTick The amount of ticks that have passed in either the current transition or animation, + * depending on the controller's AnimationState. + * @param controller the controller + */ + public AzSoundKeyframeEvent( + T entity, + double animationTick, + AzAnimationController controller, + SoundKeyframeData keyframeData + ) { + super(entity, animationTick, controller, keyframeData); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzBlockAnimator.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzBlockAnimator.java new file mode 100644 index 000000000..f8cc38a4c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzBlockAnimator.java @@ -0,0 +1,19 @@ +package mod.azure.azurelib.rewrite.animation.impl; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorConfig; +import net.minecraft.world.level.block.entity.BlockEntity; + +/** + * The {@code AzBlockAnimator} class extends the functionality of the {@link AzAnimator} to provide animation support + * specifically for {@link BlockEntity} instances. This abstract class serves as a base for creating block entity + * animators with reusable configuration and animation controller registration mechanisms. + * + * @param The type of {@link BlockEntity} that this animator will manage animations for. + */ +public abstract class AzBlockAnimator extends AzAnimator { + + protected AzBlockAnimator(AzAnimatorConfig config) { + super(config); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzEntityAnimator.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzEntityAnimator.java new file mode 100644 index 000000000..ed2d2400e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzEntityAnimator.java @@ -0,0 +1,69 @@ +package mod.azure.azurelib.rewrite.animation.impl; + +import mod.azure.azurelib.core.molang.MolangParser; +import mod.azure.azurelib.core.molang.MolangQueries; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorConfig; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; + +/** + * The {@code AzEntityAnimator} class extends {@link AzAnimator} to provide specialized animation management for + * entities. This abstract class is designed to handle various animation-related requirements for entities in a game + * framework, including the application of MoLang queries specific to entity-related properties such as position, + * health, and motion state. + * + * @param The type of entity this animator is designed to manage. + */ +public abstract class AzEntityAnimator extends AzAnimator { + + protected AzEntityAnimator() { + super(); + } + + protected AzEntityAnimator(AzAnimatorConfig config) { + super(config); + } + + /** + * Applies MoLang queries specific to an entity in the animation system. These queries provide contextual + * information about the entity's state and environment, such as its position, health, movement, and interaction + * with the world. The method extends the baseline queries defined in the superclass with additional entity-specific + * properties, particularly for living entities. + * + * @param entity The entity for which the MoLang queries are being applied. + * @param animTime The current animation time, in seconds, used for time-dependent queries. + */ + @Override + protected void applyMolangQueries(T entity, double animTime) { + super.applyMolangQueries(entity, animTime); + + var parser = MolangParser.INSTANCE; + var minecraft = Minecraft.getInstance(); + + parser.setMemoizedValue( + MolangQueries.DISTANCE_FROM_CAMERA, + () -> minecraft.gameRenderer.getMainCamera().getPosition().distanceTo(entity.position()) + ); + parser.setMemoizedValue(MolangQueries.IS_ON_GROUND, () -> RenderUtils.booleanToFloat(entity.onGround())); + parser.setMemoizedValue(MolangQueries.IS_IN_WATER, () -> RenderUtils.booleanToFloat(entity.isInWater())); + parser.setMemoizedValue( + MolangQueries.IS_IN_WATER_OR_RAIN, + () -> RenderUtils.booleanToFloat(entity.isInWaterOrRain()) + ); + parser.setMemoizedValue(MolangQueries.IS_ON_FIRE, () -> RenderUtils.booleanToFloat(entity.isOnFire())); + + if (entity instanceof LivingEntity livingEntity) { + parser.setMemoizedValue(MolangQueries.HEALTH, livingEntity::getHealth); + parser.setMemoizedValue(MolangQueries.MAX_HEALTH, livingEntity::getMaxHealth); + parser.setMemoizedValue(MolangQueries.GROUND_SPEED, () -> { + var velocity = livingEntity.getDeltaMovement(); + return Mth.sqrt((float) ((velocity.x * velocity.x) + (velocity.z * velocity.z))); + }); + parser.setMemoizedValue(MolangQueries.YAW_SPEED, () -> livingEntity.getYRot() - livingEntity.yRotO); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzItemAnimator.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzItemAnimator.java new file mode 100644 index 000000000..9fc39744b --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/impl/AzItemAnimator.java @@ -0,0 +1,25 @@ +package mod.azure.azurelib.rewrite.animation.impl; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorConfig; +import net.minecraft.world.item.ItemStack; + +/** + * The {@code AzItemAnimator} class is an abstract extension of the {@code AzAnimator} class, specifically designed to + * handle animations for {@link ItemStack} objects. It provides common functionality and structure for animating items + * within the framework.
    + *
    + * This class serves as a base for developing custom item animator implementations. Subclasses are required to implement + * methods for animation controller registration and for specifying the animation location for the corresponding + * {@code ItemStack}. + */ +public abstract class AzItemAnimator extends AzAnimator { + + protected AzItemAnimator() { + super(); + } + + protected AzItemAnimator(AzAnimatorConfig config) { + super(config); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzBakedAnimationsAdapter.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzBakedAnimationsAdapter.java new file mode 100644 index 000000000..f2420c4b6 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzBakedAnimationsAdapter.java @@ -0,0 +1,377 @@ +package mod.azure.azurelib.rewrite.animation.parse; + +import com.google.gson.*; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.core.keyframe.BoneAnimation; +import mod.azure.azurelib.core.keyframe.KeyframeStack; +import mod.azure.azurelib.core.math.Constant; +import mod.azure.azurelib.core.math.IValue; +import mod.azure.azurelib.core.molang.MolangException; +import mod.azure.azurelib.core.molang.MolangParser; +import mod.azure.azurelib.core.molang.expressions.MolangValue; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzBoneAnimation; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframe; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzKeyframeStack; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypeLoader; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimation; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimations; +import mod.azure.azurelib.rewrite.animation.primitive.AzKeyframes; +import mod.azure.azurelib.rewrite.animation.primitive.AzLoopType; +import mod.azure.azurelib.util.JsonUtil; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import org.apache.commons.lang3.math.NumberUtils; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * {@link Gson} {@link JsonDeserializer} for {@link AzBakedAnimations}.
    + * Acts as the deserialization interface for {@code BakedAnimations} + */ +public class AzBakedAnimationsAdapter implements JsonDeserializer { + + /** + * Processes a given JSON element and transforms it into a list of pairs, where each pair consists of a string key + * and a corresponding JSON element. Depending on the type of the input element, it handles primitive values, + * arrays, and objects differently, ensuring a uniform output structure. For JSON primitives, a synthetic triplet + * array is generated. For JSON arrays, the array is paired with the key "0". For JSON objects, individual entries + * are processed recursively, with special handling for nested objects without a "vector" key. + * + * @param element The JSON element to be processed. It can be a {@link JsonPrimitive}, {@link JsonObject}, or + * {@link JsonArray}. If null, an empty list is returned. + * @return A list of {@link Pair} objects where each pair contains a string key and a corresponding + * {@link JsonElement}. This list represents the processed structure of the input JSON element. + * @throws JsonParseException If the provided JSON element is of an unsupported type or is invalid. + */ + private static List> getKeyframes(JsonElement element) { + if (element == null) + return List.of(); + + if (element instanceof JsonPrimitive primitive) { + JsonArray array = new JsonArray(3); + + array.add(primitive); + array.add(primitive); + array.add(primitive); + + element = array; + } + + if (element instanceof JsonArray array) + return ObjectArrayList.of(Pair.of("0", array)); + + if (element instanceof JsonObject obj) { + if (obj.has("vector")) + return ObjectArrayList.of(Pair.of("0", obj)); + + List> list = new ObjectArrayList<>(); + + for (Map.Entry entry : obj.entrySet()) { + double timestamp = readTimestamp(entry.getKey()); + + if (timestamp == 0 && !list.isEmpty()) + throw new JsonParseException("Invalid keyframe data - multiple starting keyframes?" + entry.getKey()); + + if (entry.getValue() instanceof JsonObject entryObj && !entryObj.has("vector")) { + addBedrockKeyframes(timestamp, entryObj, list); + + continue; + } + + list.add(Pair.of(String.valueOf(timestamp), entry.getValue())); + } + + return list; + } + + throw new JsonParseException("Invalid object type provided to getTripletObj, got: " + element); + } + + private static void addBedrockKeyframes(double timestamp, JsonObject keyframe, List> keyframes) { + boolean addedFrame = false; + + if (keyframe.has("pre")) { + JsonElement pre = keyframe.get("pre"); + addedFrame = true; + + keyframes.add(Pair.of( + String.valueOf(timestamp == 0 ? timestamp : timestamp - 0.001d), + pre.isJsonArray() ? pre.getAsJsonArray() : GsonHelper.getAsJsonArray(pre.getAsJsonObject(), "vector") + )); + } + + if (keyframe.has("post")) { + JsonElement post = keyframe.get("post"); + JsonArray values = post.isJsonArray() ? post.getAsJsonArray() : GsonHelper.getAsJsonArray(post.getAsJsonObject(), "vector"); + + if (keyframe.has("lerp_mode")) { + var keyframeObj = new JsonObject(); + + keyframeObj.add("vector", values); + keyframeObj.add("easing", keyframe.get("lerp_mode")); + + keyframes.add(Pair.of(String.valueOf(timestamp), keyframeObj)); + } + else { + keyframes.add(Pair.of(String.valueOf(timestamp), values)); + } + + return; + } + + if (!addedFrame) + throw new JsonParseException("Invalid keyframe data - expected array, found " + keyframe); + } + + /** + * Calculates the overall length of the animation timeline across all provided bone animations. The calculation + * considers the maximum keyframe time for rotation, position, and scale transformations for each bone and + * determines the longest timeline among them. + * + * @param boneAnimations An array of {@link BoneAnimation} instances representing the animations for individual + * bones. Each bone animation includes keyframe stacks for rotation, position, and scale + * transformations. + * @return The maximum length of the animation timeline. If no keyframes are present, it defaults to + * {@link Double#MAX_VALUE}. + */ + private static double calculateAnimationLength(AzBoneAnimation[] boneAnimations) { + double length = 0; + + for (var animation : boneAnimations) { + length = Math.max(length, animation.rotationKeyframes().getLastKeyframeTime()); + length = Math.max(length, animation.positionKeyframes().getLastKeyframeTime()); + length = Math.max(length, animation.scaleKeyframes().getLastKeyframeTime()); + } + + return length == 0 ? Double.MAX_VALUE : length; + } + + /** + * Deserializes JSON data into an instance of {@link AzBakedAnimations}. + * + * @param json The JSON element to deserialize, expected to contain a valid structure for animations and optional + * includes. + * @param type The type of object to deserialize to; this is typically {@link AzBakedAnimations}. + * @param context A context for handling nested deserialization, such as for custom types embedded within the JSON + * structure. + * @return A newly created {@link AzBakedAnimations} instance containing parsed animations and includes as specified + * in the provided JSON data. + * @throws JsonParseException If the JSON structure is invalid or an error occurs during deserialization. + */ + @Override + public AzBakedAnimations deserialize( + JsonElement json, + Type type, + JsonDeserializationContext context + ) throws JsonParseException { + JsonObject jsonObj = json.getAsJsonObject(); + + JsonObject animationJsonList = jsonObj.getAsJsonObject("animations"); + JsonArray includeListJSONObj = jsonObj.getAsJsonArray("includes"); + Map includes = null; + if (includeListJSONObj != null) { + includes = new Object2ObjectOpenHashMap<>(includeListJSONObj.size()); + for (JsonElement entry : includeListJSONObj.asList()) { + JsonObject obj = entry.getAsJsonObject(); + ResourceLocation fileId = new ResourceLocation(obj.get("file_id").getAsString()); + for (JsonElement animName : obj.getAsJsonArray("animations")) { + String ani = animName.getAsString(); + if (includes.containsKey(ani)) { + AzureLib.LOGGER.warn( + "Animation {} is already included! File already including: {} File trying to include from again: {}", + ani, + includes.get(ani), + fileId + ); + } else { + includes.put(ani, fileId); + } + } + } + } + + Map animations = new Object2ObjectOpenHashMap<>(animationJsonList.size()); + + for (Map.Entry entry : animationJsonList.entrySet()) { + try { + animations.put( + entry.getKey(), + bakeAnimation(entry.getKey(), entry.getValue().getAsJsonObject(), context) + ); + } catch (MolangException ex) { + AzureLib.LOGGER.error("Unable to parse animation: {}", entry.getKey()); + ex.printStackTrace(); + } + } + + return new AzBakedAnimations(animations, includes); + } + + /** + * Processes the provided JSON data to create an instance of {@link AzBakedAnimation}. This method interprets the + * animation JSON object, constructs the necessary data structures such as bone animations and keyframes, and + * applies logic to calculate the animation length if not explicitly defined. + * + * @param name The name of the animation being created. + * @param animationObj The JSON object containing the animation definition. This object may include details such as + * animation length, loop type, bones, and keyframe data. + * @param context The deserialization context used for nested data structures such as {@link AzKeyframes}. + * @return A constructed {@link AzBakedAnimation} instance containing the parsed animation details. + * @throws MolangException If an error occurs while processing expressions or any other aspect of the Molang + * language during animation creation. + */ + private AzBakedAnimation bakeAnimation( + String name, + JsonObject animationObj, + JsonDeserializationContext context + ) throws MolangException { + double length = animationObj.has("animation_length") + ? GsonHelper.getAsDouble(animationObj, "animation_length") * 20d + : -1; + AzLoopType loopType = AzLoopType.fromJson(animationObj.get("loop")); + AzBoneAnimation[] boneAnimations = bakeBoneAnimations( + GsonHelper.getAsJsonObject(animationObj, "bones", new JsonObject()) + ); + AzKeyframes keyframes = context.deserialize(animationObj, AzKeyframes.class); + + if (length == -1) + length = calculateAnimationLength(boneAnimations); + + return new AzBakedAnimation(name, length, loopType, boneAnimations, keyframes); + } + + /** + * Processes a JSON object representing bone animations and constructs an array of {@link BoneAnimation} instances. + * Each bone's animation includes keyframe stacks for position, rotation, and scale transformations. + * + * @param bonesObj The JSON object containing bone animation data, where each key is the bone name and the value is + * an object with keyframe data for scale, position, and rotation. + * @return An array of {@link BoneAnimation} instances representing the deserialized animations for each bone. + * @throws MolangException If an error occurs during the processing of keyframes or Molang expressions. + */ + private AzBoneAnimation[] bakeBoneAnimations(JsonObject bonesObj) throws MolangException { + AzBoneAnimation[] animations = new AzBoneAnimation[bonesObj.size()]; + int index = 0; + + for (Map.Entry entry : bonesObj.entrySet()) { + JsonObject entryObj = entry.getValue().getAsJsonObject(); + AzKeyframeStack> scaleFrames = buildKeyframeStack( + getKeyframes(entryObj.get("scale")), + false + ); + AzKeyframeStack> positionFrames = buildKeyframeStack( + getKeyframes(entryObj.get("position")), + false + ); + AzKeyframeStack> rotationFrames = buildKeyframeStack( + getKeyframes(entryObj.get("rotation")), + true + ); + + animations[index] = new AzBoneAnimation(entry.getKey(), rotationFrames, positionFrames, scaleFrames); + index++; + } + + return animations; + } + + /** + * Builds a {@link KeyframeStack} containing keyframes for X, Y, and Z-axis transformations based on the provided + * animation data. The method processes a list of paired time-stamped keyframe data, interprets the JSON structures, + * applies appropriate transformations for rotations (if specified), and generates keyframes with defined easing + * behaviors. + * + * @param entries A list of {@link Pair} objects containing the timestamp as a {@link String} and associated + * {@link JsonElement} data describing the keyframe. Each entry represents a point in time + * within the animation timeline. + * @param isForRotation A boolean indicating whether the keyframe transformations should account for rotation. If + * true, the keyframe values undergo additional processing to convert angles to radians. + * @return A {@link KeyframeStack} containing three lists of keyframes for X, Y, and Z transformations, + * respectively. + * @throws MolangException If an error occurs during the parsing or interpretation of Molang expressions in the + * keyframe data. + */ + private AzKeyframeStack> buildKeyframeStack( + List> entries, + boolean isForRotation + ) throws MolangException { + if (entries.isEmpty()) + return new AzKeyframeStack<>(); + + List> xFrames = new ObjectArrayList<>(); + List> yFrames = new ObjectArrayList<>(); + List> zFrames = new ObjectArrayList<>(); + + IValue xPrev = null; + IValue yPrev = null; + IValue zPrev = null; + Pair prevEntry = null; + + for (Pair entry : entries) { + String key = entry.getFirst(); + JsonElement element = entry.getSecond(); + + if (key.equals("easing") || key.equals("easingArgs") || key.equals("lerp_mode")) + continue; + + double prevTime = prevEntry != null ? Double.parseDouble(prevEntry.getFirst()) : 0; + double curTime = NumberUtils.isCreatable(key) ? Double.parseDouble(entry.getFirst()) : 0; + double timeDelta = curTime - prevTime; + + JsonArray keyframeVector = element instanceof JsonArray array + ? array + : GsonHelper.getAsJsonArray(element.getAsJsonObject(), "vector"); + MolangValue rawXValue = MolangParser.parseJson(keyframeVector.get(0)); + MolangValue rawYValue = MolangParser.parseJson(keyframeVector.get(1)); + MolangValue rawZValue = MolangParser.parseJson(keyframeVector.get(2)); + IValue xValue = isForRotation && rawXValue.isConstant() + ? new Constant(Math.toRadians(-rawXValue.get())) + : rawXValue; + IValue yValue = isForRotation && rawYValue.isConstant() + ? new Constant(Math.toRadians(-rawYValue.get())) + : rawYValue; + IValue zValue = isForRotation && rawZValue.isConstant() + ? new Constant(Math.toRadians(rawZValue.get())) + : rawZValue; + + JsonObject entryObj = element instanceof JsonObject obj ? obj : null; + AzEasingType easingType = entryObj != null && entryObj.has("easing") + ? AzEasingTypeLoader.fromJson(entryObj.get("easing")) + : AzEasingTypes.LINEAR; + List easingArgs = entryObj != null && entryObj.has("easingArgs") + ? JsonUtil.jsonArrayToList( + GsonHelper.getAsJsonArray(entryObj, "easingArgs"), + ele -> new Constant(ele.getAsDouble()) + ) + : new ObjectArrayList<>(); + + xFrames.add( + new AzKeyframe<>(timeDelta * 20, prevEntry == null ? xValue : xPrev, xValue, easingType, easingArgs) + ); + yFrames.add( + new AzKeyframe<>(timeDelta * 20, prevEntry == null ? yValue : yPrev, yValue, easingType, easingArgs) + ); + zFrames.add( + new AzKeyframe<>(timeDelta * 20, prevEntry == null ? zValue : zPrev, zValue, easingType, easingArgs) + ); + + xPrev = xValue; + yPrev = yValue; + zPrev = zValue; + prevEntry = entry; + } + + return new AzKeyframeStack<>(xFrames, yFrames, zFrames); + } + + private static double readTimestamp(String timestamp) { + return NumberUtils.isCreatable(timestamp) ? Double.parseDouble(timestamp) : 0; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzKeyframesAdapter.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzKeyframesAdapter.java new file mode 100644 index 000000000..407431523 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/parse/AzKeyframesAdapter.java @@ -0,0 +1,141 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.animation.parse; + +import com.google.gson.*; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.core.keyframe.event.data.CustomInstructionKeyframeData; +import mod.azure.azurelib.core.keyframe.event.data.ParticleKeyframeData; +import mod.azure.azurelib.core.keyframe.event.data.SoundKeyframeData; +import mod.azure.azurelib.rewrite.animation.primitive.AzKeyframes; +import mod.azure.azurelib.util.JsonUtil; +import net.minecraft.util.GsonHelper; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * {@link Gson} {@link JsonDeserializer} for {@link AzKeyframes}.
    + * Acts as the deserialization interface for {@code Keyframes} + */ +public class AzKeyframesAdapter implements JsonDeserializer { + + /** + * Builds an array of {@link SoundKeyframeData} objects from a given JSON object. This method parses a JSON object + * containing sound effect data, extracting key-value pairs to create instances of {@link SoundKeyframeData}. The + * keys represent the time in seconds, which are converted into ticks, and the values specify the sound effect. + * + * @param rootObj the root JSON object containing the "sound_effects" data + * @return an array of {@link SoundKeyframeData} objects extracted from the JSON object + */ + private static SoundKeyframeData[] buildSoundFrameData(JsonObject rootObj) { + JsonObject soundsObj = GsonHelper.getAsJsonObject(rootObj, "sound_effects", new JsonObject()); + SoundKeyframeData[] sounds = new SoundKeyframeData[soundsObj.size()]; + int index = 0; + + for (Map.Entry entry : soundsObj.entrySet()) { + sounds[index] = new SoundKeyframeData( + Double.parseDouble(entry.getKey()) * 20d, + GsonHelper.getAsString(entry.getValue().getAsJsonObject(), "effect") + ); + index++; + } + + return sounds; + } + + /** + * Builds an array of {@link ParticleKeyframeData} objects from a given JSON object. This method parses a JSON + * object containing particle effect data, extracting key-value pairs to create instances of + * {@link ParticleKeyframeData}. The keys are interpreted as time in ticks, whereas the values provide effect + * details like effect type, locator, and script. + * + * @param rootObj the root JSON object containing the "particle_effects" data + * @return an array of {@link ParticleKeyframeData} objects extracted from the JSON object + */ + private static ParticleKeyframeData[] buildParticleFrameData(JsonObject rootObj) { + JsonObject particlesObj = GsonHelper.getAsJsonObject(rootObj, "particle_effects", new JsonObject()); + ParticleKeyframeData[] particles = new ParticleKeyframeData[particlesObj.size()]; + int index = 0; + + for (Map.Entry entry : particlesObj.entrySet()) { + JsonObject obj = entry.getValue().getAsJsonObject(); + String effect = GsonHelper.getAsString(obj, "effect", ""); + String locator = GsonHelper.getAsString(obj, "locator", ""); + String script = GsonHelper.getAsString(obj, "pre_effect_script", ""); + + particles[index] = new ParticleKeyframeData( + Double.parseDouble(entry.getKey()) * 20d, + effect, + locator, + script + ); + index++; + } + + return particles; + } + + /** + * Builds an array of {@link CustomInstructionKeyframeData} objects from a given JSON object. + *

    + * This method parses a JSON object containing custom instructions for keyframes, extracting each key-value pair to + * create instances of {@link CustomInstructionKeyframeData}. The keys are interpreted as time in ticks, and the + * values as instructions. + * + * @param rootObj the root JSON object containing the "timeline" data for custom instructions + * @return an array of {@link CustomInstructionKeyframeData} objects extracted from the JSON object + */ + private static CustomInstructionKeyframeData[] buildCustomFrameData(JsonObject rootObj) { + JsonObject customInstructionsObj = GsonHelper.getAsJsonObject(rootObj, "timeline", new JsonObject()); + CustomInstructionKeyframeData[] customInstructions = new CustomInstructionKeyframeData[customInstructionsObj + .size()]; + int index = 0; + + for (Map.Entry entry : customInstructionsObj.entrySet()) { + String instructions = ""; + + if (entry.getValue() instanceof JsonArray array) { + instructions = JsonUtil.GEO_GSON.fromJson(array, ObjectArrayList.class).toString(); + } else if (entry.getValue() instanceof JsonPrimitive primitive) { + instructions = primitive.getAsString(); + } + + customInstructions[index] = new CustomInstructionKeyframeData( + Double.parseDouble(entry.getKey()) * 20d, + instructions + ); + index++; + } + + return customInstructions; + } + + /** + * Deserializes a JSON element into an {@code AzKeyframes} object. This method converts the JSON representation of + * keyframe data into instances of {@code SoundKeyframeData}, {@code ParticleKeyframeData}, and + * {@code CustomInstructionKeyframeData} to construct an {@code AzKeyframes} object. + * + * @param json the JSON element containing the keyframe data + * @param type the type parameter for deserialization (not used in this method) + * @param context the deserialization context provided by Gson + * @return an instance of {@code AzKeyframes} containing deserialized keyframe data + * @throws JsonParseException if the JSON is invalid or cannot be properly parsed + */ + @Override + public AzKeyframes deserialize( + JsonElement json, + Type type, + JsonDeserializationContext context + ) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + SoundKeyframeData[] sounds = buildSoundFrameData(obj); + ParticleKeyframeData[] particles = buildParticleFrameData(obj); + CustomInstructionKeyframeData[] customInstructions = buildCustomFrameData(obj); + + return new AzKeyframes(sounds, particles, customInstructions); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehavior.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehavior.java new file mode 100644 index 000000000..86b4875db --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehavior.java @@ -0,0 +1,20 @@ +package mod.azure.azurelib.rewrite.animation.play_behavior; + +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +public abstract class AzPlayBehavior { + + private final String name; + + protected AzPlayBehavior(String name) { + this.name = name; + } + + public void onUpdate(AzAnimationControllerStateMachine.Context context) {} + + public void onFinish(AzAnimationControllerStateMachine.Context context) {} + + public String name() { + return name; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviorRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviorRegistry.java new file mode 100644 index 000000000..28355a1ca --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviorRegistry.java @@ -0,0 +1,31 @@ +package mod.azure.azurelib.rewrite.animation.play_behavior; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class AzPlayBehaviorRegistry { + + private static final Map PLAY_BEHAVIORS = new HashMap<>(); + + public static AzPlayBehavior register(AzPlayBehavior playBehavior) { + PLAY_BEHAVIORS.put(playBehavior.name(), playBehavior); + return playBehavior; + } + + public static AzPlayBehavior getOrDefault(String name, @NotNull AzPlayBehavior defaultValue) { + return PLAY_BEHAVIORS.getOrDefault(name, defaultValue); + } + + public static @Nullable AzPlayBehavior getOrNull(String name) { + return PLAY_BEHAVIORS.get(name); + } + + public static Collection getValues() { + return Collections.unmodifiableCollection(PLAY_BEHAVIORS.values()); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviors.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviors.java new file mode 100644 index 000000000..65990f9cc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/play_behavior/AzPlayBehaviors.java @@ -0,0 +1,33 @@ +package mod.azure.azurelib.rewrite.animation.play_behavior; + +import mod.azure.azurelib.rewrite.animation.controller.state.machine.AzAnimationControllerStateMachine; + +public class AzPlayBehaviors { + + public static final AzPlayBehavior HOLD_ON_LAST_FRAME = AzPlayBehaviorRegistry.register(new AzPlayBehavior("hold_on_last_frame") { + @Override + public void onFinish(AzAnimationControllerStateMachine.Context context) { + context.stateMachine().pause(); + } + }); + + public static final AzPlayBehavior LOOP = AzPlayBehaviorRegistry.register(new AzPlayBehavior("loop") { + @Override + public void onFinish(AzAnimationControllerStateMachine.Context context) { + var controller = context.animationController(); + var controllerTimer = controller.controllerTimer(); + var keyframeManager = controller.keyframeManager(); + var keyframeCallbackHandler = keyframeManager.keyframeCallbackHandler(); + + controllerTimer.reset(); + keyframeCallbackHandler.reset(); + } + }); + + public static final AzPlayBehavior PLAY_ONCE = AzPlayBehaviorRegistry.register(new AzPlayBehavior("play_once") { + @Override + public void onFinish(AzAnimationControllerStateMachine.Context context) { + context.stateMachine().stop(); + } + }); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimation.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimation.java new file mode 100644 index 000000000..89bbf79e3 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimation.java @@ -0,0 +1,19 @@ +package mod.azure.azurelib.rewrite.animation.primitive; + +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import mod.azure.azurelib.rewrite.animation.controller.keyframe.AzBoneAnimation; + +/** + * A compiled animation instance for use by the {@link AzAnimationController}
    + * Modifications or extensions of a compiled Animation are not supported, and therefore an instance of + * Animation is considered final and immutable. + */ +public record AzBakedAnimation( + String name, + double length, + AzLoopType loopType, + AzBoneAnimation[] boneAnimations, + AzKeyframes keyframes +) { + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimations.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimations.java new file mode 100644 index 000000000..be05fdd86 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzBakedAnimations.java @@ -0,0 +1,30 @@ +package mod.azure.azurelib.rewrite.animation.primitive; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Represents a container for baked animations in the AzureLib framework. This record holds mappings for precompiled + * animation instances ({@link AzBakedAnimation}) and resource includes ({@link ResourceLocation}) for use in + * animation-driven content.
    + * The `AzBakedAnimations` structure provides functionality for retrieving animations by name and supporting external + * resource references via the includes mapping, enabling extensibility and reuse of animations across various contexts. + *
    + * Immutable and designed for efficient storage and retrieval of animation data. + */ +public record AzBakedAnimations( + Map animations, + Map includes +) { + + /** + * Gets an {@link AzBakedAnimation} by its name, if present + */ + @Nullable + public AzBakedAnimation getAnimation(String name) { + return animations.get(name); + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzKeyframes.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzKeyframes.java new file mode 100644 index 000000000..7aaa77b39 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzKeyframes.java @@ -0,0 +1,23 @@ +package mod.azure.azurelib.rewrite.animation.primitive; + +import mod.azure.azurelib.core.keyframe.event.data.CustomInstructionKeyframeData; +import mod.azure.azurelib.core.keyframe.event.data.ParticleKeyframeData; +import mod.azure.azurelib.core.keyframe.event.data.SoundKeyframeData; + +/** + * Represents a collection of keyframe data used for animations.
    + * The AzKeyframes record combines different types of keyframe data into a single structure: + *

      + *
    • {@link SoundKeyframeData} for sound-related keyframes.
    • + *
    • {@link ParticleKeyframeData} for particle effect-related keyframes.
    • + *
    • {@link CustomInstructionKeyframeData} for custom instruction keyframes.
    • + *
    + *
    + * This record organizes and provides access to all three types of keyframe data, enabling cohesive handling of + * animation sequences defined in an animation system. + */ +public record AzKeyframes( + SoundKeyframeData[] sounds, + ParticleKeyframeData[] particles, + CustomInstructionKeyframeData[] customInstructions +) {} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzLoopType.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzLoopType.java new file mode 100644 index 000000000..2c55477e9 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzLoopType.java @@ -0,0 +1,148 @@ +package mod.azure.azurelib.rewrite.animation.primitive; + +import com.google.gson.JsonElement; +import mod.azure.azurelib.rewrite.animation.controller.AzAnimationController; +import org.apache.commons.lang3.function.TriFunction; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Loop type functional interface to define post-play handling for a given animation.
    + * Custom loop types are supported by extending this class and providing the extended class instance as the loop type + * for the animation + */ +public interface AzLoopType { + + String name(); + + /** + * Override in a custom instance to dynamically decide whether an animation should repeat or stop + * + * @param animatable The animating object relevant to this method call + * @param controller The {@link AzAnimationController} playing the current animation + * @param currentAnimation The current animation that just played + * @return Whether the animation should play again, or stop + */ + boolean shouldPlayAgain( + Object animatable, + AzAnimationController controller, + AzBakedAnimation currentAnimation + ); + + Map LOOP_TYPES = new ConcurrentHashMap<>(5); + + AzLoopType FALSE = register("false", (animatable, controller, currentAnimation) -> false); + + AzLoopType TRUE = register("true", (animatable, controller, currentAnimation) -> true); + + /** + * A predefined {@code AzLoopType} that indicates an animation should play only once without repeating. This loop + * type is used to configure animations that are intended to run a single time and stop when completed, instead of + * looping or holding on the last frame. The associated logic for this type always returns {@code false} for the + * repeat condition, preventing the animation from replaying once it has finished. + */ + AzLoopType PLAY_ONCE = register("play_once", FALSE); + + /** + * A pre-defined AzLoopType representing the behavior of holding on the last frame of an animation after it + * completes. + *

    + * When used as the loop type for an animation, the animation will pause on its final frame. The `pause()` method of + * the controller's state machine ensures this behavior. This is useful when the desired outcome is for the + * animation to display its last frame persistently without replaying or resetting. + *

    + * Implementation Details: - Utilizes the `controller.getStateMachine().pause()` method to halt the animation. - + * Returns `true`, indicating the controller should not proceed to the next state or reset/replay the animation. + */ + AzLoopType HOLD_ON_LAST_FRAME = register("hold_on_last_frame", (animatable, controller, currentAnimation) -> { + controller.stateMachine().pause(); + + return true; + }); + + /** + * Represents a preconfigured {@code AzLoopType} that determines the looping behavior of an animation. The + * {@code LOOP} type is designed to always repeat the animation, ensuring that the looping condition remains + * consistently true. This constant is registered with AzureLib's loop handler using the static {@code register} + * method. When used, it applies a behavior where the animation will indefinitely loop, regardless of any dynamic + * runtime evaluations about the animatable object or animation state. + */ + AzLoopType LOOP = register("loop", TRUE); + + /** + * Retrieve a AzLoopType instance based on a {@link JsonElement}. Returns either {@link AzLoopType#PLAY_ONCE} or + * {@link AzLoopType#LOOP} based on a boolean or string element type, or any other registered loop type with a + * matching type string. + * + * @param json The loop {@link JsonElement} to attempt to parse + * @return A usable AzLoopType instance + */ + static AzLoopType fromJson(JsonElement json) { + if (json == null || !json.isJsonPrimitive()) { + return PLAY_ONCE; + } + + var primitive = json.getAsJsonPrimitive(); + + if (primitive.isBoolean()) { + return primitive.getAsBoolean() ? LOOP : PLAY_ONCE; + } + + if (primitive.isString()) { + return fromString(primitive.getAsString()); + } + + return PLAY_ONCE; + } + + /** + * Retrieves an AzLoopType instance based on the given name. If the name does not match any registered loop type, + * the default {@link AzLoopType#PLAY_ONCE} is returned. + * + * @param name The name of the loop type to retrieve. + * @return The corresponding AzLoopType instance, or the default AzLoopType if no match is found. + */ + static AzLoopType fromString(String name) { + return LOOP_TYPES.getOrDefault(name, PLAY_ONCE); + } + + static AzLoopType register(String name, AzLoopType loopType) { + return register(name, (a, b, c) -> loopType.shouldPlayAgain(a, b, c)); + } + + /** + * Register a AzLoopType with AzureLib for handling loop functionality of animations..
    + * MUST be called during mod construct
    + * It is recommended you don't call this directly, and instead call it via {@code AzureLibUtil#addCustomLoopType} + * + * @param name The name of the loop type + * @param shouldPlayAgainFunction The loop type to register + * @return The registered {@code AzLoopType} + */ + static AzLoopType register( + String name, + TriFunction, AzBakedAnimation, Boolean> shouldPlayAgainFunction + ) { + var loopType = new AzLoopType() { + + @Override + public String name() { + return name; + } + + @Override + public boolean shouldPlayAgain( + Object animatable, + AzAnimationController controller, + AzBakedAnimation currentAnimation + ) { + return shouldPlayAgainFunction.apply(animatable, controller, currentAnimation); + } + }; + + LOOP_TYPES.put(name, loopType); + + return loopType; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzQueuedAnimation.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzQueuedAnimation.java new file mode 100644 index 000000000..7d01baf03 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/primitive/AzQueuedAnimation.java @@ -0,0 +1,24 @@ +package mod.azure.azurelib.rewrite.animation.primitive; + +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehavior; + +/** + * Represents an entry in an animation queue, combining an animation and its looping behavior. This record defines a + * queued animation to be played, including its associated {@link AzBakedAnimation} instance and the {@link AzPlayBehavior} + * that determines how the animation behaves once it reaches the end of its sequence.
    + *
    + * Instances of AzQueuedAnimation are immutable by design, ensuring that queued animations, once defined, cannot be + * modified, preserving their behavior within the animation controller.
    + *
    + * Fields: + *

      + *
    • {@code animation}: The {@link AzBakedAnimation} instance that contains the actual animation data to be + * played.
    • + *
    • {@code playBehavior}: The {@link AzPlayBehavior} that dictates the looping behavior or termination handling for the + * animation.
    • + *
    + */ +public record AzQueuedAnimation( + AzBakedAnimation animation, + AzPlayBehavior playBehavior +) {} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationProperties.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationProperties.java new file mode 100644 index 000000000..c54b6aa70 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationProperties.java @@ -0,0 +1,96 @@ +package mod.azure.azurelib.rewrite.animation.property; + +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; +import mod.azure.azurelib.rewrite.animation.property.codec.AzAnimationPropertiesCodec; +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AzAnimationProperties { + + public static final Function DECODER = AzAnimationPropertiesCodec.DECODER; + + public static final BiConsumer ENCODER = AzAnimationPropertiesCodec.ENCODER; + + public static final AzAnimationProperties DEFAULT = new AzAnimationProperties(1D, AzEasingTypes.NONE, 0F); + + public static final AzAnimationProperties EMPTY = new AzAnimationProperties(null, null, null); + + protected final @Nullable Double animationSpeed; + + protected final @Nullable AzEasingType easingType; + + protected final @Nullable Float transitionLength; + + public AzAnimationProperties( + @Nullable Double animationSpeed, + @Nullable AzEasingType easingType, + @Nullable Float transitionLength + ) { + this.animationSpeed = animationSpeed; + this.easingType = easingType; + this.transitionLength = transitionLength; + } + + public boolean hasAnimationSpeed() { + return animationSpeed != null; + } + + public boolean hasEasingType() { + return easingType != null; + } + + public boolean hasTransitionLength() { + return transitionLength != null; + } + + public AzAnimationProperties withAnimationSpeed(double animationSpeed) { + return new AzAnimationProperties(animationSpeed, easingType, transitionLength); + } + + public AzAnimationProperties withEasingType(@NotNull AzEasingType easingType) { + return new AzAnimationProperties(animationSpeed, easingType, transitionLength); + } + + public AzAnimationProperties withTransitionLength(float transitionLength) { + return new AzAnimationProperties(animationSpeed, easingType, transitionLength); + } + + public double animationSpeed() { + return animationSpeed == null ? DEFAULT.animationSpeed() : animationSpeed; + } + + public AzEasingType easingType() { + return easingType == null ? DEFAULT.easingType() : easingType; + } + + public float transitionLength() { + return transitionLength == null ? DEFAULT.transitionLength() : transitionLength; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + AzAnimationProperties that = (AzAnimationProperties) object; + + return Objects.equals(animationSpeed, that.animationSpeed) && Objects.equals(easingType, that.easingType) + && Objects.equals(transitionLength, that.transitionLength); + } + + @Override + public int hashCode() { + return Objects.hash(animationSpeed, easingType, transitionLength); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationStageProperties.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationStageProperties.java new file mode 100644 index 000000000..e126364db --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/AzAnimationStageProperties.java @@ -0,0 +1,93 @@ +package mod.azure.azurelib.rewrite.animation.property; + +import mod.azure.azurelib.rewrite.animation.easing.AzEasingType; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehavior; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehaviors; +import mod.azure.azurelib.rewrite.animation.property.codec.AzAnimationStagePropertiesCodec; +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AzAnimationStageProperties extends AzAnimationProperties { + + public static final Function DECODER = AzAnimationStagePropertiesCodec.DECODER; + + public static final BiConsumer ENCODER = AzAnimationStagePropertiesCodec.ENCODER; + + public static final AzAnimationStageProperties DEFAULT = new AzAnimationStageProperties( + 1D, + AzEasingTypes.NONE, + AzPlayBehaviors.PLAY_ONCE, + 0F + ); + + public static final AzAnimationStageProperties EMPTY = new AzAnimationStageProperties(null, null, null, null); + + private final AzPlayBehavior playBehavior; + + public AzAnimationStageProperties( + @Nullable Double animationSpeed, + @Nullable AzEasingType easingType, + @Nullable AzPlayBehavior playBehavior, + @Nullable Float transitionLength + ) { + super(animationSpeed, easingType, transitionLength); + this.playBehavior = playBehavior; + } + + public boolean hasPlayBehavior() { + return playBehavior != null; + } + + @Override + public AzAnimationStageProperties withAnimationSpeed(double animationSpeed) { + return new AzAnimationStageProperties(animationSpeed, easingType, playBehavior, transitionLength); + } + + @Override + public AzAnimationStageProperties withEasingType(@NotNull AzEasingType easingType) { + return new AzAnimationStageProperties(animationSpeed, easingType, playBehavior, transitionLength); + } + + public AzAnimationStageProperties withPlayBehavior(@NotNull AzPlayBehavior playBehavior) { + return new AzAnimationStageProperties(animationSpeed, easingType, playBehavior, transitionLength); + } + + @Override + public AzAnimationStageProperties withTransitionLength(float transitionLength) { + return new AzAnimationStageProperties(animationSpeed, easingType, playBehavior, transitionLength); + } + + public AzPlayBehavior playBehavior() { + return playBehavior == null ? DEFAULT.playBehavior() : playBehavior; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + if (!super.equals(object)) { + return false; + } + + AzAnimationStageProperties that = (AzAnimationStageProperties) object; + + return Objects.equals(playBehavior, that.playBehavior) && super.equals(object); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), playBehavior); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationPropertiesCodec.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationPropertiesCodec.java new file mode 100644 index 000000000..4890b71ee --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationPropertiesCodec.java @@ -0,0 +1,56 @@ +package mod.azure.azurelib.rewrite.animation.property.codec; + +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypeRegistry; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; +import mod.azure.azurelib.rewrite.animation.property.AzAnimationProperties; +import net.minecraft.network.FriendlyByteBuf; +import java.util.function.BiConsumer; +import java.util.function.Function; + + +public class AzAnimationPropertiesCodec { + + public static final Function DECODER = buf -> { + var propertyLength = buf.readByte(); + var properties = AzAnimationProperties.EMPTY; + + for (int i = 0; i < propertyLength; i++) { + var code = buf.readByte(); + + switch (code) { + case 0 -> properties = properties.withAnimationSpeed(buf.readDouble()); + case 1 -> properties = properties.withTransitionLength(buf.readFloat()); + case 2 -> { + var easingType = AzEasingTypeRegistry.getOrDefault(buf.readUtf(), AzEasingTypes.NONE); + properties = properties.withEasingType(easingType); + } + } + } + + return properties; + }; + + public static final BiConsumer ENCODER = (buf, properties) -> { + var propertyLength = 0; + propertyLength += properties.hasAnimationSpeed() ? 1 : 0; + propertyLength += properties.hasTransitionLength() ? 1 : 0; + propertyLength += properties.hasEasingType() ? 1 : 0; + + buf.writeByte(propertyLength); + + if (properties.hasAnimationSpeed()) { + buf.writeByte(0); + buf.writeDouble(properties.animationSpeed()); + } + + if (properties.hasTransitionLength()) { + buf.writeByte(1); + buf.writeFloat(properties.transitionLength()); + } + + if (properties.hasEasingType()) { + buf.writeByte(2); + buf.writeUtf(properties.easingType().name()); + } + }; +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationStagePropertiesCodec.java b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationStagePropertiesCodec.java new file mode 100644 index 000000000..9b8dccf6e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/animation/property/codec/AzAnimationStagePropertiesCodec.java @@ -0,0 +1,69 @@ +package mod.azure.azurelib.rewrite.animation.property.codec; + +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypeRegistry; +import mod.azure.azurelib.rewrite.animation.easing.AzEasingTypes; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehaviorRegistry; +import mod.azure.azurelib.rewrite.animation.play_behavior.AzPlayBehaviors; +import mod.azure.azurelib.rewrite.animation.property.AzAnimationStageProperties; +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AzAnimationStagePropertiesCodec { + + public static final Function DECODER = buf -> { + var propertyLength = buf.readByte(); + var properties = AzAnimationStageProperties.EMPTY; + + for (int i = 0; i < propertyLength; i++) { + var code = buf.readByte(); + + switch (code) { + case 0 -> properties = properties.withAnimationSpeed(buf.readDouble()); + case 1 -> properties = properties.withTransitionLength(buf.readFloat()); + case 2 -> { + var easingType = AzEasingTypeRegistry.getOrDefault(buf.readUtf(), AzEasingTypes.NONE); + properties = properties.withEasingType(easingType); + } + case 3 -> { + var playBehavior = AzPlayBehaviorRegistry.getOrDefault(buf.readUtf(), AzPlayBehaviors.PLAY_ONCE); + properties = properties.withPlayBehavior(playBehavior); + } + } + } + + return properties; + }; + + public static final BiConsumer ENCODER = (buf, properties) -> { + var propertyLength = 0; + propertyLength += properties.hasAnimationSpeed() ? 1 : 0; + propertyLength += properties.hasTransitionLength() ? 1 : 0; + propertyLength += properties.hasEasingType() ? 1 : 0; + propertyLength += properties.hasPlayBehavior() ? 1 : 0; + + buf.writeByte(propertyLength); + + if (properties.hasAnimationSpeed()) { + buf.writeByte(0); + buf.writeDouble(properties.animationSpeed()); + } + + if (properties.hasTransitionLength()) { + buf.writeByte(1); + buf.writeFloat(properties.transitionLength()); + } + + if (properties.hasEasingType()) { + buf.writeByte(2); + buf.writeUtf(properties.easingType().name()); + } + + if (properties.hasPlayBehavior()) { + buf.writeByte(3); + buf.writeUtf(properties.playBehavior().name()); + } + }; +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBakedModel.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBakedModel.java new file mode 100644 index 000000000..40c23f402 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBakedModel.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.model; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Represents a baked 3D model consisting of hierarchical bone structures. This class is immutable and provides + * read-only access to bones by name or as a list of top-level bones. Bones are uniquely identified by their names. + */ +public class AzBakedModel { + + public static final AzBakedModel EMPTY = new AzBakedModel(List.of()); + + private final Map bonesByName; + + private final List topLevelBones; + + public AzBakedModel(List topLevelBones) { + this.topLevelBones = Collections.unmodifiableList(topLevelBones); + this.bonesByName = Collections.unmodifiableMap(mapBonesByName(topLevelBones)); + } + + private Map mapBonesByName(List bones) { + var bonesByName = new HashMap(); + var nodesToMap = new ArrayDeque<>(bones); + + while (!nodesToMap.isEmpty()) { + var currentBone = nodesToMap.poll(); + nodesToMap.addAll(currentBone.getChildBones()); + currentBone.saveInitialSnapshot(); + bonesByName.put(currentBone.getName(), currentBone); + } + + return bonesByName; + } + + public @Nullable AzBone getBoneOrNull(String name) { + return bonesByName.get(name); + } + + public Optional getBone(String name) { + return Optional.ofNullable(getBoneOrNull(name)); + } + + public Map getBonesByName() { + return bonesByName; + } + + public List getTopLevelBones() { + return topLevelBones; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBone.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBone.java new file mode 100644 index 000000000..863309916 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBone.java @@ -0,0 +1,459 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ +package mod.azure.azurelib.rewrite.model; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.cache.object.GeoCube; +import mod.azure.azurelib.core.animatable.model.CoreGeoBone; +import mod.azure.azurelib.core.state.BoneSnapshot; +import org.joml.*; + +import java.util.List; +import java.util.Objects; + +/** + * Mutable bone object representing a set of cubes, as well as child bones.
    + * This is the object that is directly modified by animations to handle movement + */ +public class AzBone implements CoreGeoBone { + + private final AzBoneMetadata metadata; + + private final List children = new ObjectArrayList<>(); + + private final List cubes = new ObjectArrayList<>(); + + private final Matrix4f modelSpaceMatrix = new Matrix4f(); + + private final Matrix4f localSpaceMatrix = new Matrix4f(); + + private final Matrix4f worldSpaceMatrix = new Matrix4f(); + + private AzBoneSnapshot initialSnapshot; + + private boolean hidden; + + private boolean childrenHidden = false; + + private final Vector3f pivot; + + private final Vector3f position; + + private final Vector3f rotation; + + private final Vector3f scale; + + private boolean positionChanged = false; + + private boolean rotationChanged = false; + + private boolean scaleChanged = false; + + private Matrix3f worldSpaceNormal = new Matrix3f(); + + private boolean trackingMatrices; + + public AzBone(AzBoneMetadata metadata) { + this.metadata = metadata; + this.trackingMatrices = false; + this.hidden = metadata.dontRender() == Boolean.TRUE; + + this.position = new Vector3f(); + this.pivot = new Vector3f(); + this.rotation = new Vector3f(); + this.scale = new Vector3f(1, 1, 1); + + this.worldSpaceNormal.identity(); + this.worldSpaceMatrix.identity(); + this.localSpaceMatrix.identity(); + this.modelSpaceMatrix.identity(); + } + + @Override + public String getName() { + return metadata.name(); + } + + @Override + public AzBone getParent() { + return metadata.parent(); + } + + @Override + public float getRotX() { + return this.rotation.x; + } + + @Override + public void setRotX(float value) { + this.rotation.x = value; + + markRotationAsChanged(); + } + + @Override + public float getRotY() { + return this.rotation.y; + } + + @Override + public void setRotY(float value) { + this.rotation.y = value; + + markRotationAsChanged(); + } + + @Override + public float getRotZ() { + return this.rotation.z; + } + + @Override + public void setRotZ(float value) { + this.rotation.z = value; + + markRotationAsChanged(); + } + + @Override + public float getPosX() { + return this.position.x; + } + + @Override + public void setPosX(float value) { + this.position.x = value; + + markPositionAsChanged(); + } + + @Override + public float getPosY() { + return this.position.y; + } + + @Override + public void setPosY(float value) { + this.position.y = value; + + markPositionAsChanged(); + } + + @Override + public float getPosZ() { + return this.position.z; + } + + @Override + public void setPosZ(float value) { + this.position.z = value; + + markPositionAsChanged(); + } + + @Override + public float getScaleX() { + return this.scale.x; + } + + @Override + public void setScaleX(float value) { + this.scale.x = value; + + markScaleAsChanged(); + } + + @Override + public float getScaleY() { + return this.scale.y; + } + + @Override + public void setScaleY(float value) { + this.scale.y = value; + + markScaleAsChanged(); + } + + @Override + public float getScaleZ() { + return this.scale.z; + } + + @Override + public void setScaleZ(float value) { + this.scale.z = value; + + markScaleAsChanged(); + } + + @Override + public boolean isHidden() { + return this.hidden; + } + + @Override + public void setHidden(boolean hidden) { + this.hidden = hidden; + + setChildrenHidden(hidden); + } + + @Override + public void setChildrenHidden(boolean hideChildren) { + this.childrenHidden = hideChildren; + } + + @Override + public float getPivotX() { + return this.pivot.x; + } + + @Override + public void setPivotX(float value) { + this.pivot.x = value; + } + + @Override + public float getPivotY() { + return this.pivot.y; + } + + @Override + public void setPivotY(float value) { + this.pivot.y = value; + } + + @Override + public float getPivotZ() { + return this.pivot.z; + } + + @Override + public void setPivotZ(float value) { + this.pivot.z = value; + } + + @Override + public boolean isHidingChildren() { + return this.childrenHidden; + } + + @Override + public void markScaleAsChanged() { + this.scaleChanged = true; + } + + @Override + public void markRotationAsChanged() { + this.rotationChanged = true; + } + + @Override + public void markPositionAsChanged() { + this.positionChanged = true; + } + + @Override + public boolean hasScaleChanged() { + return this.scaleChanged; + } + + @Override + public boolean hasRotationChanged() { + return this.rotationChanged; + } + + @Override + public boolean hasPositionChanged() { + return this.positionChanged; + } + + @Override + public void resetStateChanges() { + this.scaleChanged = false; + this.rotationChanged = false; + this.positionChanged = false; + } + + /** + * @deprecated DO NOT USE OR I WILL FIND YOU. + */ + @Override + @Deprecated(forRemoval = true) + public BoneSnapshot getInitialSnapshot() { + throw new UnsupportedOperationException(); + } + + public AzBoneSnapshot getInitialAzSnapshot() { + return this.initialSnapshot; + } + + @Override + public List getChildBones() { + return this.children; + } + + @Override + public void saveInitialSnapshot() { + if (this.initialSnapshot == null) { + this.initialSnapshot = new AzBoneSnapshot(this); + } + } + + public Boolean getMirror() { + return metadata.mirror(); + } + + public Double getInflate() { + return metadata.inflate(); + } + + public Boolean shouldNeverRender() { + return metadata.dontRender(); + } + + public Boolean getReset() { + return metadata.reset(); + } + + public List getCubes() { + return this.cubes; + } + + public boolean isTrackingMatrices() { + return trackingMatrices; + } + + public void setTrackingMatrices(boolean trackingMatrices) { + this.trackingMatrices = trackingMatrices; + } + + public Matrix4f getModelSpaceMatrix() { + setTrackingMatrices(true); + + return this.modelSpaceMatrix; + } + + public void setModelSpaceMatrix(Matrix4f matrix) { + this.modelSpaceMatrix.set(matrix); + } + + public Matrix4f getLocalSpaceMatrix() { + setTrackingMatrices(true); + + return this.localSpaceMatrix; + } + + public void setLocalSpaceMatrix(Matrix4f matrix) { + this.localSpaceMatrix.set(matrix); + } + + public Matrix4f getWorldSpaceMatrix() { + setTrackingMatrices(true); + + return this.worldSpaceMatrix; + } + + public void setWorldSpaceMatrix(Matrix4f matrix) { + this.worldSpaceMatrix.set(matrix); + } + + public Matrix3f getWorldSpaceNormal() { + return worldSpaceNormal; + } + + public void setWorldSpaceNormal(Matrix3f matrix) { + this.worldSpaceNormal = matrix; + } + + /** + * Get the position of the bone relative to its owner + */ + public Vector3d getLocalPosition() { + Vector4f vec = getLocalSpaceMatrix().transform(new Vector4f(0, 0, 0, 1)); + + return new Vector3d(vec.x(), vec.y(), vec.z()); + } + + /** + * Get the position of the bone relative to the model it belongs to + */ + public Vector3d getModelPosition() { + Vector4f vec = getModelSpaceMatrix().transform(new Vector4f(0, 0, 0, 1)); + + return new Vector3d(-vec.x() * 16f, vec.y() * 16f, vec.z() * 16f); + } + + public void setModelPosition(Vector3d pos) { + // Doesn't work on bones with parent transforms + AzBone parent = metadata.parent(); + Matrix4f matrix = (parent == null ? new Matrix4f().identity() : new Matrix4f(parent.getModelSpaceMatrix())) + .invert(); + Vector4f vec = matrix.transform( + new Vector4f(-(float) pos.x / 16f, (float) pos.y / 16f, (float) pos.z / 16f, 1) + ); + + updatePosition(-vec.x() * 16f, vec.y() * 16f, vec.z() * 16f); + } + + /** + * Get the position of the bone relative to the world + */ + public Vector3d getWorldPosition() { + Vector4f vec = getWorldSpaceMatrix().transform(new Vector4f(0, 0, 0, 1)); + + return new Vector3d(vec.x(), vec.y(), vec.z()); + } + + public Matrix4f getModelRotationMatrix() { + Matrix4f matrix = new Matrix4f(getModelSpaceMatrix()); + matrix.m03(0); + matrix.m13(0); + matrix.m23(0); + + return matrix; + } + + public Vector3d getPositionVector() { + return new Vector3d(getPosX(), getPosY(), getPosZ()); + } + + public Vector3d getRotationVector() { + return new Vector3d(getRotX(), getRotY(), getRotZ()); + } + + public Vector3d getScaleVector() { + return new Vector3d(getScaleX(), getScaleY(), getScaleZ()); + } + + public void addRotationOffsetFromBone(AzBone source) { + setRotX(getRotX() + source.getRotX() - source.getInitialAzSnapshot().getRotX()); + setRotY(getRotY() + source.getRotY() - source.getInitialAzSnapshot().getRotY()); + setRotZ(getRotZ() + source.getRotZ() - source.getInitialAzSnapshot().getRotZ()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + return hashCode() == obj.hashCode(); + } + + @Override + public int hashCode() { + return Objects.hash( + getName(), + (getParent() != null ? getParent().getName() : 0), + getCubes().size(), + getChildBones().size() + ); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneMetadata.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneMetadata.java new file mode 100644 index 000000000..6afa3c3e8 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneMetadata.java @@ -0,0 +1,23 @@ +package mod.azure.azurelib.rewrite.model; + +import mod.azure.azurelib.loading.json.raw.Bone; +import org.jetbrains.annotations.Nullable; + +/** + * AzBoneMetadata is a record class representing metadata about a 3D model bone. This metadata provides information such + * as rendering preferences, inflation values, mirroring, hierarchy, and reset options for a bone in a 3D model's + * structure. + */ +public record AzBoneMetadata( + @Nullable Boolean dontRender, + @Nullable Double inflate, + Boolean mirror, + String name, + @Nullable AzBone parent, + @Nullable Boolean reset +) { + + public AzBoneMetadata(Bone bone, AzBone parent) { + this(bone.neverRender(), bone.inflate(), bone.mirror(), bone.name(), parent, bone.reset()); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneSnapshot.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneSnapshot.java new file mode 100644 index 000000000..80ad60909 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/AzBoneSnapshot.java @@ -0,0 +1,180 @@ +/** + * This class is a fork of the matching class found in the Geckolib repository. Original source: + * https://github.com/bernie-g/geckolib Copyright © 2024 Bernie-G. Licensed under the MIT License. + * https://github.com/bernie-g/geckolib/blob/main/LICENSE + */ + +package mod.azure.azurelib.rewrite.model; + +import org.joml.Vector3f; + +/** + * A state monitoring class for a given {@link AzBone}.
    + */ +public class AzBoneSnapshot { + + private final AzBone bone; + + private final Vector3f offsetPosition; + + private final Vector3f rotation; + + private final Vector3f scale; + + private double lastResetRotationTick = 0; + + private double lastResetPositionTick = 0; + + private double lastResetScaleTick = 0; + + private boolean rotAnimInProgress = true; + + private boolean posAnimInProgress = true; + + private boolean scaleAnimInProgress = true; + + public AzBoneSnapshot(AzBone bone) { + this.bone = bone; + this.offsetPosition = new Vector3f(bone.getPosX(), bone.getPosY(), bone.getPosZ()); + this.rotation = new Vector3f(bone.getRotX(), bone.getRotY(), bone.getRotZ()); + this.scale = new Vector3f(bone.getScaleX(), bone.getScaleY(), bone.getScaleZ()); + } + + public static AzBoneSnapshot copy(AzBoneSnapshot snapshot) { + AzBoneSnapshot newSnapshot = new AzBoneSnapshot(snapshot.bone); + + newSnapshot.offsetPosition.set(snapshot.offsetPosition); + newSnapshot.rotation.set(snapshot.rotation); + newSnapshot.scale.set(snapshot.scale); + + return newSnapshot; + } + + public AzBone getBone() { + return this.bone; + } + + public float getScaleX() { + return this.scale.x; + } + + public float getScaleY() { + return this.scale.y; + } + + public float getScaleZ() { + return this.scale.z; + } + + public float getOffsetX() { + return this.offsetPosition.x; + } + + public float getOffsetY() { + return this.offsetPosition.y; + } + + public float getOffsetZ() { + return this.offsetPosition.z; + } + + public float getRotX() { + return this.rotation.x; + } + + public float getRotY() { + return this.rotation.y; + } + + public float getRotZ() { + return this.rotation.z; + } + + public double getLastResetRotationTick() { + return this.lastResetRotationTick; + } + + public double getLastResetPositionTick() { + return this.lastResetPositionTick; + } + + public double getLastResetScaleTick() { + return this.lastResetScaleTick; + } + + public boolean isRotAnimInProgress() { + return this.rotAnimInProgress; + } + + public boolean isPosAnimInProgress() { + return this.posAnimInProgress; + } + + public boolean isScaleAnimInProgress() { + return this.scaleAnimInProgress; + } + + /** + * Update the scale state of this snapshot + */ + public void updateScale(float scaleX, float scaleY, float scaleZ) { + scale.set(scaleX, scaleY, scaleZ); + } + + /** + * Update the offset state of this snapshot + */ + public void updateOffset(float offsetX, float offsetY, float offsetZ) { + offsetPosition.set(offsetX, offsetY, offsetZ); + } + + /** + * Update the rotation state of this snapshot + */ + public void updateRotation(float rotX, float rotY, float rotZ) { + rotation.set(rotX, rotY, rotZ); + } + + public void startPosAnim() { + this.posAnimInProgress = true; + } + + public void stopPosAnim(double tick) { + this.posAnimInProgress = false; + this.lastResetPositionTick = tick; + } + + public void startRotAnim() { + this.rotAnimInProgress = true; + } + + public void stopRotAnim(double tick) { + this.rotAnimInProgress = false; + this.lastResetRotationTick = tick; + } + + public void startScaleAnim() { + this.scaleAnimInProgress = true; + } + + public void stopScaleAnim(double tick) { + this.scaleAnimInProgress = false; + this.lastResetScaleTick = tick; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + return hashCode() == obj.hashCode(); + } + + @Override + public int hashCode() { + return this.bone.getName().hashCode(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/cache/AzBakedModelCache.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/cache/AzBakedModelCache.java new file mode 100644 index 000000000..ce1069103 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/cache/AzBakedModelCache.java @@ -0,0 +1,49 @@ +package mod.azure.azurelib.rewrite.model.cache; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.loading.FileLoader; +import mod.azure.azurelib.loading.json.raw.Model; +import mod.azure.azurelib.loading.object.GeometryTree; +import mod.azure.azurelib.rewrite.AzResourceCache; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.factory.registry.AzBakedModelFactoryRegistry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +/** + * AzBakedModelCache is a singleton class that extends {@link AzResourceCache} and is designed to manage and cache baked + * models of type {@link AzBakedModel}. It provides functionality to asynchronously load and store models associated + * with specific resource locations. + */ +public class AzBakedModelCache extends AzResourceCache { + + private static final AzBakedModelCache INSTANCE = new AzBakedModelCache(); + + public static AzBakedModelCache getInstance() { + return INSTANCE; + } + + private final Map bakedModels; + + private AzBakedModelCache() { + this.bakedModels = new Object2ObjectOpenHashMap<>(); + } + + public CompletableFuture loadModels(Executor backgroundExecutor, ResourceManager resourceManager) { + return loadResources(backgroundExecutor, resourceManager, "geo", resource -> { + Model model = FileLoader.loadModelFile(resource, resourceManager); + + return AzBakedModelFactoryRegistry.getForNamespace(resource.getNamespace()) + .constructGeoModel(GeometryTree.fromModel(model)); + }, bakedModels::put); + } + + public @Nullable AzBakedModel getNullable(ResourceLocation resourceLocation) { + return bakedModels.get(resourceLocation); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/AzBakedModelFactory.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/AzBakedModelFactory.java new file mode 100644 index 000000000..08bec00f3 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/AzBakedModelFactory.java @@ -0,0 +1,147 @@ +package mod.azure.azurelib.rewrite.model.factory; + +import mod.azure.azurelib.cache.object.GeoCube; +import mod.azure.azurelib.cache.object.GeoQuad; +import mod.azure.azurelib.loading.json.raw.Cube; +import mod.azure.azurelib.loading.json.raw.FaceUV; +import mod.azure.azurelib.loading.json.raw.ModelProperties; +import mod.azure.azurelib.loading.json.raw.UVUnion; +import mod.azure.azurelib.loading.object.BoneStructure; +import mod.azure.azurelib.loading.object.GeometryTree; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.factory.primitive.VertexSet; +import net.minecraft.core.Direction; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +/** + * Abstract factory class for constructing baked models, bones, and cubes from raw input data such as geometry trees or + * properties. This class provides a structure for defining the creation of complex 3D models, including managing + * hierarchical relationships between bones and their associated components. + */ +public abstract class AzBakedModelFactory { + + /** + * Construct the output model from the given {@link GeometryTree}.
    + */ + public abstract AzBakedModel constructGeoModel(GeometryTree geometryTree); + + /** + * Construct a {@link AzBone} from the relevant raw input data + * + * @param boneStructure The {@code BoneStructure} comprising the structure of the bone and its children + * @param properties The loaded properties for the model + * @param parent The parent bone for this bone, or null if a top-level bone + */ + public abstract AzBone constructBone( + BoneStructure boneStructure, + ModelProperties properties, + @Nullable AzBone parent + ); + + /** + * Construct a {@link GeoCube} from the relevant raw input data + * + * @param cube The raw {@code Cube} comprising the structure and properties of the cube + * @param properties The loaded properties for the model + * @param bone The bone this cube belongs to + */ + public abstract GeoCube constructCube(Cube cube, ModelProperties properties, AzBone bone); + + /** + * Builtin method to construct the quad list from the various vertices and related data, to make it easier.
    + * Vertices have already been mirrored here if {@code mirror} is true + */ + public GeoQuad[] buildQuads( + UVUnion uvUnion, + VertexSet vertices, + Cube cube, + float textureWidth, + float textureHeight, + boolean mirror + ) { + GeoQuad[] quads = new GeoQuad[6]; + + quads[0] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.WEST); + quads[1] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.EAST); + quads[2] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.NORTH); + quads[3] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.SOUTH); + quads[4] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.UP); + quads[5] = buildQuad(vertices, cube, uvUnion, textureWidth, textureHeight, mirror, Direction.DOWN); + + return quads; + } + + /** + * Build an individual quad + */ + public GeoQuad buildQuad( + VertexSet vertices, + Cube cube, + UVUnion uvUnion, + float textureWidth, + float textureHeight, + boolean mirror, + Direction direction + ) { + if (!uvUnion.isBoxUV()) { + FaceUV faceUV = uvUnion.faceUV().fromDirection(direction); + + if (faceUV == null) + return null; + + return GeoQuad.build( + vertices.verticesForQuad(direction, false, mirror || cube.mirror() == Boolean.TRUE), + faceUV.uv(), + faceUV.uvSize(), + faceUV.uvRotation(), + textureWidth, + textureHeight, + mirror, + direction + ); + } + + double[] uv = cube.uv().boxUVCoords(); + double[] uvSize = cube.size(); + Vec3 uvSizeVec = new Vec3(Math.floor(uvSize[0]), Math.floor(uvSize[1]), Math.floor(uvSize[2])); + double[][] uvData = switch (direction) { + case WEST -> new double[][] { + new double[] { uv[0] + uvSizeVec.z + uvSizeVec.x, uv[1] + uvSizeVec.z }, + new double[] { uvSizeVec.z, uvSizeVec.y } + }; + case EAST -> new double[][] { + new double[] { uv[0], uv[1] + uvSizeVec.z }, + new double[] { uvSizeVec.z, uvSizeVec.y } + }; + case NORTH -> new double[][] { + new double[] { uv[0] + uvSizeVec.z, uv[1] + uvSizeVec.z }, + new double[] { uvSizeVec.x, uvSizeVec.y } + }; + case SOUTH -> new double[][] { + new double[] { uv[0] + uvSizeVec.z + uvSizeVec.x + uvSizeVec.z, uv[1] + uvSizeVec.z }, + new double[] { uvSizeVec.x, uvSizeVec.y } + }; + case UP -> new double[][] { + new double[] { uv[0] + uvSizeVec.z, uv[1] }, + new double[] { uvSizeVec.x, uvSizeVec.z } + }; + case DOWN -> new double[][] { + new double[] { uv[0] + uvSizeVec.z + uvSizeVec.x, uv[1] + uvSizeVec.z }, + new double[] { uvSizeVec.x, -uvSizeVec.z } + }; + }; + + return GeoQuad.build( + vertices.verticesForQuad(direction, true, mirror || cube.mirror() == Boolean.TRUE), + uvData[0], + uvData[1], + FaceUV.Rotation.NONE, + textureWidth, + textureHeight, + mirror, + direction + ); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/impl/AzBuiltinBakedModelFactory.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/impl/AzBuiltinBakedModelFactory.java new file mode 100644 index 000000000..86e3c68fe --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/impl/AzBuiltinBakedModelFactory.java @@ -0,0 +1,88 @@ +package mod.azure.azurelib.rewrite.model.factory.impl; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.cache.object.GeoCube; +import mod.azure.azurelib.loading.json.raw.Cube; +import mod.azure.azurelib.loading.json.raw.ModelProperties; +import mod.azure.azurelib.loading.object.BoneStructure; +import mod.azure.azurelib.loading.object.GeometryTree; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.model.AzBoneMetadata; +import mod.azure.azurelib.rewrite.model.factory.AzBakedModelFactory; +import mod.azure.azurelib.rewrite.model.factory.primitive.VertexSet; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.world.phys.Vec3; + +/** + * A concrete implementation of the {@link AzBakedModelFactory} that constructs baked models, bones, and cubes from raw + * geometry data. It is tailored to create and configure the model and its associated components in a hierarchical + * manner, based on the provided structure and properties. + */ +public final class AzBuiltinBakedModelFactory extends AzBakedModelFactory { + + @Override + public AzBakedModel constructGeoModel(GeometryTree geometryTree) { + var bones = new ObjectArrayList(); + + for (var boneStructure : geometryTree.topLevelBones().values()) { + bones.add(constructBone(boneStructure, geometryTree.properties(), null)); + } + + return new AzBakedModel(bones); + } + + @Override + public AzBone constructBone(BoneStructure boneStructure, ModelProperties properties, AzBone parent) { + var bone = boneStructure.self(); + var boneMetadata = new AzBoneMetadata(bone, parent); + var newBone = new AzBone(boneMetadata); + var rotation = RenderUtils.arrayToVec(bone.rotation()); + var pivot = RenderUtils.arrayToVec(bone.pivot()); + + newBone.updateRotation( + (float) Math.toRadians(-rotation.x), + (float) Math.toRadians(-rotation.y), + (float) Math.toRadians(rotation.z) + ); + newBone.updatePivot((float) -pivot.x, (float) pivot.y, (float) pivot.z); + + for (var cube : bone.cubes()) { + newBone.getCubes().add(constructCube(cube, properties, newBone)); + } + + // TODO: Avoid recursive calls here. + for (var child : boneStructure.children().values()) { + newBone.getChildBones().add(constructBone(child, properties, newBone)); + } + + return newBone; + } + + @Override + public GeoCube constructCube(Cube cube, ModelProperties properties, AzBone bone) { + var mirror = cube.mirror() == Boolean.TRUE; + var inflate = cube.inflate() != null + ? cube.inflate() / 16f + : (bone.getInflate() == null ? 0 : bone.getInflate() / 16f); + var size = RenderUtils.arrayToVec(cube.size()); + var origin = RenderUtils.arrayToVec(cube.origin()); + var rotation = RenderUtils.arrayToVec(cube.rotation()); + var pivot = RenderUtils.arrayToVec(cube.pivot()); + origin = new Vec3(-(origin.x + size.x) / 16d, origin.y / 16d, origin.z / 16d); + var vertexSize = size.multiply(1 / 16d, 1 / 16d, 1 / 16d); + + pivot = pivot.multiply(-1, 1, 1); + rotation = new Vec3(Math.toRadians(-rotation.x), Math.toRadians(-rotation.y), Math.toRadians(rotation.z)); + var quads = buildQuads( + cube.uv(), + new VertexSet(origin, vertexSize, inflate), + cube, + (float) properties.textureWidth(), + (float) properties.textureHeight(), + mirror + ); + + return new GeoCube(quads, pivot, rotation, size, inflate, mirror); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/primitive/VertexSet.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/primitive/VertexSet.java new file mode 100644 index 000000000..8cd38c448 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/primitive/VertexSet.java @@ -0,0 +1,120 @@ +package mod.azure.azurelib.rewrite.model.factory.primitive; + +import mod.azure.azurelib.cache.object.GeoVertex; +import net.minecraft.core.Direction; +import net.minecraft.world.phys.Vec3; + +/** + * Holder class to make it easier to store and refer to vertices for a given cube + */ +public record VertexSet( + GeoVertex bottomLeftBack, + GeoVertex bottomRightBack, + GeoVertex topLeftBack, + GeoVertex topRightBack, + GeoVertex topLeftFront, + GeoVertex topRightFront, + GeoVertex bottomLeftFront, + GeoVertex bottomRightFront +) { + + public VertexSet(Vec3 origin, Vec3 vertexSize, double inflation) { + this( + new GeoVertex(origin.x - inflation, origin.y - inflation, origin.z - inflation), + new GeoVertex(origin.x - inflation, origin.y - inflation, origin.z + vertexSize.z + inflation), + new GeoVertex(origin.x - inflation, origin.y + vertexSize.y + inflation, origin.z - inflation), + new GeoVertex( + origin.x - inflation, + origin.y + vertexSize.y + inflation, + origin.z + vertexSize.z + inflation + ), + new GeoVertex( + origin.x + vertexSize.x + inflation, + origin.y + vertexSize.y + inflation, + origin.z - inflation + ), + new GeoVertex( + origin.x + vertexSize.x + inflation, + origin.y + vertexSize.y + inflation, + origin.z + vertexSize.z + inflation + ), + new GeoVertex(origin.x + vertexSize.x + inflation, origin.y - inflation, origin.z - inflation), + new GeoVertex( + origin.x + vertexSize.x + inflation, + origin.y - inflation, + origin.z + vertexSize.z + inflation + ) + ); + } + + /** + * Returns the normal vertex array for a west-facing quad + */ + public GeoVertex[] quadWest() { + return new GeoVertex[] { this.topRightBack, this.topLeftBack, this.bottomLeftBack, this.bottomRightBack }; + } + + /** + * Returns the normal vertex array for an east-facing quad + */ + public GeoVertex[] quadEast() { + return new GeoVertex[] { + this.topLeftFront, + this.topRightFront, + this.bottomRightFront, + this.bottomLeftFront + }; + } + + /** + * Returns the normal vertex array for a north-facing quad + */ + public GeoVertex[] quadNorth() { + return new GeoVertex[] { this.topLeftBack, this.topLeftFront, this.bottomLeftFront, this.bottomLeftBack }; + } + + /** + * Returns the normal vertex array for a south-facing quad + */ + public GeoVertex[] quadSouth() { + return new GeoVertex[] { + this.topRightFront, + this.topRightBack, + this.bottomRightBack, + this.bottomRightFront + }; + } + + /** + * Returns the normal vertex array for a top-facing quad + */ + public GeoVertex[] quadUp() { + return new GeoVertex[] { this.topRightBack, this.topRightFront, this.topLeftFront, this.topLeftBack }; + } + + /** + * Returns the normal vertex array for a bottom-facing quad + */ + public GeoVertex[] quadDown() { + return new GeoVertex[] { + this.bottomLeftBack, + this.bottomLeftFront, + this.bottomRightFront, + this.bottomRightBack + }; + } + + /** + * Return the vertex array relevant to the quad being built, taking into account mirroring and quad type + */ + public GeoVertex[] verticesForQuad(Direction direction, boolean boxUv, boolean mirror) { + return switch (direction) { + case WEST -> mirror ? quadEast() : quadWest(); + case EAST -> mirror ? quadWest() : quadEast(); + case NORTH -> quadNorth(); + case SOUTH -> quadSouth(); + case UP -> mirror && !boxUv ? quadDown() : quadUp(); + case DOWN -> mirror && !boxUv ? quadUp() : quadDown(); + }; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/registry/AzBakedModelFactoryRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/registry/AzBakedModelFactoryRegistry.java new file mode 100644 index 000000000..200d12c56 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/model/factory/registry/AzBakedModelFactoryRegistry.java @@ -0,0 +1,38 @@ +package mod.azure.azurelib.rewrite.model.factory.registry; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.rewrite.model.factory.AzBakedModelFactory; +import mod.azure.azurelib.rewrite.model.factory.impl.AzBuiltinBakedModelFactory; +import mod.azure.azurelib.util.AzureLibUtil; + +import java.util.Map; + +/** + * A registry for managing instances of {@link AzBakedModelFactory} that are used to handle the creation of baked models + * for specific namespaces. This allows custom behavior for different mods or namespaces when constructing models.
    + * This class provides functionality to register, retrieve, and manage baked model factories. It ensures that a default + * factory is available for any namespace that does not explicitly register a custom factory. + */ +public class AzBakedModelFactoryRegistry { + + private static final Map FACTORIES = new Object2ObjectOpenHashMap<>(1); + + private static final AzBakedModelFactory DEFAULT_FACTORY = new AzBuiltinBakedModelFactory(); + + public static AzBakedModelFactory getForNamespace(String namespace) { + return FACTORIES.getOrDefault(namespace, DEFAULT_FACTORY); + } + + /** + * Register a custom {@link AzBakedModelFactory} to handle loading models in a custom way.
    + * MUST be called during mod construct
    + * It is recommended you don't call this directly, and instead call it via + * {@link AzureLibUtil#addCustomBakedModelFactory} + * + * @param namespace The namespace (modid) to register the factory for + * @param factory The factory responsible for model loading under the given namespace + */ + public static void register(String namespace, AzBakedModelFactory factory) { + FACTORIES.put(namespace, factory); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzLayerRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzLayerRenderer.java new file mode 100644 index 000000000..4a5e44490 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzLayerRenderer.java @@ -0,0 +1,52 @@ +package mod.azure.azurelib.rewrite.render; + +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * Provides a mechanism to manage and apply multiple render layers for a given animatable context. This class acts as a + * wrapper for handling collections of render layers and delegates the rendering responsibilities to the individual + * layers. + * + * @param The type of animatable entity or object the render layers apply to. + */ +public class AzLayerRenderer { + + private final Supplier>> renderLayerSupplier; + + public AzLayerRenderer(Supplier>> renderLayerSupplier) { + this.renderLayerSupplier = renderLayerSupplier; + } + + /** + * Calls back to the various {@link AzRenderLayer RenderLayers} that have been registered to this renderer for their + * {@link AzRenderLayer#preRender pre-render} actions. + */ + protected void preApplyRenderLayers(AzRendererPipelineContext context) { + for (var renderLayer : renderLayerSupplier.get()) { + renderLayer.preRender(context); + } + } + + /** + * Calls back to the various {@link AzRenderLayer RenderLayers} that have been registered to this renderer for their + * {@link AzRenderLayer#renderForBone per-bone} render actions. + */ + public void applyRenderLayersForBone(AzRendererPipelineContext context, AzBone bone) { + for (var renderLayer : renderLayerSupplier.get()) { + renderLayer.renderForBone(context, bone); + } + } + + /** + * Render the various {@link AzRenderLayer RenderLayers} that have been registered to this renderer + */ + protected void applyRenderLayers(AzRendererPipelineContext context) { + for (var renderLayer : renderLayerSupplier.get()) { + renderLayer.render(context); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzModelRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzModelRenderer.java new file mode 100644 index 000000000..92e01806c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzModelRenderer.java @@ -0,0 +1,165 @@ +package mod.azure.azurelib.rewrite.render; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import mod.azure.azurelib.cache.object.GeoCube; +import mod.azure.azurelib.cache.object.GeoQuad; +import mod.azure.azurelib.cache.object.GeoVertex; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.util.RenderUtils; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; + +/** + * AzModelRenderer provides a generic and extensible base class for rendering models by processing hierarchical bone + * structures recursively. It leverages a rendering pipeline and a layer renderer to facilitate advanced rendering + * tasks, including layer application and animated texture processing. + * + * @param the type of animatable object this renderer supports + */ +public class AzModelRenderer { + + private final Matrix4f poseStateCache = new Matrix4f(); + + private final Vector3f normalCache = new Vector3f(); + + private final AzRendererPipeline rendererPipeline; + + protected final AzLayerRenderer layerRenderer; + + public AzModelRenderer(AzRendererPipeline rendererPipeline, AzLayerRenderer layerRenderer) { + this.layerRenderer = layerRenderer; + this.rendererPipeline = rendererPipeline; + } + + /** + * The actual render method that sub-type renderers should override to handle their specific rendering tasks.
    + */ + protected void render(AzRendererPipelineContext context, boolean isReRender) { + var animatable = context.animatable(); + var model = context.bakedModel(); + + rendererPipeline.updateAnimatedTextureFrame(animatable); + + for (var bone : model.getTopLevelBones()) { + renderRecursively(context, bone, isReRender); + } + } + + /** + * Renders the provided {@link AzBone} and its associated child bones + */ + protected void renderRecursively(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + var poseStack = context.poseStack(); + + poseStack.pushPose(); + RenderUtils.prepMatrixForBone(poseStack, bone); + renderCubesOfBone(context, bone); + + if (!isReRender) { + layerRenderer.applyRenderLayersForBone(context, bone); + } + + renderChildBones(context, bone, isReRender); + poseStack.popPose(); + } + + /** + * Renders the {@link GeoCube GeoCubes} associated with a given {@link AzBone} + */ + protected void renderCubesOfBone(AzRendererPipelineContext context, AzBone bone) { + if (bone.isHidden()) { + return; + } + + var poseStack = context.poseStack(); + + for (var cube : bone.getCubes()) { + poseStack.pushPose(); + + renderCube(context, cube); + + poseStack.popPose(); + } + } + + /** + * Render the child bones of a given {@link AzBone}.
    + * Note that this does not render the bone itself. That should be done through + * {@link AzModelRenderer#renderCubesOfBone} separately + */ + protected void renderChildBones(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + if (bone.isHidingChildren()) + return; + + for (var childBone : bone.getChildBones()) { + renderRecursively(context, childBone, isReRender); + } + } + + /** + * Renders an individual {@link GeoCube}.
    + * This tends to be called recursively from something like {@link AzModelRenderer#renderCubesOfBone} + */ + protected void renderCube(AzRendererPipelineContext context, GeoCube cube) { + var poseStack = context.poseStack(); + + RenderUtils.translateToPivotPoint(poseStack, cube); + RenderUtils.rotateMatrixAroundCube(poseStack, cube); + RenderUtils.translateAwayFromPivotPoint(poseStack, cube); + + var normalisedPoseState = poseStack.last().normal(); + var poseState = poseStateCache.set(poseStack.last().pose()); + + for (var quad : cube.quads()) { + if (quad == null) { + continue; + } + + normalCache.set(quad.normal()); + var normal = normalisedPoseState.transform(normalCache); + + RenderUtils.fixInvertedFlatCube(cube, normal); + createVerticesOfQuad(context, quad, poseState, normal); + } + } + + private final Vector4f poseStateTransformCache = new Vector4f(); + + /** + * Applies the {@link GeoQuad Quad's} {@link GeoVertex vertices} to the given {@link VertexConsumer buffer} for + * rendering + */ + protected void createVerticesOfQuad( + AzRendererPipelineContext context, + GeoQuad quad, + Matrix4f poseState, + Vector3f normal + ) { + var buffer = context.vertexConsumer(); + var packedOverlay = context.packedOverlay(); + var packedLight = context.packedLight(); + + for (var vertex : quad.vertices()) { + var position = vertex.position(); + poseStateTransformCache.set(position.x(), position.y(), position.z(), 1.0f); + var vector4f = poseState.transform(poseStateTransformCache); + + buffer.vertex( + vector4f.x(), + vector4f.y(), + vector4f.z(), + context.red(), + context.green(), + context.blue(), + context.alpha(), + vertex.texU(), + vertex.texV(), + packedOverlay, + packedLight, + normal.x(), + normal.y(), + normal.z()); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzPhasedRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzPhasedRenderer.java new file mode 100644 index 000000000..c295c1812 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzPhasedRenderer.java @@ -0,0 +1,29 @@ +package mod.azure.azurelib.rewrite.render; + +import com.mojang.blaze3d.vertex.PoseStack; + +/** + * Represents a phased renderer interface used as part of a rendering pipeline. Allows customization at specific stages + * of the rendering process for animatable models.
    + * The interface provides two key methods, enabling actions to be taken before and after the core rendering operations + * within the pipeline, specifically focusing on transforming and modifying render contexts.
    + * This is part of a flexible rendering system that enables complex rendering logic while maintaining separation of + * concerns and modularity. + * + * @param The type of animatable object being rendered. + */ +public interface AzPhasedRenderer { + + /** + * Called before rendering the model to buffer. Allows for render modifications and preparatory work such as scaling + * and translating.
    + * {@link PoseStack} translations made here are kept until the end of the render process + */ + void preRender(AzRendererPipelineContext context, boolean isReRender); + + /** + * Called after rendering the model to buffer. Post-render modifications should be performed here.
    + * {@link PoseStack} transformations will be unused and lost once this method ends + */ + void postRender(AzRendererPipelineContext context, boolean isReRender); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzProvider.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzProvider.java new file mode 100644 index 000000000..4d4a7c85a --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzProvider.java @@ -0,0 +1,78 @@ +package mod.azure.azurelib.rewrite.render; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.animation.AzAnimatorAccessor; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.cache.AzBakedModelCache; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * The {@code AzProvider} class serves as a utility for providing animation-related resources, such as baked models and + * animators for animatable objects of type {@code T}. This class facilitates the dynamic retrieval and caching of + * resources to enhance performance during runtime and minimize redundant resource generation. + * + * @param The type of the animatable object this provider works with (e.g., an entity, block, or item). + */ +public class AzProvider { + + private final Supplier> animatorSupplier; + + private final Function modelLocationProvider; + + public AzProvider( + Supplier> animatorSupplier, + Function modelLocationProvider + ) { + this.animatorSupplier = animatorSupplier; + this.modelLocationProvider = modelLocationProvider; + } + + /** + * Provides a baked model associated with the specified animatable object. This method retrieves the model resource + * location for the animatable object using the configured model location provider, then fetches the corresponding + * baked model from the {@link AzBakedModelCache}. + * + * @param animatable the animatable object for which the baked model should be retrieved, must not be null + * @return the baked model associated with the animatable object, or null if no model is found + */ + public @Nullable AzBakedModel provideBakedModel(@NotNull T animatable) { + var modelResourceLocation = modelLocationProvider.apply(animatable); + return AzBakedModelCache.getInstance().getNullable(modelResourceLocation); + } + + /** + * Provides an {@link AzAnimator} instance associated with the given animatable object. If the animator is not + * already cached, this method will create a new animator, register its controllers, and cache it for future use. + * + * @param animatable the animatable object for which the animator should be provided + * @return an {@link AzAnimator} instance associated with the animatable object, or null if the animator could not + * be created or retrieved + */ + public @Nullable AzAnimator provideAnimator(T animatable) { + // TODO: Instead of caching the entire animator itself, we're going to want to cache the relevant data for the + // entity. + var accessor = AzAnimatorAccessor.cast(animatable); + var cachedAnimator = accessor.getAnimatorOrNull(); + + if (cachedAnimator == null) { + // If the cached animator is null, create a new one. We use a separate reference here just for some + cachedAnimator = animatorSupplier.get(); + + if (cachedAnimator != null) { + // If the new animator we created is not null, then register its controllers. + cachedAnimator.registerControllers( + cachedAnimator.getAnimationControllerContainer() + ); + // Also cache the animator so that the next time we fetch the animator, it's ready for us. + accessor.setAnimator(cachedAnimator); + } + } + + return cachedAnimator; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererConfig.java new file mode 100644 index 000000000..98772a1cc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererConfig.java @@ -0,0 +1,283 @@ +package mod.azure.azurelib.rewrite.render; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +/** + * The {@code AzRendererConfig} class is a configuration class used for defining rendering configurations for generic + * animatable objects. It allows customization of model and texture locations, animators, render layers, and scale + * factors. + * + * @param The type of animatable object this configuration applies to. + */ +public class AzRendererConfig { + + protected final Supplier<@Nullable AzAnimator> animatorProvider; + + protected final Function modelLocationProvider; + + protected final Function renderTypeFunction; + + private final UnaryOperator> preRenderEntry; + + private final UnaryOperator> postRenderEntry; + + protected final List> renderLayers; + + protected final Function textureLocationProvider; + + private final Function alphaFunction; + + private final Function scaleHeight; + + private final Function scaleWidth; + + public AzRendererConfig( + Supplier> animatorProvider, + Function modelLocationProvider, + Function renderTypeFunction, + List> renderLayers, + UnaryOperator> preRenderEntry, + UnaryOperator> postRenderEntry, + Function textureLocationProvider, + Function alphaFunction, + Function scaleHeight, + Function scaleWidth + ) { + this.animatorProvider = animatorProvider; + this.modelLocationProvider = modelLocationProvider; + this.renderTypeFunction = renderTypeFunction; + this.renderLayers = Collections.unmodifiableList(renderLayers); + this.preRenderEntry = preRenderEntry; + this.postRenderEntry = postRenderEntry; + this.textureLocationProvider = textureLocationProvider; + this.scaleHeight = scaleHeight; + this.scaleWidth = scaleWidth; + this.alphaFunction = alphaFunction; + } + + public @Nullable AzAnimator createAnimator() { + return animatorProvider.get(); + } + + public ResourceLocation modelLocation(T animatable) { + return modelLocationProvider.apply(animatable); + } + + public ResourceLocation textureLocation(T animatable) { + return textureLocationProvider.apply(animatable); + } + + public RenderType getRenderType(T entity) { + return renderTypeFunction.apply(entity); + } + + public List> renderLayers() { + return renderLayers; + } + + public AzRendererPipelineContext preRenderEntry(AzRendererPipelineContext animatable) { + return preRenderEntry.apply(animatable); + } + + public AzRendererPipelineContext postRenderEntry(AzRendererPipelineContext animatable) { + return postRenderEntry.apply(animatable); + } + + public float alpha(T entity) { + return alphaFunction.apply(entity); + } + + public float scaleHeight(T entity) { + return scaleHeight.apply(entity); + } + + public float scaleWidth(T entity) { + return scaleWidth.apply(entity); + } + + public static class Builder { + + private final Function modelLocationProvider; + + protected Function renderTypeProvider; + + private final List> renderLayers; + + protected UnaryOperator> preRenderEntry; + + protected UnaryOperator> postRenderEntry; + + protected final Function textureLocationProvider; + + protected Supplier<@Nullable AzAnimator> animatorProvider; + + protected Function alphaFunction; + + protected Function scaleHeight; + + protected Function scaleWidth; + + protected Builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + this.animatorProvider = () -> null; + this.modelLocationProvider = modelLocationProvider; + this.renderTypeProvider = $ -> RenderType.entityCutoutNoCull(textureLocationProvider.apply($)); + this.renderLayers = new ObjectArrayList<>(); + this.preRenderEntry = $ -> $; + this.postRenderEntry = $ -> $; + this.textureLocationProvider = textureLocationProvider; + this.alphaFunction = $ -> 1.0F; + this.scaleHeight = $ -> 1.0F; + this.scaleWidth = $ -> 1.0F; + } + + /** + * Sets the animator provider for the builder. The animator provider is responsible for supplying an instance of + * {@link AzAnimator} that defines the animation logic for the target object. + * + * @param animatorProvider a {@link Supplier} that provides a {@link AzAnimator} instance or null if no custom + * animation logic is required + * @return the updated {@code Builder} instance for chaining configuration methods + */ + public Builder setAnimatorProvider(Supplier<@Nullable AzAnimator> animatorProvider) { + this.animatorProvider = animatorProvider; + return this; + } + + /** + * Adds a {@link AzRenderLayer} to this config, to be called after the main model is rendered each frame + */ + public Builder addRenderLayer(AzRenderLayer renderLayer) { + this.renderLayers.add(renderLayer); + return this; + } + + public Builder setPrerenderEntry( + UnaryOperator> preRenderEntry + ) { + this.preRenderEntry = preRenderEntry; + return this; + } + + public Builder setPostRenderEntry( + UnaryOperator> postRenderEntry + ) { + this.postRenderEntry = postRenderEntry; + return this; + } + + /** + * Sets the alpha value provider for the builder. The alpha value determines the opacity level of the rendered + * object and is calculated dynamically based on the specified function. + * + * @param alphaFunction a {@link Function} that takes an object of type {@code T} and returns a {@code Float} + * value representing the alpha (opacity) level, where 0.0 is fully transparent and 1.0 is + * fully opaque + * @return the updated {@code Builder} instance for chaining configuration methods + */ + public Builder setAlpha(Function alphaFunction) { + this.alphaFunction = alphaFunction; + return this; + } + + /** + * Sets the alpha transparency level for the builder, which determines the level of transparency to be applied. + * + * @param alpha the alpha transparency value to set, where 0.0 represents fully transparent and 1.0 represents + * fully opaque + * @return the updated {@code Builder} instance for chaining configuration methods + */ + public Builder setAlpha(float alpha) { + this.alphaFunction = $ -> alpha; + return this; + } + + /** + * Sets the scaling factor uniformly for both width and height dimensions. + * + * @param scale the uniform scaling factor to be applied to both width and height + * @return the {@code Builder} instance for method chaining + */ + public Builder setScale(float scale) { + return setScale(scale, scale); + } + + /** + * Sets the scaling factors for both width and height. + * + * @param scaleWidth the scaling factor for the width + * @param scaleHeight the scaling factor for the height + * @return the updated builder instance for chaining operations + */ + public Builder setScale(float scaleWidth, float scaleHeight) { + this.scaleHeight = $ -> scaleHeight; + this.scaleWidth = $ -> scaleWidth; + return this; + } + + /** + * Sets the scaling function for both the width and height dimensions of the target object. The provided + * function dynamically calculates scaling factors based on the input object of type {@code T}. + * + * @param scaleFunction a {@link Function} that takes an object of type {@code T} and returns a {@code Float} + * value representing the scaling factor to be applied uniformly to both width and height + * @return the updated {@code Builder} instance for chaining configuration methods + */ + public Builder setScale(Function scaleFunction) { + this.scaleHeight = scaleFunction; + this.scaleWidth = scaleFunction; + return this; + } + + /** + * Sets the scaling functions for height and width dimensions. These functions dynamically calculate scaling + * factors based on the input object of type {@code T}. + * + * @param scaleHeightFunction a {@link Function} that takes an object of type {@code T} and returns a + * {@code Float} representing the scaling factor for the height dimension + * @param scaleWidthFunction a {@link Function} that takes an object of type {@code T} and returns a + * {@code Float} representing the scaling factor for the width dimension + * @return the updated {@code Builder} instance for chaining configuration methods + */ + public Builder setScale(Function scaleHeightFunction, Function scaleWidthFunction) { + this.scaleHeight = scaleHeightFunction; + this.scaleWidth = scaleWidthFunction; + return this; + } + + /** + * Builds and returns a finalized {@link AzRendererConfig} instance with the current configuration settings + * provided through the builder. + * + * @return a new instance of {@link AzRendererConfig} configured with the specified animator provider, model + * location provider, texture location provider, render layers, and scale factors. + */ + public AzRendererConfig build() { + return new AzRendererConfig<>( + animatorProvider, + modelLocationProvider, + renderTypeProvider, + renderLayers, + preRenderEntry, + postRenderEntry, + textureLocationProvider, + alphaFunction, + scaleHeight, + scaleWidth + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipeline.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipeline.java new file mode 100644 index 000000000..1b1bba049 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipeline.java @@ -0,0 +1,186 @@ +package mod.azure.azurelib.rewrite.render; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import mod.azure.azurelib.cache.texture.AnimatableTexture; +import mod.azure.azurelib.core.object.Color; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import org.jetbrains.annotations.Nullable; + +/** + * Abstract base class for defining a rendering pipeline. The {@code AzRendererPipeline} provides a structured framework + * to handle complex rendering tasks by separating responsibilities into different components, such as layer rendering + * and model rendering. + * + * @param The type of the object to be rendered. + */ +public abstract class AzRendererPipeline implements AzPhasedRenderer { + + protected final AzRendererConfig config; + + private final AzRendererPipelineContext context; + + private final AzLayerRenderer layerRenderer; + + private final AzModelRenderer modelRenderer; + + protected AzRendererPipeline(AzRendererConfig config) { + this.config = config; + this.context = createContext(this); + this.layerRenderer = createLayerRenderer(config); + this.modelRenderer = createModelRenderer(layerRenderer); + } + + /** + * Creates a rendering pipeline context for the specified renderer pipeline. This method is intended to be + * implemented by subclasses to provide a specific implementation of the {@link AzRendererPipelineContext} for + * rendering. + * + * @param rendererPipeline the renderer pipeline for which the context is to be created + * @return a new instance of {@link AzRendererPipelineContext} specific to the given renderer pipeline + */ + protected abstract AzRendererPipelineContext createContext(AzRendererPipeline rendererPipeline); + + /** + * Creates an instance of {@link AzModelRenderer} using the provided {@link AzLayerRenderer}. This method is part of + * the rendering pipeline and is responsible for generating a model renderer which can handle hierarchical + * structures and advanced rendering tasks. + * + * @param layerRenderer the {@link AzLayerRenderer} instance used to decorate and handle additional render layers + * within the model rendering process + * @return a new instance of {@link AzModelRenderer} configured with the provided layer renderer + */ + protected abstract AzModelRenderer createModelRenderer(AzLayerRenderer layerRenderer); + + /** + * Creates an instance of {@link AzLayerRenderer} using the provided {@link AzRendererConfig}. This method is + * responsible for generating a layer renderer configured with the provided rendering configuration, allowing for + * the management and application of multiple render layers. + * + * @param config The configuration object of type {@link AzRendererConfig} that provides the necessary settings and + * parameters for the layer renderer. + * @return A newly created {@link AzLayerRenderer} instance configured based on the specified + * {@link AzRendererConfig}. + */ + protected abstract AzLayerRenderer createLayerRenderer(AzRendererConfig config); + + /** + * Update the current frame of a {@link AnimatableTexture potentially animated} texture used by this + * GeoRenderer.
    + * This should only be called immediately prior to rendering, and only + * + * @see AnimatableTexture#setAndUpdate + */ + protected abstract void updateAnimatedTextureFrame(T animatable); + + /** + * Initial access point for rendering. It all begins here.
    + * All AzureLib renderers should immediately defer their respective default {@code render} calls to this, for + * consistent handling + */ + public void render( + PoseStack poseStack, + AzBakedModel model, + T animatable, + MultiBufferSource bufferSource, + @Nullable RenderType renderType, + @Nullable VertexConsumer buffer, + float yaw, + float partialTick, + int packedLight + ) { + renderType = config.getRenderType(animatable); + context.populate(animatable, model, bufferSource, packedLight, partialTick, poseStack, renderType, buffer); + + poseStack.pushPose(); + + preRender(context, false); + + // TODO: + // if (firePreRenderEvent(poseStack, model, bufferSource, partialTick, packedLight)) { + layerRenderer.preApplyRenderLayers(context); + modelRenderer.render(context, false); + layerRenderer.applyRenderLayers(context); + postRender(context, false); + // TODO: + // firePostRenderEvent(poseStack, model, bufferSource, partialTick, packedLight); + // } + + poseStack.popPose(); + + renderFinal(context); + doPostRenderCleanup(); + } + + /** + * Re-renders the provided {@link AzBakedModel}.
    + * Usually you'd use this for rendering alternate {@link RenderType} layers or for sub-model rendering whilst inside + * a {@link AzRenderLayer} or similar + */ + public void reRender(AzRendererPipelineContext context) { + var poseStack = context.poseStack(); + + poseStack.pushPose(); + + preRender(context, true); + modelRenderer.render(context, true); + postRender(context, true); + + poseStack.popPose(); + } + + /** + * Call after all other rendering work has taken place, including reverting the {@link PoseStack}'s state. This + * method is not called in {@link AzRendererPipeline#reRender re-render} + */ + protected void renderFinal(AzRendererPipelineContext context) {} + + /** + * Called after all render operations are completed and the render pass is considered functionally complete. + *

    + * Use this method to clean up any leftover persistent objects stored during rendering or any other post-render + * maintenance tasks as required + */ + protected void doPostRenderCleanup() {} + + /** + * Scales the {@link PoseStack} in preparation for rendering the model, excluding when re-rendering the model as + * part of a {@link AzRenderLayer} or external render call.
    + * Override and call super with modified scale values as needed to further modify the scale of the model (E.G. child + * entities) + */ + protected void scaleModelForRender( + AzRendererPipelineContext context, + float widthScale, + float heightScale, + boolean isReRender + ) { + if (!isReRender && (widthScale != 1 || heightScale != 1)) { + var poseStack = context.poseStack(); + poseStack.scale(widthScale, heightScale, widthScale); + } + } + + /** + * Provides access to the rendering configuration associated with this rendering pipeline. + * + * @return An instance of {@link AzRendererConfig} that contains the configuration details for this rendering + * pipeline, including animator, model location, texture location, render layers, and scaling parameters. + */ + public AzRendererConfig config() { + return config; + } + + /** + * Provides access to the rendering pipeline context associated with this rendering pipeline. + * + * @return An instance of {@link AzRendererPipelineContext} representing the context for the current rendering + * pipeline, containing relevant rendering data and configurations for processing animations and models. + */ + public AzRendererPipelineContext context() { + return context; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipelineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipelineContext.java new file mode 100644 index 000000000..24fb83a5e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/AzRendererPipelineContext.java @@ -0,0 +1,229 @@ +package mod.azure.azurelib.rewrite.render; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import mod.azure.azurelib.core.object.Color; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; + +import java.util.Objects; + +/** + * An abstract base class representing the rendering context for a custom rendering pipeline. This class provides + * generic rendering properties and behavior that can be extended to customize rendering for different types of + * animatable objects. + * + * @param the type of the animatable object being rendered + */ +public abstract class AzRendererPipelineContext { + + private final AzRendererPipeline rendererPipeline; + + private T animatable; + + private AzBakedModel bakedModel; + + private MultiBufferSource multiBufferSource; + + private int packedLight; + + private int packedOverlay; + + private float partialTick; + + private PoseStack poseStack; + + private float red; + + private float green; + + private float blue; + + private float alpha; + + private RenderType renderType; + + private VertexConsumer vertexConsumer; + + protected AzRendererPipelineContext(AzRendererPipeline rendererPipeline) { + this.rendererPipeline = rendererPipeline; + } + + /** + * Populates the rendering context with all necessary parameters required to render a specific animatable object. + * This method initializes the rendering pipeline with data such as the model, buffer source, lighting, and other + * associated properties for rendering the specified animatable object. + * + * @param animatable The animatable object that is being rendered. + * @param bakedModel The pre-baked 3D model associated with the animatable object. + * @param multiBufferSource The multibuffer source used for rendering vertex data. + * @param packedLight The packed light value for controlling light effects during rendering. + * @param partialTick The partial tick value for interpolating animations or movements. + * @param poseStack The pose stack used to manage rendering transformations. + * @param renderType The render type that determines how the object will be rendered, e.g., opaque, + * translucent, etc. + * @param vertexConsumer The vertex consumer used for buffering vertex attributes during rendering. + */ + public void populate( + T animatable, + AzBakedModel bakedModel, + MultiBufferSource multiBufferSource, + int packedLight, + float partialTick, + PoseStack poseStack, + RenderType renderType, + VertexConsumer vertexConsumer + ) { + this.animatable = animatable; + this.bakedModel = bakedModel; + this.multiBufferSource = multiBufferSource; + this.packedLight = packedLight; + this.packedOverlay = getPackedOverlay(animatable, 0, partialTick); + this.partialTick = partialTick; + this.poseStack = poseStack; + this.renderType = renderType; + this.vertexConsumer = vertexConsumer; + Color renderColor = getRenderColor(animatable, partialTick, packedLight); + this.red = renderColor.getRedFloat(); + this.green = renderColor.getGreenFloat(); + this.blue = renderColor.getBlueFloat(); + this.alpha = renderColor.getAlphaFloat(); + + if (renderType == null) { + var textureLocation = rendererPipeline.config().textureLocation(animatable); + this.renderType = getDefaultRenderType(animatable, textureLocation, multiBufferSource, partialTick); + } + + Objects.requireNonNull(this.renderType); + + if (vertexConsumer == null) { + this.vertexConsumer = multiBufferSource.getBuffer(this.renderType); + } + } + + /** + * Gets the {@link RenderType} to render the given animatable with.
    + * Uses the {@link RenderType#entityCutoutNoCull} {@code RenderType} by default.
    + * Override this to change the way a model will render (such as translucent models, etc) + */ + public abstract RenderType getDefaultRenderType( + T animatable, + ResourceLocation texture, + MultiBufferSource bufferSource, + float partialTick + ); + + /** + * Gets a tint-applying color to render the given animatable with.
    + * Returns {@link Color#WHITE} by default + */ + public Color getRenderColor(T animatable, float partialTick, int packedLight) { + return Color.WHITE; + } + + /** + * Gets a packed overlay coordinate pair for rendering.
    + * Mostly just used for the red tint when an entity is hurt, but can be used for other things like the + * {@link net.minecraft.world.entity.monster.Creeper} white tint when exploding. + */ + protected int getPackedOverlay(T animatable, float u, float partialTick) { + return OverlayTexture.NO_OVERLAY; + } + + public AzRendererPipeline rendererPipeline() { + return rendererPipeline; + } + + public T animatable() { + return animatable; + } + + public AzBakedModel bakedModel() { + return bakedModel; + } + + public MultiBufferSource multiBufferSource() { + return multiBufferSource; + } + + public float red() { + return red; + } + + public float green() { + return green; + } + + public float blue() { + return blue; + } + + public float alpha() { + return alpha; + } + + public void setRed(float red) { + this.red = red; + } + + public void setGreen(float green) { + this.green = green; + } + + public void setBlue(float blue) { + this.blue = blue; + } + + public void setAlpha(float alpha) { + this.alpha = alpha; + } + + public void setColor(Color color) { + this.red = color.getRedFloat(); + this.green = color.getGreenFloat(); + this.blue = color.getBlueFloat(); + } + + public int packedLight() { + return packedLight; + } + + public void setPackedLight(int packedLight) { + this.packedLight = packedLight; + } + + public int packedOverlay() { + return packedOverlay; + } + + public void setPackedOverlay(int packedOverlay) { + this.packedOverlay = packedOverlay; + } + + public float partialTick() { + return partialTick; + } + + public PoseStack poseStack() { + return poseStack; + } + + public RenderType renderType() { + return renderType; + } + + public void setRenderType(RenderType renderType) { + this.renderType = renderType; + } + + public VertexConsumer vertexConsumer() { + return vertexConsumer; + } + + public void setVertexConsumer(VertexConsumer vertexConsumer) { + this.vertexConsumer = vertexConsumer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModel.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModel.java new file mode 100644 index 000000000..f937b1e4b --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModel.java @@ -0,0 +1,77 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class AzArmorModel extends HumanoidModel { + + private final AzArmorRendererPipeline rendererPipeline; + + public AzArmorModel(AzArmorRendererPipeline rendererPipeline) { + super(Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.PLAYER_INNER_ARMOR)); + this.rendererPipeline = rendererPipeline; + } + + @Override + public void renderToBuffer( + @NotNull PoseStack poseStack, + @Nullable VertexConsumer buffer, + int packedLight, + int packedOverlay, + float red, + float green, + float blue, + float alpha + ) { + var mc = Minecraft.getInstance(); + var context = rendererPipeline.context(); + var currentEntity = context.currentEntity(); + var currentStack = context.currentStack(); + MultiBufferSource bufferSource = Minecraft.getInstance().levelRenderer.renderBuffers.bufferSource(); + + var shouldOutline = Minecraft.getInstance().levelRenderer.shouldShowEntityOutlines() && mc + .shouldEntityAppearGlowing( + currentEntity + ); + + if (shouldOutline) { + bufferSource = Minecraft.getInstance().levelRenderer.renderBuffers.outlineBufferSource(); + } + + var config = rendererPipeline.config(); + var animatable = context.animatable(); + var partialTick = mc.getFrameTime(); + var textureLocation = config.textureLocation(animatable); + var renderType = context.getDefaultRenderType(animatable, textureLocation, bufferSource, partialTick); + buffer = ItemRenderer.getArmorFoilBuffer(bufferSource, renderType, false, currentStack.hasFoil()); + + var model = rendererPipeline.renderer().provider().provideBakedModel(animatable); + rendererPipeline.render(poseStack, model, animatable, bufferSource, null, buffer, 0, partialTick, packedLight); + } + + /** + * Applies settings and transformations pre-render based on the default model + */ + public void applyBaseModel(HumanoidModel baseModel) { + this.young = baseModel.young; + this.crouching = baseModel.crouching; + this.riding = baseModel.riding; + this.rightArmPose = baseModel.rightArmPose; + this.leftArmPose = baseModel.leftArmPose; + } + + @Override + public void setAllVisible(boolean pVisible) { + super.setAllVisible(pVisible); + var boneContext = rendererPipeline.context().boneContext(); + boneContext.setAllVisible(pVisible); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModelRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModelRenderer.java new file mode 100644 index 000000000..ab30de465 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorModelRenderer.java @@ -0,0 +1,79 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzModelRenderer; +import mod.azure.azurelib.rewrite.render.AzPhasedRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class AzArmorModelRenderer extends AzModelRenderer { + + private final AzArmorRendererPipeline armorRendererPipeline; + + public AzArmorModelRenderer( + AzArmorRendererPipeline armorRendererPipeline, + AzLayerRenderer layerRenderer + ) { + super(armorRendererPipeline, layerRenderer); + this.armorRendererPipeline = armorRendererPipeline; + } + + /** + * The actual render method that subtype renderers should override to handle their specific rendering tasks.
    + * {@link AzPhasedRenderer#preRender} has already been called by this stage, and {@link AzPhasedRenderer#postRender} + * will be called directly after + */ + @Override + public void render(AzRendererPipelineContext context, boolean isReRender) { + var poseStack = context.poseStack(); + poseStack.pushPose(); + poseStack.translate(0, 24 / 16f, 0); + poseStack.scale(-1, -1, 1); + + if (!isReRender) { + var animatable = context.animatable(); + var animator = armorRendererPipeline.renderer().animator(); + + if (animator != null) { + animator.animate(animatable, context.partialTick()); + } + } + + armorRendererPipeline.modelRenderTranslations = new Matrix4f(poseStack.last().pose()); + + super.render(context, isReRender); + poseStack.popPose(); + } + + /** + * Renders the provided {@link AzBone} and its associated child bones + */ + @Override + public void renderRecursively(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + var poseStack = context.poseStack(); + // TODO: This is dangerous. + var ctx = armorRendererPipeline.context(); + + if (bone.isTrackingMatrices()) { + Matrix4f poseState = new Matrix4f(poseStack.last().pose()); + Matrix4f localMatrix = RenderUtils.invertAndMultiplyMatrices( + poseState, + armorRendererPipeline.entityRenderTranslations + ); + + bone.setModelSpaceMatrix( + RenderUtils.invertAndMultiplyMatrices(poseState, armorRendererPipeline.modelRenderTranslations) + ); + bone.setLocalSpaceMatrix(RenderUtils.translateMatrix(localMatrix, new Vector3f())); + bone.setWorldSpaceMatrix( + RenderUtils.translateMatrix(new Matrix4f(localMatrix), ctx.currentEntity().position().toVector3f()) + ); + } + + super.renderRecursively(context, bone, isReRender); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRenderer.java new file mode 100644 index 000000000..5c7c0fcac --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRenderer.java @@ -0,0 +1,80 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import mod.azure.azurelib.rewrite.animation.impl.AzItemAnimator; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.render.AzProvider; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +public class AzArmorRenderer { + + private final AzProvider provider; + + private final AzArmorRendererPipeline rendererPipeline; + + @Nullable + private AzItemAnimator reusedAzItemAnimator; + + public AzArmorRenderer(AzRendererConfig config) { + this.provider = new AzProvider<>(config::createAnimator, config::modelLocation); + this.rendererPipeline = createPipeline(config); + } + + protected AzArmorRendererPipeline createPipeline(AzRendererConfig config) { + return new AzArmorRendererPipeline(config, this); + } + + /** + * Prepare the renderer for the current render cycle.
    + * Must be called prior to render as the default HumanoidModel doesn't give render context.
    + * Params have been left nullable so that the renderer can be called for model/texture purposes safely. If you do + * grab the renderer using null parameters, you should not use it for actual rendering. + * + * @param entity The entity being rendered with the armor on + * @param stack The ItemStack being rendered + * @param slot The slot being rendered + * @param baseModel The default (vanilla) model that would have been rendered if this model hadn't replaced it + */ + public void prepForRender( + @Nullable Entity entity, + ItemStack stack, + @Nullable EquipmentSlot slot, + @Nullable HumanoidModel baseModel + ) { + if (entity == null || slot == null || baseModel == null) { + return; + } + + rendererPipeline.context().prepare(entity, stack, slot, baseModel); + + var model = provider.provideBakedModel(stack); + prepareAnimator(stack, model); + } + + private void prepareAnimator(ItemStack stack, AzBakedModel model) { + var cachedEntityAnimator = (AzItemAnimator) provider.provideAnimator(stack); + + if (cachedEntityAnimator != null && model != null) { + cachedEntityAnimator.setActiveModel(model); + } + + // Point the renderer's current animator reference to the cached entity animator before rendering. + reusedAzItemAnimator = cachedEntityAnimator; + } + + public @Nullable AzItemAnimator animator() { + return reusedAzItemAnimator; + } + + public AzProvider provider() { + return provider; + } + + public AzArmorRendererPipeline rendererPipeline() { + return rendererPipeline; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererConfig.java new file mode 100644 index 000000000..8b715f2cb --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererConfig.java @@ -0,0 +1,171 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.armor.bone.AzArmorBoneProvider; +import mod.azure.azurelib.rewrite.render.armor.bone.AzDefaultArmorBoneProvider; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +public class AzArmorRendererConfig extends AzRendererConfig { + + private final AzArmorBoneProvider boneProvider; + + private AzArmorRendererConfig( + Supplier> animatorProvider, + AzArmorBoneProvider boneProvider, + Function modelLocationProvider, + Function renderTypeProvider, + List> renderLayers, + UnaryOperator> preRenderEntry, + UnaryOperator> postRenderEntry, + Function textureLocationProvider, + Function alphaFunction, + Function scaleHeight, + Function scaleWidth + ) { + super( + animatorProvider, + modelLocationProvider, + renderTypeProvider, + renderLayers, + preRenderEntry, + postRenderEntry, + textureLocationProvider, + alphaFunction, + scaleHeight, + scaleWidth + ); + this.boneProvider = boneProvider; + } + + public AzArmorBoneProvider boneProvider() { + return boneProvider; + } + + public static Builder builder( + ResourceLocation modelLocation, + ResourceLocation textureLocation + ) { + return new Builder($ -> modelLocation, $ -> textureLocation); + } + + public static Builder builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + return new Builder(modelLocationProvider, textureLocationProvider); + } + + public static class Builder extends AzRendererConfig.Builder { + + private AzArmorBoneProvider boneProvider; + + protected Builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + super(modelLocationProvider, textureLocationProvider); + this.boneProvider = new AzDefaultArmorBoneProvider(); + this.renderTypeProvider = $ -> RenderType.armorCutoutNoCull(textureLocationProvider.apply($)); + } + + @Override + public Builder addRenderLayer(AzRenderLayer renderLayer) { + return (Builder) super.addRenderLayer(renderLayer); + } + + public Builder setRenderType(RenderType renderType) { + this.renderTypeProvider = $ -> renderType; + return this; + } + + public Builder setRenderType(Function renderTypeProvider) { + this.renderTypeProvider = renderTypeProvider; + return this; + } + + @Override + public Builder setAnimatorProvider(Supplier<@Nullable AzAnimator> animatorProvider) { + return (Builder) super.setAnimatorProvider(animatorProvider); + } + + @Override + public Builder setPrerenderEntry(UnaryOperator> preRenderEntry + ) { + return (AzArmorRendererConfig.Builder) super.setPrerenderEntry(preRenderEntry); + } + + @Override + public Builder setPostRenderEntry(UnaryOperator> preRenderEntry + ) { + return (AzArmorRendererConfig.Builder) super.setPostRenderEntry(preRenderEntry); + } + + @Override + public Builder setAlpha(Function alphaFunction) { + return (AzArmorRendererConfig.Builder) super.setAlpha(alphaFunction); + } + + @Override + public Builder setAlpha(float alpha) { + return (AzArmorRendererConfig.Builder) super.setAlpha(alpha); + } + + @Override + public Builder setScale(Function scaleFunction) { + return (AzArmorRendererConfig.Builder) super.setScale(scaleFunction); + } + + @Override + public Builder setScale( + Function scaleHeightFunction, + Function scaleWidthFunction + ) { + return (AzArmorRendererConfig.Builder) super.setScale(scaleHeightFunction, scaleWidthFunction); + } + + @Override + public Builder setScale(float scale) { + return (AzArmorRendererConfig.Builder) super.setScale(scale); + } + + @Override + public Builder setScale(float scaleWidth, float scaleHeight) { + return (AzArmorRendererConfig.Builder) super.setScale(scaleWidth, scaleHeight); + } + + public Builder setBoneProvider(AzArmorBoneProvider boneProvider) { + this.boneProvider = boneProvider; + return this; + } + + @Override + public AzArmorRendererConfig build() { + var baseConfig = super.build(); + + return new AzArmorRendererConfig( + baseConfig::createAnimator, + boneProvider, + baseConfig::modelLocation, + baseConfig::getRenderType, + baseConfig.renderLayers(), + baseConfig::preRenderEntry, + baseConfig::postRenderEntry, + baseConfig::textureLocation, + baseConfig::alpha, + baseConfig::scaleHeight, + baseConfig::scaleWidth + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipeline.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipeline.java new file mode 100644 index 000000000..306780d75 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipeline.java @@ -0,0 +1,130 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import mod.azure.azurelib.cache.texture.AnimatableTexture; +import mod.azure.azurelib.rewrite.render.*; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix4f; + +public class AzArmorRendererPipeline extends AzRendererPipeline { + + private final AzArmorModel armorModel; + + private final AzArmorRenderer armorRenderer; + + protected Matrix4f entityRenderTranslations = new Matrix4f(); + + protected Matrix4f modelRenderTranslations = new Matrix4f(); + + public AzArmorRendererPipeline(AzRendererConfig config, AzArmorRenderer armorRenderer) { + super(config); + this.armorModel = new AzArmorModel<>(this); + this.armorRenderer = armorRenderer; + } + + @Override + protected AzRendererPipelineContext createContext(AzRendererPipeline rendererPipeline) { + return new AzArmorRendererPipelineContext(rendererPipeline); + } + + @Override + protected AzModelRenderer createModelRenderer(AzLayerRenderer layerRenderer) { + return new AzArmorModelRenderer(this, layerRenderer); + } + + @Override + protected AzLayerRenderer createLayerRenderer(AzRendererConfig config) { + return new AzLayerRenderer<>(config::renderLayers); + } + + @Override + protected void updateAnimatedTextureFrame(ItemStack animatable) { + var currentEntity = context().currentEntity(); + + if (currentEntity != null) { + AnimatableTexture.setAndUpdate(config.textureLocation(animatable)); + } + } + + @Override + public void preRender(AzRendererPipelineContext context, boolean isReRender) { + var armorContext = (AzArmorRendererPipelineContext) context; + var baseModel = armorContext.baseModel(); + var boneContext = armorContext.boneContext(); + var config = config(); + var currentSlot = armorContext.currentSlot(); + var scaleWidth = config.scaleWidth(context.animatable()); + var scaleHeight = config.scaleHeight(context.animatable()); + + var animatable = armorContext.animatable(); + var model = armorRenderer.provider().provideBakedModel(animatable); + var poseStack = armorContext.poseStack(); + + this.entityRenderTranslations = new Matrix4f(poseStack.last().pose()); + + armorModel.applyBaseModel(baseModel); + boneContext.grabRelevantBones(model, config.boneProvider()); + boneContext.applyBaseTransformations(baseModel); + scaleModelForBaby(armorContext, isReRender); + scaleModelForRender(context, scaleWidth, scaleHeight, isReRender); + + boneContext.applyBoneVisibilityBySlot(currentSlot); + if (config.alpha(context.animatable()) < 1) { + armorContext.setAlpha(config.alpha(context.animatable())); + armorContext.setTranslucent(true); + } + config.preRenderEntry(context); + } + + @Override + public void postRender(AzRendererPipelineContext context, boolean isReRender) { + config.postRenderEntry(context); + } + + /** + * Apply custom scaling to account for {@link net.minecraft.client.model.AgeableListModel AgeableListModel} baby + * models + */ + public void scaleModelForBaby(AzArmorRendererPipelineContext context, boolean isReRender) { + if (!armorModel.young || isReRender) { + return; + } + + var baseModel = context.baseModel(); + var currentSlot = context.currentSlot(); + var poseStack = context.poseStack(); + + if (currentSlot == EquipmentSlot.HEAD) { + if (baseModel.scaleHead) { + float headScale = 1.5f / baseModel.babyHeadScale; + + poseStack.scale(headScale, headScale, headScale); + } + + poseStack.translate(0, baseModel.babyYHeadOffset / 16f, baseModel.babyZHeadOffset / 16f); + } else { + float bodyScale = 1 / baseModel.babyBodyScale; + + poseStack.scale(bodyScale, bodyScale, bodyScale); + poseStack.translate(0, baseModel.bodyYOffset / 16f, 0); + } + } + + public AzArmorModel armorModel() { + return armorModel; + } + + @Override + public AzArmorRendererConfig config() { + return (AzArmorRendererConfig) super.config(); + } + + @Override + public AzArmorRendererPipelineContext context() { + return (AzArmorRendererPipelineContext) super.context(); + } + + public AzArmorRenderer renderer() { + return armorRenderer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipelineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipelineContext.java new file mode 100644 index 000000000..a1004f5d4 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererPipelineContext.java @@ -0,0 +1,108 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import mod.azure.azurelib.core.object.Color; +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.armor.bone.AzArmorBoneContext; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.DyeableArmorItem; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class AzArmorRendererPipelineContext extends AzRendererPipelineContext { + + private final AzArmorBoneContext boneContext; + + private HumanoidModel baseModel; + + private Entity currentEntity; + + private EquipmentSlot currentSlot; + + private ItemStack currentStack; + + private boolean translucent = false; + + public AzArmorRendererPipelineContext(AzRendererPipeline rendererPipeline) { + super(rendererPipeline); + this.baseModel = null; + this.boneContext = new AzArmorBoneContext(); + this.currentEntity = null; + this.currentSlot = null; + this.currentStack = null; + } + + @Override + public @NotNull RenderType getDefaultRenderType( + ItemStack animatable, + ResourceLocation texture, + @Nullable MultiBufferSource bufferSource, + float partialTick + ) { + return translucent + ? RenderType.itemEntityTranslucentCull(texture) + : RenderType.armorCutoutNoCull(texture); + } + + public void prepare( + @Nullable Entity entity, + ItemStack stack, + @Nullable EquipmentSlot slot, + @Nullable HumanoidModel baseModel + ) { + this.baseModel = baseModel; + this.currentEntity = entity; + this.currentStack = stack; + this.currentSlot = slot; + } + + /** + * Sets whether the rendering pipeline should render with a translucent effect or not. + * + * @param translucent A boolean value indicating whether to enable or disable translucency. If true, the rendering + * pipeline will apply a translucent effect to rendered elements. If false, it will render with + * an opaque effect. + */ + public void setTranslucent(boolean translucent) { + this.translucent = translucent; + } + + /** + * Gets a tint-applying color to render the given animatable with + *

    + * Returns {@link Color#WHITE} by default + */ + @Override + public Color getRenderColor(ItemStack animatable, float partialTick, int packedLight) { + return this.currentStack.getItem() instanceof DyeableArmorItem dyeableArmorItem + ? Color.ofOpaque(dyeableArmorItem.getColor(animatable)) + : Color.WHITE; + } + + public HumanoidModel baseModel() { + return baseModel; + } + + public AzArmorBoneContext boneContext() { + return boneContext; + } + + public Entity currentEntity() { + return currentEntity; + } + + public EquipmentSlot currentSlot() { + return currentSlot; + } + + public ItemStack currentStack() { + return currentStack; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererRegistry.java new file mode 100644 index 000000000..631b01b46 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/AzArmorRendererRegistry.java @@ -0,0 +1,34 @@ +package mod.azure.azurelib.rewrite.render.armor; + +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class AzArmorRendererRegistry { + + private static final Map ITEM_TO_RENDERER = new HashMap<>(); + + private static final Map> ITEM_TO_RENDERER_SUPPLIER = new HashMap<>(); + + public static void register(Item item, Supplier armorRendererSupplier) { + ITEM_TO_RENDERER_SUPPLIER.put(item, armorRendererSupplier); + } + + public static void register(Supplier armorRendererSupplier, Item item, Item... items) { + register(item, armorRendererSupplier); + + for (var otherItem : items) { + register(otherItem, armorRendererSupplier); + } + } + + public static @Nullable AzArmorRenderer getOrNull(Item item) { + return ITEM_TO_RENDERER.computeIfAbsent(item, ($) -> { + var rendererSupplier = ITEM_TO_RENDERER_SUPPLIER.get(item); + return rendererSupplier == null ? null : rendererSupplier.get(); + }); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneContext.java new file mode 100644 index 000000000..3f127eb85 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneContext.java @@ -0,0 +1,197 @@ +package mod.azure.azurelib.rewrite.render.armor.bone; + +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.world.entity.EquipmentSlot; +import org.jetbrains.annotations.Nullable; + +public class AzArmorBoneContext { + + private AzBakedModel lastModel; + + protected AzBone head; + + protected AzBone body; + + protected AzBone rightArm; + + protected AzBone leftArm; + + protected AzBone rightLeg; + + protected AzBone leftLeg; + + protected AzBone rightBoot; + + protected AzBone leftBoot; + + public AzArmorBoneContext() { + this.head = null; + this.body = null; + this.rightArm = null; + this.leftArm = null; + this.rightLeg = null; + this.leftLeg = null; + this.rightBoot = null; + this.leftBoot = null; + } + + public void setAllVisible(boolean pVisible) { + setBoneVisible(this.head, pVisible); + setBoneVisible(this.body, pVisible); + setBoneVisible(this.rightArm, pVisible); + setBoneVisible(this.leftArm, pVisible); + setBoneVisible(this.rightLeg, pVisible); + setBoneVisible(this.leftLeg, pVisible); + setBoneVisible(this.rightBoot, pVisible); + setBoneVisible(this.leftBoot, pVisible); + } + + /** + * Gets and caches the relevant armor model bones for this baked model if it hasn't been done already + */ + public void grabRelevantBones(AzBakedModel model, AzArmorBoneProvider boneProvider) { + if (this.lastModel == model) { + return; + } + + this.lastModel = model; + this.head = boneProvider.getHeadBone(model); + this.body = boneProvider.getBodyBone(model); + this.rightArm = boneProvider.getRightArmBone(model); + this.leftArm = boneProvider.getLeftArmBone(model); + this.rightLeg = boneProvider.getRightLegBone(model); + this.leftLeg = boneProvider.getLeftLegBone(model); + this.rightBoot = boneProvider.getRightBootBone(model); + this.leftBoot = boneProvider.getLeftBootBone(model); + } + + /** + * Transform the currently rendering {@link AzBakedModel} to match the positions and rotations of the base model + */ + public void applyBaseTransformations(HumanoidModel baseModel) { + if (this.head != null) { + ModelPart headPart = baseModel.head; + + RenderUtils.matchModelPartRot(headPart, this.head); + this.head.updatePosition(headPart.x, -headPart.y, headPart.z); + } + + if (this.body != null) { + ModelPart bodyPart = baseModel.body; + + RenderUtils.matchModelPartRot(bodyPart, this.body); + this.body.updatePosition(bodyPart.x, -bodyPart.y, bodyPart.z); + } + + if (this.rightArm != null) { + ModelPart rightArmPart = baseModel.rightArm; + + RenderUtils.matchModelPartRot(rightArmPart, this.rightArm); + this.rightArm.updatePosition(rightArmPart.x + 5, 2 - rightArmPart.y, rightArmPart.z); + } + + if (this.leftArm != null) { + ModelPart leftArmPart = baseModel.leftArm; + + RenderUtils.matchModelPartRot(leftArmPart, this.leftArm); + this.leftArm.updatePosition(leftArmPart.x - 5f, 2f - leftArmPart.y, leftArmPart.z); + } + + if (this.rightLeg != null) { + ModelPart rightLegPart = baseModel.rightLeg; + + RenderUtils.matchModelPartRot(rightLegPart, this.rightLeg); + this.rightLeg.updatePosition(rightLegPart.x + 2, 12 - rightLegPart.y, rightLegPart.z); + + if (this.rightBoot != null) { + RenderUtils.matchModelPartRot(rightLegPart, this.rightBoot); + this.rightBoot.updatePosition(rightLegPart.x + 2, 12 - rightLegPart.y, rightLegPart.z); + } + } + + if (this.leftLeg != null) { + ModelPart leftLegPart = baseModel.leftLeg; + + RenderUtils.matchModelPartRot(leftLegPart, this.leftLeg); + this.leftLeg.updatePosition(leftLegPart.x - 2, 12 - leftLegPart.y, leftLegPart.z); + + if (this.leftBoot != null) { + RenderUtils.matchModelPartRot(leftLegPart, this.leftBoot); + this.leftBoot.updatePosition(leftLegPart.x - 2, 12 - leftLegPart.y, leftLegPart.z); + } + } + } + + /** + * Resets the bone visibility for the model based on the current {@link ModelPart} and {@link EquipmentSlot}, and + * then sets the bones relevant to the current part as visible for rendering.
    + *
    + * If you are rendering a geo entity with armor, you should probably be calling this prior to rendering + */ + public void applyBoneVisibilityByPart(EquipmentSlot currentSlot, ModelPart currentPart, HumanoidModel model) { + setAllVisible(false); + + currentPart.visible = true; + AzBone bone = null; + + if (currentPart == model.hat || currentPart == model.head) { + bone = this.head; + } else if (currentPart == model.body) { + bone = this.body; + } else if (currentPart == model.leftArm) { + bone = this.leftArm; + } else if (currentPart == model.rightArm) { + bone = this.rightArm; + } else if (currentPart == model.leftLeg) { + bone = currentSlot == EquipmentSlot.FEET ? this.leftBoot : this.leftLeg; + } else if (currentPart == model.rightLeg) { + bone = currentSlot == EquipmentSlot.FEET ? this.rightBoot : this.rightLeg; + } + + if (bone != null) { + bone.setHidden(false); + } + } + + /** + * Resets the bone visibility for the model based on the currently rendering slot, and then sets bones relevant to + * the current slot as visible for rendering.
    + *
    + * This is only called by default for non-geo entities (I.E. players or vanilla mobs) + */ + public void applyBoneVisibilityBySlot(EquipmentSlot currentSlot) { + setAllVisible(false); + + switch (currentSlot) { + case HEAD -> setBoneVisible(this.head, true); + case CHEST -> { + setBoneVisible(this.body, true); + setBoneVisible(this.rightArm, true); + setBoneVisible(this.leftArm, true); + } + case LEGS -> { + setBoneVisible(this.rightLeg, true); + setBoneVisible(this.leftLeg, true); + } + case FEET -> { + setBoneVisible(this.rightBoot, true); + setBoneVisible(this.leftBoot, true); + } + case MAINHAND, OFFHAND -> { /* NO-OP */ } + } + } + + /** + * Sets a bone as visible or hidden, with nullability + */ + protected void setBoneVisible(@Nullable AzBone bone, boolean visible) { + if (bone == null) + return; + + bone.setHidden(!visible); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneProvider.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneProvider.java new file mode 100644 index 000000000..05e09427e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzArmorBoneProvider.java @@ -0,0 +1,96 @@ +package mod.azure.azurelib.rewrite.render.armor.bone; + +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import org.jetbrains.annotations.Nullable; + +public interface AzArmorBoneProvider { + + String BONE_ARMOR_BODY_NAME = "armorBody"; + + String BONE_ARMOR_HEAD_NAME = "armorHead"; + + String BONE_ARMOR_LEFT_ARM_NAME = "armorLeftArm"; + + String BONE_ARMOR_RIGHT_ARM_NAME = "armorRightArm"; + + String BONE_ARMOR_LEFT_BOOT_NAME = "armorLeftBoot"; + + String BONE_ARMOR_RIGHT_BOOT_NAME = "armorRightBoot"; + + String BONE_ARMOR_LEFT_LEG_NAME = "armorLeftLeg"; + + String BONE_ARMOR_RIGHT_LEG_NAME = "armorRightLeg"; + + /** + * Returns the 'head' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the head model piece, or null if not using it + */ + @Nullable + AzBone getHeadBone(AzBakedModel model); + + /** + * Returns the 'body' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the body model piece, or null if not using it + */ + @Nullable + AzBone getBodyBone(AzBakedModel model); + + /** + * Returns the 'right arm' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the right arm model piece, or null if not using it + */ + @Nullable + AzBone getRightArmBone(AzBakedModel model); + + /** + * Returns the 'left arm' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the left arm model piece, or null if not using it + */ + @Nullable + AzBone getLeftArmBone(AzBakedModel model); + + /** + * Returns the 'right leg' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the right leg model piece, or null if not using it + */ + @Nullable + AzBone getRightLegBone(AzBakedModel model); + + /** + * Returns the 'left leg' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the left leg model piece, or null if not using it + */ + @Nullable + AzBone getLeftLegBone(AzBakedModel model); + + /** + * Returns the 'right boot' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the right boot model piece, or null if not using it + */ + @Nullable + AzBone getRightBootBone(AzBakedModel model); + + /** + * Returns the 'left boot' GeoBone from this model.
    + * Override if your geo model has different bone names for these bones + * + * @return The bone for the left boot model piece, or null if not using it + */ + @Nullable + AzBone getLeftBootBone(AzBakedModel model); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzDefaultArmorBoneProvider.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzDefaultArmorBoneProvider.java new file mode 100644 index 000000000..7588f271f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/armor/bone/AzDefaultArmorBoneProvider.java @@ -0,0 +1,48 @@ +package mod.azure.azurelib.rewrite.render.armor.bone; + +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.model.AzBone; +import org.jetbrains.annotations.Nullable; + +public class AzDefaultArmorBoneProvider implements AzArmorBoneProvider { + + @Nullable + public AzBone getHeadBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_HEAD_NAME); + } + + @Nullable + public AzBone getBodyBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_BODY_NAME); + } + + @Nullable + public AzBone getRightArmBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_RIGHT_ARM_NAME); + } + + @Nullable + public AzBone getLeftArmBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_LEFT_ARM_NAME); + } + + @Nullable + public AzBone getRightLegBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_RIGHT_LEG_NAME); + } + + @Nullable + public AzBone getLeftLegBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_LEFT_LEG_NAME); + } + + @Nullable + public AzBone getRightBootBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_RIGHT_BOOT_NAME); + } + + @Nullable + public AzBone getLeftBootBone(AzBakedModel model) { + return model.getBoneOrNull(BONE_ARMOR_LEFT_BOOT_NAME); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityModelRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityModelRenderer.java new file mode 100644 index 000000000..0552b5d8e --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityModelRenderer.java @@ -0,0 +1,155 @@ +package mod.azure.azurelib.rewrite.render.block; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzModelRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.DirectionalBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +/** + * The AzBlockEntityModelRenderer is a specialized model renderer class for rendering block entities in a 3D space. It + * extends the AzModelRenderer class and provides functionality specific to handling and rendering block entities based + * on their corresponding properties and transformations. + * + * @param The type of BlockEntity that this renderer is responsible for + */ +public class AzBlockEntityModelRenderer extends AzModelRenderer { + + private final AzBlockEntityRendererPipeline blockEntityRendererPipeline; + + public AzBlockEntityModelRenderer( + AzBlockEntityRendererPipeline blockEntityRendererPipeline, + AzLayerRenderer layerRenderer + ) { + super(blockEntityRendererPipeline, layerRenderer); + this.blockEntityRendererPipeline = blockEntityRendererPipeline; + } + + /** + * The actual render method that subtype renderers should override to handle their specific rendering tasks.
    + * {@link AzBlockEntityRendererPipeline#preRender} has already been called by this stage, and + * {@link AzBlockEntityRendererPipeline#postRender} will be called directly after + */ + @Override + public void render(AzRendererPipelineContext context, boolean isReRender) { + var entity = context.animatable(); + var poseStack = context.poseStack(); + + if (!isReRender) { + + poseStack.translate(0.5, 0, 0.5); + rotateBlock(getFacing(entity), poseStack); + var animator = blockEntityRendererPipeline.getRenderer().getAnimator(); + + if (animator != null) { + animator.animate(entity, context.partialTick()); + } + } + + blockEntityRendererPipeline.modelRenderTranslations = new Matrix4f(poseStack.last().pose()); + + var textureLocation = blockEntityRendererPipeline.config().textureLocation(entity); + RenderSystem.setShaderTexture(0, textureLocation); + super.render(context, isReRender); + } + + /** + * Renders the provided {@link AzBone} and its associated child bones + */ + @Override + public void renderRecursively(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + var buffer = context.vertexConsumer(); + var bufferSource = context.multiBufferSource(); + var entity = context.animatable(); + var poseStack = context.poseStack(); + var renderType = context.renderType(); + + poseStack.pushPose(); + RenderUtils.translateMatrixToBone(poseStack, bone); + RenderUtils.translateToPivotPoint(poseStack, bone); + RenderUtils.rotateMatrixAroundBone(poseStack, bone); + RenderUtils.scaleMatrixForBone(poseStack, bone); + + if (bone.isTrackingMatrices()) { + Matrix4f poseState = new Matrix4f(poseStack.last().pose()); + Matrix4f localMatrix = RenderUtils.invertAndMultiplyMatrices( + poseState, + blockEntityRendererPipeline.entityRenderTranslations + ); + + bone.setModelSpaceMatrix( + RenderUtils.invertAndMultiplyMatrices(poseState, blockEntityRendererPipeline.modelRenderTranslations) + ); + bone.setLocalSpaceMatrix( + RenderUtils.translateMatrix(localMatrix, Vec3.ZERO.toVector3f()) + ); + bone.setWorldSpaceMatrix( + RenderUtils.translateMatrix( + new Matrix4f(localMatrix), + new Vector3f( + entity.getBlockPos().getX(), + entity.getBlockPos().getY(), + entity.getBlockPos().getZ() + ) + ) + ); + } + + RenderUtils.translateAwayFromPivotPoint(poseStack, bone); + + if (!isReRender && buffer instanceof BufferBuilder builder && !builder.building) { + context.setVertexConsumer(bufferSource.getBuffer(renderType)); + } + + renderCubesOfBone(context, bone); + + if (!isReRender) { + layerRenderer.applyRenderLayersForBone(context, bone); + } + + renderChildBones(context, bone, isReRender); + + poseStack.popPose(); + } + + /** + * Attempt to extract a direction from the block so that the model can be oriented correctly + */ + protected Direction getFacing(T block) { + BlockState blockState = block.getBlockState(); + + if (blockState.hasProperty(HorizontalDirectionalBlock.FACING)) + return blockState.getValue(HorizontalDirectionalBlock.FACING); + + if (blockState.hasProperty(DirectionalBlock.FACING)) + return blockState.getValue(DirectionalBlock.FACING); + + return Direction.NORTH; + } + + /** + * Rotate the {@link PoseStack} based on the determined {@link Direction} the block is facing + */ + protected void rotateBlock(Direction facing, PoseStack poseStack) { + switch (facing) { + case SOUTH -> poseStack.mulPose(Axis.YP.rotationDegrees(180)); + case WEST -> poseStack.mulPose(Axis.YP.rotationDegrees(90)); + case NORTH -> poseStack.mulPose(Axis.YP.rotationDegrees(0)); + case EAST -> poseStack.mulPose(Axis.YP.rotationDegrees(270)); + case UP -> poseStack.mulPose(Axis.XP.rotationDegrees(90)); + case DOWN -> poseStack.mulPose(Axis.XN.rotationDegrees(90)); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRenderer.java new file mode 100644 index 000000000..833019e30 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRenderer.java @@ -0,0 +1,64 @@ +package mod.azure.azurelib.rewrite.render.block; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.rewrite.animation.impl.AzBlockAnimator; +import mod.azure.azurelib.rewrite.render.AzProvider; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The {@code AzBlockEntityRenderer} class is an abstract base class for rendering custom block entities. It leverages + * an animation and rendering pipeline mechanism to provide extended functionalities, such as dynamic animations and + * model customization. + * + * @param The specific type of {@link BlockEntity} that this renderer processes. + */ +public abstract class AzBlockEntityRenderer implements BlockEntityRenderer { + + private final AzProvider provider; + + private final AzBlockEntityRendererPipeline rendererPipeline; + + @Nullable + private AzBlockAnimator reusedAzBlockAnimator; + + protected AzBlockEntityRenderer(AzBlockEntityRendererConfig config) { + this.provider = new AzProvider<>(config::createAnimator, config::modelLocation); + this.rendererPipeline = createPipeline(config); + } + + protected AzBlockEntityRendererPipeline createPipeline(AzBlockEntityRendererConfig config) { + return new AzBlockEntityRendererPipeline<>(config, this); + } + + @Override + public void render( + @NotNull T entity, + float partialTick, + @NotNull PoseStack poseStack, + @NotNull MultiBufferSource source, + int packedLight, + int packedOverlay + ) { + var cachedEntityAnimator = (AzBlockAnimator) provider.provideAnimator(entity); + var model = provider.provideBakedModel(entity); + var context = rendererPipeline.context(); + + if (cachedEntityAnimator != null && model != null) { + cachedEntityAnimator.setActiveModel(model); + } + + // Point the renderer's current animator reference to the cached entity animator before rendering. + reusedAzBlockAnimator = cachedEntityAnimator; + + // Execute the render pipeline. + rendererPipeline.render(poseStack, model, entity, source, null, null, 0, partialTick, packedLight); + } + + public AzBlockAnimator getAnimator() { + return reusedAzBlockAnimator; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererConfig.java new file mode 100644 index 000000000..2b6d144f0 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererConfig.java @@ -0,0 +1,157 @@ +package mod.azure.azurelib.rewrite.render.block; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +/** + * The {@code AzBlockEntityRendererConfig} class is a specialized configuration for rendering block entities. It extends + * the generic {@link AzRendererConfig} and provides additional methods to streamline the creation of configurations + * specifically for block entity renderers. + * + * @param The type of block entity this configuration is tailored for. + */ +public class AzBlockEntityRendererConfig extends AzRendererConfig { + + private AzBlockEntityRendererConfig( + Supplier> animatorProvider, + Function modelLocationProvider, + Function renderTypeFunction, + List> renderLayers, + UnaryOperator> preRenderEntry, + UnaryOperator> postRenderEntry, + Function textureLocationProvider, + Function alphaFunction, + Function scaleHeight, + Function scaleWidth + ) { + super( + animatorProvider, + modelLocationProvider, + renderTypeFunction, + renderLayers, + preRenderEntry, + postRenderEntry, + textureLocationProvider, + alphaFunction, + scaleHeight, + scaleWidth + ); + } + + public static Builder builder( + ResourceLocation modelLocation, + ResourceLocation textureLocation + ) { + return new Builder<>($ -> modelLocation, $ -> textureLocation); + } + + public static Builder builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + return new Builder<>(modelLocationProvider, textureLocationProvider); + } + + public static class Builder extends AzRendererConfig.Builder { + + protected Builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + super(modelLocationProvider, textureLocationProvider); + } + + @Override + public Builder addRenderLayer(AzRenderLayer renderLayer) { + return (Builder) super.addRenderLayer(renderLayer); + } + + public Builder setRenderType(RenderType renderType) { + this.renderTypeProvider = $ -> renderType; + return this; + } + + public Builder setRenderType(Function renderTypeProvider) { + this.renderTypeProvider = renderTypeProvider; + return this; + } + + @Override + public Builder setPrerenderEntry( + UnaryOperator> preRenderEntry + ) { + return (AzBlockEntityRendererConfig.Builder) super.setPrerenderEntry(preRenderEntry); + } + + @Override + public Builder setPostRenderEntry( + UnaryOperator> preRenderEntry + ) { + return (AzBlockEntityRendererConfig.Builder) super.setPostRenderEntry(preRenderEntry); + } + + @Override + public Builder setAnimatorProvider(Supplier<@Nullable AzAnimator> animatorProvider) { + return (Builder) super.setAnimatorProvider(animatorProvider); + } + + @Override + public Builder setAlpha(Function alphaFunction) { + return (AzBlockEntityRendererConfig.Builder) super.setAlpha(alphaFunction); + } + + @Override + public Builder setAlpha(float alpha) { + return (AzBlockEntityRendererConfig.Builder) super.setAlpha(alpha); + } + + @Override + public Builder setScale(Function scaleFunction) { + return (AzBlockEntityRendererConfig.Builder) super.setScale(scaleFunction); + } + + @Override + public Builder setScale(Function scaleHeightFunction, Function scaleWidthFunction) { + return (AzBlockEntityRendererConfig.Builder) super.setScale(scaleHeightFunction, scaleWidthFunction); + } + + @Override + public Builder setScale(float scale) { + return (AzBlockEntityRendererConfig.Builder) super.setScale(scale); + } + + @Override + public Builder setScale(float scaleWidth, float scaleHeight) { + return (AzBlockEntityRendererConfig.Builder) super.setScale(scaleWidth, scaleHeight); + } + + @Override + public AzBlockEntityRendererConfig build() { + var baseConfig = super.build(); + + return new AzBlockEntityRendererConfig<>( + baseConfig::createAnimator, + baseConfig::modelLocation, + baseConfig::getRenderType, + baseConfig.renderLayers(), + baseConfig::preRenderEntry, + baseConfig::postRenderEntry, + baseConfig::textureLocation, + baseConfig::alpha, + baseConfig::scaleHeight, + baseConfig::scaleWidth + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipeline.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipeline.java new file mode 100644 index 000000000..ad8e0c1ea --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipeline.java @@ -0,0 +1,88 @@ +package mod.azure.azurelib.rewrite.render.block; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.cache.texture.AnimatableTexture; +import mod.azure.azurelib.rewrite.render.*; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.joml.Matrix4f; + +/** + * AzBlockEntityRendererPipeline is a specific implementation of the {@link AzRendererPipeline} tailored for rendering + * block entities. It manages the rendering pipeline with customized configurations and rendering behavior for block + * entities, while integrating with the parent pipeline logic. + * + * @param The type of {@link BlockEntity} that this renderer pipeline is designed to render. + */ +public class AzBlockEntityRendererPipeline extends AzRendererPipeline { + + private final AzBlockEntityRenderer blockEntityRenderer; + + protected Matrix4f entityRenderTranslations = new Matrix4f(); + + protected Matrix4f modelRenderTranslations = new Matrix4f(); + + public AzBlockEntityRendererPipeline( + AzBlockEntityRendererConfig config, + AzBlockEntityRenderer blockEntityRenderer + ) { + super(config); + this.blockEntityRenderer = blockEntityRenderer; + } + + @Override + protected AzBlockEntityRendererPipelineContext createContext(AzRendererPipeline rendererPipeline) { + return new AzBlockEntityRendererPipelineContext<>(this); + } + + @Override + protected AzModelRenderer createModelRenderer(AzLayerRenderer layerRenderer) { + return new AzBlockEntityModelRenderer<>(this, layerRenderer); + } + + @Override + protected AzLayerRenderer createLayerRenderer(AzRendererConfig config) { + return new AzLayerRenderer<>(config::renderLayers); + } + + /** + * Update the current frame of a {@link AnimatableTexture potentially animated} texture used by this + * GeoRenderer.
    + * This should only be called immediately prior to rendering, and only + * + * @see AnimatableTexture#setAndUpdate(ResourceLocation, int) + */ + @Override + public void updateAnimatedTextureFrame(T entity) { + AnimatableTexture.setAndUpdate(config.textureLocation(entity)); + } + + /** + * Called before rendering the model to buffer. Allows for render modifications and preparatory work such as scaling + * and translating.
    + * {@link PoseStack} translations made here are kept until the end of the render process + */ + @Override + public void preRender(AzRendererPipelineContext context, boolean isReRender) { + var poseStack = context.poseStack(); + this.entityRenderTranslations.set(poseStack.last().pose()); + + var scaleWidth = config.scaleWidth(context.animatable()); + var scaleHeight = config.scaleHeight(context.animatable()); + scaleModelForRender(context, scaleWidth, scaleHeight, isReRender); + if (config.alpha(context.animatable()) < 1) { + context.setAlpha(config.alpha(context.animatable())); + } + config.preRenderEntry(context); + } + + @Override + public void postRender(AzRendererPipelineContext context, boolean isReRender) { + config.postRenderEntry(context); + } + + public AzBlockEntityRenderer getRenderer() { + return blockEntityRenderer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipelineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipelineContext.java new file mode 100644 index 000000000..e5939ca88 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/block/AzBlockEntityRendererPipelineContext.java @@ -0,0 +1,34 @@ +package mod.azure.azurelib.rewrite.render.block; + +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a specialized rendering context for handling {@link BlockEntity} rendering in a pipeline-based rendering + * framework. This class extends {@link AzRendererPipelineContext} to provide specific functionality tailored to block + * entities within the AzureLib rendering system. + * + * @param The type of {@link BlockEntity} to be rendered. + */ +public class AzBlockEntityRendererPipelineContext extends AzRendererPipelineContext { + + public AzBlockEntityRendererPipelineContext(AzRendererPipeline rendererPipeline) { + super(rendererPipeline); + } + + @Override + public @NotNull RenderType getDefaultRenderType( + T animatable, + ResourceLocation texture, + @Nullable MultiBufferSource bufferSource, + float partialTick + ) { + return RenderType.entityCutoutNoCull(texture); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLayerRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLayerRenderer.java new file mode 100644 index 000000000..3076cd1a7 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLayerRenderer.java @@ -0,0 +1,34 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.world.entity.Entity; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * A renderer class responsible for rendering additional entity layers for a particular animatable entity type. It + * extends functionality from {@link AzLayerRenderer} and enables conditional rendering based on entity states. + * + * @param The type of animatable entity this renderer is applied to. + */ +public class AzEntityLayerRenderer extends AzLayerRenderer { + + public AzEntityLayerRenderer(Supplier>> renderLayerSupplier) { + super(renderLayerSupplier); + } + + /** + * Render the various {@link AzRenderLayer RenderLayers} that have been registered to this renderer + */ + @Override + public void applyRenderLayers(AzRendererPipelineContext context) { + var animatable = context.animatable(); + + if (!animatable.isSpectator()) { + super.applyRenderLayers(context); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLeashRenderUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLeashRenderUtil.java new file mode 100644 index 000000000..48c6535cc --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityLeashRenderUtil.java @@ -0,0 +1,159 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; + +/** + * Utility class for rendering entity leash visuals within the Minecraft rendering engine. This class provides static + * methods to handle leash rendering logic, enabling flexible re-use and separation from the default rendering behavior. + *
    + * This utility replicates the leash rendering logic from {@link net.minecraft.client.renderer.entity.MobRenderer} to + * provide enhanced customization for entity rendering purposes. + */ +public class AzEntityLeashRenderUtil { + + /** + * Static rendering code for rendering a leash segment.
    + * It's a like-for-like from {@link net.minecraft.client.renderer.entity.MobRenderer#renderLeash} that had to be + * duplicated here for flexible usage + */ + public static void renderLeash( + AzEntityRenderer azEntityRenderer, + M mob, + float partialTick, + PoseStack poseStack, + MultiBufferSource bufferSource, + E leashHolder + ) { + double lerpBodyAngle = (Mth.lerp(partialTick, mob.yBodyRotO, mob.yBodyRot) * Mth.DEG_TO_RAD) + Mth.HALF_PI; + Vec3 leashOffset = mob.getLeashOffset(); + double xAngleOffset = Math.cos(lerpBodyAngle) * leashOffset.z + Math.sin(lerpBodyAngle) * leashOffset.x; + double zAngleOffset = Math.sin(lerpBodyAngle) * leashOffset.z - Math.cos(lerpBodyAngle) * leashOffset.x; + double lerpOriginX = Mth.lerp(partialTick, mob.xo, mob.getX()) + xAngleOffset; + double lerpOriginY = Mth.lerp(partialTick, mob.yo, mob.getY()) + leashOffset.y; + double lerpOriginZ = Mth.lerp(partialTick, mob.zo, mob.getZ()) + zAngleOffset; + Vec3 ropeGripPosition = leashHolder.getRopeHoldPosition(partialTick); + float xDif = (float) (ropeGripPosition.x - lerpOriginX); + float yDif = (float) (ropeGripPosition.y - lerpOriginY); + float zDif = (float) (ropeGripPosition.z - lerpOriginZ); + float offsetMod = Mth.invSqrt(xDif * xDif + zDif * zDif) * 0.025f / 2f; + float xOffset = zDif * offsetMod; + float zOffset = xDif * offsetMod; + VertexConsumer vertexConsumer = bufferSource.getBuffer(RenderType.leash()); + BlockPos entityEyePos = BlockPos.containing(mob.getEyePosition(partialTick)); + BlockPos holderEyePos = BlockPos.containing(leashHolder.getEyePosition(partialTick)); + int entityBlockLight = azEntityRenderer.getBlockLightLevel((T) mob, entityEyePos); + int holderBlockLight = leashHolder.isOnFire() + ? 15 + : leashHolder.level() + .getBrightness( + LightLayer.BLOCK, + holderEyePos + ); + int entitySkyLight = mob.level().getBrightness(LightLayer.SKY, entityEyePos); + int holderSkyLight = mob.level().getBrightness(LightLayer.SKY, holderEyePos); + + poseStack.pushPose(); + poseStack.translate(xAngleOffset, leashOffset.y, zAngleOffset); + + Matrix4f posMatrix = new Matrix4f(poseStack.last().pose()); + + for (int segment = 0; segment <= 24; ++segment) { + renderLeashPiece( + vertexConsumer, + posMatrix, + xDif, + yDif, + zDif, + entityBlockLight, + holderBlockLight, + entitySkyLight, + holderSkyLight, + 0.025f, + 0.025f, + xOffset, + zOffset, + segment, + false + ); + } + + for (int segment = 24; segment >= 0; --segment) { + renderLeashPiece( + vertexConsumer, + posMatrix, + xDif, + yDif, + zDif, + entityBlockLight, + holderBlockLight, + entitySkyLight, + holderSkyLight, + 0.025f, + 0.0f, + xOffset, + zOffset, + segment, + true + ); + } + + poseStack.popPose(); + } + + /** + * Static rendering code for rendering a leash segment.
    + * It's a like-for-like from {@link net.minecraft.client.renderer.entity.MobRenderer#addVertexPair} that had to be + * duplicated here for flexible usage + */ + private static void renderLeashPiece( + VertexConsumer buffer, + Matrix4f positionMatrix, + float xDif, + float yDif, + float zDif, + int entityBlockLight, + int holderBlockLight, + int entitySkyLight, + int holderSkyLight, + float width, + float yOffset, + float xOffset, + float zOffset, + int segment, + boolean isLeashKnot + ) { + var piecePosPercent = segment / 24f; + var lerpBlockLight = (int) Mth.lerp(piecePosPercent, entityBlockLight, holderBlockLight); + var lerpSkyLight = (int) Mth.lerp(piecePosPercent, entitySkyLight, holderSkyLight); + var packedLight = LightTexture.pack(lerpBlockLight, lerpSkyLight); + var knotColourMod = segment % 2 == (isLeashKnot ? 1 : 0) ? 0.7f : 1f; + var red = 0.5f * knotColourMod; + var green = 0.4f * knotColourMod; + var blue = 0.3f * knotColourMod; + var x = xDif * piecePosPercent; + var y = yDif > 0.0f + ? yDif * piecePosPercent * piecePosPercent + : yDif - yDif * (1.0f - piecePosPercent) * (1.0f - piecePosPercent); + var z = zDif * piecePosPercent; + + buffer.vertex(positionMatrix, x - xOffset, y + yOffset, z + zOffset).color(red, green, blue, 1).uv2( + packedLight).endVertex(); + buffer.vertex(positionMatrix, x + xOffset, y + width - yOffset, z - zOffset).color(red, green, blue, 1).uv2( + packedLight).endVertex(); + } + + private AzEntityLeashRenderUtil() { + throw new UnsupportedOperationException(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityModelRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityModelRenderer.java new file mode 100644 index 000000000..104161d2a --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityModelRenderer.java @@ -0,0 +1,254 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzModelRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Pose; +import org.joml.Matrix4f; + +/** + * AzEntityModelRenderer is a class responsible for rendering animated 3D entity models in a pipeline-based rendering + * setup. Extends the {@link AzModelRenderer} class and utilizes the {@link AzEntityRendererPipeline} to handle various + * rendering tasks, such as applying model transformations and managing animated states in the rendering lifecycle.
    + * + * @param The type of entity that this renderer applies to, extends the {@link Entity} class. + */ +public class AzEntityModelRenderer extends AzModelRenderer { + + private final AzEntityRendererPipeline entityRendererPipeline; + + public AzEntityModelRenderer(AzEntityRendererPipeline entityRendererPipeline, AzLayerRenderer layerRenderer) { + super(entityRendererPipeline, layerRenderer); + this.entityRendererPipeline = entityRendererPipeline; + } + + /** + * The actual render method that subtype renderers should override to handle their specific rendering tasks.
    + * {@link AzEntityRendererPipeline#preRender} has already been called by this stage, and + * {@link AzEntityRendererPipeline#postRender} will be called directly after + */ + @Override + public void render(AzRendererPipelineContext context, boolean isReRender) { + var animatable = context.animatable(); + var partialTick = context.partialTick(); + var poseStack = context.poseStack(); + + poseStack.pushPose(); + + LivingEntity livingEntity = animatable instanceof LivingEntity entity ? entity : null; + + boolean shouldSit = animatable.isPassenger() && (animatable.getVehicle() != null); + float lerpBodyRot = livingEntity == null + ? 0 + : Mth.rotLerp( + partialTick, + livingEntity.yBodyRotO, + livingEntity.yBodyRot + ); + float lerpHeadRot = livingEntity == null + ? 0 + : Mth.rotLerp( + partialTick, + livingEntity.yHeadRotO, + livingEntity.yHeadRot + ); + float netHeadYaw = lerpHeadRot - lerpBodyRot; + + if (shouldSit && animatable.getVehicle() instanceof LivingEntity livingentity) { + lerpBodyRot = Mth.rotLerp(partialTick, livingentity.yBodyRotO, livingentity.yBodyRot); + netHeadYaw = lerpHeadRot - lerpBodyRot; + float clampedHeadYaw = Mth.clamp(Mth.wrapDegrees(netHeadYaw), -85, 85); + lerpBodyRot = lerpHeadRot - clampedHeadYaw; + + if (clampedHeadYaw * clampedHeadYaw > 2500f) + lerpBodyRot += clampedHeadYaw * 0.2f; + + netHeadYaw = lerpHeadRot - lerpBodyRot; + } + + if (animatable.getPose() == Pose.SLEEPING && livingEntity != null) { + Direction bedDirection = livingEntity.getBedOrientation(); + + if (bedDirection != null) { + float eyePosOffset = livingEntity.getEyeHeight(Pose.STANDING) - 0.1F; + + poseStack.translate( + -bedDirection.getStepX() * eyePosOffset, + 0, + -bedDirection.getStepZ() * eyePosOffset + ); + } + } + + float nativeScale = livingEntity != null ? livingEntity.getScale() : 1; + float ageInTicks = animatable.tickCount + partialTick; + float limbSwingAmount = 0; + float limbSwing = 0; + + poseStack.scale(nativeScale, nativeScale, nativeScale); + applyRotations(animatable, poseStack, ageInTicks, lerpBodyRot, partialTick, nativeScale); + + if (!shouldSit && animatable.isAlive() && livingEntity != null) { + limbSwingAmount = Mth.lerp( + partialTick, + livingEntity.walkAnimation.speedOld, + livingEntity.walkAnimation.speed() + ); + limbSwing = livingEntity.walkAnimation.position() - livingEntity.walkAnimation.speed() * (1 - partialTick); + + if (livingEntity.isBaby()) { + limbSwing *= 3f; + } + + if (limbSwingAmount > 1f) { + limbSwingAmount = 1f; + } + } + + if (!isReRender) { + var animator = entityRendererPipeline.getRenderer().getAnimator(); + + if (animator != null) { + animator.animate(animatable, context.partialTick()); + } + } + + entityRendererPipeline.modelRenderTranslations.set(poseStack.last().pose()); + + if (!animatable.isInvisibleTo(Minecraft.getInstance().player)) { + super.render(context, isReRender); + } + + poseStack.popPose(); + } + + /** + * Renders the provided {@link AzBone} and its associated child bones + */ + @Override + public void renderRecursively(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + var buffer = context.vertexConsumer(); + var bufferSource = context.multiBufferSource(); + var entity = context.animatable(); + var poseStack = context.poseStack(); + var renderType = context.renderType(); + + poseStack.pushPose(); + RenderUtils.translateMatrixToBone(poseStack, bone); + RenderUtils.translateToPivotPoint(poseStack, bone); + RenderUtils.rotateMatrixAroundBone(poseStack, bone); + RenderUtils.scaleMatrixForBone(poseStack, bone); + + if (bone.isTrackingMatrices()) { + Matrix4f poseState = new Matrix4f(poseStack.last().pose()); + Matrix4f localMatrix = RenderUtils.invertAndMultiplyMatrices( + poseState, + entityRendererPipeline.entityRenderTranslations + ); + + bone.setModelSpaceMatrix( + RenderUtils.invertAndMultiplyMatrices(poseState, entityRendererPipeline.modelRenderTranslations) + ); + bone.setLocalSpaceMatrix( + RenderUtils.translateMatrix( + localMatrix, + entityRendererPipeline.getRenderer().getRenderOffset(entity, 1).toVector3f() + ) + ); + bone.setWorldSpaceMatrix( + RenderUtils.translateMatrix(new Matrix4f(localMatrix), entity.position().toVector3f()) + ); + } + + RenderUtils.translateAwayFromPivotPoint(poseStack, bone); + + if (!isReRender && buffer instanceof BufferBuilder builder && !builder.building) { + context.setVertexConsumer(bufferSource.getBuffer(renderType)); + } + + renderCubesOfBone(context, bone); + + if (!isReRender) { + layerRenderer.applyRenderLayersForBone(context, bone); + } + + renderChildBones(context, bone, isReRender); + + poseStack.popPose(); + } + + /** + * Applies rotation transformations to the renderer prior to render time to account for various entity states, + * default scale of 1 + */ + protected void applyRotations( + T animatable, + PoseStack poseStack, + float ageInTicks, + float rotationYaw, + float partialTick + ) { + applyRotations(animatable, poseStack, ageInTicks, rotationYaw, partialTick, 1); + } + + /** + * Applies rotation transformations to the renderer prior to render time to account for various entity states, + * scalable + */ + protected void applyRotations( + T animatable, + PoseStack poseStack, + float ageInTicks, + float rotationYaw, + float partialTick, + float nativeScale + ) { + if (animatable.isFullyFrozen()) { + rotationYaw += (float) (Math.cos(animatable.tickCount * 3.25d) * Math.PI * 0.4d); + } + + if (!animatable.hasPose(Pose.SLEEPING)) { + poseStack.mulPose(Axis.YP.rotationDegrees(180f - rotationYaw)); + } + + if (animatable instanceof LivingEntity livingEntity) { + var config = entityRendererPipeline.getRenderer().config(); + var deathMaxRotation = config.getDeathMaxRotation(animatable); + + if (livingEntity.deathTime > 0) { + float deathRotation = (livingEntity.deathTime + partialTick - 1f) / 20f * 1.6f; + + poseStack.mulPose( + Axis.ZP.rotationDegrees(Math.min(Mth.sqrt(deathRotation), 1) * deathMaxRotation) + ); + } else if (livingEntity.isAutoSpinAttack()) { + poseStack.mulPose(Axis.XP.rotationDegrees(-90f - livingEntity.getXRot())); + poseStack.mulPose(Axis.YP.rotationDegrees((livingEntity.tickCount + partialTick) * -75f)); + } else if (animatable.hasPose(Pose.SLEEPING)) { + Direction bedOrientation = livingEntity.getBedOrientation(); + + poseStack.mulPose( + Axis.YP.rotationDegrees( + bedOrientation != null ? RenderUtils.getDirectionAngle(bedOrientation) : rotationYaw + ) + ); + poseStack.mulPose(Axis.ZP.rotationDegrees(deathMaxRotation)); + poseStack.mulPose(Axis.YP.rotationDegrees(270f)); + } else if (LivingEntityRenderer.isEntityUpsideDown(livingEntity)) { + poseStack.translate(0, (animatable.getBbHeight() + 0.1f) / nativeScale, 0); + poseStack.mulPose(Axis.ZP.rotationDegrees(180f)); + } + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityNameRenderUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityNameRenderUtil.java new file mode 100644 index 000000000..44fced46b --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityNameRenderUtil.java @@ -0,0 +1,60 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; + +import java.util.Objects; + +public class AzEntityNameRenderUtil { + + public static boolean shouldShowName(EntityRenderDispatcher entityRenderDispatcher, T entity) { + var nameRenderDistance = entity.isDiscrete() ? 32d : 64d; + + if (!(entity instanceof LivingEntity)) { + return false; + } + + if (entityRenderDispatcher.distanceToSqr(entity) >= nameRenderDistance * nameRenderDistance) { + return false; + } + + if ( + entity instanceof Mob && (!entity.shouldShowName() && (!entity.hasCustomName() + || entity != entityRenderDispatcher.crosshairPickEntity)) + ) { + return false; + } + + final var minecraft = Minecraft.getInstance(); + // TODO: See if we can do this null check better. + var player = Objects.requireNonNull(minecraft.player); + var visibleToClient = !entity.isInvisibleTo(player); + var entityTeam = entity.getTeam(); + + if (entityTeam == null) { + return Minecraft.renderNames() && entity != minecraft.getCameraEntity() && visibleToClient + && !entity.isVehicle(); + } + + var playerTeam = minecraft.player.getTeam(); + + return switch (entityTeam.getNameTagVisibility()) { + case ALWAYS -> visibleToClient; + case NEVER -> false; + case HIDE_FOR_OTHER_TEAMS -> playerTeam == null + ? visibleToClient + : entityTeam.isAlliedTo( + playerTeam + ) && (entityTeam.canSeeFriendlyInvisibles() || visibleToClient); + case HIDE_FOR_OWN_TEAM -> + playerTeam == null ? visibleToClient : !entityTeam.isAlliedTo(playerTeam) && visibleToClient; + }; + } + + private AzEntityNameRenderUtil() { + throw new UnsupportedOperationException(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRenderer.java new file mode 100644 index 000000000..293719954 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRenderer.java @@ -0,0 +1,120 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.rewrite.animation.impl.AzEntityAnimator; +import mod.azure.azurelib.rewrite.render.AzProvider; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * AzEntityRenderer is an abstract class responsible for rendering entities in the game. It extends the base + * functionality of {@link EntityRenderer} to provide additional rendering capabilities specific to animated and custom + * entities. This class is parameterized with a generic type {@code T}, which must extend {@link Entity}. It integrates + * several abstractions such as animation management, model caching, and advanced rendering pipelines for handling + * complex rendering behavior. Users are expected to configure this renderer using an {@link AzEntityRendererConfig}. + * Key components: - {@link AzEntityRendererConfig}: Defines configuration options such as textures, models, and + * animator providers. - {@link AzProvider}: Supplies baked models and animators for entities. - + * {@link AzEntityRendererPipeline}: Manages rendering logic through a custom pipeline. + */ +public abstract class AzEntityRenderer extends EntityRenderer { + + private final AzEntityRendererConfig config; + + private final AzProvider provider; + + private final AzEntityRendererPipeline rendererPipeline; + + @Nullable + private AzEntityAnimator reusedAzEntityAnimator; + + protected AzEntityRenderer(AzEntityRendererConfig config, EntityRendererProvider.Context context) { + super(context); + this.config = config; + this.provider = new AzProvider<>(config::createAnimator, config::modelLocation); + this.rendererPipeline = createPipeline(config); + } + + protected AzEntityRendererPipeline createPipeline(AzEntityRendererConfig config) { + return new AzEntityRendererPipeline<>(config, this); + } + + @Override + public final @NotNull ResourceLocation getTextureLocation(@NotNull T animatable) { + return config.textureLocation(animatable); + } + + public void superRender( + @NotNull T entity, + float entityYaw, + float partialTick, + @NotNull PoseStack poseStack, + @NotNull MultiBufferSource bufferSource, + int packedLight + ) { + super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight); + } + + @Override + public void render( + @NotNull T entity, + float entityYaw, + float partialTick, + @NotNull PoseStack poseStack, + @NotNull MultiBufferSource bufferSource, + int packedLight + ) { + var cachedEntityAnimator = (AzEntityAnimator) provider.provideAnimator(entity); + var azBakedModel = provider.provideBakedModel(entity); + + if (cachedEntityAnimator != null && azBakedModel != null) { + cachedEntityAnimator.setActiveModel(azBakedModel); + } + + this.shadowRadius = config.shadowRadius(entity); + + // Point the renderer's current animator reference to the cached entity animator before rendering. + reusedAzEntityAnimator = cachedEntityAnimator; + + // Execute the render pipeline. + rendererPipeline.render( + poseStack, + azBakedModel, + entity, + bufferSource, + null, + null, + entityYaw, + partialTick, + packedLight + ); + } + + /** + * Whether the entity's nametag should be rendered or not.
    + * Pretty much exclusively used in {@link EntityRenderer#renderNameTag} + */ + @Override + public boolean shouldShowName(@NotNull T entity) { + return AzEntityNameRenderUtil.shouldShowName(entityRenderDispatcher, entity); + } + + // Proxy method override for super.getBlockLightLevel external access. + @Override + public int getBlockLightLevel(@NotNull T entity, @NotNull BlockPos pos) { + return super.getBlockLightLevel(entity, pos); + } + + public AzEntityAnimator getAnimator() { + return reusedAzEntityAnimator; + } + + public AzEntityRendererConfig config() { + return config; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererConfig.java new file mode 100644 index 000000000..d5d563f7f --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererConfig.java @@ -0,0 +1,221 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +/** + * Configures the rendering behavior for custom entities in the game. This extends {@link AzRendererConfig}, adding + * extra functionality specifically for handling entity death rotations. + * + * @param the entity type this configuration applies to, extending {@link Entity} + */ +public class AzEntityRendererConfig extends AzRendererConfig { + + private final Function deathMaxRotationProvider; + + private final Function shadowRadius; + + private AzEntityRendererConfig( + Supplier> animatorProvider, + Function deathMaxRotationProvider, + Function shadowRadius, + Function modelLocationProvider, + Function renderTypeFunction, + List> renderLayers, + UnaryOperator> preRenderEntry, + UnaryOperator> postRenderEntry, + Function textureLocationProvider, + Function alphaFunction, + Function scaleHeight, + Function scaleWidth + ) { + super( + animatorProvider, + modelLocationProvider, + renderTypeFunction, + renderLayers, + preRenderEntry, + postRenderEntry, + textureLocationProvider, + alphaFunction, + scaleHeight, + scaleWidth + ); + this.deathMaxRotationProvider = deathMaxRotationProvider; + this.shadowRadius = shadowRadius; + } + + public float getDeathMaxRotation(T entity) { + return deathMaxRotationProvider.apply(entity); + } + + public float shadowRadius(T entity) { + return shadowRadius.apply(entity); + } + + public static Builder builder( + ResourceLocation modelLocation, + ResourceLocation textureLocation + ) { + return new Builder<>($ -> modelLocation, $ -> textureLocation); + } + + public static Builder builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + return new Builder<>(modelLocationProvider, textureLocationProvider); + } + + public static class Builder extends AzRendererConfig.Builder { + + private Function deathMaxRotationProvider; + + protected Function shadowRadius; + + protected Builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + super(modelLocationProvider, textureLocationProvider); + this.deathMaxRotationProvider = $ -> 90F; + this.shadowRadius = $ -> 0.0F; + } + + @Override + public Builder addRenderLayer(AzRenderLayer renderLayer) { + return (Builder) super.addRenderLayer(renderLayer); + } + + public Builder setRenderType(RenderType renderType) { + this.renderTypeProvider = $ -> renderType; + return this; + } + + public Builder setRenderType(Function renderTypeProvider) { + this.renderTypeProvider = renderTypeProvider; + return this; + } + + @Override + public Builder setPrerenderEntry( + UnaryOperator> preRenderEntry + ) { + return (AzEntityRendererConfig.Builder) super.setPrerenderEntry(preRenderEntry); + } + + @Override + public Builder setPostRenderEntry( + UnaryOperator> preRenderEntry + ) { + return (AzEntityRendererConfig.Builder) super.setPostRenderEntry(preRenderEntry); + } + + @Override + public Builder setAnimatorProvider(Supplier<@Nullable AzAnimator> animatorProvider) { + return (Builder) super.setAnimatorProvider(animatorProvider); + } + + @Override + public Builder setAlpha(Function alphaFunction) { + return (AzEntityRendererConfig.Builder) super.setAlpha(alphaFunction); + } + + @Override + public Builder setAlpha(float alpha) { + return (AzEntityRendererConfig.Builder) super.setAlpha(alpha); + } + + @Override + public Builder setScale(Function scaleFunction) { + return (AzEntityRendererConfig.Builder) super.setScale(scaleFunction); + } + + @Override + public Builder setScale(Function scaleHeightFunction, Function scaleWidthFunction) { + return (AzEntityRendererConfig.Builder) super.setScale(scaleHeightFunction, scaleWidthFunction); + } + + @Override + public Builder setScale(float scale) { + return (AzEntityRendererConfig.Builder) super.setScale(scale); + } + + @Override + public Builder setScale(float scaleWidth, float scaleHeight) { + return (AzEntityRendererConfig.Builder) super.setScale(scaleWidth, scaleHeight); + } + + public Builder setDeathMaxRotation(float angle) { + this.deathMaxRotationProvider = $ -> angle; + return this; + } + + /** + * Sets a provider for the max rotation value for dying entities.
    + * You might want to modify this for different aesthetics, such as a + * {@link net.minecraft.world.entity.monster.Spider} flipping upside down on death.
    + */ + public Builder setDeathMaxRotation(Function deathMaxRotationProvider) { + this.deathMaxRotationProvider = deathMaxRotationProvider; + return this; + } + + /** + * Sets a provider function for the shadow radius of an entity. The shadow radius determines the size of the + * shadow cast by the entity when rendered. This can be dynamic based on the entity's state. + * + * @param shadowRadiusFunction A function that provides the shadow radius value based on the entity. The + * function should return a Float representing the desired shadow radius. + * @return The current {@code Builder} instance with the shadow radius provider function set, allowing for + * method chaining. + */ + public Builder setShadowRadius(Function shadowRadiusFunction) { + this.shadowRadius = shadowRadiusFunction; + return this; + } + + /** + * Sets the shadow radius for the builder configuration. This value determines the size of the shadow rendered + * beneath the entity model. + * + * @param shadowRadius the radius of the shadow for the entity + * @return the current instance of the builder for chaining additional configurations + */ + public Builder setShadowRadius(float shadowRadius) { + this.shadowRadius = $ -> shadowRadius; + return this; + } + + @Override + public AzEntityRendererConfig build() { + var baseConfig = super.build(); + + return new AzEntityRendererConfig<>( + baseConfig::createAnimator, + deathMaxRotationProvider, + shadowRadius, + baseConfig::modelLocation, + baseConfig::getRenderType, + baseConfig.renderLayers(), + baseConfig::preRenderEntry, + baseConfig::postRenderEntry, + baseConfig::textureLocation, + baseConfig::alpha, + baseConfig::scaleHeight, + baseConfig::scaleWidth + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipeline.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipeline.java new file mode 100644 index 000000000..d19627ed9 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipeline.java @@ -0,0 +1,116 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.cache.texture.AnimatableTexture; +import mod.azure.azurelib.rewrite.render.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; +import org.joml.Matrix4f; + +/** + * Represents a renderer pipeline specifically designed for rendering entities. This pipeline facilitates stages of + * rendering where contextual work like pre-translations, texture animations, and leash rendering are managed within a + * customizable structure. + * + * @param The type of entity this renderer pipeline handles. Extends from the base {@link Entity}. + */ +public class AzEntityRendererPipeline extends AzRendererPipeline { + + private final AzEntityRenderer entityRenderer; + + protected Matrix4f entityRenderTranslations = new Matrix4f(); + + protected Matrix4f modelRenderTranslations = new Matrix4f(); + + public AzEntityRendererPipeline(AzEntityRendererConfig config, AzEntityRenderer entityRenderer) { + super(config); + this.entityRenderer = entityRenderer; + } + + @Override + protected AzRendererPipelineContext createContext(AzRendererPipeline rendererPipeline) { + return new AzEntityRendererPipelineContext<>(this); + } + + @Override + protected AzModelRenderer createModelRenderer(AzLayerRenderer layerRenderer) { + return new AzEntityModelRenderer<>(this, layerRenderer); + } + + @Override + protected AzLayerRenderer createLayerRenderer(AzRendererConfig config) { + return new AzEntityLayerRenderer<>(config::renderLayers); + } + + /** + * Update the current frame of a {@link AnimatableTexture potentially animated} texture used by this + * GeoRenderer.
    + * This should only be called immediately prior to rendering, and only + * + * @see AnimatableTexture#setAndUpdate(ResourceLocation, int) + */ + @Override + public void updateAnimatedTextureFrame(T entity) { + AnimatableTexture.setAndUpdate(config.textureLocation(entity)); + } + + /** + * Called before rendering the model to buffer. Allows for render modifications and preparatory work such as scaling + * and translating.
    + * {@link PoseStack} translations made here are kept until the end of the render process + */ + @Override + public void preRender(AzRendererPipelineContext context, boolean isReRender) { + var poseStack = context.poseStack(); + this.entityRenderTranslations.set(poseStack.last().pose()); + + var config = entityRenderer.config(); + var scaleWidth = config.scaleWidth(context.animatable()); + var scaleHeight = config.scaleHeight(context.animatable()); + + scaleModelForRender(context, scaleWidth, scaleHeight, isReRender); + if (config.alpha(context.animatable()) < 1) { + context.setAlpha(config.alpha(context.animatable())); + } + config.preRenderEntry(context); + } + + @Override + public void postRender(AzRendererPipelineContext context, boolean isReRender) { + config.postRenderEntry(context); + } + + /** + * Renders the final frame of the entity, including handling special cases such as entities with leashes. + * + * @param context the rendering context that contains all required data for rendering, such as the entity, pose + * stack, light information, and buffer source + */ + @Override + public void renderFinal(AzRendererPipelineContext context) { + var bufferSource = context.multiBufferSource(); + var entity = context.animatable(); + var packedLight = context.packedLight(); + var partialTick = context.partialTick(); + var poseStack = context.poseStack(); + + entityRenderer.superRender(entity, 0, partialTick, poseStack, bufferSource, packedLight); + + if (!(entity instanceof Mob mob)) { + return; + } + + var leashHolder = mob.getLeashHolder(); + + if (leashHolder == null) { + return; + } + + AzEntityLeashRenderUtil.renderLeash(entityRenderer, mob, partialTick, poseStack, bufferSource, leashHolder); + } + + public AzEntityRenderer getRenderer() { + return entityRenderer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipelineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipelineContext.java new file mode 100644 index 000000000..219502717 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/entity/AzEntityRendererPipelineContext.java @@ -0,0 +1,53 @@ +package mod.azure.azurelib.rewrite.render.entity; + +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A context class specifically for rendering entities using a custom rendering pipeline. This class extends + * {@code AzRendererPipelineContext} and provides implementations for methods to customize entity rendering, such as + * determining default render types and packed overlay settings. + * + * @param the type of entity being rendered, extending {@code Entity} + */ +public class AzEntityRendererPipelineContext extends AzRendererPipelineContext { + + public AzEntityRendererPipelineContext(AzRendererPipeline rendererPipeline) { + super(rendererPipeline); + } + + @Override + public @NotNull RenderType getDefaultRenderType( + T animatable, + ResourceLocation texture, + @Nullable MultiBufferSource bufferSource, + float partialTick + ) { + return RenderType.entityCutoutNoCull(texture); + } + + /** + * Gets a packed overlay coordinate pair for rendering.
    + * Mostly just used for the red tint when an entity is hurt, but can be used for other things like the + * {@link net.minecraft.world.entity.monster.Creeper} white tint when exploding. + */ + @Override + public int getPackedOverlay(T entity, float u, float partialTick) { + if (!(entity instanceof LivingEntity livingEntity)) { + return OverlayTexture.NO_OVERLAY; + } + + return OverlayTexture.pack( + OverlayTexture.u(u), + OverlayTexture.v(livingEntity.hurtTime > 0 || livingEntity.deathTime > 0) + ); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemGuiRenderUtil.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemGuiRenderUtil.java new file mode 100644 index 000000000..b7cb8da6b --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemGuiRenderUtil.java @@ -0,0 +1,57 @@ +package mod.azure.azurelib.rewrite.render.item; + +import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.core.object.Color; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.world.item.ItemStack; + +public class AzItemGuiRenderUtil { + + /** + * Wrapper method to handle rendering the item in a GUI context (defined by + * {@link net.minecraft.world.item.ItemDisplayContext#GUI} normally).
    + * Just includes some additional required transformations and settings. + */ + public static void renderInGui( + AzItemRendererConfig config, + AzItemRendererPipeline rendererPipeline, + ItemStack stack, + AzBakedModel model, + ItemStack currentItemStack, + PoseStack poseStack, + MultiBufferSource source, + int packedLight + ) { + if (config.useEntityGuiLighting()) { + Lighting.setupForEntityInInventory(); + } else { + Lighting.setupForFlatItems(); + } + + var partialTick = Minecraft.getInstance().getFrameTime(); + var bSource = + source instanceof MultiBufferSource.BufferSource bufferSource + ? bufferSource + : Minecraft.getInstance().levelRenderer.renderBuffers.bufferSource(); + var textureLocation = config.textureLocation(stack); + var renderType = rendererPipeline.context() + .getDefaultRenderType(stack, textureLocation, bSource, partialTick); + var withGlint = currentItemStack != null && currentItemStack.hasFoil(); + var buffer = ItemRenderer.getFoilBufferDirect(source, renderType, true, withGlint); + + poseStack.pushPose(); + + rendererPipeline.render(poseStack, model, stack, bSource, renderType, buffer, 0, partialTick, packedLight); + + bSource.endBatch(); + RenderSystem.enableDepthTest(); + Lighting.setupFor3DItems(); + + poseStack.popPose(); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemModelRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemModelRenderer.java new file mode 100644 index 000000000..744ba6375 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemModelRenderer.java @@ -0,0 +1,77 @@ +package mod.azure.azurelib.rewrite.render.item; + +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzModelRenderer; +import mod.azure.azurelib.rewrite.render.AzPhasedRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; + +/** + * AzItemModelRenderer is a specialized implementation of {@link AzModelRenderer} for rendering {@link ItemStack} + * objects. It provides customized rendering logic for rendering item models in a layered and recursive manner. + */ +public class AzItemModelRenderer extends AzModelRenderer { + + private final AzItemRendererPipeline itemRendererPipeline; + + public AzItemModelRenderer(AzItemRendererPipeline itemRendererPipeline, AzLayerRenderer layerRenderer) { + super(itemRendererPipeline, layerRenderer); + this.itemRendererPipeline = itemRendererPipeline; + } + + /** + * The actual render method that subtype renderers should override to handle their specific rendering tasks.
    + * {@link AzPhasedRenderer#preRender} has already been called by this stage, and {@link AzPhasedRenderer#postRender} + * will be called directly after + */ + @Override + public void render(AzRendererPipelineContext context, boolean isReRender) { + if (!isReRender) { + var animatable = context.animatable(); + var animator = itemRendererPipeline.getRenderer().getAnimator(); + + if (animator != null) { + animator.animate(animatable, context.partialTick()); + } + } + + var poseStack = context.poseStack(); + + itemRendererPipeline.modelRenderTranslations = new Matrix4f(poseStack.last().pose()); + + super.render(context, isReRender); + } + + /** + * Renders the provided {@link AzBone} and its associated child bones + */ + @Override + public void renderRecursively(AzRendererPipelineContext context, AzBone bone, boolean isReRender) { + if (bone.isTrackingMatrices()) { + var animatable = context.animatable(); + var poseStack = context.poseStack(); + var poseState = new Matrix4f(poseStack.last().pose()); + var localMatrix = RenderUtils.invertAndMultiplyMatrices( + poseState, + itemRendererPipeline.itemRenderTranslations + ); + + bone.setModelSpaceMatrix( + RenderUtils.invertAndMultiplyMatrices(poseState, itemRendererPipeline.modelRenderTranslations) + ); + bone.setLocalSpaceMatrix( + RenderUtils.translateMatrix(localMatrix, getRenderOffset(animatable, 1).toVector3f()) + ); + } + + super.renderRecursively(context, bone, isReRender); + } + + public Vec3 getRenderOffset(ItemStack itemStack, float f) { + return Vec3.ZERO; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRenderer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRenderer.java new file mode 100644 index 000000000..f5441ca6d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRenderer.java @@ -0,0 +1,97 @@ +package mod.azure.azurelib.rewrite.render.item; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.core.object.Color; +import mod.azure.azurelib.rewrite.animation.impl.AzItemAnimator; +import mod.azure.azurelib.rewrite.model.AzBakedModel; +import mod.azure.azurelib.rewrite.render.AzProvider; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * AzItemRenderer is an abstract base class for rendering custom animated items in a game framework. It provides + * utilities for handling item models, textures, and animations via a configurable pipeline and provider system. This + * class supports rendering of items both in GUI contexts and in-world as entities, enabling advanced visual effects + * such as custom animations and lighting.
    + * The rendering process utilizes a pipeline to manage render layers, textures, and baked models, integrating with game + * frame components like PoseStack and MultiBufferSource. + */ +public abstract class AzItemRenderer { + + private final AzItemRendererConfig config; + + private final AzProvider provider; + + public final AzItemRendererPipeline rendererPipeline; + + @Nullable + private AzItemAnimator reusedAzItemAnimator; + + protected AzItemRenderer( + AzItemRendererConfig config + ) { + this.rendererPipeline = createPipeline(config); + this.provider = new AzProvider<>(config::createAnimator, config::modelLocation); + this.config = config; + } + + protected AzItemRendererPipeline createPipeline(AzItemRendererConfig config) { + return new AzItemRendererPipeline(config, this); + } + + public void renderByGui( + ItemStack stack, + @NotNull PoseStack poseStack, + @NotNull MultiBufferSource source, + int packedLight + ) { + var model = provider.provideBakedModel(stack); + + prepareAnimator(stack, model); + + AzItemGuiRenderUtil.renderInGui(config, rendererPipeline, stack, model, stack, poseStack, source, packedLight); + } + + public void renderByItem( + ItemStack stack, + @NotNull PoseStack poseStack, + @NotNull MultiBufferSource source, + int packedLight + ) { + var model = provider.provideBakedModel(stack); + var partialTick = Minecraft.getInstance().getFrameTime(); + var textureLocation = config.textureLocation(stack); + var renderType = rendererPipeline.context() + .getDefaultRenderType(stack, textureLocation, source, partialTick); + // TODO: Why the null check here? + var withGlint = stack != null && stack.hasFoil(); + var buffer = ItemRenderer.getFoilBufferDirect(source, renderType, false, withGlint); + + prepareAnimator(stack, model); + + rendererPipeline.render(poseStack, model, stack, source, renderType, buffer, 0, partialTick, packedLight); + } + + private void prepareAnimator(ItemStack stack, AzBakedModel model) { + var cachedEntityAnimator = (AzItemAnimator) provider.provideAnimator(stack); + + if (cachedEntityAnimator != null && model != null) { + cachedEntityAnimator.setActiveModel(model); + } + + // Point the renderer's current animator reference to the cached entity animator before rendering. + reusedAzItemAnimator = cachedEntityAnimator; + } + + public @Nullable AzItemAnimator getAnimator() { + return reusedAzItemAnimator; + } + + public AzItemRendererConfig config() { + return config; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererConfig.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererConfig.java new file mode 100644 index 000000000..052dbf8ec --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererConfig.java @@ -0,0 +1,194 @@ +package mod.azure.azurelib.rewrite.render.item; + +import mod.azure.azurelib.rewrite.animation.AzAnimator; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.layer.AzRenderLayer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +/** + * Configuration class for rendering items using customized settings in an animation framework. Extends + * {@link AzRendererConfig} specifically for handling {@link ItemStack}. Provides additional settings specific to item + * rendering, such as GUI lighting and custom offsets. + */ +public class AzItemRendererConfig extends AzRendererConfig { + + private final boolean useEntityGuiLighting; + + private final boolean useNewOffset; + + private AzItemRendererConfig( + Supplier> animatorProvider, + Function modelLocationProvider, + Function renderTypeProvider, + List> renderLayers, + UnaryOperator> preRenderEntry, + UnaryOperator> postRenderEntry, + Function textureLocationProvider, + Function alphaFunction, + Function scaleHeight, + Function scaleWidth, + boolean useEntityGuiLighting, + boolean useNewOffset + ) { + super( + animatorProvider, + modelLocationProvider, + renderTypeProvider, + renderLayers, + preRenderEntry, + postRenderEntry, + textureLocationProvider, + alphaFunction, + scaleHeight, + scaleWidth + ); + this.useEntityGuiLighting = useEntityGuiLighting; + this.useNewOffset = useNewOffset; + } + + public boolean useEntityGuiLighting() { + return useEntityGuiLighting; + } + + public boolean useNewOffset() { + return useNewOffset; + } + + public static Builder builder( + ResourceLocation modelLocation, + ResourceLocation textureLocation + ) { + return new Builder($ -> modelLocation, $ -> textureLocation); + } + + public static Builder builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + return new Builder(modelLocationProvider, textureLocationProvider); + } + + public static class Builder extends AzRendererConfig.Builder { + + private boolean useEntityGuiLighting; + + private boolean useNewOffset; + + protected Builder( + Function modelLocationProvider, + Function textureLocationProvider + ) { + super(modelLocationProvider, textureLocationProvider); + this.useEntityGuiLighting = false; + this.useNewOffset = false; + } + + @Override + public Builder addRenderLayer(AzRenderLayer renderLayer) { + return (Builder) super.addRenderLayer(renderLayer); + } + + public Builder setRenderType(RenderType renderType) { + this.renderTypeProvider = $ -> renderType; + return this; + } + + public Builder setRenderType(Function renderTypeProvider) { + this.renderTypeProvider = renderTypeProvider; + return this; + } + + @Override + public Builder setPrerenderEntry(UnaryOperator> preRenderEntry + ) { + return (AzItemRendererConfig.Builder) super.setPrerenderEntry(preRenderEntry); + } + + @Override + public Builder setPostRenderEntry(UnaryOperator> preRenderEntry + ) { + return (AzItemRendererConfig.Builder) super.setPostRenderEntry(preRenderEntry); + } + + @Override + public Builder setAnimatorProvider(Supplier<@Nullable AzAnimator> animatorProvider) { + return (Builder) super.setAnimatorProvider(animatorProvider); + } + + @Override + public Builder setAlpha(Function alphaFunction) { + return (AzItemRendererConfig.Builder) super.setAlpha(alphaFunction); + } + + @Override + public Builder setAlpha(float alpha) { + return (AzItemRendererConfig.Builder) super.setAlpha(alpha); + } + + @Override + public Builder setScale(Function scaleFunction) { + return (AzItemRendererConfig.Builder) super.setScale(scaleFunction); + } + + @Override + public Builder setScale( + Function scaleHeightFunction, + Function scaleWidthFunction + ) { + return (AzItemRendererConfig.Builder) super.setScale(scaleHeightFunction, scaleWidthFunction); + } + + @Override + public Builder setScale(float scale) { + return (AzItemRendererConfig.Builder) super.setScale(scale); + } + + @Override + public Builder setScale(float scaleWidth, float scaleHeight) { + return (AzItemRendererConfig.Builder) super.setScale(scaleWidth, scaleHeight); + } + + public Builder useEntityGuiLighting() { + this.useEntityGuiLighting = true; + return this; + } + + /** + * @param useNewOffset Determines whether to apply the y offset for a model due to the change in BlockBench + * 4.11. + */ + public Builder useNewOffset(boolean useNewOffset) { + this.useNewOffset = useNewOffset; + return this; + } + + @Override + public AzItemRendererConfig build() { + var baseConfig = super.build(); + + return new AzItemRendererConfig( + baseConfig::createAnimator, + baseConfig::modelLocation, + baseConfig::getRenderType, + baseConfig.renderLayers(), + baseConfig::preRenderEntry, + baseConfig::postRenderEntry, + baseConfig::textureLocation, + baseConfig::alpha, + baseConfig::scaleHeight, + baseConfig::scaleWidth, + useEntityGuiLighting, + useNewOffset + ); + } + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipeline.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipeline.java new file mode 100644 index 000000000..297c66576 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipeline.java @@ -0,0 +1,93 @@ +package mod.azure.azurelib.rewrite.render.item; + +import com.mojang.blaze3d.vertex.PoseStack; +import mod.azure.azurelib.cache.texture.AnimatableTexture; +import mod.azure.azurelib.rewrite.render.AzLayerRenderer; +import mod.azure.azurelib.rewrite.render.AzRendererConfig; +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix4f; + +/** + * Extends the {@link AzRendererPipeline} to provide a specific implementation for rendering {@link ItemStack} objects. + * This pipeline includes methods and configurations designed for item rendering and leverages additional utilities such + * as translation matrices and scaling functionalities for accurate rendering. + */ +public class AzItemRendererPipeline extends AzRendererPipeline { + + private final AzItemRenderer itemRenderer; + + protected Matrix4f itemRenderTranslations = new Matrix4f(); + + protected Matrix4f modelRenderTranslations = new Matrix4f(); + + public AzItemRendererPipeline(AzItemRendererConfig config, AzItemRenderer itemRenderer) { + super(config); + this.itemRenderer = itemRenderer; + } + + @Override + protected AzRendererPipelineContext createContext(AzRendererPipeline rendererPipeline) { + return new AzItemRendererPipelineContext(rendererPipeline); + } + + @Override + protected AzItemModelRenderer createModelRenderer(AzLayerRenderer layerRenderer) { + return new AzItemModelRenderer(this, layerRenderer); + } + + @Override + protected AzLayerRenderer createLayerRenderer(AzRendererConfig config) { + return new AzLayerRenderer<>(config::renderLayers); + } + + /** + * Called before rendering the model to buffer. Allows for render modifications and preparatory work such as scaling + * and translating.
    + * {@link PoseStack} translations made here are kept until the end of the render process + */ + @Override + public void preRender(AzRendererPipelineContext context, boolean isReRender) { + var itemContext = (AzItemRendererPipelineContext) context; + var poseStack = context.poseStack(); + this.itemRenderTranslations = new Matrix4f(poseStack.last().pose()); + + var config = itemRenderer.config(); + var scaleWidth = config.scaleWidth(context.animatable()); + var scaleHeight = config.scaleHeight(context.animatable()); + scaleModelForRender(context, scaleWidth, scaleHeight, isReRender); + + if (!isReRender) { + var useNewOffset = config.useNewOffset(); + poseStack.translate(0.5f, useNewOffset ? 0.0f : 0.51f, 0.5f); + } + if (config.alpha(context.animatable()) < 1) { + itemContext.setAlpha(config.alpha(context.animatable())); + itemContext.setTranslucent(true); + } + config.preRenderEntry(context); + } + + @Override + public void postRender(AzRendererPipelineContext context, boolean isReRender) { + config.postRenderEntry(context); + } + + /** + * Update the current frame of a {@link AnimatableTexture potentially animated} texture used by this + * GeoRenderer.
    + * This should only be called immediately prior to rendering, and only + * + * @see AnimatableTexture#setAndUpdate(ResourceLocation, int) + */ + @Override + public void updateAnimatedTextureFrame(ItemStack animatable) { + AnimatableTexture.setAndUpdate(config.textureLocation(animatable)); + } + + public AzItemRenderer getRenderer() { + return itemRenderer; + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipelineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipelineContext.java new file mode 100644 index 000000000..da79a5300 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererPipelineContext.java @@ -0,0 +1,49 @@ +package mod.azure.azurelib.rewrite.render.item; + +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A specialized subclass of {@link AzRendererPipelineContext} designed for rendering {@link ItemStack} objects. + * Provides the default rendering context and pipeline for rendering item models within a custom rendering framework. + *
    + * This context delegates rendering operations to its associated {@link AzRendererPipeline} while providing additional + * configuration and control over the rendering process of an {@link ItemStack}. + */ +public class AzItemRendererPipelineContext extends AzRendererPipelineContext { + + private boolean translucent = false; + + public AzItemRendererPipelineContext(AzRendererPipeline rendererPipeline) { + super(rendererPipeline); + } + + /** + * Sets whether the rendering pipeline should render with a translucent effect or not. + * + * @param translucent A boolean value indicating whether to enable or disable translucency. If true, the rendering + * pipeline will apply a translucent effect to rendered elements. If false, it will render with + * an opaque effect. + */ + public void setTranslucent(boolean translucent) { + this.translucent = translucent; + } + + @Override + public @NotNull RenderType getDefaultRenderType( + ItemStack animatable, + ResourceLocation texture, + @Nullable MultiBufferSource bufferSource, + float partialTick + ) { + return translucent + ? RenderType.itemEntityTranslucentCull(texture) + : RenderType.entityCutoutNoCull(texture); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererRegistry.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererRegistry.java new file mode 100644 index 000000000..1164ae072 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/item/AzItemRendererRegistry.java @@ -0,0 +1,39 @@ +package mod.azure.azurelib.rewrite.render.item; + +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * The AzItemRendererRegistry class manages the association between items and their renderers in the context of the + * AzureLib framework. It provides functionality to register and retrieve item renderers dynamically, ensuring that + * appropriate renderers can be applied to specific item types or instances. + */ +public class AzItemRendererRegistry { + + private static final Map ITEM_TO_RENDERER = new HashMap<>(); + + private static final Map> ITEM_TO_RENDERER_SUPPLIER = new HashMap<>(); + + public static void register(Item item, Supplier itemRendererSupplier) { + ITEM_TO_RENDERER_SUPPLIER.put(item, itemRendererSupplier); + } + + public static void register(Supplier itemRendererSupplier, Item item, Item... items) { + register(item, itemRendererSupplier); + + for (var otherItem : items) { + register(otherItem, itemRendererSupplier); + } + } + + public static @Nullable AzItemRenderer getOrNull(Item item) { + return ITEM_TO_RENDERER.computeIfAbsent(item, ($) -> { + var rendererSupplier = ITEM_TO_RENDERER_SUPPLIER.get(item); + return rendererSupplier == null ? null : rendererSupplier.get(); + }); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzArmorLayer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzArmorLayer.java new file mode 100644 index 000000000..c0bf7db8d --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzArmorLayer.java @@ -0,0 +1,418 @@ +package mod.azure.azurelib.rewrite.render.layer; + +import com.mojang.authlib.GameProfile; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.rewrite.render.armor.AzArmorRenderer; +import mod.azure.azurelib.rewrite.render.armor.AzArmorRendererRegistry; +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.blockentity.SkullBlockRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.ItemTags; +import net.minecraft.util.FastColor; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.*; +import net.minecraft.world.level.block.AbstractSkullBlock; +import net.minecraft.world.level.block.entity.SkullBlockEntity; +import net.minecraft.world.level.entity.EntityAccess; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Builtin class for handling dynamic armor rendering on AzureLib entities.
    + * Supports {@link ArmorItem Vanilla} armor models.
    + * Unlike a traditional armor renderer, this renderer renders per-bone, giving much more flexible armor rendering. + */ +public class AzArmorLayer implements AzRenderLayer { + + protected static final Map ARMOR_PATH_CACHE = new Object2ObjectOpenHashMap<>(); + + protected static final HumanoidModel INNER_ARMOR_MODEL = new HumanoidModel<>( + Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.PLAYER_INNER_ARMOR) + ); + + protected static final HumanoidModel OUTER_ARMOR_MODEL = new HumanoidModel<>( + Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.PLAYER_OUTER_ARMOR) + ); + + @Nullable + protected ItemStack mainHandStack; + + @Nullable + protected ItemStack offhandStack; + + @Nullable + protected ItemStack helmetStack; + + @Nullable + protected ItemStack chestplateStack; + + @Nullable + protected ItemStack leggingsStack; + + @Nullable + protected ItemStack bootsStack; + + /** + * Prepares the necessary item stacks for rendering by accessing the relevant equipment slots of the animatable + * instance. If the animatable instance is not a LivingEntity, the method returns without action. + * + * @param context The rendering context containing the animatable instance and other necessary data for rendering. + */ + @Override + public void preRender(AzRendererPipelineContext context) { + this.mainHandStack = context.animatable().getItemBySlot(EquipmentSlot.MAINHAND); + this.offhandStack = context.animatable().getItemBySlot(EquipmentSlot.OFFHAND); + this.helmetStack = context.animatable().getItemBySlot(EquipmentSlot.HEAD); + this.chestplateStack = context.animatable().getItemBySlot(EquipmentSlot.CHEST); + this.leggingsStack = context.animatable().getItemBySlot(EquipmentSlot.LEGS); + this.bootsStack = context.animatable().getItemBySlot(EquipmentSlot.FEET); + } + + @Override + public void render(AzRendererPipelineContext context) {} + + /** + * Renders the given armor or skull block for the specified bone using the provided rendering context. Depending on + * the type of item, it delegates rendering to appropriate methods. + * + * @param context The rendering context containing necessary parameters for rendering, like pose stack, light level, + * etc. + * @param bone The specific bone of the model where the armor or skull block will be rendered. + */ + @Override + public void renderForBone(AzRendererPipelineContext context, AzBone bone) { + var armorStack = getArmorItemForBone(context, bone); + + if (armorStack == null) { + return; + } + + if ( + armorStack.getItem() instanceof BlockItem blockItem && blockItem + .getBlock() instanceof AbstractSkullBlock skullBlock + ) { + renderSkullAsArmor(context, bone, armorStack, skullBlock); + } else { + renderArmor(context, bone, armorStack); + } + } + + /** + * Renders armor items on a given bone within the render cycle of a model. This method determines the appropriate + * equipment slot, renderer, and model for the armor item and handles the rendering process accordingly. + * + * @param context The rendering context containing the animatable instance and other data essential for + * rendering. + * @param bone The specific bone of the model where the armor piece will be rendered. + * @param armorStack The ItemStack representing the armor item to render. + */ + public void renderArmor( + AzRendererPipelineContext context, + AzBone bone, + ItemStack armorStack + ) { + var slot = getEquipmentSlotForBone(context, bone, armorStack); + var renderer = AzArmorRendererRegistry.getOrNull(armorStack.getItem()); + var model = getModelForItem(armorStack, slot); + var modelPart = getModelPartForBone(context, bone, model); + + if (!modelPart.cubes.isEmpty()) { + context.poseStack().pushPose(); + context.poseStack().scale(-1, -1, 1); + + + if (renderer != null) { + prepModelPartForRender(context, bone, modelPart); + renderAzArmorPiece(context, slot, armorStack, renderer, context.animatable(), model, modelPart); + } else if (armorStack.getItem() instanceof ArmorItem) { + prepModelPartForRender(context, bone, modelPart); + renderVanillaArmorPiece(context, bone, slot, armorStack, modelPart); + } + + context.poseStack().popPose(); + } + } + + /** + * Return an EquipmentSlot for a given {@link ItemStack} and animatable instance.
    + * This is what determines the base model to use for rendering a particular stack + */ + protected @NotNull EquipmentSlot getEquipmentSlotForBone( + AzRendererPipelineContext context, + AzBone bone, + ItemStack stack + ) { + var animatable = context.animatable(); + + for (var slot : EquipmentSlot.values()) { + var isHumanoidArmorSlotType = slot.getType() == EquipmentSlot.Type.ARMOR; + + if (isHumanoidArmorSlotType && stack == animatable.getItemBySlot(slot)) { + return slot; + } + } + + return EquipmentSlot.CHEST; + } + + /** + * Return a ModelPart for a given {@link AzBone}.
    + * This is then transformed into position for the final render + */ + @NotNull + protected ModelPart getModelPartForBone( + AzRendererPipelineContext context, + AzBone bone, + HumanoidModel baseModel + ) { + return baseModel.body; + } + + /** + * Get the {@link ItemStack} relevant to the bone being rendered.
    + * Return null if this bone should be ignored + */ + @Nullable + protected ItemStack getArmorItemForBone(AzRendererPipelineContext context, AzBone bone) { + return null; + } + + protected void renderAzArmorPiece( + AzRendererPipelineContext context, + EquipmentSlot slot, + ItemStack armorStack, + AzArmorRenderer renderer, + LivingEntity entity, + HumanoidModel model, + ModelPart modelPart + ) { + var renderPipelines = renderer.rendererPipeline(); + var boneContext = renderPipelines.context().boneContext(); + var armorModel = renderPipelines.armorModel(); + + renderer.prepForRender(entity, armorStack, slot, model); + boneContext.applyBoneVisibilityByPart(slot, modelPart, model); + armorModel.renderToBuffer(context.poseStack(), null, context.packedLight(), OverlayTexture.NO_OVERLAY, context.red(), context.green(), context.blue(), context.alpha()); + } + + /** + * Renders an individual armor piece base on the given {@link AzBone} and {@link ItemStack} + */ + protected void renderVanillaArmorPiece( + AzRendererPipelineContext context, + AzBone bone, + EquipmentSlot slot, + ItemStack armorStack, + ModelPart modelPart + ) { + var buffer = getVanillaArmorBuffer( + context, + armorStack, + slot, + bone, + false + ); + + modelPart.render(context.poseStack(), buffer, context.packedLight(), context.packedOverlay()); + + if (armorStack.hasFoil()) + modelPart.render( + context.poseStack(), + getVanillaArmorBuffer( + context, + armorStack, + slot, + bone, + true + ), + context.packedLight(), + context.packedOverlay(), + context.red(), + context.green(), + context.blue(), + context.alpha() + ); + } + + /** + * Retrieves a {@link VertexConsumer} for rendering vanilla-styled armor. The method determines whether the armor + * should apply a glint effect or not and selects the appropriate render type accordingly. + * + * @param context The rendering context providing necessary data for rendering, including the animatable instance + * and the buffer source. + * @param stack The armor {@link ItemStack} being rendered. + * @param slot The {@link EquipmentSlot} the armor piece occupies. + * @param bone The model bone associated with the armor piece. + * @param forGlint A flag indicating whether the armor piece should render with a glint effect. + * @return The {@link VertexConsumer} used to render the designated armor piece with the appropriate style and + * effect. + */ + protected VertexConsumer getVanillaArmorBuffer( + AzRendererPipelineContext context, + ItemStack stack, + EquipmentSlot slot, + AzBone bone, + boolean forGlint + ) { + if (forGlint) { + return context.multiBufferSource().getBuffer(RenderType.armorEntityGlint()); + } + + return context.multiBufferSource() + .getBuffer(RenderType.armorCutoutNoCull(getVanillaArmorResource(context.animatable(), stack, slot, bone.getName()))); + } + + /** + * Retrieves the appropriate {@link AzArmorRenderer} for the given {@link ItemStack}. This method uses the + * {@link AzArmorRendererRegistry} to fetch a renderer if one is registered for the specified item's class or + * instance. + * + * @param stack The {@link ItemStack} for which the renderer is to be obtained. + * @return The {@link AzArmorRenderer} associated with the item in the stack, or null if no renderer exists. + */ + protected @Nullable AzArmorRenderer getRendererForItem(ItemStack stack) { + var item = stack.getItem(); + return AzArmorRendererRegistry.getOrNull(item); + } + + /** + * Returns a cached instance of a base HumanoidModel that is used for rendering/modelling the provided + * {@link ItemStack} + */ + protected HumanoidModel getModelForItem(ItemStack stack, EquipmentSlot slot) { + var renderer = getRendererForItem(stack); + + if (renderer == null) { + return (HumanoidModel) (slot == EquipmentSlot.LEGS ? INNER_ARMOR_MODEL : OUTER_ARMOR_MODEL); + } + + return (HumanoidModel) renderer.rendererPipeline().armorModel(); + } + + /** + * Render a given {@link AbstractSkullBlock} as a worn armor piece in relation to a given {@link AzBone} + */ + protected void renderSkullAsArmor( + AzRendererPipelineContext context, + AzBone bone, + ItemStack stack, + AbstractSkullBlock skullBlock + ) { + GameProfile skullProfile = null; + CompoundTag stackTag = stack.getTag(); + + if (stackTag != null) { + Tag skullTag = stackTag.get(PlayerHeadItem.TAG_SKULL_OWNER); + + if (skullTag instanceof CompoundTag compoundTag) { + skullProfile = NbtUtils.readGameProfile(compoundTag); + } + else if (skullTag instanceof StringTag tag) { + String skullOwner = tag.getAsString(); + + if (!skullOwner.isBlank()) { + CompoundTag profileTag = new CompoundTag(); + + SkullBlockEntity.updateGameprofile(new GameProfile(null, skullOwner), name -> + stackTag.put(PlayerHeadItem.TAG_SKULL_OWNER, NbtUtils.writeGameProfile(profileTag, name))); + + skullProfile = NbtUtils.readGameProfile(profileTag); + } + } + } + var type = skullBlock.getType(); + var model = SkullBlockRenderer.createSkullRenderers(Minecraft.getInstance().getEntityModels()) + .get(type); + var renderType = SkullBlockRenderer.getRenderType(type, skullProfile); + + context.poseStack().pushPose(); + RenderUtils.translateAndRotateMatrixForBone(context.poseStack(), bone); + context.poseStack().scale(1.1875f, 1.1875f, 1.1875f); + context.poseStack().translate(-0.5f, 0, -0.5f); + SkullBlockRenderer.renderSkull( + null, + 0, + 0, + context.poseStack(), + context.multiBufferSource(), + context.packedLight(), + model, + renderType + ); + context.poseStack().popPose(); + } + + /** + * Prepares the given {@link ModelPart} for render by setting its translation, position, and rotation values based + * on the provided {@link AzBone} + * + * @param context + * @param bone The AzBone to base the translations on + * @param sourcePart The ModelPart to translate + */ + protected void prepModelPartForRender(AzRendererPipelineContext context, AzBone bone, ModelPart sourcePart) { + var firstCube = bone.getCubes().get(0); + var armorCube = sourcePart.cubes.get(0); + var armorBoneSizeX = firstCube.size().x(); + var armorBoneSizeY = firstCube.size().y(); + var armorBoneSizeZ = firstCube.size().z(); + var actualArmorSizeX = Math.abs(armorCube.maxX - armorCube.minX); + var actualArmorSizeY = Math.abs(armorCube.maxY - armorCube.minY); + var actualArmorSizeZ = Math.abs(armorCube.maxZ - armorCube.minZ); + var scaleX = (float) (armorBoneSizeX / actualArmorSizeX); + var scaleY = (float) (armorBoneSizeY / actualArmorSizeY); + var scaleZ = (float) (armorBoneSizeZ / actualArmorSizeZ); + + sourcePart.setPos( + -(bone.getPivotX() - ((bone.getPivotX() * scaleX) - bone.getPivotX()) / scaleX), + -(bone.getPivotY() - ((bone.getPivotY() * scaleY) - bone.getPivotY()) / scaleY), + (bone.getPivotZ() - ((bone.getPivotZ() * scaleZ) - bone.getPivotZ()) / scaleZ) + ); + + sourcePart.xRot = -bone.getRotX(); + sourcePart.yRot = -bone.getRotY(); + sourcePart.zRot = bone.getRotZ(); + + context.poseStack().scale(scaleX, scaleY, scaleZ); + } + + /** + * Gets a cached resource path for the vanilla armor layer texture for this armor piece. + *

    + * Equivalent to {@link net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer#getArmorLocation HumanoidArmorLayer.getArmorLocation} + */ + public ResourceLocation getVanillaArmorResource(Entity entity, ItemStack stack, EquipmentSlot slot, String type) { + String domain = "minecraft"; + String path = ((ArmorItem) stack.getItem()).getMaterial().getName(); + String[] materialNameSplit = path.split(":", 2); + + if (materialNameSplit.length > 1) { + domain = materialNameSplit[0]; + path = materialNameSplit[1]; + } + + if (!type.isBlank()) + type = "_" + type; + + String texture = String.format("%s:textures/models/armor/%s_layer_%d%s.png", domain, path, (slot == EquipmentSlot.LEGS ? 2 : 1), type); + return ARMOR_PATH_CACHE.computeIfAbsent(texture, ResourceLocation::new); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzAutoGlowingLayer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzAutoGlowingLayer.java new file mode 100644 index 000000000..a2c9ce681 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzAutoGlowingLayer.java @@ -0,0 +1,69 @@ +package mod.azure.azurelib.rewrite.render.layer; + +import mod.azure.azurelib.cache.texture.AutoGlowingTexture; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import mod.azure.azurelib.util.ClientUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.world.entity.Entity; + +/** + * A {@link AzRenderLayer} dedicated to rendering the auto-generated glow layer functionality provided by AzureLib. This + * utilizes texture files with the _glowing suffix to create glowing effects for models. + */ +public class AzAutoGlowingLayer implements AzRenderLayer { + + @Override + public void preRender(AzRendererPipelineContext context) {} + + /** + * Handles the main rendering logic for the animatable object in the pipeline context. This includes switching to a + * custom {@link RenderType} for glowing textures and rendering the object using the pipeline's re-render mechanism. + *

    + * The rendering context's state is modified temporarily to apply a custom render type and packed light. After + * rendering, the context is restored to its original state for consistency. + *

    + * + * @param context the rendering pipeline context, containing the animatable object and rendering state + */ + @Override + public void render(AzRendererPipelineContext context) { + var animatable = context.animatable(); + var renderPipeline = context.rendererPipeline(); + var textureLocation = renderPipeline.config().textureLocation(animatable); + var renderType = AutoGlowingTexture.getRenderType(textureLocation); + + if (context.animatable() instanceof Entity entity) { + var isInvisibleButVisibleToPlayer = entity.isInvisible() && !entity.isInvisibleTo(ClientUtils.getClientPlayer()); + var shouldAppearGlowing = Minecraft.getInstance().shouldEntityAppearGlowing(entity); + + if (isInvisibleButVisibleToPlayer) { + renderType = RenderType.outline(textureLocation); + } else if (shouldAppearGlowing) { + renderType = AutoGlowingTexture.getOutlineRenderType(textureLocation); + } + } + + if (context.renderType() != null) { + var prevRenderType = context.renderType(); + var prevPackedLight = context.packedLight(); + var prevVertexConsumer = context.vertexConsumer(); + + context.setRenderType(renderType); + context.setPackedLight(0xF00000); + context.setVertexConsumer(context.multiBufferSource().getBuffer(renderType)); + + renderPipeline.reRender(context); + + // Restore context for sanity + // TODO: Should probably cache the context as a whole somewhere and then restore it (a "previous" context). + context.setRenderType(prevRenderType); + context.setPackedLight(prevPackedLight); + context.setVertexConsumer(prevVertexConsumer); + } + } + + @Override + public void renderForBone(AzRendererPipelineContext context, AzBone bone) {} +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzBlockAndItemLayer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzBlockAndItemLayer.java new file mode 100644 index 000000000..5f0f1deb8 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzBlockAndItemLayer.java @@ -0,0 +1,191 @@ +package mod.azure.azurelib.rewrite.render.layer; + +import mod.azure.azurelib.util.RenderUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.function.Function; + +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; + +/** + * A {@link AzRenderLayer} responsible for rendering {@link net.minecraft.world.level.block.state.BlockState + * BlockStates} or {@link net.minecraft.world.item.ItemStack ItemStacks} onto a specified {@link AzRendererPipeline}. + * This layer handles the rendering of physical elements, such as blocks and items, associated with animation bones. + */ +public class AzBlockAndItemLayer implements AzRenderLayer { + + protected final Function itemStackProvider; + + protected final Function blockStateProvider; + + public AzBlockAndItemLayer() { + this(bone -> null, bone -> null); + } + + public AzBlockAndItemLayer( + Function itemStackProvider, + Function blockStateProvider + ) { + super(); + + this.itemStackProvider = itemStackProvider; + this.blockStateProvider = blockStateProvider; + } + + @Override + public void preRender(AzRendererPipelineContext context) {} + + @Override + public void render(AzRendererPipelineContext context) {} + + /** + * Renders an {@link ItemStack} or {@link BlockState} associated with the specified bone in the rendering context. + * If both the {@link ItemStack} and {@link BlockState} are {@code null}, no rendering occurs. + *

    + * This method applies the bone's transformations to the current rendering matrix stack before rendering, ensuring + * the item or block appears correctly positioned and oriented relative to the bone. + *

    + * + * @param context the rendering pipeline context, containing rendering state and utilities + * @param bone the bone for which to render associated elements + */ + @Override + public void renderForBone(AzRendererPipelineContext context, AzBone bone) { + var animatable = context.animatable(); + var stack = itemStackForBone(bone, animatable); + var blockState = blockStateForBone(bone, animatable); + + if (stack == null && blockState == null) + return; + + context.poseStack().pushPose(); + RenderUtils.translateAndRotateMatrixForBone(context.poseStack(), bone); + + if (stack != null) + renderItemForBone(context, bone, stack, animatable); + + if (blockState != null) + renderBlockForBone(context, bone, blockState, animatable); + + context.poseStack().popPose(); + } + + /** + * Retrieves the {@link ItemStack} associated with the given bone for rendering purposes. Returns {@code null} if + * there is no {@link ItemStack} to render for this bone. + * + * @param bone the bone for which to retrieve the {@link ItemStack} + * @return the {@link ItemStack} relevant to the specified bone, or {@code null} if none exists + */ + public ItemStack itemStackForBone(AzBone bone, T animatable) { + return itemStackProvider.apply(bone); + } + + /** + * Retrieves the {@link BlockState} associated with the given bone for rendering purposes. Returns {@code null} if + * there is no {@link BlockState} to render for this bone. + * + * @param bone the bone for which to retrieve the {@link BlockState} + * @return the {@link BlockState} relevant to the specified bone, or {@code null} if none exists + */ + public BlockState blockStateForBone(AzBone bone, T animatable) { + return blockStateProvider.apply(bone); + } + + /** + * Determines the specific {@link ItemDisplayContext} to use for rendering the given {@link ItemStack} on the + * specified bone. By default, this method returns {@link ItemDisplayContext#NONE}. + * + * @param bone the bone where the {@link ItemStack} will be rendered + * @param stack the {@link ItemStack} to render + * @return the {@link ItemDisplayContext} to use for rendering + */ + protected ItemDisplayContext getTransformTypeForStack(AzBone bone, ItemStack stack, T animatable) { + return ItemDisplayContext.NONE; + } + + /** + * Renders the given {@link ItemStack} for the specified bone in the rendering context. The rendering adjusts based + * on whether the animatable object is a {@link LivingEntity}. + * + * @param context the rendering pipeline context + * @param bone the bone where the {@link ItemStack} will be rendered + * @param itemStack the {@link ItemStack} to render + */ + protected void renderItemForBone( + AzRendererPipelineContext context, + AzBone bone, + ItemStack itemStack, + T animatable + ) { + if (context.animatable() instanceof LivingEntity livingEntity) { + Minecraft.getInstance() + .getItemRenderer() + .renderStatic( + livingEntity, + itemStack, + getTransformTypeForStack(bone, itemStack, animatable), + false, + context.poseStack(), + context.multiBufferSource(), + livingEntity.level(), + context.packedLight(), + context.packedOverlay(), + livingEntity.getId() + ); + } else { + Minecraft.getInstance() + .getItemRenderer() + .renderStatic( + itemStack, + getTransformTypeForStack(bone, itemStack, animatable), + context.packedLight(), + context.packedOverlay(), + context.poseStack(), + context.multiBufferSource(), + Minecraft.getInstance().level, + context.animatable().hashCode() + ); + } + } + + /** + * Renders the given {@link BlockState} for the specified bone in the rendering context. The block is rendered with + * adjusted position and scale to fit within the bone's space. + * + * @param context the rendering pipeline context + * @param bone the bone where the {@link BlockState} will be rendered + * @param blockState the {@link BlockState} to render + */ + protected void renderBlockForBone( + AzRendererPipelineContext context, + AzBone bone, + BlockState blockState, + T animatable + ) { + context.poseStack().pushPose(); + + context.poseStack().translate(-0.25f, -0.25f, -0.25f); + context.poseStack().scale(0.5f, 0.5f, 0.5f); + + Minecraft.getInstance() + .getBlockRenderer() + .renderSingleBlock( + blockState, + context.poseStack(), + context.multiBufferSource(), + context.packedLight(), + OverlayTexture.NO_OVERLAY + ); + + context.poseStack().popPose(); + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzRenderLayer.java b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzRenderLayer.java new file mode 100644 index 000000000..a6305b1c7 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/render/layer/AzRenderLayer.java @@ -0,0 +1,41 @@ +package mod.azure.azurelib.rewrite.render.layer; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import mod.azure.azurelib.rewrite.model.AzBone; +import mod.azure.azurelib.rewrite.render.AzRendererPipeline; +import mod.azure.azurelib.rewrite.render.AzRendererPipelineContext; +import net.minecraft.client.renderer.MultiBufferSource; + +/** + * Render layer base class for rendering additional layers of effects or textures over an existing model at runtime.
    + * Contains the base boilerplate and helper code for various render layer features + */ +public interface AzRenderLayer { + + /** + * This method is called by the {@link AzRendererPipeline} before rendering, immediately after + * {@link AzRendererPipeline#preRender} has been called.
    + * This allows for RenderLayers to perform pre-render manipulations such as hiding or showing bones + */ + void preRender(AzRendererPipelineContext context); + + /** + * This is the method that is actually called by the render for your render layer to function.
    + * This is called after the animatable has been rendered, but before supplementary rendering like nametags. + */ + void render(AzRendererPipelineContext context); + + /** + * This method is called by the {@link AzRendererPipeline} for each bone being rendered.
    + * This is a more expensive call, particularly if being used to render something on a different buffer.
    + * It does however have the benefit of having the matrix translations and other transformations already applied from + * render-time.
    + * It's recommended to avoid using this unless necessary.
    + *
    + * The {@link AzBone} in question has already been rendered by this stage.
    + *
    + * If you do use it, and you render something that changes the {@link VertexConsumer buffer}, you need to + * reset it back to the previous buffer using {@link MultiBufferSource#getBuffer} before ending the method + */ + void renderForBone(AzRendererPipelineContext context, AzBone bone); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/util/MoveAnalysis.java b/common/src/main/java/mod/azure/azurelib/rewrite/util/MoveAnalysis.java new file mode 100644 index 000000000..0962385c1 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/util/MoveAnalysis.java @@ -0,0 +1,123 @@ +package mod.azure.azurelib.rewrite.util; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; + +/** + * A utility class for analyzing the movement of an {@link Entity} in the game world. + *

    + * This class tracks the movement of an entity by calculating its position deltas + * over time. It provides methods to check if the entity is moving horizontally, + * vertically, or overall in any direction. + *

    + */ +public class MoveAnalysis { + + /** + * The {@link Entity} whose movement is being analyzed. + */ + private final Entity entity; + + /** + * The tick count of the last update. Used to avoid redundant updates. + */ + private int lastTick; + + /** + * The position of the entity at the last update. + */ + private Vec3 lastPosition; + + /** + * The change in the entity’s X-coordinate since the last update. + */ + private double deltaX; + + /** + * The change in the entity’s Y-coordinate since the last update. + */ + private double deltaY; + + /** + * The change in the entity’s Z-coordinate since the last update. + */ + private double deltaZ; + + /** + * Constructs a new {@code MoveAnalysis} instance for the specified entity. + * + * @param entity The {@link Entity} to analyze. + */ + public MoveAnalysis(Entity entity) { + this.entity = entity; + this.lastPosition = entity.position(); + } + + /** + * Updates the movement analysis with the entity's current position. + *

    + * This method calculates the change in position (delta) for each axis (X, Y, Z) + * since the last update. The update only occurs if the entity's tick count has + * increased since the last recorded tick. + *

    + */ + public void update() { + if (entity.tickCount == lastTick) { + // Only update on tick differences. + return; + } + + var prevPos = lastPosition; + var prevPosX = prevPos.x; + var prevPosY = prevPos.y; + var prevPosZ = prevPos.z; + + var pos = entity.position(); + var posX = pos.x; + var posY = pos.y; + var posZ = pos.z; + + this.deltaX = posX - prevPosX; + this.deltaY = posY - prevPosY; + this.deltaZ = posZ - prevPosZ; + + this.lastPosition = entity.position(); + this.lastTick = entity.tickCount; + } + + /** + * Checks if the entity is moving horizontally. + *

    + * Horizontal movement is defined as any change in the X or Z coordinates. + *

    + * + * @return {@code true} if the entity is moving horizontally; {@code false} otherwise. + */ + public boolean isMovingHorizontally() { + return deltaX != 0 || deltaZ != 0; + } + + /** + * Checks if the entity is moving vertically. + *

    + * Vertical movement is defined as any change in the Y coordinate. + *

    + * + * @return {@code true} if the entity is moving vertically; {@code false} otherwise. + */ + public boolean isMovingVertically() { + return deltaY != 0; + } + + /** + * Checks if the entity is moving in any direction. + *

    + * This includes both horizontal and vertical movement. + *

    + * + * @return {@code true} if the entity is moving; {@code false} otherwise. + */ + public boolean isMoving() { + return isMovingHorizontally() || isMovingVertically(); + } +} \ No newline at end of file diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/util/codec/AzListStreamCodec.java b/common/src/main/java/mod/azure/azurelib/rewrite/util/codec/AzListStreamCodec.java new file mode 100644 index 000000000..b92234216 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/util/codec/AzListStreamCodec.java @@ -0,0 +1,37 @@ +package mod.azure.azurelib.rewrite.util.codec; + +import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class AzListStreamCodec { + + private final Function decoder; // Function to decode an element from the buffer + private final BiConsumer encoder; // BiConsumer to encode an element into the buffer + + public AzListStreamCodec(Function decoder, BiConsumer encoder) { + this.decoder = decoder; + this.encoder = encoder; + } + + public @NotNull List decode(FriendlyByteBuf buf) { + int size = buf.readByte(); // Read the size of the list + List list = new ArrayList<>(size); + + for (int i = 0; i < size; i++) { + list.add(decoder.apply(buf)); // Decode each element using the provided decoder + } + + return list; + } + + public void encode(FriendlyByteBuf buf, List elements) { + buf.writeByte(elements.size()); // Write the size of the list + elements.forEach(element -> encoder.accept(buf, element)); // Encode each element using the provided encoder + } + +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/util/state/State.java b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/State.java new file mode 100644 index 000000000..14a197244 --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/State.java @@ -0,0 +1,21 @@ +package mod.azure.azurelib.rewrite.util.state; + +/** + * Represents a state within a state machine. A state defines the behavior that occurs when entering, updating, and + * exiting the state. The lifecycle of a state consists of three main methods:
    + *
      + *
    1. onEnter - Triggered when the state is entered, providing an opportunity to perform initialization.
    2. + *
    3. onUpdate - Called during state execution, typically to update or process logic related to the state.
    4. + *
    5. onExit - Triggered when transitioning out of the state, used for cleanup or finalization.
    6. + *
    + * + * @param the type of the context associated with the state, which must extend {@link StateMachineContext} + */ +public interface State { + + void onEnter(C context); + + void onUpdate(C context); + + void onExit(C context); +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachine.java b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachine.java new file mode 100644 index 000000000..b88b8f0fa --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachine.java @@ -0,0 +1,40 @@ +package mod.azure.azurelib.rewrite.util.state; + +/** + * Represents a state machine that handles transitions between different states. A state machine maintains a current + * state and allows transitions to new states, invoking lifecycle methods on each state during transitions. + * + * @param the type of the context associated with the state machine, which extends {@link StateMachineContext} + * @param the type of the states managed by the state machine, which extends {@link State} + */ +public abstract class StateMachine> { + + private final C reusableContext; + + private T state; + + public StateMachine(T initialState) { + this.state = initialState; + this.reusableContext = createContext(); + } + + protected abstract C createContext(); + + public void update(C context) { + state.onUpdate(context); + } + + public C getContext() { + return reusableContext; + } + + public T getState() { + return state; + } + + public void setState(T newState) { + state.onExit(reusableContext); + this.state = newState; + newState.onEnter(reusableContext); + } +} diff --git a/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachineContext.java b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachineContext.java new file mode 100644 index 000000000..5d388151c --- /dev/null +++ b/common/src/main/java/mod/azure/azurelib/rewrite/util/state/StateMachineContext.java @@ -0,0 +1,14 @@ +package mod.azure.azurelib.rewrite.util.state; + +/** + * Represents the contract for a state machine context. A state machine context serves as an intermediary object that + * provides necessary data and functionality to state machine components or processes. It is generally used to + * encapsulate and manage shared resources, state-related details, and other dependencies required during the lifecycle + * of state transitions within a state machine.
    + * Implementations of this interface can define custom context-specific properties and methods, tailored to the + * requirements of a specific state machine.
    + * The context is typically associated with the {@link State} and {@link StateMachine} interfaces. Implementing classes + * provide relevant properties or methods that a state or state machine would require during its lifecycle operations + * (e.g., onEnter, onUpdate, onExit for states). + */ +public interface StateMachineContext {} diff --git a/common/src/main/java/mod/azure/azurelib/util/AzureLibUtil.java b/common/src/main/java/mod/azure/azurelib/util/AzureLibUtil.java index 7f96cab85..1a71fe10b 100644 --- a/common/src/main/java/mod/azure/azurelib/util/AzureLibUtil.java +++ b/common/src/main/java/mod/azure/azurelib/util/AzureLibUtil.java @@ -29,11 +29,17 @@ * Helper class for various AzureLib-specific functions. */ public final class AzureLibUtil { + + public static T self(Object object) { + return (T) object; + } + /** * Creates a new AnimatableInstanceCache for the given animatable object * * @param animatable The animatable object */ + @Deprecated(forRemoval = true) public static AnimatableInstanceCache createInstanceCache(GeoAnimatable animatable) { AnimatableInstanceCache cache = animatable.animatableCacheOverride(); @@ -47,6 +53,7 @@ public static AnimatableInstanceCache createInstanceCache(GeoAnimatable animatab * @param animatable The animatable object * @param singletonObject Whether the object is a singleton/flyweight object, and uses ints to differentiate animatable instances */ + @Deprecated(forRemoval = true) public static AnimatableInstanceCache createInstanceCache(GeoAnimatable animatable, boolean singletonObject) { AnimatableInstanceCache cache = animatable.animatableCacheOverride(); @@ -63,6 +70,7 @@ public static AnimatableInstanceCache createInstanceCache(GeoAnimatable animatab * @param name The name of the {@code LoopType} handler * @param loopType The {@code LoopType} implementation to use for the given name */ + @Deprecated(forRemoval = true) synchronized public static Animation.LoopType addCustomLoopType(String name, Animation.LoopType loopType) { return Animation.LoopType.register(name, loopType); } @@ -74,6 +82,7 @@ synchronized public static Animation.LoopType addCustomLoopType(String name, Ani * @param name The name of the {@code EasingType} handler * @param easingType The {@code EasingType} implementation to use for the given name */ + @Deprecated(forRemoval = true) synchronized public static EasingType addCustomEasingType(String name, EasingType easingType) { return EasingType.register(name, easingType); } @@ -85,6 +94,7 @@ synchronized public static EasingType addCustomEasingType(String name, EasingTyp * @param namespace The namespace (modid) to register the factory for * @param factory The factory responsible for model loading under the given namespace */ + @Deprecated(forRemoval = true) synchronized public static void addCustomBakedModelFactory(String namespace, BakedModelFactory factory) { BakedModelFactory.register(namespace, factory); } @@ -96,6 +106,7 @@ synchronized public static void addCustomBakedModelFactory(String namespace, Bak * @param dataTicket The SerializableDataTicket to register * @return The dataTicket you passed in */ + @Deprecated(forRemoval = true) synchronized public static SerializableDataTicket addDataTicket(SerializableDataTicket dataTicket) { return DataTickets.registerSerializable(dataTicket); } diff --git a/common/src/main/java/mod/azure/azurelib/util/IncompatibleModsCheck.java b/common/src/main/java/mod/azure/azurelib/util/IncompatibleModsCheck.java deleted file mode 100644 index 9125aa7fd..000000000 --- a/common/src/main/java/mod/azure/azurelib/util/IncompatibleModsCheck.java +++ /dev/null @@ -1,30 +0,0 @@ -package mod.azure.azurelib.util; - -import mod.azure.azurelib.AzureLib; -import mod.azure.azurelib.AzureLibMod; -import mod.azure.azurelib.client.screen.OptifineWarningScreen; -import net.minecraft.client.Minecraft; - -public class IncompatibleModsCheck { - public static boolean optifinePresent = false; - - public static void run() { - try { - Class.forName("net.optifine.Config"); - optifinePresent = true; - } catch (ClassNotFoundException e) { - optifinePresent = false; - } - } - - public static void warnings(Minecraft mc) { - if (IncompatibleModsCheck.optifinePresent) { - if (AzureLibMod.config.disableOptifineWarning) { - AzureLib.LOGGER.fatal("Optifine Has been detected, Disabled Warning Status: false"); - mc.setScreen(new OptifineWarningScreen()); - } else { - AzureLib.LOGGER.fatal("Optifine Has been detected, Disabled Warning Status: true"); - } - } - } -} diff --git a/common/src/main/java/mod/azure/azurelib/util/JsonUtil.java b/common/src/main/java/mod/azure/azurelib/util/JsonUtil.java index 0cbd28e77..a05ea26cc 100644 --- a/common/src/main/java/mod/azure/azurelib/util/JsonUtil.java +++ b/common/src/main/java/mod/azure/azurelib/util/JsonUtil.java @@ -12,6 +12,10 @@ import java.util.Map; import java.util.function.Function; +import mod.azure.azurelib.rewrite.animation.parse.AzBakedAnimationsAdapter; +import mod.azure.azurelib.rewrite.animation.parse.AzKeyframesAdapter; +import mod.azure.azurelib.rewrite.animation.primitive.AzBakedAnimations; +import mod.azure.azurelib.rewrite.animation.primitive.AzKeyframes; import org.jetbrains.annotations.Nullable; import com.google.gson.Gson; @@ -63,6 +67,8 @@ public final class JsonUtil { .registerTypeAdapter(UVUnion.class, UVUnion.deserializer()) .registerTypeAdapter(Animation.Keyframes.class, new KeyFramesAdapter()) .registerTypeAdapter(BakedAnimations.class, new BakedAnimationsAdapter()) + .registerTypeAdapter(AzKeyframes.class, new AzKeyframesAdapter()) + .registerTypeAdapter(AzBakedAnimations.class, new AzBakedAnimationsAdapter()) .create(); /** diff --git a/common/src/main/java/mod/azure/azurelib/util/RenderUtils.java b/common/src/main/java/mod/azure/azurelib/util/RenderUtils.java index eba3f5df4..fba17c370 100644 --- a/common/src/main/java/mod/azure/azurelib/util/RenderUtils.java +++ b/common/src/main/java/mod/azure/azurelib/util/RenderUtils.java @@ -42,6 +42,14 @@ * Helper class for various methods and functions useful while rendering */ public final class RenderUtils { + private static final Matrix4f TRANSLATE_MATRIX_CACHE = new Matrix4f(); + + private static final Quaternionf X_QUATERNION_CACHE = new Quaternionf(); + + private static final Quaternionf Y_QUATERNION_CACHE = new Quaternionf(); + + private static final Quaternionf Z_QUATERNION_CACHE = new Quaternionf(); + public static void translateMatrixToBone(PoseStack poseStack, CoreGeoBone bone) { poseStack.translate(-bone.getPosX() / 16f, bone.getPosY() / 16f, bone.getPosZ() / 16f); } @@ -60,9 +68,9 @@ public static void rotateMatrixAroundBone(PoseStack poseStack, CoreGeoBone bone) public static void rotateMatrixAroundCube(PoseStack poseStack, GeoCube cube) { Vec3 rotation = cube.rotation(); - poseStack.mulPose(new Quaternionf().rotationXYZ(0, 0, (float) rotation.z())); - poseStack.mulPose(new Quaternionf().rotationXYZ(0, (float) rotation.y(), 0)); - poseStack.mulPose(new Quaternionf().rotationXYZ((float) rotation.x(), 0, 0)); + poseStack.mulPose(Z_QUATERNION_CACHE.rotationXYZ(0, 0, (float) rotation.z())); + poseStack.mulPose(Y_QUATERNION_CACHE.rotationXYZ(0, (float) rotation.y(), 0)); + poseStack.mulPose(X_QUATERNION_CACHE.rotationXYZ((float) rotation.x(), 0, 0)); } public static void scaleMatrixForBone(PoseStack poseStack, CoreGeoBone bone) { @@ -123,7 +131,8 @@ public static void faceRotation(PoseStack poseStack, Entity animatable, float pa * Add a positional vector to a matrix. This is specifically implemented to act as a translation of an x/y/z coordinate triplet to a render matrix */ public static Matrix4f translateMatrix(Matrix4f matrix, Vector3f vector) { - return matrix.add(new Matrix4f().m30(vector.x).m31(vector.y).m32(vector.z)); + TRANSLATE_MATRIX_CACHE.m30(vector.x).m31(vector.y).m32(vector.z); + return matrix.add(TRANSLATE_MATRIX_CACHE); } /** @@ -168,7 +177,10 @@ public static double getCurrentSystemTick() { } /** - * Returns the current time (in ticks) that the {@link org.lwjgl.glfw.GLFW GLFW} instance has been running. This is effectively a permanent timer that counts up since the game was launched. + * Calculates and retrieves the current game tick. + * The value is determined by multiplying the current rendering time by 20. + * + * @return The current tick as a double value. */ public static double getCurrentTick() { return Blaze3D.getTime() * 20d; @@ -237,6 +249,7 @@ public static float getDirectionAngle(Direction direction) { * @return The GeoModel, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoModel getGeoModelForEntityType(EntityType entityType) { EntityRenderer renderer = Minecraft.getInstance().getEntityRenderDispatcher().renderers.get(entityType); @@ -250,6 +263,7 @@ public static GeoModel getGeoModelForEntityType(EntityType entityType) { * @return The {@code GeoAnimatable} instance, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoAnimatable getReplacedAnimatable(EntityType entityType) { EntityRenderer renderer = Minecraft.getInstance().getEntityRenderDispatcher().renderers.get(entityType); @@ -265,6 +279,7 @@ public static GeoAnimatable getReplacedAnimatable(EntityType entityType) { * @return The GeoModel, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoModel getGeoModelForEntity(Entity entity) { EntityRenderer renderer = Minecraft.getInstance().getEntityRenderDispatcher().getRenderer(entity); @@ -280,6 +295,7 @@ public static GeoModel getGeoModelForEntity(Entity entity) { * @return The GeoModel, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoModel getGeoModelForItem(Item item) { if (RenderProvider.of(item).getCustomRenderer()instanceof GeoRenderer geoRenderer) return geoRenderer.getGeoModel(); @@ -296,6 +312,7 @@ public static GeoModel getGeoModelForItem(Item item) { * @return The GeoModel, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoModel getGeoModelForBlock(BlockEntity blockEntity) { BlockEntityRenderer renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(blockEntity); @@ -311,6 +328,7 @@ public static GeoModel getGeoModelForBlock(BlockEntity blockEntity) { * @return The GeoModel, or null if one isn't found */ @Nullable + @Deprecated(forRemoval = true) public static GeoModel getGeoModelForArmor(ItemStack stack) { if (RenderProvider.of(stack).getHumanoidArmorModel(null, stack, null, null)instanceof GeoArmorRenderer armorRenderer) return armorRenderer.getGeoModel(); diff --git a/common/src/main/resources/META-INF/accesstransformer.cfg b/common/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 000000000..726c9f3cb --- /dev/null +++ b/common/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,31 @@ +public net.minecraft.sounds.SoundEvent m_262856_(Lnet/minecraft/resources/ResourceLocation;F)Lnet/minecraft/sounds/SoundEvent; # createFixedRangeEvent +public net.minecraft.world.entity.Entity f_19857_ # yRot +public net.minecraft.world.entity.Entity f_19858_ # xRot +public net.minecraft.world.entity.Entity m_7939_()Lnet/minecraft/world/phys/Vec3; # getLeashOffset + +public net.minecraft.client.model.geom.ModelPart$Cube +public net.minecraft.client.model.geom.ModelPart f_104212_ # cubes +public net.minecraft.client.renderer.LevelRenderer m_109817_()Z # shouldShowEntityOutlines +public net.minecraft.client.renderer.entity.EntityRenderDispatcher f_114362_ # renderers +public net.minecraft.client.renderer.RenderStateShard$ShaderStateShard +public net.minecraft.client.renderer.RenderStateShard$TextureStateShard +public net.minecraft.client.renderer.RenderStateShard$TransparencyStateShard +public net.minecraft.client.renderer.RenderStateShard$WriteMaskStateShard +public net.minecraft.client.renderer.RenderType$CompositeState + +public net.minecraft.world.entity.WalkAnimationState f_267406_ # speedOld + +public net.minecraft.client.renderer.RenderType m_173209_(Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;ILnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType; # create +public net.minecraft.client.renderer.RenderType m_173215_(Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;IZZLnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType; # create + +public net.minecraft.client.model.AgeableListModel f_102007_ # scaleHead +public net.minecraft.client.model.AgeableListModel f_170338_ # babyYHeadOffset +public net.minecraft.client.model.AgeableListModel f_170339_ # babyZHeadOffset +public net.minecraft.client.model.AgeableListModel f_102010_ # babyHeadScale +public net.minecraft.client.model.AgeableListModel f_102011_ # babyBodyScale +public net.minecraft.client.model.AgeableListModel f_102012_ # bodyYOffset +public net.minecraft.client.model.PlayerModel f_103379_ # ear + +public-f net.minecraft.client.renderer.LevelRenderer f_109464_ # renderBuffers +public-f net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer m_289609_(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/item/ArmorItem;Lnet/minecraft/client/model/HumanoidModel;ZFFFLjava/lang/String;)V # renderModel +public com.mojang.blaze3d.vertex.BufferBuilder f_85661_ # building \ No newline at end of file diff --git a/common/src/main/resources/assets/azurelib/lang/es_es.json b/common/src/main/resources/assets/azurelib/lang/es_es.json new file mode 100644 index 000000000..d7d3d9ef2 --- /dev/null +++ b/common/src/main/resources/assets/azurelib/lang/es_es.json @@ -0,0 +1,54 @@ +{ + "category.azurelib.binds": "Atajos de AzureLib", + "key.azurelib.reload": "Recargar Arma", + "key.azurelib.scope": "Apuntar con el Arma", + "key.azurelib.fire": "Disparo Alternativo del Arma", + "text.azurelib.value.true": "Verdadero", + "text.azurelib.value.false": "Falso", + "text.azurelib.value.edit": "Editar", + "text.azurelib.value.back": "Atrás", + "text.azurelib.value.revert.default": "Usar configuración predeterminada", + "text.azurelib.value.revert.default.dialog": "¿Estás seguro de que quieres restablecer la configuración a los valores predeterminados?", + "text.azurelib.value.revert.changes": "Deshacer cambios", + "text.azurelib.value.revert.changes.dialog": "¿Estás seguro de que quieres descartar los cambios que has realizado?", + "text.azurelib.value.add_element": "Añadir elemento", + "text.azurelib.screen.select_config": "Seleccionar configuración", + "text.azurelib.screen.dialog.confirm": "Confirmar", + "text.azurelib.screen.dialog.cancel": "Cancelar", + "text.azurelib.screen.color_dialog": "Seleccionar color", + "text.azurelib.screen.color.red": "Rojo: %s", + "text.azurelib.screen.color.green": "Verde: %s", + "text.azurelib.screen.color.blue": "Azul: %s", + "text.azurelib.screen.color.alpha": "Alfa: %s", + "text.azurelib.error.character_value_empty": "¡El campo de carácter no puede estar vacío!", + "text.azurelib.error.nan": "¡El valor '%s' no es un número válido!", + "text.azurelib.error.num_bounds": "¡El valor '%s' no está dentro del intervalo <%s;%s>!", + "text.azurelib.error.pattern_mismatch": "¡El valor '%s' no coincide con el patrón \\%s\\!", + "config.screen.azurelib": "Configuración de AzureLib", + "config.azurelib.option.useVanillaUseKey": "Activar si las armas AzureDooms usan la tecla de uso de Vanilla o personalizada", + "config.azurelib.option.bool": "Boolean de Prueba", + "config.azurelib.option.number": "Número de Prueba", + "config.azurelib.option.longNumber": "Número Long de Prueba", + "config.azurelib.option.floatNumber": "Número Float de Prueba", + "config.azurelib.option.doubleNumber": "Número Double de Prueba", + "config.azurelib.option.string": "String de Prueba", + "config.azurelib.option.color": "Color de Prueba", + "config.azurelib.option.color2": "Color ARGB de Prueba", + "config.azurelib.option.boolArray": "Array Booleanos de Prueba", + "config.azurelib.option.intArray": "Array de Enteros de Prueba", + "config.azurelib.option.longArray": "Array de Longs de Prueba", + "config.azurelib.option.floatArray": "Array de Floats de Prueba", + "config.azurelib.option.stringArray": "Array de Strings de Prueba", + "config.azurelib.option.testEnum": "Enum de Prueba", + "config.azurelib.option.nestedTest": "Anidado de Prueba", + "config.azurelib.option.testInt": "Entero de Prueba", + "config.azurelib.option.genericwarning": "Advertencia Genérica", + "config.azurelib.option.disableOptifineWarning": "Desactivar Advertencia de Optfine", + "header.azurelib.optifine": "Advertencia: Optifine presente en el cliente del juego", + "message.azurelib.optifine": "Advertencia: Optifine no es compatible con la mayoría de los mods, y puede causar desde pequeños artefactos visuales hasta cuelgues del juego. Se recomienda eliminar el mod para evitar estos problemas y otros. Esta advertencia se puede desactivar en la configuración de AzureLibs, sin embargo, usted acepta la responsabilidad por cualquier error o problema que encuentre. Si necesitabas Optifine por alguna razón, consulta la lista de alternativas de Optifine de PrismLauncher:", + "label.azurelib.open_mods_folder": "Abrir Carpeta de Mods", + "label.azurelib.optifine_alternatives": "Alternativas a Optifine", + "label.azurelib.proceed_anyway": "Proceder de todos modos", + "enchantment.azurelib.incendiaryenchantment": "Accesorio Incendiario", + "enchantment.azurelib.incendiaryenchantment.desc": "Permite que las balas disparadas prendan fuego a las criaturas" +} \ No newline at end of file diff --git a/common/src/main/resources/assets/azurelib/lang/fr_fr.json b/common/src/main/resources/assets/azurelib/lang/fr_fr.json index 776f76956..a4b536393 100644 --- a/common/src/main/resources/assets/azurelib/lang/fr_fr.json +++ b/common/src/main/resources/assets/azurelib/lang/fr_fr.json @@ -1,4 +1,25 @@ { "category.azurelib.binds": "Liaisons AzureLib", - "key.azurelib.reload": "Recharger le pistolet/l'arme" + "key.azurelib.reload": "Recharger le pistolet/l'arme", + "text.azurelib.value.true": "Oui", + "text.azurelib.value.false": "Non", + "text.azurelib.value.edit": "Modifier", + "text.azurelib.value.back": "Retour", + "text.azurelib.value.revert.default": "Utiliser la configuration par défaut", + "text.azurelib.value.revert.default.dialog": "Êtes-vous sûr de vouloir réinitialiser la configuration avec les valeurs par défaut ?", + "text.azurelib.value.revert.changes": "Annuler les modifications", + "text.azurelib.value.revert.changes.dialog": "Êtes-vous sûr de vouloir abandonner les modifications que vous avez apportées ?", + "text.azurelib.value.add_element": "Ajouter un élément", + "text.azurelib.screen.select_config": "Sélectionner une configuration", + "text.azurelib.screen.dialog.confirm": "Confirmer", + "text.azurelib.screen.dialog.cancel": "Annuler", + "text.azurelib.screen.color_dialog": "Sélectionner une couleur", + "text.azurelib.screen.color.red": "Rouge : %s", + "text.azurelib.screen.color.green": "Vert : %s", + "text.azurelib.screen.color.blue": "Bleu : %s", + "text.azurelib.screen.color.alpha": "Alpha : %s", + "text.azurelib.error.character_value_empty": "Le champ de caractère ne peut pas être vide !", + "text.azurelib.error.nan": "La valeur '%s' n'est pas un nombre valide !", + "text.azurelib.error.num_bounds": "La valeur '%s' n'est pas comprise dans l'intervalle <%s;%s> !", + "text.azurelib.error.pattern_mismatch": "La valeur '%s' ne correspond pas au motif \\%s\\ !" } diff --git a/common/src/main/resources/assets/azurelib/lang/ru_ru.json b/common/src/main/resources/assets/azurelib/lang/ru_ru.json new file mode 100644 index 000000000..8b5055a3c --- /dev/null +++ b/common/src/main/resources/assets/azurelib/lang/ru_ru.json @@ -0,0 +1,54 @@ +{ + "category.azurelib.binds": "Привязки клавиш AzureLib", + "key.azurelib.reload": "Перезарядить оружие", + "key.azurelib.scope": "Прицелиться оружием", + "key.azurelib.fire": "Альтернативный огонь из оружия", + "text.azurelib.value.true": "Истина", + "text.azurelib.value.false": "Ложь", + "text.azurelib.value.edit": "Редактировать", + "text.azurelib.value.back": "Назад", + "text.azurelib.value.revert.default": "Использовать конфигурацию по умолчанию", + "text.azurelib.value.revert.default.dialog": "Вы уверены, что хотите вернуть настройки к значениям по умолчанию?", + "text.azurelib.value.revert.changes": "Откатить изменения", + "text.azurelib.value.revert.changes.dialog": "Вы уверены, что хотите отменить внесённые в данный момент изменения?", + "text.azurelib.value.add_element": "Добавить элемент", + "text.azurelib.screen.select_config": "Выбрать конфигурацию", + "text.azurelib.screen.dialog.confirm": "Подтвердить", + "text.azurelib.screen.dialog.cancel": "Отменить", + "text.azurelib.screen.color_dialog": "Выбрать цвет", + "text.azurelib.screen.color.red": "Красный: %s", + "text.azurelib.screen.color.green": "Зелёный: %s", + "text.azurelib.screen.color.blue": "Синий: %s", + "text.azurelib.screen.color.alpha": "Прозрачность: %s", + "text.azurelib.error.character_value_empty": "Поле символов не может быть пустым!", + "text.azurelib.error.nan": "Значение '%s' не является допустимым числом!", + "text.azurelib.error.num_bounds": "Значение '%s' не находится в интервале <%s;%s>!", + "text.azurelib.error.pattern_mismatch": "Значение '%s' не соответствует паттерну \\%s\\!", + "config.screen.azurelib": "Настройки AzureLib", + "config.azurelib.option.disableOptifineWarning": "Отключить экран предупреждения Optifine", + "config.azurelib.option.useVanillaUseKey": "Переключить, будут ли пушки AzureDooms использовать ванильные настрйоки клавиатуры или пользовательские", + "config.azurelib.option.bool": "Тесировать логическое значение", + "config.azurelib.option.number": "Тестировать число", + "config.azurelib.option.longNumber": "Тестировать длинное число", + "config.azurelib.option.floatNumber": "Тестировать число с плавающей запятой", + "config.azurelib.option.doubleNumber": "Тестировать вещественное число", + "config.azurelib.option.string": "Тестировать строку", + "config.azurelib.option.color": "Тестировать цвет", + "config.azurelib.option.color2": "Тестировать цвет ARGB", + "config.azurelib.option.boolArray": "Тестировать массив логических значений", + "config.azurelib.option.intArray": "Тестировать массив целочисленных", + "config.azurelib.option.longArray": "Тестировать массив длинных целочисленных", + "config.azurelib.option.floatArray": "Тестировать массив чисел с плавающей запятой", + "config.azurelib.option.stringArray": "Тестировать массив строк", + "config.azurelib.option.testEnum": "Тестировать перечислимый тип", + "config.azurelib.option.nestedTest": "Тестировать вложенный", + "config.azurelib.option.testInt": "Тестировать целое число", + "config.azurelib.option.genericwarning": "Общее предупреждение", + "header.azurelib.optifine": "Внимание: Optifine присутствует в игровом клиенте", + "message.azurelib.optifine": "Внимание: Optifine не поддерживается большинством модов, начиная от незначительных визуальных искажений и заканчивая сбоями в игре. Рекомендуется удалить мод, чтобы избежать этих и других проблем. Это предупреждение можно отключить в настройках AzureLibs, однако вы принимаете на себя ответственность за любые ошибки или неполадки, с которыми вы столкнётесь. Если вам по той или иной причине понадобился Optifine, ознакомьтесь со списком альтернатив Optifine в PrismLauncher:", + "label.azurelib.open_mods_folder": "Открыть папку модов", + "label.azurelib.optifine_alternatives": "Альтернативы Optifine", + "label.azurelib.proceed_anyway": "Продолить в любом случае", + "enchantment.azurelib.incendiaryenchantment": "Зажигательная привязанность", + "enchantment.azurelib.incendiaryenchantment.desc": "Позволяет стрелять пулями, поджигающими мобов" +} diff --git a/common/src/main/resources/assets/azurelib/lang/zh_cn.json b/common/src/main/resources/assets/azurelib/lang/zh_cn.json new file mode 100644 index 000000000..4c1638105 --- /dev/null +++ b/common/src/main/resources/assets/azurelib/lang/zh_cn.json @@ -0,0 +1,55 @@ +{ + "category.azurelib.binds": "AzureLib 绑定", + "key.azurelib.reload": "重新装填枪械/武器", + "key.azurelib.scope": "武器瞄准", + "key.azurelib.fire": "武器替代射击", + "text.azurelib.value.true": "真", + "text.azurelib.value.false": "假", + "text.azurelib.value.edit": "编辑", + "text.azurelib.value.back": "返回", + "text.azurelib.value.revert.default": "使用默认配置", + "text.azurelib.value.revert.default.dialog": "你确定要将配置重置为默认值吗?", + "text.azurelib.value.revert.changes": "回滚更改", + "text.azurelib.value.revert.changes.dialog": "你确定要放弃你当前所做的更改吗?", + "text.azurelib.value.add_element": "添加元素", + "text.azurelib.screen.select_config": "选择配置", + "text.azurelib.screen.dialog.confirm": "确认", + "text.azurelib.screen.dialog.cancel": "取消", + "text.azurelib.screen.color_dialog": "选择颜色", + "text.azurelib.screen.color.red": "红色: %s", + "text.azurelib.screen.color.green": "绿色: %s", + "text.azurelib.screen.color.blue": "蓝色: %s", + "text.azurelib.screen.color.alpha": "透明度: %s", + "text.azurelib.error.character_value_empty": "字符字段不能为空!", + "text.azurelib.error.nan": "值 '%s' 不是一个有效的数字!", + "text.azurelib.error.num_bounds": "值 '%s' 不在区间 <%s;%s> 内!", + "text.azurelib.error.pattern_mismatch": "值 '%s' 与模式 \\%s\\ 不匹配!", + "config.screen.azurelib": "AzureLib 配置", + "config.azurelib.option.disableOptifineWarning": "禁用 Optfine 警告", + "config.azurelib.option.useVanillaUseKey": "切换 AzureDooms 枪械是否使用 Vanilla Use 键或自定义", + "config.azurelib.option.bool": "测试布尔值", + "config.azurelib.option.number": "测试数字", + "config.azurelib.option.longNumber": "测试长数字", + "config.azurelib.option.floatNumber": "测试浮点数", + "config.azurelib.option.doubleNumber": "测试双精度数", + "config.azurelib.option.string": "测试字符串", + "config.azurelib.option.color": "测试颜色", + "config.azurelib.option.color2": "测试颜色 ARGB", + "config.azurelib.option.boolArray": "测试布尔数组", + "config.azurelib.option.intArray": "测试整数数组", + "config.azurelib.option.longArray": "测试长数组", + "config.azurelib.option.floatArray": "测试浮点数组", + "config.azurelib.option.stringArray": "测试字符串数组", + "config.azurelib.option.testEnum": "测试枚举", + "config.azurelib.option.nestedTest": "测试嵌套", + "config.azurelib.option.testInt": "测试整数", + "config.azurelib.option.genericwarning": "通用警告", + "header.azurelib.optifine": "警告: 游戏客户端存在 Optifine", + "message.azurelib.optifine": "警告: Optifine 未被大多数模组支持,可能会导致从轻微视觉错误到游戏崩溃等问题。建议移除该模组以避免这些问题及其他问题。此警告可在 AzureLibs 配置中禁用,但你需对遇到的任何错误或问题负责。如果你因某种原因需要 Optifine,可查看 PrismLauncher 的 Optifine 替代列表:", + "label.azurelib.open_mods_folder": "打开模组文件夹", + "label.azurelib.optifine_alternatives": "Optifine 替代品", + "label.azurelib.proceed_anyway": "仍然继续", + "enchantment.azurelib.incendiaryenchantment": "燃烧附件", + "enchantment.azurelib.incendiaryenchantment.desc": "使发射的子弹点燃目标" + } + diff --git a/common/src/main/resources/azurelib.aw b/common/src/main/resources/azurelib.aw index 2d6102d7a..42c869fb5 100644 --- a/common/src/main/resources/azurelib.aw +++ b/common/src/main/resources/azurelib.aw @@ -4,6 +4,7 @@ accessible field net/minecraft/world/entity/Entity yRot F accessible field net/minecraft/world/entity/Entity xRot F accessible method net/minecraft/world/entity/Entity getLeashOffset ()Lnet/minecraft/world/phys/Vec3; +accessible class net/minecraft/client/model/geom/ModelPart$Cube accessible field net/minecraft/client/model/geom/ModelPart cubes Ljava/util/List; accessible method net/minecraft/client/renderer/LevelRenderer shouldShowEntityOutlines ()Z accessible field net/minecraft/client/renderer/entity/EntityRenderDispatcher renderers Ljava/util/Map; @@ -24,6 +25,7 @@ accessible field net/minecraft/client/model/AgeableListModel babyZHeadOffset F accessible field net/minecraft/client/model/AgeableListModel babyHeadScale F accessible field net/minecraft/client/model/AgeableListModel babyBodyScale F accessible field net/minecraft/client/model/AgeableListModel bodyYOffset F +accessible field net/minecraft/client/model/PlayerModel ear Lnet/minecraft/client/model/geom/ModelPart; accessible field net/minecraft/client/renderer/LevelRenderer renderBuffers Lnet/minecraft/client/renderer/RenderBuffers; mutable field net/minecraft/client/renderer/LevelRenderer renderBuffers Lnet/minecraft/client/renderer/RenderBuffers; diff --git a/eclipse-formatter.xml b/eclipse-formatter.xml new file mode 100644 index 000000000..6bc8eedd2 --- /dev/null +++ b/eclipse-formatter.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fabric/build.gradle b/fabric/build.gradle index 3e566fe11..f733dcafd 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,39 +1,20 @@ -import groovy.json.JsonOutput -import groovy.json.JsonSlurper - plugins { - id 'java' - id 'idea' - id 'maven-publish' + id 'multiloader-loader' id 'fabric-loom' - id "me.modmuss50.mod-publish-plugin" version "0.4.0" -} - -base { - archivesName = "${mod_id}-fabric-${minecraft_version}" -} - -repositories { - mavenCentral() - maven { url 'https://dl.cloudsmith.io/public/azuredoom-mods/azurelib/maven/' } - maven { url 'https://maven.blamejared.com' } - maven { url 'https://api.modrinth.com/maven' } - maven { url "https://cfa2.cursemaven.com" } - maven { url "https://maven.cloudsmith.io/azuredoom-mods/azurelib/" } - maven { url "https://plugins.gradle.org/m2/" } - maven { url "https://maven.shedaniel.me/" } - maven { url "https://maven.terraformersmc.com/" } - maven { url "https://maven.terraformersmc.com/releases" } // modmenu + id 'me.modmuss50.mod-publish-plugin' + id 'com.diffplug.spotless' version "7.0.0.BETA3" } dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" - mappings loom.officialMojangMappings() + mappings loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${parchment_minecraft}:${parchment_version}@zip") + } modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" - compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1' modApi "com.terraformersmc:modmenu:${modmenu_version}" - compileOnly project(":common") + include(implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:0.4.1"))) } loom { @@ -59,77 +40,29 @@ loom { } } -tasks.withType(JavaCompile).configureEach { - source(project(":common").sourceSets.main.allSource) -} - -tasks.withType(Javadoc).configureEach { - source(project(":common").sourceSets.main.allJava) -} - -tasks.named("sourcesJar", Jar) { - from(project(":common").sourceSets.main.allSource) -} - -processResources { - from project(":common").sourceSets.main.resources -} - -// TODO: Everything below here might be fine to move to the top-level build.gradle file. -// They were originally in the previous fabric build.gradle. - bvanseg - -tasks.withType(JavaCompile).configureEach { - it.options.release = 17 -} - -jar { - from "LICENSE" -} - -publishing { - repositories { - maven { - name = "azurelib" - url = "https://maven.azuredoom.com/mods" - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } - } - } - publications { - maven(MavenPublication) { - artifactId base.archivesName.get() - from components.java - pom.withXml { - asNode().dependencies.dependency.each { dep -> - if (dep.groupId.text() == 'com.terraformersmc' && dep.artifactId.text() == 'modmenu') { - dep.parent().remove(dep) - } - } - } - } - } -} - -// Disables Gradle's custom module metadata from being published to maven. The -// metadata includes mapped dependencies which are not reasonably consumable by -// other mod developers. -tasks.withType(GenerateModuleMetadata).configureEach { - enabled = false -} - -if (file('key.properties').exists()) { +def keyPropsFile = new File(rootProject.projectDir, "key.properties") +if (keyPropsFile.exists()) { publishMods { def releaseProp = new Properties() - File secretPropsFile = file("key.properties") - releaseProp.load(secretPropsFile.newInputStream()) + releaseProp.load(new FileInputStream(keyPropsFile)) file = remapJar.archiveFile - changelog = rootProject.file("changelog.txt").text + changelog = rootProject.file("changelog.md").text type = STABLE modLoaders.add("fabric") modLoaders.add("quilt") + discord { + webhookUrl = releaseProp.getProperty("discordKey") + dryRunWebhookUrl = releaseProp.getProperty("discordKey") + username = "AzureBot" + avatarUrl = "https://cdn.modrinth.com/data/7zlUOZvb/8b2cb16452c1d3bb33574519a6f5cf4ce86f13a3_96.webp" + content = changelog.map { "# A new version of AzureLib for Fabric has been released for 1.20.1 \n" + it} + setPlatforms(publishMods.platforms.curseforge, publishMods.platforms.modrinth) + style { + look = "MODERN" + thumbnailUrl = "https://cdn.modrinth.com/data/7zlUOZvb/8b2cb16452c1d3bb33574519a6f5cf4ce86f13a3_96.webp" + } + } curseforge { projectId = "817423" projectSlug = "azurelib" diff --git a/fabric/changelog.txt b/fabric/changelog.txt deleted file mode 100644 index 6e4e66b86..000000000 --- a/fabric/changelog.txt +++ /dev/null @@ -1,5 +0,0 @@ -v1.0.33 - -- Updated Fabric name -- Add Modmenu badge -- Tweak MixinHumanoidArmorLayer \ No newline at end of file diff --git a/fabric/src/main/java/mod/azure/azurelib/ClientListener.java b/fabric/src/main/java/mod/azure/azurelib/ClientListener.java index d55644d2f..fe89adfbd 100644 --- a/fabric/src/main/java/mod/azure/azurelib/ClientListener.java +++ b/fabric/src/main/java/mod/azure/azurelib/ClientListener.java @@ -3,9 +3,7 @@ import com.mojang.blaze3d.platform.InputConstants; import mod.azure.azurelib.network.Networking; import mod.azure.azurelib.platform.Services; -import mod.azure.azurelib.util.IncompatibleModsCheck; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.client.KeyMapping; import org.lwjgl.glfw.GLFW; @@ -14,7 +12,6 @@ public final class ClientListener implements ClientModInitializer { @Override public void onInitializeClient() { - ClientLifecycleEvents.CLIENT_STARTED.register(IncompatibleModsCheck::warnings); Keybindings.RELOAD = new KeyMapping("key.azurelib.reload", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_R, "category.azurelib.binds"); KeyBindingHelper.registerKeyBinding(Keybindings.RELOAD); diff --git a/fabric/src/main/java/mod/azure/azurelib/FabricAzureLibMod.java b/fabric/src/main/java/mod/azure/azurelib/FabricAzureLibMod.java index 995f7229f..6d51d7c0a 100644 --- a/fabric/src/main/java/mod/azure/azurelib/FabricAzureLibMod.java +++ b/fabric/src/main/java/mod/azure/azurelib/FabricAzureLibMod.java @@ -10,9 +10,10 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.*; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -27,10 +28,21 @@ public void onInitialize() { AzureLibMod.config = AzureLibMod.registerConfig(AzureLibConfig.class, ConfigFormats.json()).getConfigInstance(); AzureLib.initialize(); new FabricAzureLibNetwork(); + Registry.register(BuiltInRegistries.BLOCK, AzureLib.modResource("lightblock"), FabricAzureLibMod.TICKING_LIGHT_BLOCK); FabricAzureLibMod.TICKING_LIGHT_ENTITY = Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, AzureLib.MOD_ID + ":lightblock", FabricBlockEntityTypeBuilder.create(TickingLightEntity::new, FabricAzureLibMod.TICKING_LIGHT_BLOCK).build(null)); + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> ConfigIO.FILE_WATCH_MANAGER.stopService()); if (AzureLibMod.config.useIncendiaryEnchantment) Registry.register(BuiltInRegistries.ENCHANTMENT, AzureLib.modResource("incendiaryenchantment"), INCENDIARYENCHANTMENT); } + + private static EntityType mob(String id, EntityType.EntityFactory factory, float height, float width) { + final var type = FabricEntityTypeBuilder.create(MobCategory.MONSTER, factory).dimensions( + EntityDimensions.scalable(height, width)).fireImmune().trackedUpdateRate(1).trackRangeBlocks( + 90).build(); + Registry.register(BuiltInRegistries.ENTITY_TYPE, AzureLib.modResource(id), type); + + return type; + } } diff --git a/fabric/src/main/java/mod/azure/azurelib/platform/FabricAzureLibNetwork.java b/fabric/src/main/java/mod/azure/azurelib/platform/FabricAzureLibNetwork.java index a0871a9ed..7c9a78395 100644 --- a/fabric/src/main/java/mod/azure/azurelib/platform/FabricAzureLibNetwork.java +++ b/fabric/src/main/java/mod/azure/azurelib/platform/FabricAzureLibNetwork.java @@ -3,13 +3,7 @@ import mod.azure.azurelib.network.AbstractPacket; import mod.azure.azurelib.network.Networking; import mod.azure.azurelib.network.S2C_SendConfigData; -import mod.azure.azurelib.network.packet.AnimDataSyncPacket; -import mod.azure.azurelib.network.packet.AnimTriggerPacket; -import mod.azure.azurelib.network.packet.BlockEntityAnimDataSyncPacket; -import mod.azure.azurelib.network.packet.BlockEntityAnimTriggerPacket; -import mod.azure.azurelib.network.packet.EntityAnimDataSyncPacket; -import mod.azure.azurelib.network.packet.EntityAnimTriggerPacket; -import mod.azure.azurelib.network.packet.EntityPacketOnClient; +import mod.azure.azurelib.network.packet.*; import mod.azure.azurelib.platform.services.AzureLibNetwork; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; @@ -44,6 +38,16 @@ public void registerClientReceiverPackets() { ClientPlayNetworking.registerGlobalReceiver(BLOCK_ENTITY_ANIM_TRIGGER_SYNC_PACKET_ID, (client, $2, buf, $4) -> this.handlePacket(client, BlockEntityAnimTriggerPacket.receive(buf))); ClientPlayNetworking.registerGlobalReceiver(CUSTOM_ENTITY_ID, (client, handler, buf, responseSender) -> EntityPacketOnClient.onPacket(client, buf)); + + ClientPlayNetworking.registerGlobalReceiver(AZ_ENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID, + (client, $2, buf, $4) -> + this.handlePacket(client, AzEntityDispatchCommandPacket.receive(buf))); + ClientPlayNetworking.registerGlobalReceiver(AZ_ITEM_STACK_DISPATCH_COMMAND_SYNC_PACKET_ID, + (client, $2, buf, $4) -> + this.handlePacket(client, AzItemStackDispatchCommandPacket.receive(buf))); + ClientPlayNetworking.registerGlobalReceiver(AZ_BLOCKENTITY_DISPATCH_COMMAND_SYNC_PACKET_ID, + (client, $2, buf, $4) -> + this.handlePacket(client, AzBlockEntityDispatchCommandPacket.receive(buf))); } @Override diff --git a/fabric/src/main/resources/azurelib.fabric.mixins.json b/fabric/src/main/resources/azurelib.fabric.mixins.json index 99ce67cfd..161f22e08 100644 --- a/fabric/src/main/resources/azurelib.fabric.mixins.json +++ b/fabric/src/main/resources/azurelib.fabric.mixins.json @@ -2,14 +2,19 @@ "required": true, "package": "mod.azure.azurelib.mixins", "compatibilityLevel": "JAVA_17", + "refmap": "azurelib.refmap.json", "injectors": { "defaultRequire": 1 }, "mixins": [ + "AbstractContainerMenuMixin_AzItemIDFix", + "ItemStackMixin_AzItemStackIdentityRegistry", "PlayerListMixin" ], "client": [ - "AccessorWarningScreen", + "BlockEntityMixin_AzBlockEntityAnimatorCache", + "EntityMixin_AzEntityAnimatorCache", + "ItemStackMixin_AzItemAnimatorCache", "FabricMixinHumanoidArmorLayer", "ItemRendererAccessor", "MinecraftMixin", diff --git a/gradle.properties b/gradle.properties index 6364e5c1b..5b60ef749 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,39 +3,44 @@ # Every field you add must be added to the root build.gradle expandProps map. # Gradle -org.gradle.jvmargs = -Xmx3G -org.gradle.daemon = false +org.gradle.jvmargs = -Xmx3G +org.gradle.daemon = false # Project -group = mod.azure.azurelib +group = mod.azure.azurelib +modmuss50_mod_publish_version = 0.8.4 +parchment_minecraft = 1.20.1 +parchment_version = 2023.09.03 +java_version = 17 # Common -minecraft_version = 1.20.1 -archives_base_name = azurelib-1.20.1 +minecraft_version = 1.20.1 +archives_base_name = azurelib-1.20.1 # Forge -neo_version = 47.1.3 -loader_version_range = [47,) -neo_version_range = [47.1,) -minecraft_version_range = [1.20.1, 1.20.2) -mapping_channel = official -mapping_version = 1.20.1 +neo_version = 47.4.0 +loader_version_range = [47,) +neo_version_range = [47.1,) +minecraft_version_range = [1.20.1, 1.20.2) +mapping_channel = official +mapping_version = 1.20.1 # Fabric -fabric_version = 0.83.0+1.20.1 -fabric_loader_version = 0.14.21 -modmenu_version = 7.2.2 +fabric_version = 0.83.0+1.20.1 +fabric_loader_version = 0.14.25 +modmenu_version = 7.2.2 # Mod options -maven_group = mod.azure.azurelib -mod_name = AzureLib -mod_author = AzureDoom -version = 2.0.41 -mod_id = azurelib -mod_license = MIT -mod_description = Based off Geckolib but now just for my own needs. -mod_credits = AzureDoom, Gecko, Eliot, Chappie, DerToaster, Tslat, Bvanseg -mod_logo = azurelib.png -mod_url = https://modrinth.com/mod/azurelib -mod_issues = https://github.com/AzureDoom/AzureLib/issues -mod_sources = https://github.com/AzureDoom/AzureLib \ No newline at end of file +maven_group = mod.azure.azurelib +mod_name = AzureLib +mod_author = AzureDoom +version = 3.0.0 +mod_id = azurelib +maven_url = https://maven.azuredoom.com/mods +mod_license = MIT +mod_description = Based off Geckolib but now just for my own needs. +mod_credits = AzureDoom, Gecko, Eliot, Chappie, DerToaster, Tslat, Bvanseg +mod_logo = azurelib.png +mod_url = https://modrinth.com/mod/azurelib +mod_issues = https://github.com/AzureDoom/AzureLib/issues +mod_sources = https://github.com/AzureDoom/AzureLib \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c..94113f200 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/neo/build.gradle b/neo/build.gradle new file mode 100644 index 000000000..ca803d209 --- /dev/null +++ b/neo/build.gradle @@ -0,0 +1,99 @@ +plugins { + id 'multiloader-loader' + id 'net.neoforged.moddev.legacyforge' + id 'me.modmuss50.mod-publish-plugin' + id 'com.diffplug.spotless' version "7.0.0.BETA3" +} + +mixin { + add(sourceSets.main, "${mod_id}.refmap.json") + + config("${mod_id}.mixins.json") + config("${mod_id}.forge.mixins.json") +} + +legacyForge { + version = "${minecraft_version}-${neo_version}" + + def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') + if (at.exists()) { + accessTransformers.from(at.absolutePath) + } + parchment { + minecraftVersion = parchment_minecraft + mappingsVersion = parchment_version + } + runs { + client { + client() + } + data { + data() + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + server { + server() + } + } + + mods { + "${mod_id}" { + sourceSet sourceSets.main + } + } +} + +sourceSets.main.resources.srcDir 'src/generated/resources' + +dependencies { + annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT:processor") + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")) + jarJar(implementation("io.github.llamalad7:mixinextras-forge:0.4.1")) + // Testing a crash (Confirmed no crash with using MixinExtras!) + //modImplementation("maven.modrinth:easy-anvils:v8.0.2-1.20.1-Forge") + //modImplementation("maven.modrinth:puzzles-lib:v8.1.32-1.20.1-Forge") +} + +jar { + finalizedBy('reobfJar') + manifest.attributes([ + "MixinConfigs": "${mod_id}.mixins.json,${mod_id}.forge.mixins.json" + ]) +} + +def keyPropsFile = new File(rootProject.projectDir, "key.properties") +if (keyPropsFile.exists()) { + publishMods { + def releaseProp = new Properties() + releaseProp.load(new FileInputStream(keyPropsFile)) + file = jar.archiveFile + changelog = rootProject.file("changelog.md").text + type = STABLE + modLoaders.add("neoforge") + modLoaders.add("forge") + + discord { + webhookUrl = releaseProp.getProperty("discordKey") + dryRunWebhookUrl = releaseProp.getProperty("discordKey") + username = "AzureBot" + avatarUrl = "https://cdn.modrinth.com/data/7zlUOZvb/8b2cb16452c1d3bb33574519a6f5cf4ce86f13a3_96.webp" + content = changelog.map { "# A new version of AzureLib for Forge has been released for 1.20.1 \n" + it} + setPlatforms(publishMods.platforms.curseforge, publishMods.platforms.modrinth) + style { + look = "MODERN" + thumbnailUrl = "https://cdn.modrinth.com/data/7zlUOZvb/8b2cb16452c1d3bb33574519a6f5cf4ce86f13a3_96.webp" + } + } + curseforge { + projectId = "817423" + projectSlug = "azurelib" + accessToken = releaseProp.getProperty("curseKey") + minecraftVersions.add(project.minecraft_version) + } + modrinth { + projectId = "7zlUOZvb" + accessToken = releaseProp.getProperty('modrinthKey') + minecraftVersions.add(project.minecraft_version) + } + } +} \ No newline at end of file diff --git a/neoforge/src/main/java/mod/azure/azurelib/ClientModListener.java b/neo/src/main/java/mod/azure/azurelib/ClientModListener.java similarity index 62% rename from neoforge/src/main/java/mod/azure/azurelib/ClientModListener.java rename to neo/src/main/java/mod/azure/azurelib/ClientModListener.java index 9775fd29c..e92e9073d 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/ClientModListener.java +++ b/neo/src/main/java/mod/azure/azurelib/ClientModListener.java @@ -1,8 +1,6 @@ package mod.azure.azurelib; import com.mojang.blaze3d.platform.InputConstants; -import mod.azure.azurelib.client.AzureLibClient; -import mod.azure.azurelib.config.ConfigHolder; import net.minecraft.client.KeyMapping; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.ConfigScreenHandler; @@ -18,19 +16,34 @@ import java.util.Map; import java.util.Optional; +import mod.azure.azurelib.client.AzureLibClient; +import mod.azure.azurelib.config.ConfigHolder; + @EventBusSubscriber(modid = AzureLib.MOD_ID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class ClientModListener { @SubscribeEvent public static void registerKeys(final RegisterKeyMappingsEvent event) { - Keybindings.RELOAD = new KeyMapping("key.azurelib.reload", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_R, - "category.azurelib.binds"); + Keybindings.RELOAD = new KeyMapping( + "key.azurelib.reload", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_R, + "category.azurelib.binds" + ); event.register(Keybindings.RELOAD); - Keybindings.SCOPE = new KeyMapping("key.azurelib.scope", InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_LEFT_ALT, - "category.azurelib.binds"); + Keybindings.SCOPE = new KeyMapping( + "key.azurelib.scope", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_LEFT_ALT, + "category.azurelib.binds" + ); event.register(Keybindings.SCOPE); - Keybindings.FIRE_WEAPON = new KeyMapping("key.azurelib.fire", InputConstants.Type.KEYSYM, - GLFW.GLFW_KEY_UNKNOWN, "category.azurelib.binds"); + Keybindings.FIRE_WEAPON = new KeyMapping( + "key.azurelib.fire", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_UNKNOWN, + "category.azurelib.binds" + ); event.register(Keybindings.FIRE_WEAPON); } @@ -43,13 +56,15 @@ public static void clientInit(final FMLClientSetupEvent event) { Optional optional = modList.getModContainerById(modId); optional.ifPresent(modContainer -> { List> list = entry.getValue(); - modContainer.registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, - () -> new ConfigScreenHandler.ConfigScreenFactory((minecraft, screen) -> { - if (list.size() == 1) { - return AzureLibClient.getConfigScreen(list.get(0).getConfigId(), screen); - } - return AzureLibClient.getConfigScreenByGroup(list, modId, screen); - })); + modContainer.registerExtensionPoint( + ConfigScreenHandler.ConfigScreenFactory.class, + () -> new ConfigScreenHandler.ConfigScreenFactory((minecraft, screen) -> { + if (list.size() == 1) { + return AzureLibClient.getConfigScreen(list.get(0).getConfigId(), screen); + } + return AzureLibClient.getConfigScreenByGroup(list, modId, screen); + }) + ); }); } } diff --git a/neoforge/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java b/neo/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java similarity index 62% rename from neoforge/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java rename to neo/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java index e6734c3b3..5f1ee7807 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java +++ b/neo/src/main/java/mod/azure/azurelib/NeoForgeAzureLibMod.java @@ -1,13 +1,8 @@ package mod.azure.azurelib; -import mod.azure.azurelib.config.AzureLibConfig; -import mod.azure.azurelib.config.format.ConfigFormats; -import mod.azure.azurelib.config.io.ConfigIO; -import mod.azure.azurelib.enchantments.IncendiaryEnchantment; -import mod.azure.azurelib.entities.TickingLightBlock; -import mod.azure.azurelib.entities.TickingLightEntity; -import mod.azure.azurelib.network.Networking; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.Item; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -19,6 +14,14 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; +import mod.azure.azurelib.config.AzureLibConfig; +import mod.azure.azurelib.config.format.ConfigFormats; +import mod.azure.azurelib.config.io.ConfigIO; +import mod.azure.azurelib.enchantments.IncendiaryEnchantment; +import mod.azure.azurelib.entities.TickingLightBlock; +import mod.azure.azurelib.entities.TickingLightEntity; +import mod.azure.azurelib.network.Networking; + @Mod.EventBusSubscriber @Mod(AzureLib.MOD_ID) public final class NeoForgeAzureLibMod { @@ -35,6 +38,8 @@ public NeoForgeAzureLibMod() { AzureEnchantments.ENCHANTMENTS.register(modEventBus); AzureBlocks.BLOCKS.register(modEventBus); AzureEntities.TILE_TYPES.register(modEventBus); + AzureEntities.ENTITY_TYPES.register(modEventBus); + AzureItems.ITEMS.register(modEventBus); } private void init(FMLCommonSetupEvent event) { @@ -43,20 +48,56 @@ private void init(FMLCommonSetupEvent event) { } public class AzureEnchantments { - public static final DeferredRegister ENCHANTMENTS = DeferredRegister.create(ForgeRegistries.ENCHANTMENTS, AzureLib.MOD_ID); - public static final RegistryObject INCENDIARYENCHANTMENT = ENCHANTMENTS.register("incendiaryenchantment", () -> new IncendiaryEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.MAINHAND)); + + public static final DeferredRegister ENCHANTMENTS = DeferredRegister.create( + ForgeRegistries.ENCHANTMENTS, + AzureLib.MOD_ID + ); + + public static final RegistryObject INCENDIARYENCHANTMENT = ENCHANTMENTS.register( + "incendiaryenchantment", + () -> new IncendiaryEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.MAINHAND) + ); } public class AzureBlocks { - public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, AzureLib.MOD_ID); - public static final RegistryObject TICKING_LIGHT_BLOCK = BLOCKS.register("lightblock", () -> new TickingLightBlock()); + public static final DeferredRegister BLOCKS = DeferredRegister.create( + ForgeRegistries.BLOCKS, + AzureLib.MOD_ID + ); + + public static final RegistryObject TICKING_LIGHT_BLOCK = BLOCKS.register( + "lightblock", + TickingLightBlock::new + ); } public class AzureEntities { - public static final DeferredRegister> TILE_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, AzureLib.MOD_ID); + public static final DeferredRegister> TILE_TYPES = DeferredRegister.create( + ForgeRegistries.BLOCK_ENTITY_TYPES, + AzureLib.MOD_ID + ); + + public static final DeferredRegister> ENTITY_TYPES = DeferredRegister.create( + ForgeRegistries.ENTITY_TYPES, + AzureLib.MOD_ID + ); + + public static final RegistryObject> TICKING_LIGHT_ENTITY = TILE_TYPES + .register( + "lightblock", + () -> BlockEntityType.Builder.of(TickingLightEntity::new, AzureBlocks.TICKING_LIGHT_BLOCK.get()) + .build(null) + ); + } + + public class AzureItems { - public static final RegistryObject> TICKING_LIGHT_ENTITY = TILE_TYPES.register("lightblock", () -> BlockEntityType.Builder.of(TickingLightEntity::new, AzureBlocks.TICKING_LIGHT_BLOCK.get()).build(null)); + public static final DeferredRegister ITEMS = DeferredRegister.create( + ForgeRegistries.ITEMS, + AzureLib.MOD_ID + ); } } diff --git a/neoforge/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java b/neo/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java similarity index 85% rename from neoforge/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java rename to neo/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java index 4bd6137f8..51843281a 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java +++ b/neo/src/main/java/mod/azure/azurelib/event/GeoRenderEvent.java @@ -1,12 +1,6 @@ package mod.azure.azurelib.event; import com.mojang.blaze3d.vertex.PoseStack; -import mod.azure.azurelib.animatable.GeoItem; -import mod.azure.azurelib.animatable.GeoReplacedEntity; -import mod.azure.azurelib.cache.object.BakedGeoModel; -import mod.azure.azurelib.core.animatable.GeoAnimatable; -import mod.azure.azurelib.renderer.*; -import mod.azure.azurelib.renderer.layer.GeoRenderLayer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.ItemStack; @@ -15,7 +9,15 @@ import net.minecraftforge.eventbus.api.Event; import org.jetbrains.annotations.Nullable; +import mod.azure.azurelib.animatable.GeoItem; +import mod.azure.azurelib.animatable.GeoReplacedEntity; +import mod.azure.azurelib.cache.object.BakedGeoModel; +import mod.azure.azurelib.core.animatable.GeoAnimatable; +import mod.azure.azurelib.renderer.*; +import mod.azure.azurelib.renderer.layer.GeoRenderLayer; + public interface GeoRenderEvent { + /** * Returns the renderer for this event * @@ -32,6 +34,7 @@ public interface GeoRenderEvent { * Renderer events for armor pieces being rendered by {@link GeoArmorRenderer} */ abstract class Armor extends Event implements GeoRenderEvent { + private final GeoArmorRenderer renderer; public Armor(GeoArmorRenderer renderer) { @@ -76,17 +79,30 @@ public EquipmentSlot getEquipmentSlot() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the armor piece will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the armor piece will not be rendered and the corresponding {@link Post} event will + * not be fired. */ @Cancelable public static class Pre extends Armor { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoArmorRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoArmorRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -123,13 +139,25 @@ public int getPackedLight() { * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends Armor { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoArmorRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoArmorRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -166,6 +194,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends Armor { + public CompileRenderLayers(GeoArmorRenderer renderer) { super(renderer); } @@ -173,7 +202,8 @@ public CompileRenderLayers(GeoArmorRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); @@ -185,6 +215,7 @@ public void addLayer(GeoRenderLayer renderLayer) { * Renderer events for {@link BlockEntity BlockEntities} being rendered by {@link GeoBlockRenderer} */ abstract class Block extends Event implements GeoRenderEvent { + private final GeoBlockRenderer renderer; public Block(GeoBlockRenderer renderer) { @@ -212,17 +243,30 @@ public BlockEntity getBlockEntity() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the block entity will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the block entity will not be rendered and the corresponding {@link Post} event + * will not be fired. */ @Cancelable public static class Pre extends Block { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoBlockRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoBlockRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -259,13 +303,25 @@ public int getPackedLight() { * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends Block { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoBlockRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoBlockRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -302,6 +358,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends Block { + public CompileRenderLayers(GeoBlockRenderer renderer) { super(renderer); } @@ -309,7 +366,8 @@ public CompileRenderLayers(GeoBlockRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); @@ -318,9 +376,11 @@ public void addLayer(GeoRenderLayer renderLayer) { } /** - * Renderer events for {@link net.minecraft.world.entity.Entity Entities} being rendered by {@link GeoEntityRenderer} + * Renderer events for {@link net.minecraft.world.entity.Entity Entities} being rendered by + * {@link GeoEntityRenderer} */ abstract class Entity extends Event implements GeoRenderEvent { + private final GeoEntityRenderer renderer; public Entity(GeoEntityRenderer renderer) { @@ -348,17 +408,30 @@ public net.minecraft.world.entity.Entity getEntity() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the entity will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the entity will not be rendered and the corresponding {@link Post} event will not + * be fired. */ @Cancelable public static class Pre extends Entity { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -395,13 +468,25 @@ public int getPackedLight() { * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends Entity { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -438,6 +523,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends Entity { + public CompileRenderLayers(GeoEntityRenderer renderer) { super(renderer); } @@ -445,7 +531,8 @@ public CompileRenderLayers(GeoEntityRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); @@ -457,6 +544,7 @@ public void addLayer(GeoRenderLayer renderLayer) { * Renderer events for {@link ItemStack Items} being rendered by {@link GeoItemRenderer} */ abstract class Item extends Event implements GeoRenderEvent { + private final GeoItemRenderer renderer; public Item(GeoItemRenderer renderer) { @@ -484,17 +572,30 @@ public ItemStack getItemStack() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the ItemStack will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the ItemStack will not be rendered and the corresponding {@link Post} event will + * not be fired. */ @Cancelable public static class Pre extends Item { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoItemRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoItemRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -531,13 +632,25 @@ public int getPackedLight() { * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends Item { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoItemRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoItemRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -574,6 +687,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends Item { + public CompileRenderLayers(GeoItemRenderer renderer) { super(renderer); } @@ -581,7 +695,8 @@ public CompileRenderLayers(GeoItemRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); @@ -593,6 +708,7 @@ public void addLayer(GeoRenderLayer renderLayer) { * Renderer events for miscellaneous {@link GeoAnimatable animatables} being rendered by {@link GeoObjectRenderer} */ abstract class Object extends Event implements GeoRenderEvent { + private final GeoObjectRenderer renderer; public Object(GeoObjectRenderer renderer) { @@ -613,17 +729,30 @@ public GeoObjectRenderer getRenderer() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the object will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the object will not be rendered and the corresponding {@link Post} event will not + * be fired. */ @Cancelable public static class Pre extends Object { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoObjectRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoObjectRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -656,17 +785,29 @@ public int getPackedLight() { /** * Post-render event for miscellaneous animatables being rendered by {@link GeoObjectRenderer} -

    + *

    * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends Object { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoObjectRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoObjectRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -703,6 +844,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends Object { + public CompileRenderLayers(GeoObjectRenderer renderer) { super(renderer); } @@ -710,7 +852,8 @@ public CompileRenderLayers(GeoObjectRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); @@ -719,9 +862,11 @@ public void addLayer(GeoRenderLayer renderLayer) { } /** - * Renderer events for miscellaneous {@link GeoReplacedEntity replaced entities} being rendered by {@link GeoReplacedEntityRenderer} + * Renderer events for miscellaneous {@link GeoReplacedEntity replaced entities} being rendered by + * {@link GeoReplacedEntityRenderer} */ abstract class ReplacedEntity extends Event implements GeoRenderEvent { + private final GeoReplacedEntityRenderer renderer; public ReplacedEntity(GeoReplacedEntityRenderer renderer) { @@ -749,17 +894,30 @@ public net.minecraft.world.entity.Entity getReplacedEntity() { * This event is called before rendering, but after {@link GeoRenderer#preRender} *

    * This event is Cancelable
    - * If the event is cancelled, the entity will not be rendered and the corresponding {@link Post} event will not be fired. + * If the event is cancelled, the entity will not be rendered and the corresponding {@link Post} event will not + * be fired. */ @Cancelable public static class Pre extends ReplacedEntity { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Pre(GeoReplacedEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Pre( + GeoReplacedEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -796,13 +954,25 @@ public int getPackedLight() { * This event is called after {@link GeoRenderer#postRender} */ public static class Post extends ReplacedEntity { + private final PoseStack poseStack; + private final BakedGeoModel model; + private final MultiBufferSource bufferSource; + private final float partialTick; + private final int packedLight; - public Post(GeoReplacedEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { + public Post( + GeoReplacedEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { super(renderer); this.poseStack = poseStack; @@ -839,6 +1009,7 @@ public int getPackedLight() { * Use this event to add render layers to the renderer as needed */ public static class CompileRenderLayers extends ReplacedEntity { + public CompileRenderLayers(GeoReplacedEntityRenderer renderer) { super(renderer); } @@ -846,11 +1017,12 @@ public CompileRenderLayers(GeoReplacedEntityRenderer renderer) { /** * Adds a {@link GeoRenderLayer} to the renderer *

    - * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and renderer + * Type-safety is not checked here, so ensure that your layer is compatible with this animatable and + * renderer */ public void addLayer(GeoRenderLayer renderLayer) { getRenderer().addRenderLayer(renderLayer); } } } -} \ No newline at end of file +} diff --git a/neo/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java b/neo/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java new file mode 100644 index 000000000..6ee8ba6fc --- /dev/null +++ b/neo/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java @@ -0,0 +1,24 @@ +package mod.azure.azurelib.items; + +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.Item; +import net.minecraftforge.common.ForgeSpawnEggItem; + +import java.util.function.Supplier; + +public class NeoForgeAzureSpawnEgg extends ForgeSpawnEggItem { + + public NeoForgeAzureSpawnEgg( + Supplier> type, + int primaryColor, + int secondaryColor + ) { + super(type, primaryColor, secondaryColor, new Item.Properties().stacksTo(64)); + } + + public NeoForgeAzureSpawnEgg(EntityType type, int primaryColor, int secondaryColor) { + super(() -> type, primaryColor, secondaryColor, new Item.Properties().stacksTo(64)); + } + +} diff --git a/neoforge/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java b/neo/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java similarity index 52% rename from neoforge/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java rename to neo/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java index 7755fddc8..f11293751 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java +++ b/neo/src/main/java/mod/azure/azurelib/mixins/ClientHooksMixin.java @@ -1,7 +1,5 @@ package mod.azure.azurelib.mixins; -import mod.azure.azurelib.animatable.GeoItem; -import mod.azure.azurelib.animatable.client.RenderProvider; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.Model; import net.minecraft.world.entity.EquipmentSlot; @@ -13,12 +11,33 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import mod.azure.azurelib.animatable.GeoItem; +import mod.azure.azurelib.animatable.client.RenderProvider; +import mod.azure.azurelib.rewrite.render.armor.AzArmorRendererRegistry; + @Mixin(ForgeHooksClient.class) public class ClientHooksMixin { @Inject(method = "getArmorModel", at = @At("RETURN"), remap = false, cancellable = true) - private static void injectAzureArmors(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot slot, HumanoidModel _default, CallbackInfoReturnable cir) { + private static void injectAzureArmors( + LivingEntity entityLiving, + ItemStack itemStack, + EquipmentSlot slot, + HumanoidModel _default, + CallbackInfoReturnable cir + ) { if (itemStack.getItem() instanceof GeoItem) - cir.setReturnValue((Model) RenderProvider.of(itemStack).getGenericArmorModel(entityLiving, itemStack, slot, (HumanoidModel) _default)); + cir.setReturnValue( + RenderProvider.of(itemStack) + .getGenericArmorModel(entityLiving, itemStack, slot, (HumanoidModel) _default) + ); + + var renderer = AzArmorRendererRegistry.getOrNull(itemStack.getItem()); + + if (renderer != null) { + var rendererPipeline = renderer.rendererPipeline(); + var armorModel = rendererPipeline.armorModel(); + cir.setReturnValue(armorModel); + } } } diff --git a/neo/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java b/neo/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java new file mode 100644 index 000000000..c7fd6029e --- /dev/null +++ b/neo/src/main/java/mod/azure/azurelib/mixins/NeoMixinHumanoidArmorLayer.java @@ -0,0 +1,120 @@ +package mod.azure.azurelib.mixins; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.PlayerModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import mod.azure.azurelib.animatable.GeoItem; +import mod.azure.azurelib.animatable.client.RenderProvider; +import mod.azure.azurelib.renderer.GeoArmorRenderer; +import mod.azure.azurelib.rewrite.render.armor.AzArmorRendererRegistry; + +/** + * Render hook for injecting AzureLib's armor rendering functionalities + */ +@Mixin(value = HumanoidArmorLayer.class, priority = 700) +public class NeoMixinHumanoidArmorLayer> { + + @ModifyExpressionValue( + method = "renderArmorPiece", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/LivingEntity;getItemBySlot(Lnet/minecraft/world/entity/EquipmentSlot;)Lnet/minecraft/world/item/ItemStack;" + ) + ) + private ItemStack azurelib$captureItemBySlot( + ItemStack original, + @Share("item_by_slot") LocalRef itemBySlotRef + ) { + itemBySlotRef.set(original); + return original; + } + + @Inject( + method = "renderArmorPiece", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/entity/layers/HumanoidArmorLayer;renderModel(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/item/ArmorItem;Lnet/minecraft/client/model/Model;ZFFFLnet/minecraft/resources/ResourceLocation;)V" + ), cancellable = true + ) + public void azurelib$renderAzurelibModel( + PoseStack poseStack, + MultiBufferSource bufferSource, + T entity, + EquipmentSlot equipmentSlot, + int packedLight, + A baseModel, + CallbackInfo ci, + @Share("item_by_slot") LocalRef itemBySlotRef + ) { + var stack = itemBySlotRef.get(); + var renderProvider = RenderProvider.of(stack); + @SuppressWarnings("unchecked") + var humanoidModel = (HumanoidModel) baseModel; + var geckolibModel = renderProvider + .getGenericArmorModel(entity, stack, equipmentSlot, humanoidModel); + + if (geckolibModel != null && stack.getItem() instanceof GeoItem) { + if (geckolibModel instanceof GeoArmorRenderer geoArmorRenderer) { + geoArmorRenderer.prepForRender(entity, stack, equipmentSlot, baseModel); + } + + baseModel.copyPropertiesTo((A) geckolibModel); + + geckolibModel.renderToBuffer(poseStack, null, packedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1); + ci.cancel(); + } + + var renderer = AzArmorRendererRegistry.getOrNull(stack.getItem()); + + if (renderer != null) { + var rendererPipeline = renderer.rendererPipeline(); + var armorModel = rendererPipeline.armorModel(); + @SuppressWarnings("unchecked") + var typedHumanoidModel = (HumanoidModel) armorModel; + + renderer.prepForRender(entity, stack, equipmentSlot, baseModel); + baseModel.copyPropertiesTo(typedHumanoidModel); + azurelib$testVisibility((A) typedHumanoidModel, entity, equipmentSlot); + armorModel.renderToBuffer(poseStack, null, packedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1); + ci.cancel(); + } + } + + @Unique + private void azurelib$testVisibility(A model, @Nullable Entity entity, EquipmentSlot equipmentSlot) { + if (entity instanceof Player && model instanceof PlayerModel playerModel) { + switch (equipmentSlot) { + case HEAD -> { + playerModel.hat.visible = false; + playerModel.ear.visible = false; + } + case CHEST -> { + playerModel.jacket.visible = false; + playerModel.rightSleeve.visible = false; + playerModel.leftSleeve.visible = false; + } + case LEGS -> { + playerModel.leftPants.visible = false; + playerModel.rightPants.visible = false; + } + } + } + } +} diff --git a/neoforge/src/main/java/mod/azure/azurelib/network/IPacket.java b/neo/src/main/java/mod/azure/azurelib/network/IPacket.java similarity index 100% rename from neoforge/src/main/java/mod/azure/azurelib/network/IPacket.java rename to neo/src/main/java/mod/azure/azurelib/network/IPacket.java diff --git a/neoforge/src/main/java/mod/azure/azurelib/network/Networking.java b/neo/src/main/java/mod/azure/azurelib/network/Networking.java similarity index 72% rename from neoforge/src/main/java/mod/azure/azurelib/network/Networking.java rename to neo/src/main/java/mod/azure/azurelib/network/Networking.java index 6778ca3ba..39dc979f2 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/network/Networking.java +++ b/neo/src/main/java/mod/azure/azurelib/network/Networking.java @@ -1,27 +1,28 @@ package mod.azure.azurelib.network; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -import mod.azure.azurelib.AzureLib; import net.minecraft.CrashReport; import net.minecraft.ReportedException; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; + +import mod.azure.azurelib.AzureLib; public final class Networking { public static final Marker MARKER = MarkerManager.getMarker("Network"); + private static final String NETWORK_VERSION = "2.0.0"; + private static final SimpleChannel CHANNEL = NetworkRegistry.ChannelBuilder - .named(AzureLib.modResource("network_channel")) - .networkProtocolVersion(() -> NETWORK_VERSION) - .clientAcceptedVersions(NETWORK_VERSION::equals) - .serverAcceptedVersions(NETWORK_VERSION::equals) - .simpleChannel(); + .named(AzureLib.modResource("network_channel")) + .networkProtocolVersion(() -> NETWORK_VERSION) + .clientAcceptedVersions(NETWORK_VERSION::equals) + .serverAcceptedVersions(NETWORK_VERSION::equals) + .simpleChannel(); public static void sendClientPacket(ServerPlayer target, IPacket packet) { CHANNEL.sendTo(packet, target.connection.connection, NetworkDirection.PLAY_TO_CLIENT); @@ -40,7 +41,12 @@ private static

    > void registerNetworkPacket(Class

    packet try { packet = packetType.newInstance(); } catch (InstantiationException | IllegalAccessException e) { - throw new ReportedException(CrashReport.forThrowable(e, "Couldn't instantiate packet for registration. Make sure you have provided public constructor with no parameters.")); + throw new ReportedException( + CrashReport.forThrowable( + e, + "Couldn't instantiate packet for registration. Make sure you have provided public constructor with no parameters." + ) + ); } CHANNEL.registerMessage(packetIndex++, packetType, IPacket::encode, packet::decode, IPacket::handle); } diff --git a/neoforge/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java b/neo/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java similarity index 95% rename from neoforge/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java rename to neo/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java index 69a3b0f69..ada2e7b21 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java +++ b/neo/src/main/java/mod/azure/azurelib/network/S2C_SendConfigData.java @@ -1,16 +1,17 @@ package mod.azure.azurelib.network; -import mod.azure.azurelib.AzureLib; -import mod.azure.azurelib.AzureLibException; -import mod.azure.azurelib.config.ConfigHolder; -import mod.azure.azurelib.config.adapter.TypeAdapter; -import mod.azure.azurelib.config.value.ConfigValue; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; import java.util.Map; import java.util.function.Supplier; +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.AzureLibException; +import mod.azure.azurelib.config.ConfigHolder; +import mod.azure.azurelib.config.adapter.TypeAdapter; +import mod.azure.azurelib.config.value.ConfigValue; + public class S2C_SendConfigData implements IPacket { private final String config; @@ -49,7 +50,7 @@ public S2C_SendConfigData decode(FriendlyByteBuf buffer) { String fieldId = buffer.readUtf(); ConfigValue value = serialized.get(fieldId); if (value == null) { - AzureLib.LOGGER.fatal(Networking.MARKER, "Received unknown config value " + fieldId); + AzureLib.LOGGER.fatal(Networking.MARKER, "Received unknown config value " + fieldId); throw new AzureLibException("Unknown config field: " + fieldId); } setValue(value, buffer); diff --git a/neo/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java b/neo/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java new file mode 100644 index 000000000..3babc54cf --- /dev/null +++ b/neo/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java @@ -0,0 +1,265 @@ +package mod.azure.azurelib.platform; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraftforge.common.MinecraftForge; + +import mod.azure.azurelib.cache.object.BakedGeoModel; +import mod.azure.azurelib.event.GeoRenderEvent; +import mod.azure.azurelib.platform.services.AzureEvents; +import mod.azure.azurelib.renderer.*; + +public class AzureLibEventsNeoForge implements AzureEvents { + + /** + * Fire the {@link GeoRenderEvent.Block.CompileRenderLayers} event + */ + @Override + public void fireCompileBlockRenderLayers(GeoBlockRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Block.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.Block.Pre} event + */ + @Override + public boolean fireBlockPreRender( + GeoBlockRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Block.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Block.Post} event + */ + @Override + public void fireBlockPostRender( + GeoBlockRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Block.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Armor.CompileRenderLayers} event + */ + @Override + public void fireCompileArmorRenderLayers(GeoArmorRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Armor.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.Armor.Pre} event + */ + @Override + public boolean fireArmorPreRender( + GeoArmorRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Armor.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Armor.Post} event + */ + @Override + public void fireArmorPostRender( + GeoArmorRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Armor.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Entity.CompileRenderLayers} event + */ + @Override + public void fireCompileEntityRenderLayers(GeoEntityRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Entity.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.Entity.Pre} event + */ + @Override + public boolean fireEntityPreRender( + GeoEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Entity.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Entity.Post} event + */ + @Override + public void fireEntityPostRender( + GeoEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Entity.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.ReplacedEntity.CompileRenderLayers} event + */ + @Override + public void fireCompileReplacedEntityRenderLayers(GeoReplacedEntityRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.ReplacedEntity.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.ReplacedEntity.Pre} event + */ + @Override + public boolean fireReplacedEntityPreRender( + GeoReplacedEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.ReplacedEntity.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.ReplacedEntity.Post} event + */ + @Override + public void fireReplacedEntityPostRender( + GeoReplacedEntityRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.ReplacedEntity.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Item.CompileRenderLayers} event + */ + @Override + public void fireCompileItemRenderLayers(GeoItemRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Item.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.Item.Pre} event + */ + @Override + public boolean fireItemPreRender( + GeoItemRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Item.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Item.Post} event + */ + @Override + public void fireItemPostRender( + GeoItemRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Item.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Object.CompileRenderLayers} event + */ + @Override + public void fireCompileObjectRenderLayers(GeoObjectRenderer renderer) { + MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Object.CompileRenderLayers(renderer)); + } + + /** + * Fire the {@link GeoRenderEvent.Object.Pre} event + */ + @Override + public boolean fireObjectPreRender( + GeoObjectRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + return !MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Object.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } + + /** + * Fire the {@link GeoRenderEvent.Object.Post} event + */ + @Override + public void fireObjectPostRender( + GeoObjectRenderer renderer, + PoseStack poseStack, + BakedGeoModel model, + MultiBufferSource bufferSource, + float partialTick, + int packedLight + ) { + MinecraftForge.EVENT_BUS.post( + new GeoRenderEvent.Object.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight) + ); + } +} diff --git a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java similarity index 92% rename from neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java rename to neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java index 78512041e..3c5475913 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java +++ b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibInitializer.java @@ -1,12 +1,13 @@ package mod.azure.azurelib.platform; -import mod.azure.azurelib.cache.AzureLibCache; -import mod.azure.azurelib.platform.services.AzureLibInitializer; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.loading.FMLEnvironment; +import mod.azure.azurelib.cache.AzureLibCache; +import mod.azure.azurelib.platform.services.AzureLibInitializer; + public class NeoForgeAzureLibInitializer implements AzureLibInitializer { + @Override public void initialize() { if (FMLEnvironment.dist == Dist.CLIENT) { @@ -14,4 +15,4 @@ public void initialize() { } Services.NETWORK.registerClientReceiverPackets(); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java similarity index 50% rename from neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java rename to neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java index c2595d6e8..f5db032d9 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java +++ b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgeAzureLibNetwork.java @@ -1,15 +1,8 @@ package mod.azure.azurelib.platform; -import mod.azure.azurelib.AzureLib; -import mod.azure.azurelib.network.AbstractPacket; -import mod.azure.azurelib.network.Networking; -import mod.azure.azurelib.network.S2C_SendConfigData; -import mod.azure.azurelib.network.packet.*; -import mod.azure.azurelib.platform.services.AzureLibNetwork; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; @@ -21,9 +14,23 @@ import java.util.function.Supplier; +import mod.azure.azurelib.AzureLib; +import mod.azure.azurelib.network.AbstractPacket; +import mod.azure.azurelib.network.Networking; +import mod.azure.azurelib.network.S2C_SendConfigData; +import mod.azure.azurelib.network.packet.*; +import mod.azure.azurelib.platform.services.AzureLibNetwork; + public class NeoForgeAzureLibNetwork implements AzureLibNetwork { + private static final String VER = "1"; - private static final SimpleChannel PACKET_CHANNEL = NetworkRegistry.newSimpleChannel(AzureLib.modResource("main"), () -> VER, VER::equals, VER::equals); + + private static final SimpleChannel PACKET_CHANNEL = NetworkRegistry.newSimpleChannel( + AzureLib.modResource("main"), + () -> VER, + VER::equals, + VER::equals + ); @Override public Packet createPacket(Entity entity) { @@ -40,12 +47,70 @@ private void handlePacket(AbstractPacket packet, Supplier public void registerClientReceiverPackets() { int id = 0; - PACKET_CHANNEL.registerMessage(id++, AnimDataSyncPacket.class, AnimDataSyncPacket::encode, AnimDataSyncPacket::receive, this::handlePacket); - PACKET_CHANNEL.registerMessage(id++, AnimTriggerPacket.class, AnimTriggerPacket::encode, AnimTriggerPacket::receive, this::handlePacket); - PACKET_CHANNEL.registerMessage(id++, EntityAnimDataSyncPacket.class, EntityAnimDataSyncPacket::encode, EntityAnimDataSyncPacket::receive, this::handlePacket); - PACKET_CHANNEL.registerMessage(id++, EntityAnimTriggerPacket.class, EntityAnimTriggerPacket::encode, EntityAnimTriggerPacket::receive, this::handlePacket); - PACKET_CHANNEL.registerMessage(id++, BlockEntityAnimDataSyncPacket.class, BlockEntityAnimDataSyncPacket::encode, BlockEntityAnimDataSyncPacket::receive, this::handlePacket); - PACKET_CHANNEL.registerMessage(id++, BlockEntityAnimTriggerPacket.class, BlockEntityAnimTriggerPacket::encode, BlockEntityAnimTriggerPacket::receive, this::handlePacket); + PACKET_CHANNEL.registerMessage( + id++, + AnimDataSyncPacket.class, + AnimDataSyncPacket::encode, + AnimDataSyncPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + AnimTriggerPacket.class, + AnimTriggerPacket::encode, + AnimTriggerPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + EntityAnimDataSyncPacket.class, + EntityAnimDataSyncPacket::encode, + EntityAnimDataSyncPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + EntityAnimTriggerPacket.class, + EntityAnimTriggerPacket::encode, + EntityAnimTriggerPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + BlockEntityAnimDataSyncPacket.class, + BlockEntityAnimDataSyncPacket::encode, + BlockEntityAnimDataSyncPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + BlockEntityAnimTriggerPacket.class, + BlockEntityAnimTriggerPacket::encode, + BlockEntityAnimTriggerPacket::receive, + this::handlePacket + ); + + PACKET_CHANNEL.registerMessage( + id++, + AzBlockEntityDispatchCommandPacket.class, + AzBlockEntityDispatchCommandPacket::encode, + AzBlockEntityDispatchCommandPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + AzEntityDispatchCommandPacket.class, + AzEntityDispatchCommandPacket::encode, + AzEntityDispatchCommandPacket::receive, + this::handlePacket + ); + PACKET_CHANNEL.registerMessage( + id++, + AzItemStackDispatchCommandPacket.class, + AzItemStackDispatchCommandPacket::encode, + AzItemStackDispatchCommandPacket::receive, + this::handlePacket + ); } @Override diff --git a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java similarity index 99% rename from neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java rename to neo/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java index 0023c1284..a75197ab1 100644 --- a/neoforge/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java +++ b/neo/src/main/java/mod/azure/azurelib/platform/NeoForgePlatformHelper.java @@ -1,9 +1,5 @@ package mod.azure.azurelib.platform; -import mod.azure.azurelib.NeoForgeAzureLibMod; -import mod.azure.azurelib.entities.TickingLightBlock; -import mod.azure.azurelib.entities.TickingLightEntity; -import mod.azure.azurelib.platform.services.IPlatformHelper; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.fml.ModList; @@ -13,23 +9,25 @@ import java.nio.file.Path; +import mod.azure.azurelib.NeoForgeAzureLibMod; +import mod.azure.azurelib.entities.TickingLightBlock; +import mod.azure.azurelib.entities.TickingLightEntity; +import mod.azure.azurelib.platform.services.IPlatformHelper; + public class NeoForgePlatformHelper implements IPlatformHelper { @Override public String getPlatformName() { - return "Forge"; } @Override public boolean isModLoaded(String modId) { - return ModList.get().isLoaded(modId); } @Override public boolean isDevelopmentEnvironment() { - return !FMLLoader.isProduction(); } @@ -62,4 +60,4 @@ public Enchantment getIncendairyenchament() { public Path modsDir() { return FMLPaths.MODSDIR.get(); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/resources/META-INF/mods.toml b/neo/src/main/resources/META-INF/mods.toml similarity index 100% rename from neoforge/src/main/resources/META-INF/mods.toml rename to neo/src/main/resources/META-INF/mods.toml diff --git a/neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureEvents b/neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureEvents similarity index 100% rename from neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureEvents rename to neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureEvents diff --git a/neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibInitializer b/neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibInitializer similarity index 100% rename from neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibInitializer rename to neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibInitializer diff --git a/neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibNetwork b/neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibNetwork similarity index 100% rename from neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibNetwork rename to neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.AzureLibNetwork diff --git a/neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.IPlatformHelper b/neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.IPlatformHelper similarity index 100% rename from neoforge/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.IPlatformHelper rename to neo/src/main/resources/META-INF/services/mod.azure.azurelib.platform.services.IPlatformHelper diff --git a/neoforge/src/main/resources/azurelib.forge.mixins.json b/neo/src/main/resources/azurelib.forge.mixins.json similarity index 53% rename from neoforge/src/main/resources/azurelib.forge.mixins.json rename to neo/src/main/resources/azurelib.forge.mixins.json index 3fabba704..2ca5e974f 100644 --- a/neoforge/src/main/resources/azurelib.forge.mixins.json +++ b/neo/src/main/resources/azurelib.forge.mixins.json @@ -7,15 +7,19 @@ "defaultRequire": 1 }, "mixins": [ + "AbstractContainerMenuMixin_AzItemIDFix", + "ItemStackMixin_AzItemStackIdentityRegistry", "PlayerListMixin" ], "client": [ - "AccessorWarningScreen", - "ClientHooksMixin", + "BlockEntityMixin_AzBlockEntityAnimatorCache", + "EntityMixin_AzEntityAnimatorCache", + "ItemStackMixin_AzItemAnimatorCache", "ItemRendererAccessor", "MinecraftMixin", "MixinItemRenderer", - "NeoMixinHumanoidArmorLayer", - "TextureManagerMixin" + "TextureManagerMixin", + "ClientHooksMixin", + "NeoMixinHumanoidArmorLayer" ] } \ No newline at end of file diff --git a/neoforge/build.gradle b/neoforge/build.gradle deleted file mode 100644 index e4006484e..000000000 --- a/neoforge/build.gradle +++ /dev/null @@ -1,154 +0,0 @@ -plugins { - id 'idea' - id 'maven-publish' - id 'net.minecraftforge.gradle' - id 'org.spongepowered.mixin' - id "me.modmuss50.mod-publish-plugin" version "0.4.0" -} - -base { - archivesName = "${mod_id}-neo-${minecraft_version}" -} - -mixin { - add(sourceSets.main, "${mod_id}.refmap.json") - - config("${mod_id}.mixins.json") - config("${mod_id}.forge.mixins.json") -} - -minecraft { - mappings channel: 'official', version: minecraft_version - - copyIdeResources = true //Calls processResources when in dev - - if (file('src/main/resources/META-INF/accesstransformer.cfg').exists()) { - accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - } - - runs { - client { - workingDirectory project.file('run') - ideaModule "${rootProject.name}.${project.name}.main" - taskName 'Client' - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - mods { - modClientRun { - source sourceSets.main - source project(":common").sourceSets.main - } - } - } - - server { - workingDirectory project.file('run') - ideaModule "${rootProject.name}.${project.name}.main" - taskName 'Server' - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - mods { - modServerRun { - source sourceSets.main - source project(":common").sourceSets.main - } - } - } - - data { - workingDirectory project.file('run') - ideaModule "${rootProject.name}.${project.name}.main" - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - taskName 'Data' - property 'mixin.env.remapRefMap', 'true' - property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" - mods { - modDataRun { - source sourceSets.main - source project(":common").sourceSets.main - } - } - } - } -} - -sourceSets.main.resources.srcDir 'src/generated/resources' - -repositories { - maven { url "https://cfa2.cursemaven.com" } - mavenCentral() -} - -dependencies { - minecraft "net.minecraftforge:forge:${minecraft_version}-${neo_version}" - compileOnly project(":common") - annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT:processor") -} - -tasks.withType(JavaCompile).configureEach { - source(project(":common").sourceSets.main.allSource) -} -tasks.withType(Javadoc).configureEach { - source(project(":common").sourceSets.main.allJava) -} -tasks.named("sourcesJar", Jar) { - from(project(":common").sourceSets.main.allSource) -} - -processResources { - from project(":common").sourceSets.main.resources -} - -jar.finalizedBy('reobfJar') - -publishing { - repositories { - maven { - name = "azurelib" - url = "https://maven.azuredoom.com/mods" - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } - } - } - publications { - maven(MavenPublication) { - artifactId base.archivesName.get() - artifact jar - artifact sourcesJar - pom.withXml { - asNode().dependencies.dependency.each { dep -> - if (dep.groupId.text() == 'net.minecraftforge' && dep.artifactId.text() == 'forge') { - dep.parent().remove(dep) - } - } - } - } - } -} - -if (file('key.properties').exists()) { - publishMods { - def releaseProp = new Properties() - File secretPropsFile = file("key.properties") - releaseProp.load(secretPropsFile.newInputStream()) - file = jar.archiveFile - changelog = rootProject.file("changelog.txt").text - type = STABLE - modLoaders.add("neoforge") - modLoaders.add("forge") - - curseforge { - projectId = "817423" - projectSlug = "azurelib" - accessToken = releaseProp.getProperty("curseKey") - minecraftVersions.add("1.20.1") - } - modrinth { - projectId = "7zlUOZvb" - accessToken = releaseProp.getProperty('modrinthKey') - minecraftVersions.add("1.20.1") - } - } -} \ No newline at end of file diff --git a/neoforge/changelog.txt b/neoforge/changelog.txt deleted file mode 100644 index e5969dbe6..000000000 --- a/neoforge/changelog.txt +++ /dev/null @@ -1,3 +0,0 @@ -v1.0.34 - -- Adds Missing EntityPacket \ No newline at end of file diff --git a/neoforge/src/main/java/mod/azure/azurelib/ClientNonModListener.java b/neoforge/src/main/java/mod/azure/azurelib/ClientNonModListener.java deleted file mode 100644 index a646c1081..000000000 --- a/neoforge/src/main/java/mod/azure/azurelib/ClientNonModListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package mod.azure.azurelib; - -import mod.azure.azurelib.util.IncompatibleModsCheck; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.TitleScreen; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.ScreenEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -@Mod.EventBusSubscriber(Dist.CLIENT) -public class ClientNonModListener { - @SubscribeEvent - public static void onClientStart(ScreenEvent.Init.Post event) { - if (event.getScreen() instanceof TitleScreen) { - IncompatibleModsCheck.warnings(Minecraft.getInstance()); - } - } -} diff --git a/neoforge/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java b/neoforge/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java deleted file mode 100644 index 7ea6912e2..000000000 --- a/neoforge/src/main/java/mod/azure/azurelib/items/NeoForgeAzureSpawnEgg.java +++ /dev/null @@ -1,20 +0,0 @@ -package mod.azure.azurelib.items; - -import java.util.function.Supplier; - -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.Mob; -import net.minecraft.world.item.Item; -import net.minecraftforge.common.ForgeSpawnEggItem; - -public class NeoForgeAzureSpawnEgg extends ForgeSpawnEggItem { - - public NeoForgeAzureSpawnEgg(Supplier> type, int primaryColor, int secondaryColor) { - super(type, primaryColor, secondaryColor, new Item.Properties().stacksTo(64)); - } - - public NeoForgeAzureSpawnEgg(EntityType type, int primaryColor, int secondaryColor) { - super(() -> type, primaryColor, secondaryColor, new Item.Properties().stacksTo(64)); - } - -} \ No newline at end of file diff --git a/neoforge/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java b/neoforge/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java deleted file mode 100644 index 952f33f39..000000000 --- a/neoforge/src/main/java/mod/azure/azurelib/platform/AzureLibEventsNeoForge.java +++ /dev/null @@ -1,155 +0,0 @@ -package mod.azure.azurelib.platform; - -import com.mojang.blaze3d.vertex.PoseStack; -import mod.azure.azurelib.cache.object.BakedGeoModel; -import mod.azure.azurelib.event.GeoRenderEvent; -import mod.azure.azurelib.platform.services.AzureEvents; -import mod.azure.azurelib.renderer.*; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraftforge.common.MinecraftForge; - -public class AzureLibEventsNeoForge implements AzureEvents { - /** - * Fire the {@link GeoRenderEvent.Block.CompileRenderLayers} event - */ - @Override - public void fireCompileBlockRenderLayers(GeoBlockRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Block.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.Block.Pre} event - */ - @Override - public boolean fireBlockPreRender(GeoBlockRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Block.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Block.Post} event - */ - @Override - public void fireBlockPostRender(GeoBlockRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Block.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Armor.CompileRenderLayers} event - */ - @Override - public void fireCompileArmorRenderLayers(GeoArmorRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Armor.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.Armor.Pre} event - */ - @Override - public boolean fireArmorPreRender(GeoArmorRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Armor.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Armor.Post} event - */ - @Override - public void fireArmorPostRender(GeoArmorRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Armor.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Entity.CompileRenderLayers} event - */ - @Override - public void fireCompileEntityRenderLayers(GeoEntityRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Entity.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.Entity.Pre} event - */ - @Override - public boolean fireEntityPreRender(GeoEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Entity.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Entity.Post} event - */ - @Override - public void fireEntityPostRender(GeoEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Entity.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.ReplacedEntity.CompileRenderLayers} event - */ - @Override - public void fireCompileReplacedEntityRenderLayers(GeoReplacedEntityRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.ReplacedEntity.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.ReplacedEntity.Pre} event - */ - @Override - public boolean fireReplacedEntityPreRender(GeoReplacedEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.ReplacedEntity.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.ReplacedEntity.Post} event - */ - @Override - public void fireReplacedEntityPostRender(GeoReplacedEntityRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.ReplacedEntity.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Item.CompileRenderLayers} event - */ - @Override - public void fireCompileItemRenderLayers(GeoItemRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Item.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.Item.Pre} event - */ - @Override - public boolean fireItemPreRender(GeoItemRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Item.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Item.Post} event - */ - @Override - public void fireItemPostRender(GeoItemRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Item.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Object.CompileRenderLayers} event - */ - @Override - public void fireCompileObjectRenderLayers(GeoObjectRenderer renderer) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Object.CompileRenderLayers(renderer)); - } - - /** - * Fire the {@link GeoRenderEvent.Object.Pre} event - */ - @Override - public boolean fireObjectPreRender(GeoObjectRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - return !MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Object.Pre(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } - - /** - * Fire the {@link GeoRenderEvent.Object.Post} event - */ - @Override - public void fireObjectPostRender(GeoObjectRenderer renderer, PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) { - MinecraftForge.EVENT_BUS.post(new GeoRenderEvent.Object.Post(renderer, poseStack, model, bufferSource, partialTick, packedLight)); - } -} diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/main/resources/META-INF/accesstransformer.cfg deleted file mode 100644 index 6ebcd7064..000000000 --- a/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ /dev/null @@ -1,15 +0,0 @@ -public net.minecraft.client.model.geom.ModelPart$Cube -public net.minecraft.client.model.geom.ModelPart f_104212_ # cubes -public net.minecraft.world.entity.Entity m_7939_()Lnet/minecraft/world/phys/Vec3; # getLeashOffset -public net.minecraft.world.entity.WalkAnimationState f_267406_ # speedOld - -public net.minecraft.client.model.AgeableListModel f_102007_ # scaleHead -public net.minecraft.client.model.AgeableListModel f_170338_ # babyYHeadOffset -public net.minecraft.client.model.AgeableListModel f_170339_ # babyZHeadOffset -public net.minecraft.client.model.AgeableListModel f_102010_ # babyHeadScale -public net.minecraft.client.model.AgeableListModel f_102011_ # babyBodyScale -public net.minecraft.client.model.AgeableListModel f_102012_ # bodyYOffset - -public-f net.minecraft.client.renderer.LevelRenderer f_109464_ # renderBuffers -public-f net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer m_289609_(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/world/item/ArmorItem;Lnet/minecraft/client/model/HumanoidModel;ZFFFLjava/lang/String;)V # renderModel -public com.mojang.blaze3d.vertex.BufferBuilder f_85661_ # building \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bfbf75323..27dfb0b6c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,5 +24,5 @@ plugins { rootProject.name = 'AzureLib 1.20.1' include("common") include("fabric") -include("neoforge") +include("neo")