Showing 542 changed files with 14,524 additions and 11,888 deletions.
28 changes: 28 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
tab_width = 4

[*.gradle]
indent_style = tab

[*.java]
indent_style = tab
max_line_length = off
ij_continuation_indent_size = 8 # IntelliJ default
# Wrapping and Braces
ij_java_keep_simple_blocks_in_one_line = true
ij_java_keep_simple_classes_in_one_line = true
ij_java_keep_simple_lambdas_in_one_line = true
ij_java_keep_simple_methods_in_one_line = true

[*.{json,properties}]
indent_style = space
indent_size = 2

[.editorconfig]
indent_style = space
indent_size = 4
4 changes: 3 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ body:
description: What version of Minecraft are you running? If you do not know what version you are using, look in the bottom left corner of the main menu in game.
options:
- "1.21.0/1.21.1"
- "1.21.2/1.21.3"
- "1.21.4"
validations:
required: true
- type: input
id: skyblocker-version
attributes:
label: Skyblocker Version
description: What version of Skyblocker are you running? Every part is important! If you do not know what version you are using, look at the file name in your "mods" folder.
placeholder: ex. skyblocker-1.21.0+1.21.0.jar
placeholder: ex. skyblocker-1.22.2+1.21.3.jar
validations:
required: true
- type: textarea
Expand Down
4 changes: 3 additions & 1 deletion .github/ISSUE_TEMPLATE/crash_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ body:
description: What version of Minecraft are you running? If you do not know what version you are using, look in the bottom left corner of the main menu in game.
options:
- "1.21.0/1.21.1"
- "1.21.2/1.21.3"
- "1.21.4"
validations:
required: true
- type: input
id: skyblocker-version
attributes:
label: Skyblocker Version
description: What version of Skyblocker are you running? Every part is important! If you do not know what version you are using, look at the file name in your "mods" folder.
placeholder: ex. skyblocker-1.21.0+1.21.0.jar
placeholder: ex. skyblocker-1.22.2+1.21.3.jar
validations:
required: true
- type: textarea
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
\
[![Discord](https://img.shields.io/discord/879732108745125969?logo=discord&labelColor=cecece&color=7289DA&label=)](https://discord.com/invite/aNNJHQykck)

Hypixel Skyblock Mod for Minecraft 1.21 & 1.21.1
Hypixel Skyblock Mod for Minecraft 1.21.4

Installation guide is [here](https://github.com/SkyblockerMod/Skyblocker/wiki/installation) or use our [Modpack](https://modrinth.com/modpack/skyblocker-modpack)
## Features
Expand Down
9 changes: 5 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
plugins {
id 'fabric-loom' version '1.7-SNAPSHOT'
id 'fabric-loom' version '1.9-SNAPSHOT'
id 'maven-publish'
id "me.modmuss50.mod-publish-plugin" version "0.7.2"
id "me.modmuss50.mod-publish-plugin" version "0.8.1"
id "de.hysky.skyblocker.annotation-processor"
}

version = "${project.mod_version}+${project.minecraft_version}"
Expand Down Expand Up @@ -226,7 +227,7 @@ java {

jar {
from("LICENSE") {
rename { "${it}_${base.archivesName.get()}"}
rename { "${it}_${base.archivesName.get()}" }
}
}

Expand Down Expand Up @@ -285,4 +286,4 @@ publishing {
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}
}
22 changes: 22 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'java-gradle-plugin'
}

repositories {
mavenCentral()
}

dependencies {
implementation "org.ow2.asm:asm:${project.asm_version}"
implementation "org.ow2.asm:asm-tree:${project.asm_version}"
}

gradlePlugin {
plugins {
simplePlugin {
id = 'de.hysky.skyblocker.annotation-processor'
// The plugin entry point could be changed to a different class that then appropriately calls the different processors when there's more than one.
implementationClass = 'de.hysky.skyblocker.Processor'
}
}
}
1 change: 1 addition & 0 deletions buildSrc/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
asm_version=9.7
113 changes: 113 additions & 0 deletions buildSrc/src/main/java/de/hysky/skyblocker/Processor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package de.hysky.skyblocker;

import de.hysky.skyblocker.hud.HudProcessor;
import de.hysky.skyblocker.init.InitProcessor;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.compile.JavaCompile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
import java.util.function.Consumer;

public class Processor implements Plugin<Project> {

public static final Logger logger = Logging.getLogger(Processor.class);
public static File classesDir;

@Override
public void apply(@NotNull Project project) {
// https://docs.gradle.org/current/userguide/task_configuration_avoidance.html
// This only configures the `compileJava` task and not other `JavaCompile` tasks such as `compileTestJava`. https://stackoverflow.com/a/77047012
project.getTasks().withType(JavaCompile.class).named("compileJava").get().doLast(task -> {
JavaCompile javaCompile = (JavaCompile) task;
classesDir = javaCompile.getDestinationDirectory().get().getAsFile();

new InitProcessor().apply(javaCompile);
new HudProcessor().apply(javaCompile);
});
}

public static void forEachClass(@NotNull File directory, final Consumer<InputStream> consumer) {
try {
Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
if (!path.toString().endsWith(".class")) return FileVisitResult.CONTINUE;
try (InputStream inputStream = Files.newInputStream(path)) {
consumer.accept(inputStream);
} catch (IOException e) {
logger.error("Failed to run consumer on class {}", path, e);

}

return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
logger.error("Failed to walk classes", e);
}
}

public static void forEachClass(final Consumer<InputStream> consumer) {
forEachClass(classesDir, consumer);
}

public static @Nullable File findClass(File directory, String className) {
if (!className.endsWith(".class")) className += ".class";

if (!directory.isDirectory()) throw new IllegalArgumentException("Not a directory");

for (File file : Objects.requireNonNull(directory.listFiles())) {
if (file.isDirectory()) {
File foundFile = findClass(file, className);

if (foundFile != null) return foundFile;
} else if (file.getName().equals(className)) {
return file;
}
}
return null;
}

public static @Nullable File findClass(String className) {
return findClass(classesDir, className);
}

/**
* Pretty much [child instanceof superClass]
* <p>
* Classes are full name. Example: de/hysky/skyblocker/SkyblockerMod
* @param child the class to test
* @param superClass super
* @return if child is an instance of superclass
*/
public static boolean instanceOf(String child, String superClass) {
Path start = classesDir.toPath();
String sup = child;
while (sup != null) {
if (sup.equals(superClass)) return true;
Path resolve = start.resolve(sup + ".class");
try (InputStream stream = Files.newInputStream(resolve)) {
ClassReader classReader = new ClassReader(stream);
sup = classReader.getSuperName();
} catch (IOException e) {
logger.error("Failed to read class {}", resolve, e);
return false;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package de.hysky.skyblocker.hud;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

public class HudInjectClassVisitor extends ClassVisitor {

private final List<ClassNode> widgetClasses;

protected HudInjectClassVisitor(ClassVisitor delegate, List<ClassNode> widgetClasses) {
super(Opcodes.ASM9, delegate);
this.widgetClasses = widgetClasses;
}


@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);

if ((access & Opcodes.ACC_PRIVATE) != 0 && (access & Opcodes.ACC_STATIC) != 0 && name.equals("instantiateWidgets") && descriptor.equals("()V")) {
MethodNode methodNode = new MethodNode(Opcodes.ASM9, access, name, descriptor, signature, exceptions);

for (ClassNode widget : widgetClasses) {
methodNode.visitTypeInsn(Opcodes.NEW, widget.name);
methodNode.visitInsn(Opcodes.DUP);
methodNode.visitMethodInsn(Opcodes.INVOKESPECIAL, widget.name, "<init>", "()V", false);
methodNode.visitMethodInsn(Opcodes.INVOKESTATIC, "de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster", "addWidgetInstance", "(Lde/hysky/skyblocker/skyblock/tabhud/widget/HudWidget;)V", false);
}


// Return from the method
methodNode.visitInsn(Opcodes.RETURN);

// Apply our new method node to the visitor to replace the original one
methodNode.accept(methodVisitor);
}

return methodVisitor;
}
}
100 changes: 100 additions & 0 deletions buildSrc/src/main/java/de/hysky/skyblocker/hud/HudProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package de.hysky.skyblocker.hud;

import de.hysky.skyblocker.Processor;
import org.gradle.api.tasks.compile.JavaCompile;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public class HudProcessor {

private final Map<ClassNode, Integer> annotatedClassesConstructors = new HashMap<>();

public void apply(JavaCompile task) {
long start = System.currentTimeMillis();

Processor.forEachClass(this::visitClass);

List<ClassNode> constructors = new ArrayList<>(annotatedClassesConstructors.keySet());
constructors.sort(Comparator.comparingInt(annotatedClassesConstructors::get));

inject(constructors);

System.out.println("Injecting widget instancing took: " + (System.currentTimeMillis() - start) + "ms");
}

private void visitClass(InputStream inputStream) {
try {
ClassReader classReader = new ClassReader(inputStream);
ClassNode classNode = new ClassNode(Opcodes.ASM9);
classReader.accept(classNode, 0);

// Look for the annotation
boolean annotationFound = false;
int priority = 0;
List<AnnotationNode> annotationNodes = new ArrayList<>(classNode.visibleAnnotations == null ? List.of() : classNode.visibleAnnotations);
annotationNodes.addAll(classNode.invisibleAnnotations == null ? List.of() : classNode.invisibleAnnotations);
for (AnnotationNode annotationNode : annotationNodes) {
String desc = annotationNode.desc;
if (!desc.equals("Lde/hysky/skyblocker/annotations/RegisterWidget;")) continue;

annotationFound = true;
// null if no parameters are given, defaults don't show up :shrug:
if (annotationNode.values != null) {
for (int i = 0; i < annotationNode.values.size(); i++) {
if ("priority".equals(annotationNode.values.get(i))) {
priority = (int) annotationNode.values.get(i + 1);
}
}
}
break;
}
if (!annotationFound) return;
if (!Processor.instanceOf(classNode.name, "de/hysky/skyblocker/skyblock/tabhud/widget/HudWidget")) {
throw new IllegalArgumentException("Class " + classNode.name + " has @RegisterWidget annotation but does not extend HudWidget");
}

// Look for constructor
MethodNode constructor = null;
for (MethodNode method : classNode.methods) {
if (!method.name.equals("<init>")) continue;
if (!method.desc.equals("()V")) continue;
constructor = method;
break;
}
if (constructor == null) throw new IllegalStateException("No parameterless constructor found for " + classNode.name);

annotatedClassesConstructors.put(classNode, priority);



} catch (IOException e) {
throw new RuntimeException(e);
}
}

private void inject(List<ClassNode> constructors) {
Path mainClassFile = Objects.requireNonNull(Processor.findClass("ScreenMaster.class"), "ScreenMaster class wasn't found :(").toPath();

try (InputStream inputStream = Files.newInputStream(mainClassFile)) {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classReader.accept(new HudInjectClassVisitor(classWriter, constructors), 0);
try (OutputStream outputStream = Files.newOutputStream(mainClassFile)) {
outputStream.write(classWriter.toByteArray());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Loading