Add indigo support to custom block textures
This commit is contained in:
@@ -31,3 +31,8 @@ SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"
|
|||||||
path = "**/*.gradle.kts"
|
path = "**/*.gradle.kts"
|
||||||
SPDX-License-Identifier = "CC0-1.0"
|
SPDX-License-Identifier = "CC0-1.0"
|
||||||
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]
|
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = ["**/META-INF/services/*"]
|
||||||
|
SPDX-License-Identifier = "CC0-1.0"
|
||||||
|
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import moe.nea.licenseextractificator.LicenseDiscoveryTask
|
import moe.nea.licenseextractificator.LicenseDiscoveryTask
|
||||||
|
import net.fabricmc.loom.LoomGradleExtension
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -38,9 +39,12 @@ compileTestKotlin.kotlinOptions {
|
|||||||
jvmTarget = "21"
|
jvmTarget = "21"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
maven("https://maven.terraformersmc.com/releases/")
|
maven("https://maven.terraformersmc.com/releases/")
|
||||||
maven("https://maven.shedaniel.me")
|
maven("https://maven.shedaniel.me")
|
||||||
|
maven("https://maven.fabricmc.net")
|
||||||
maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1")
|
maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1")
|
||||||
maven("https://api.modrinth.com/maven") {
|
maven("https://api.modrinth.com/maven") {
|
||||||
content {
|
content {
|
||||||
@@ -73,11 +77,11 @@ repositories {
|
|||||||
excludeModule("io.github.cottonmc", "LibGui")
|
excludeModule("io.github.cottonmc", "LibGui")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven( "https://repo.hypixel.net/repository/Hypixel/")
|
maven("https://repo.hypixel.net/repository/Hypixel/")
|
||||||
maven("https://maven.azureaaron.net/snapshots")
|
maven("https://maven.azureaaron.net/snapshots")
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets.all {
|
sourceSets.all {
|
||||||
languageSettings {
|
languageSettings {
|
||||||
@@ -124,6 +128,8 @@ dependencies {
|
|||||||
modImplementation(libs.moulconfig)
|
modImplementation(libs.moulconfig)
|
||||||
modImplementation(libs.manninghamMills)
|
modImplementation(libs.manninghamMills)
|
||||||
modCompileOnly(libs.explosiveenhancement)
|
modCompileOnly(libs.explosiveenhancement)
|
||||||
|
compileOnly(project(":javaplugin"))
|
||||||
|
annotationProcessor(project(":javaplugin"))
|
||||||
include(libs.manninghamMills)
|
include(libs.manninghamMills)
|
||||||
include(libs.moulconfig)
|
include(libs.moulconfig)
|
||||||
|
|
||||||
@@ -209,8 +215,21 @@ loom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<JavaCompile> {
|
tasks.withType<JavaCompile> {
|
||||||
|
this.sourceCompatibility = "21"
|
||||||
|
this.targetCompatibility = "21"
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
options.release.set(21)
|
val module = "ALL-UNNAMED"
|
||||||
|
options.forkOptions.jvmArgs!!.addAll(listOf(
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.util=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.api=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.code=$module",
|
||||||
|
))
|
||||||
|
options.isFork = true
|
||||||
|
afterEvaluate {
|
||||||
|
options.compilerArgs.add("-Xplugin:IntermediaryNameReplacement mappingFile=${LoomGradleExtension.get(project).mappingsFile.absolutePath} sourceNs=named")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.jar {
|
tasks.jar {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ runtime_required = [
|
|||||||
runtime_optional = [
|
runtime_optional = [
|
||||||
"devauth",
|
"devauth",
|
||||||
"freecammod",
|
"freecammod",
|
||||||
"sodium",
|
# "sodium",
|
||||||
# "qolify",
|
# "qolify",
|
||||||
# "citresewn",
|
# "citresewn",
|
||||||
# "ncr",
|
# "ncr",
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
16
javaplugin/build.gradle.kts
Normal file
16
javaplugin/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
java
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation("net.fabricmc:stitch:0.6.2")
|
||||||
|
}
|
||||||
|
tasks.withType(JavaCompile::class) {
|
||||||
|
val module = "ALL-UNNAMED"
|
||||||
|
options.compilerArgs.addAll(listOf(
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.util=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.api=$module",
|
||||||
|
"--add-exports=jdk.compiler/com.sun.tools.javac.code=$module",
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import com.sun.source.tree.ClassTree;
|
||||||
|
import com.sun.source.tree.CompilationUnitTree;
|
||||||
|
import com.sun.source.tree.VariableTree;
|
||||||
|
import com.sun.source.util.TreeScanner;
|
||||||
|
import com.sun.tools.javac.code.Symbol;
|
||||||
|
import com.sun.tools.javac.tree.JCTree;
|
||||||
|
import com.sun.tools.javac.tree.TreeMaker;
|
||||||
|
import com.sun.tools.javac.util.List;
|
||||||
|
import com.sun.tools.javac.util.Names;
|
||||||
|
|
||||||
|
public class InitReplacer extends TreeScanner<Void, Void> {
|
||||||
|
private final MappingTree mappingTree;
|
||||||
|
private final TreeMaker treeMaker;
|
||||||
|
private final Names names;
|
||||||
|
private final IntermediaryNameResolutionTask plugin;
|
||||||
|
private Symbol.ClassSymbol classTree;
|
||||||
|
private CompilationUnitTree compilationUnitTree;
|
||||||
|
|
||||||
|
public InitReplacer(MappingTree mappingTree, IntermediaryNameResolutionTask plugin) {
|
||||||
|
this.mappingTree = mappingTree;
|
||||||
|
this.treeMaker = plugin.treeMaker;
|
||||||
|
this.names = plugin.names;
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitClass(ClassTree node, Void unused) {
|
||||||
|
this.classTree = plugin.utils.getSymbol(node);
|
||||||
|
return super.visitClass(node, unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitCompilationUnit(CompilationUnitTree node, Void unused) {
|
||||||
|
this.compilationUnitTree = node;
|
||||||
|
return super.visitCompilationUnit(node, unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitVariable(VariableTree node, Void unused) {
|
||||||
|
var annotation = node
|
||||||
|
.getModifiers().getAnnotations()
|
||||||
|
.stream()
|
||||||
|
.filter(it -> it.getAnnotationType().toString().equals("IntermediaryName")) // Crazy type-safety!
|
||||||
|
.findAny();
|
||||||
|
if (annotation.isEmpty())
|
||||||
|
return super.visitVariable(node, unused);
|
||||||
|
var jcAnnotation = (JCTree.JCAnnotation) annotation.get();
|
||||||
|
var jcNode = (JCTree.JCVariableDecl) node;
|
||||||
|
if (node.getInitializer() != null) {
|
||||||
|
plugin.utils.reportError(
|
||||||
|
compilationUnitTree.getSourceFile(),
|
||||||
|
jcNode.getInitializer(),
|
||||||
|
"Providing an initializer for a variable is illegal for @IntermediaryName annotated fields"
|
||||||
|
);
|
||||||
|
return super.visitVariable(node, unused);
|
||||||
|
}
|
||||||
|
var target = plugin.utils.getAnnotationValue(jcAnnotation, "value");
|
||||||
|
var targetClass = plugin.utils.resolveClassLiteralExpression(target).tsym.flatName().toString();
|
||||||
|
var intermediaryClass = mappingTree.resolveClassToIntermediary(targetClass);
|
||||||
|
var remapper = treeMaker.Select(treeMaker.This(classTree.type), names.fromString("remapper"));
|
||||||
|
var remappingCall = treeMaker.Apply(
|
||||||
|
List.nil(),
|
||||||
|
treeMaker.Select(remapper, names.fromString("mapClassName")),
|
||||||
|
List.of(treeMaker.Literal("intermediary"),
|
||||||
|
treeMaker.Literal(intermediaryClass)));
|
||||||
|
jcNode.init = remappingCall;
|
||||||
|
jcNode.mods.annotations = List.filter(jcNode.mods.annotations, jcAnnotation);
|
||||||
|
return super.visitVariable(node, unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import com.sun.source.tree.CompilationUnitTree;
|
||||||
|
import com.sun.source.tree.ExpressionTree;
|
||||||
|
import com.sun.source.tree.MethodInvocationTree;
|
||||||
|
import com.sun.source.util.TreeScanner;
|
||||||
|
import com.sun.tools.javac.tree.JCTree;
|
||||||
|
import com.sun.tools.javac.util.List;
|
||||||
|
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
|
public class IntermediaryMethodReplacer extends TreeScanner<Void, Void> {
|
||||||
|
private final MappingTree mappings;
|
||||||
|
private final IntermediaryNameResolutionTask plugin;
|
||||||
|
private JavaFileObject sourceFile;
|
||||||
|
private CompilationUnitTree compilationUnit;
|
||||||
|
|
||||||
|
public IntermediaryMethodReplacer(MappingTree mappings, IntermediaryNameResolutionTask plugin) {
|
||||||
|
this.mappings = mappings;
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitCompilationUnit(CompilationUnitTree node, Void unused) {
|
||||||
|
sourceFile = node.getSourceFile();
|
||||||
|
compilationUnit = node;
|
||||||
|
return super.visitCompilationUnit(node, unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replaceMethodName(JCTree.JCMethodInvocation node) {
|
||||||
|
var select = node.getMethodSelect();
|
||||||
|
if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return;
|
||||||
|
if (!fieldAccess.name.contentEquals("methodName")) return;
|
||||||
|
if (!(node.args.head instanceof JCTree.JCMemberReference methodReference)) {
|
||||||
|
plugin.utils.reportError(sourceFile, node, "Please provide a Class::method reference directly (and nothing else)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var clearName = methodReference.name.toString();
|
||||||
|
var classRef = methodReference.expr;
|
||||||
|
var type = plugin.utils.resolveClassName(classRef, compilationUnit);
|
||||||
|
var intermediaryName = mappings.resolveMethodToIntermediary(
|
||||||
|
type.tsym.flatName().toString(),
|
||||||
|
clearName
|
||||||
|
);
|
||||||
|
fieldAccess.name = plugin.names.fromString("id");
|
||||||
|
node.args = List.of(plugin.treeMaker.Literal(intermediaryName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replaceClassName(JCTree.JCMethodInvocation node) {
|
||||||
|
var select = node.getMethodSelect();
|
||||||
|
if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return;
|
||||||
|
if (!fieldAccess.name.contentEquals("className")) return;
|
||||||
|
if (node.getTypeArguments().size() != 1) {
|
||||||
|
plugin.utils.reportError(sourceFile, node, "You need to explicitly provide the class you want the intermediary name for");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var head = node.typeargs.head;
|
||||||
|
var resolved = plugin.utils.resolveClassName(head, compilationUnit);
|
||||||
|
var mappedName = mappings.resolveClassToIntermediary(resolved.tsym.flatName().toString());
|
||||||
|
fieldAccess.name = plugin.names.fromString("id");
|
||||||
|
node.typeargs = List.nil();
|
||||||
|
node.args = List.of(plugin.treeMaker.Literal(mappedName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
|
||||||
|
replaceClassName((JCTree.JCMethodInvocation) node);
|
||||||
|
replaceMethodName((JCTree.JCMethodInvocation) node);
|
||||||
|
return super.visitMethodInvocation(node, unused);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import com.sun.source.util.JavacTask;
|
||||||
|
import com.sun.source.util.Plugin;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class IntermediaryNameResolutionPlugin implements Plugin {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "IntermediaryNameReplacement";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(JavacTask task, String... args) {
|
||||||
|
Map<String, String> argMap = new HashMap<>();
|
||||||
|
for (String arg : args) {
|
||||||
|
String[] parts = arg.split("=", 2);
|
||||||
|
argMap.put(parts[0], parts.length == 2 ? parts[1] : "true");
|
||||||
|
}
|
||||||
|
task.addTaskListener(new IntermediaryNameResolutionTask(this, task, argMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import com.sun.source.util.JavacTask;
|
||||||
|
import com.sun.source.util.TaskEvent;
|
||||||
|
import com.sun.source.util.TaskListener;
|
||||||
|
import com.sun.tools.javac.api.BasicJavacTask;
|
||||||
|
import com.sun.tools.javac.tree.TreeMaker;
|
||||||
|
import com.sun.tools.javac.util.Names;
|
||||||
|
import net.fabricmc.stitch.commands.tinyv2.TinyV2Reader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class IntermediaryNameResolutionTask implements TaskListener {
|
||||||
|
TreeMaker treeMaker;
|
||||||
|
Names names;
|
||||||
|
MappingTree mappings;
|
||||||
|
Utils utils;
|
||||||
|
|
||||||
|
public IntermediaryNameResolutionTask(IntermediaryNameResolutionPlugin intermediaryNameResolutionPlugin, JavacTask task, Map<String, String> argMap) {
|
||||||
|
var context = ((BasicJavacTask) task).getContext();
|
||||||
|
var mappingFile = new File(argMap.get("mappingFile"));
|
||||||
|
System.err.println("Loading mappings from " + mappingFile);
|
||||||
|
try {
|
||||||
|
var tinyV2File = TinyV2Reader.read(mappingFile.toPath());
|
||||||
|
mappings = new MappingTree(tinyV2File, argMap.get("sourceNs"), argMap.getOrDefault("targetNs", "intermediary"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
treeMaker = TreeMaker.instance(context);
|
||||||
|
names = Names.instance(context);
|
||||||
|
utils = Utils.instance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finished(TaskEvent e) {
|
||||||
|
if (e.getKind() != TaskEvent.Kind.ENTER) return;
|
||||||
|
if (e.getCompilationUnit() == null || e.getSourceFile() == null) return;
|
||||||
|
e.getCompilationUnit().accept(new InitReplacer(mappings, this), null);
|
||||||
|
e.getCompilationUnit().accept(new IntermediaryMethodReplacer(mappings, this), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import net.fabricmc.stitch.commands.tinyv2.TinyClass;
|
||||||
|
import net.fabricmc.stitch.commands.tinyv2.TinyFile;
|
||||||
|
import net.fabricmc.stitch.commands.tinyv2.TinyMethod;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class MappingTree {
|
||||||
|
|
||||||
|
private final Map<String, TinyClass> classLookup;
|
||||||
|
private final int targetIndex;
|
||||||
|
private final int sourceIndex;
|
||||||
|
|
||||||
|
public MappingTree(TinyFile tinyV2File, String sourceNamespace, String targetNamespace) {
|
||||||
|
sourceIndex = tinyV2File.getHeader().getNamespaces().indexOf(sourceNamespace);
|
||||||
|
if (sourceIndex < 0)
|
||||||
|
throw new RuntimeException("Could not find source namespace " + sourceNamespace + " in mappings file.");
|
||||||
|
this.classLookup = tinyV2File
|
||||||
|
.getClassEntries()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it));
|
||||||
|
targetIndex = tinyV2File.getHeader().getNamespaces().indexOf(targetNamespace);
|
||||||
|
if (targetIndex < 0)
|
||||||
|
throw new RuntimeException("Could not find target namespace " + targetNamespace + " in mappings file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveMethodToIntermediary(String className, String methodName) {
|
||||||
|
var classData = classLookup.get(className.replace(".", "/"));
|
||||||
|
TinyMethod candidate = null;
|
||||||
|
for (TinyMethod method : classData.getMethods()) {
|
||||||
|
if (method.getMethodNames().get(sourceIndex).equals(methodName)) {
|
||||||
|
if (candidate != null) {
|
||||||
|
throw new RuntimeException("Found two candidates for method " + className + "." + methodName);
|
||||||
|
}
|
||||||
|
candidate = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidate.getMethodNames().get(targetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveClassToIntermediary(String className) {
|
||||||
|
return classLookup.get(className.replace(".", "/"))
|
||||||
|
.getClassNames().get(targetIndex)
|
||||||
|
.replace("/", ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
121
javaplugin/src/main/java/moe/nea/firmament/javaplugin/Utils.java
Normal file
121
javaplugin/src/main/java/moe/nea/firmament/javaplugin/Utils.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package moe.nea.firmament.javaplugin;
|
||||||
|
|
||||||
|
import com.sun.source.tree.AnnotationTree;
|
||||||
|
import com.sun.source.tree.AssignmentTree;
|
||||||
|
import com.sun.source.tree.ClassTree;
|
||||||
|
import com.sun.source.tree.CompilationUnitTree;
|
||||||
|
import com.sun.source.tree.ExpressionTree;
|
||||||
|
import com.sun.source.tree.IdentifierTree;
|
||||||
|
import com.sun.source.tree.MemberSelectTree;
|
||||||
|
import com.sun.source.tree.Tree;
|
||||||
|
import com.sun.tools.javac.comp.Attr;
|
||||||
|
import com.sun.tools.javac.code.Symbol;
|
||||||
|
import com.sun.tools.javac.code.Type;
|
||||||
|
import com.sun.tools.javac.code.Types;
|
||||||
|
import com.sun.tools.javac.comp.AttrContext;
|
||||||
|
import com.sun.tools.javac.comp.Enter;
|
||||||
|
import com.sun.tools.javac.comp.Env;
|
||||||
|
import com.sun.tools.javac.tree.JCTree;
|
||||||
|
import com.sun.tools.javac.util.Context;
|
||||||
|
import com.sun.tools.javac.util.JCDiagnostic;
|
||||||
|
import com.sun.tools.javac.util.JavacMessages;
|
||||||
|
import com.sun.tools.javac.util.Log;
|
||||||
|
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import java.util.ListResourceBundle;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
private static final Context.Key<Utils> KEY = new Context.Key<>();
|
||||||
|
private final Log log;
|
||||||
|
private final JCDiagnostic.Factory diagnostics;
|
||||||
|
private final Types types;
|
||||||
|
private final Attr attr;
|
||||||
|
private final Enter enter;
|
||||||
|
|
||||||
|
private Utils(Context context) {
|
||||||
|
context.put(KEY, this);
|
||||||
|
JavacMessages.instance(context).add(l -> new ListResourceBundle() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object[][] getContents() {
|
||||||
|
return new Object[][]{
|
||||||
|
new Object[]{"compiler.err.firmament.generic", "{0}"}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log = Log.instance(context);
|
||||||
|
diagnostics = JCDiagnostic.Factory.instance(context);
|
||||||
|
types = Types.instance(context);
|
||||||
|
attr = Attr.instance(context);
|
||||||
|
enter = Enter.instance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Utils instance(Context context) {
|
||||||
|
var utils = context.get(KEY);
|
||||||
|
if (utils == null) {
|
||||||
|
utils = new Utils(context);
|
||||||
|
}
|
||||||
|
return utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type resolveClassName(ExpressionTree expression) {
|
||||||
|
var tree = (JCTree) expression;
|
||||||
|
return tree.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type resolveClassName(ExpressionTree tree, CompilationUnitTree unit) {
|
||||||
|
return resolveClassName(tree, enter.getTopLevelEnv((JCTree.JCCompilationUnit) unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type resolveClassName(ExpressionTree tree, Env<AttrContext> env) {
|
||||||
|
var t = resolveClassName(tree);
|
||||||
|
if (t != null) return t;
|
||||||
|
return attr.attribType((JCTree) tree, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Symbol getSymbol(IdentifierTree tree) {
|
||||||
|
return ((JCTree.JCIdent) tree).sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Symbol.ClassSymbol getSymbol(ClassTree tree) {
|
||||||
|
return ((JCTree.JCClassDecl) tree).sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionTree getAnnotationValue(
|
||||||
|
AnnotationTree tree,
|
||||||
|
String name) {
|
||||||
|
// TODO: strip parenthesis
|
||||||
|
for (var argument : tree.getArguments()) {
|
||||||
|
var assignment = (AssignmentTree) argument;
|
||||||
|
if (((IdentifierTree) assignment.getVariable()).getName().toString().equals(name))
|
||||||
|
return assignment.getExpression();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type.ClassType resolveClassLiteralExpression(ExpressionTree tree) {
|
||||||
|
if (!(tree instanceof MemberSelectTree select))
|
||||||
|
throw new RuntimeException("Cannot resolve non field access class literal: " + tree);
|
||||||
|
if (!select.getIdentifier().toString().equals("class"))
|
||||||
|
throw new RuntimeException("Class literal " + select + "accessed non .class attribute");
|
||||||
|
|
||||||
|
return (Type.ClassType) resolveClassName(select.getExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reportError(
|
||||||
|
JavaFileObject file,
|
||||||
|
Tree node,
|
||||||
|
String message
|
||||||
|
) {
|
||||||
|
var originalSource = log.useSource(file);
|
||||||
|
var error = diagnostics.error(
|
||||||
|
JCDiagnostic.DiagnosticFlag.API,
|
||||||
|
log.currentSource(),
|
||||||
|
((JCTree) node).pos(),
|
||||||
|
"firmament.generic",
|
||||||
|
message
|
||||||
|
);
|
||||||
|
log.report(error);
|
||||||
|
log.useSource(originalSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
moe.nea.firmament.javaplugin.IntermediaryNameResolutionPlugin
|
||||||
@@ -33,3 +33,4 @@ pluginManagement {
|
|||||||
rootProject.name = "Firmament"
|
rootProject.name = "Firmament"
|
||||||
|
|
||||||
include("symbols")
|
include("symbols")
|
||||||
|
include("javaplugin")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.init;
|
package moe.nea.firmament.init;
|
||||||
|
|
||||||
import me.shedaniel.mm.api.ClassTinkerers;
|
import me.shedaniel.mm.api.ClassTinkerers;
|
||||||
@@ -14,11 +13,15 @@ import java.lang.reflect.Modifier;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ClientPlayerRiser extends RiserUtils {
|
public class ClientPlayerRiser extends RiserUtils {
|
||||||
String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657");
|
@IntermediaryName(net.minecraft.entity.player.PlayerEntity.class)
|
||||||
String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937");
|
String PlayerEntity;
|
||||||
|
@IntermediaryName(net.minecraft.world.World.class)
|
||||||
|
String World;
|
||||||
String GameProfile = "com.mojang.authlib.GameProfile";
|
String GameProfile = "com.mojang.authlib.GameProfile";
|
||||||
String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338");
|
@IntermediaryName(net.minecraft.util.math.BlockPos.class)
|
||||||
String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742");
|
String BlockPos;
|
||||||
|
@IntermediaryName(net.minecraft.client.network.AbstractClientPlayerEntity.class)
|
||||||
|
String AbstractClientPlayerEntity;
|
||||||
String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer";
|
String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer";
|
||||||
// World world, BlockPos pos, float yaw, GameProfile gameProfile
|
// World world, BlockPos pos, float yaw, GameProfile gameProfile
|
||||||
Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile));
|
Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile));
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public class EarlyRiser implements Runnable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
new ClientPlayerRiser().addTinkerers();
|
new ClientPlayerRiser().addTinkerers();
|
||||||
new HandledScreenRiser().addTinkerers();
|
new HandledScreenRiser().addTinkerers();
|
||||||
|
new SectionBuilderRiser().addTinkerers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
package moe.nea.firmament.init;
|
package moe.nea.firmament.init;
|
||||||
|
|
||||||
import me.shedaniel.mm.api.ClassTinkerers;
|
import me.shedaniel.mm.api.ClassTinkerers;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
@@ -17,8 +19,10 @@ import org.objectweb.asm.tree.VarInsnNode;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
public class HandledScreenRiser extends RiserUtils {
|
public class HandledScreenRiser extends RiserUtils {
|
||||||
String Screen = remapper.mapClassName("intermediary", "net.minecraft.class_437");
|
@IntermediaryName(net.minecraft.client.gui.screen.Screen.class)
|
||||||
String HandledScreen = remapper.mapClassName("intermediary", "net.minecraft.class_465");
|
String Screen;
|
||||||
|
@IntermediaryName(net.minecraft.client.gui.screen.ingame.HandledScreen.class)
|
||||||
|
String HandledScreen;
|
||||||
Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
|
Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
|
||||||
String mouseScrolled = remapper.mapMethodName("intermediary", "net.minecraft.class_364", "method_25401",
|
String mouseScrolled = remapper.mapMethodName("intermediary", "net.minecraft.class_364", "method_25401",
|
||||||
mouseScrolledDesc.getDescriptor());
|
mouseScrolledDesc.getDescriptor());
|
||||||
|
|||||||
63
src/main/java/moe/nea/firmament/init/Intermediary.java
Normal file
63
src/main/java/moe/nea/firmament/init/Intermediary.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package moe.nea.firmament.init;
|
||||||
|
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.fabricmc.loader.api.MappingResolver;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Intermediary {
|
||||||
|
private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver();
|
||||||
|
|
||||||
|
static String methodName(Object object) {
|
||||||
|
throw new AssertionError("Cannot be called at runtime");
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> String className() {
|
||||||
|
throw new AssertionError("Cannot be called at runtime");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String id(String source) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public record Class(
|
||||||
|
// Type intermediaryClass
|
||||||
|
// ) {
|
||||||
|
// public Class(String intermediaryClass) {
|
||||||
|
// this(Type.getObjectType(intermediaryClass.replace('.', '/')));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public String getMappedName() {
|
||||||
|
// return RESOLVER.mapClassName("intermediary", intermediaryClass.getInternalName()
|
||||||
|
// .replace('/', '.'));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public record Method(
|
||||||
|
// Type intermediaryClassName,
|
||||||
|
// String intermediaryMethodName,
|
||||||
|
// Type intermediaryReturnType,
|
||||||
|
// List<Type> intermediaryArgumentTypes
|
||||||
|
// ) {
|
||||||
|
// public Method(
|
||||||
|
// String intermediaryClassName,
|
||||||
|
// String intermediaryMethodName,
|
||||||
|
// String intermediaryReturnType,
|
||||||
|
// String... intermediaryArgumentTypes
|
||||||
|
// ) {
|
||||||
|
// this(intermediaryClassName, intermediaryMethodName, intermediaryReturnType, List.of(intermediaryArgumentTypes));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public String getMappedMethodName() {
|
||||||
|
// return RESOLVER.mapMethodName("intermediary",
|
||||||
|
// intermediaryClassName.getInternalName().replace('/', '.'));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public Type getIntermediaryDescriptor() {
|
||||||
|
// return Type.getMethodType(intermediaryReturnType, intermediaryArgumentTypes.toArray(Type[]::new));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
}
|
||||||
21
src/main/java/moe/nea/firmament/init/IntermediaryName.java
Normal file
21
src/main/java/moe/nea/firmament/init/IntermediaryName.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package moe.nea.firmament.init;
|
||||||
|
|
||||||
|
import net.fabricmc.loader.api.MappingResolver;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects the intermediary name of the given field into this field by replacing its initializer with a call to
|
||||||
|
* {@link MappingResolver#mapClassName(String, String)}
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface IntermediaryName {
|
||||||
|
// String method() default "";
|
||||||
|
//
|
||||||
|
// String field() default "";
|
||||||
|
Class<?> value();
|
||||||
|
}
|
||||||
116
src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java
Normal file
116
src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package moe.nea.firmament.init;
|
||||||
|
|
||||||
|
import me.shedaniel.mm.api.ClassTinkerers;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.client.render.block.BlockModels;
|
||||||
|
import net.minecraft.client.render.block.BlockRenderManager;
|
||||||
|
import net.minecraft.client.render.chunk.SectionBuilder;
|
||||||
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.LocalVariableNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
|
public class SectionBuilderRiser extends RiserUtils {
|
||||||
|
|
||||||
|
@IntermediaryName(SectionBuilder.class)
|
||||||
|
String SectionBuilder;
|
||||||
|
@IntermediaryName(BlockPos.class)
|
||||||
|
String BlockPos;
|
||||||
|
@IntermediaryName(BlockRenderManager.class)
|
||||||
|
String BlockRenderManager;
|
||||||
|
@IntermediaryName(BlockState.class)
|
||||||
|
String BlockState;
|
||||||
|
@IntermediaryName(BakedModel.class)
|
||||||
|
String BakedModel;
|
||||||
|
String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures";
|
||||||
|
|
||||||
|
Type getModelDesc = Type.getMethodType(
|
||||||
|
getTypeForClassName(BlockRenderManager),
|
||||||
|
getTypeForClassName(BlockState)
|
||||||
|
);
|
||||||
|
String getModel = remapper.mapMethodName(
|
||||||
|
"intermediary",
|
||||||
|
Intermediary.<BlockRenderManager>className(),
|
||||||
|
Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel),
|
||||||
|
Type.getMethodDescriptor(
|
||||||
|
getTypeForClassName(Intermediary.<BakedModel>className()),
|
||||||
|
getTypeForClassName(Intermediary.<BlockState>className())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTinkerers() {
|
||||||
|
if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo"))
|
||||||
|
ClassTinkerers.addTransformation(SectionBuilder, this::handle, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handle(ClassNode classNode) {
|
||||||
|
for (MethodNode method : classNode.methods) {
|
||||||
|
if (method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate") &&
|
||||||
|
method.name.startsWith("redirect$")) {
|
||||||
|
handleIndigo(method);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new RuntimeException("Could not inject tesselation hook despite fabric renderer indigo being loaded").printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIndigo(MethodNode method) {
|
||||||
|
LocalVariableNode blockPosVar = null, blockStateVar = null;
|
||||||
|
for (LocalVariableNode localVariable : method.localVariables) {
|
||||||
|
if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) {
|
||||||
|
blockPosVar = localVariable;
|
||||||
|
}
|
||||||
|
if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) {
|
||||||
|
blockStateVar = localVariable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockPosVar == null || blockStateVar == null) {
|
||||||
|
System.err.println("Firmament could inject into indigo: missing either block pos or blockstate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (AbstractInsnNode instruction : method.instructions) {
|
||||||
|
if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue;
|
||||||
|
var methodInsn = (MethodInsnNode) instruction;
|
||||||
|
if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager))))
|
||||||
|
continue;
|
||||||
|
method.instructions.insertBefore(
|
||||||
|
methodInsn,
|
||||||
|
new MethodInsnNode(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
getTypeForClassName(CustomBlockTextures).getInternalName(),
|
||||||
|
"enterFallbackCall",
|
||||||
|
Type.getMethodDescriptor(Type.VOID_TYPE)
|
||||||
|
));
|
||||||
|
|
||||||
|
var insnList = new InsnList();
|
||||||
|
insnList.add(new MethodInsnNode(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
getTypeForClassName(CustomBlockTextures).getInternalName(),
|
||||||
|
"exitFallbackCall",
|
||||||
|
Type.getMethodDescriptor(Type.VOID_TYPE)
|
||||||
|
));
|
||||||
|
insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index));
|
||||||
|
insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index));
|
||||||
|
insnList.add(new MethodInsnNode(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
getTypeForClassName(CustomBlockTextures).getInternalName(),
|
||||||
|
"patchIndigo",
|
||||||
|
Type.getMethodDescriptor(getTypeForClassName(BakedModel),
|
||||||
|
getTypeForClassName(BakedModel),
|
||||||
|
getTypeForClassName(BlockPos),
|
||||||
|
getTypeForClassName(BlockState)),
|
||||||
|
false
|
||||||
|
));
|
||||||
|
method.instructions.insert(methodInsn, insnList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import kotlinx.coroutines.plus
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import net.minecraft.client.render.chunk.SectionBuilder
|
||||||
import net.minecraft.command.CommandRegistryAccess
|
import net.minecraft.command.CommandRegistryAccess
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import moe.nea.firmament.commands.registerFirmamentCommand
|
import moe.nea.firmament.commands.registerFirmamentCommand
|
||||||
@@ -112,6 +113,8 @@ object Firmament {
|
|||||||
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
|
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
|
||||||
TickEvent.publish(TickEvent(tick++))
|
TickEvent.publish(TickEvent(tick++))
|
||||||
})
|
})
|
||||||
|
// TODO: remove me
|
||||||
|
Class.forName(SectionBuilder::class.java.name)
|
||||||
IDataHolder.registerEvents()
|
IDataHolder.registerEvents()
|
||||||
RepoManager.initialize()
|
RepoManager.initialize()
|
||||||
SBData.init()
|
SBData.init()
|
||||||
|
|||||||
@@ -157,7 +157,10 @@ object CustomBlockTextures {
|
|||||||
currentIslandReplacements = replacements
|
currentIslandReplacements = replacements
|
||||||
if (lastReplacements != replacements) {
|
if (lastReplacements != replacements) {
|
||||||
MC.nextTick {
|
MC.nextTick {
|
||||||
MC.worldRenderer.reload()
|
MC.worldRenderer.chunks?.chunks?.forEach {
|
||||||
|
// false schedules rebuilds outside a 27 block radius to happen async
|
||||||
|
it.scheduleRebuild(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +262,10 @@ object CustomBlockTextures {
|
|||||||
return BakedReplacements(map.mapValues { LocationReplacements(it.value) })
|
return BakedReplacements(map.mapValues { LocationReplacements(it.value) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel {
|
||||||
|
return getReplacementModel(state, pos) ?: orig
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onStart(event: FinalizeResourceManagerEvent) {
|
fun onStart(event: FinalizeResourceManagerEvent) {
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ mutable field net/minecraft/screen/slot/Slot x I
|
|||||||
mutable field net/minecraft/screen/slot/Slot y I
|
mutable field net/minecraft/screen/slot/Slot y I
|
||||||
|
|
||||||
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
|
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
|
||||||
|
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
|
||||||
|
|||||||
Reference in New Issue
Block a user