Basically getting this thing to work

This commit is contained in:
Astatin3
2024-07-12 10:15:00 -06:00
parent 32a74555a3
commit 2f2a89b26a
7 changed files with 356 additions and 82 deletions
+3
View File
@@ -1,3 +1,6 @@
run/
build/
# Compiled class file # Compiled class file
*.class *.class
+5 -80
View File
@@ -1,81 +1,6 @@
# Meteor Addon Template # No More Render
A mod that will run the latest version of Fabric without any gui!
A template to allow easy usage of the Meteor Addon API. ### Todo:
- Remove meteorclient refrences
### How to use - Make the CLI better
- Clone this project
- Use this template to create new modules/commands
- Build the executable using the gradle `build` task.
- Run the mod with Meteor.
### Project structure
```text
.
│── .github
│ ╰── workflows
│ │── dev_build.yml
│ ╰── pull_request.yml
│── gradle
│ ╰── wrapper
│ │── gradle-wrapper.jar
│ ╰── gradle-wrapper.properties
│── src
│ ╰── main
│ │── java
│ │ ╰── com
│ │ ╰── example
│ │ ╰── addon
│ │ │── commands
│ │ │ ╰── CommandExample
│ │ │── hud
│ │ │ ╰── HudExample
│ │ │── modules
│ │ │ ╰── ModuleExample
│ │ ╰── AddonTemplate
│ ╰── resources
│ │── assets
│ │ ╰── template
│ │ ╰── icon.png
│ │── addon-template.mixins.json
│ ╰── fabric.mod.json
│── .editorconfig
│── .gitignore
│── build.gradle
│── gradle.properties
│── gradlew
│── gradlew.bat
│── LICENSE
│── README.md
╰── settings.gradle
```
This is the default project structure. Each folder/file has a specific purpose.
Here is a brief explanation of the ones you might need to modify:
- `.github/workflows`: Contains the GitHub Actions configuration files.
- `gradle`: Contains the Gradle wrapper files.
Edit the `gradle.properties` file to change the version of the Gradle wrapper.
- `src/main/java/com/example/addon`: Contains the main class of the addon.
Here you can register your custom commands, modules, and HUDs.
Edit the `getPackage` method to reflect the package of your addon.
- `src/main/resources`: Contains the resources of the addon.
- `assets`: Contains the assets of the addon.
You can add your own assets here, separated in subfolders.
- `template`: Contains the assets of the template.
You can replace the `icon.png` file with your own addon icon.
Also, rename this folder to reflect the name of your addon.
- `addon-template.mixins.json`: Contains the Mixin configuration for the addon.
You can add your own mixins in the `client` array.
- `fabric.mod.json`: Contains the metadata of the addon.
Edit the various fields to reflect the metadata of your addon.
- `build.gradle`: Contains the Gradle build script.
You can manage the dependencies of the addon here.
Remember to keep the `fabric-loom` version up-to-date.
- `gradle.properties`: Contains the properties of the Gradle build.
These will be used by the build script.
- `LICENSE`: Contains the license of the addon.
You can edit this file to change the license of your addon.
- `README.md`: Contains the documentation of the addon.
You can edit this file to reflect the documentation of your addon, and showcase its features.
@@ -3,7 +3,9 @@
"package": "com.example.addon.mixin", "package": "com.example.addon.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"client": [ "client": [
"MixinGameRenderer" "HeadlessWindow",
"HeadlessMinecraftClient",
"HeadlessWidgetAccessor"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
@@ -0,0 +1,284 @@
package com.example.addon.mixin;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.RunArgs;
import net.minecraft.client.gl.WindowFramebuffer;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.world.WorldListWidget;
import net.minecraft.client.gui.widget.*;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
@Mixin(MinecraftClient.class)
public class HeadlessMinecraftClient {
// @Inject(method = "<init>", at = @At("RETURN"))
// private void onInit(CallbackInfo ci) {
// startCommandThread();
// System.out.println("#########################################");
// }
@Shadow private Thread thread;
@Inject(method = "<init>", at = @At("TAIL"))
private void onGameLoaded(RunArgs args, CallbackInfo ci) {
// AddonTemplate.LOG.info("Hello from ExampleMixin!");
System.out.println("#########################################");
startCommandThread();
}
private void startCommandThread() {
Thread commandThread = new Thread(() -> {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
try {
while ((line = reader.readLine()) != null) {
parseCommand(line);
}
} catch (Exception e) {
e.printStackTrace();
}
});
commandThread.setName("Headless Command Thread");
commandThread.setDaemon(true);
commandThread.start();
}
MinecraftClient self = (MinecraftClient)(Object)this;
private void parseCommand(String command) {
String[] split = command.split(" ");
try {
switch (split[0].toLowerCase()) {
case "quit":
case "exit":
self.stop();
break;
case "tick":
self.tick();
break;
case "listelements":
case "elems":
listElements();
break;
case "clickelement":
case "celem":
clickElement(Integer.parseInt(split[1]));
break;
case "writeelement":
case "welem":
setElemText(Integer.parseInt(split[1]), command.substring(split[0].length()+split[1].length()-2));
break;
case "key":
if (split.length > 1) {
simulateKeyPress(split[1]);
}
break;
case "keydown":
if (split.length > 1) {
simulateKeyDown(split[1]);
}
break;
case "keyup":
if (split.length > 1) {
simulateKeyUp(split[1]);
}
break;
default:
System.out.println("Unknown command: " + command);
}
}catch(Exception e){
e.printStackTrace();
}
}
private void listElements() {
Screen currentScreen = self.currentScreen;
if (currentScreen == null) {
System.out.println("No screen is currently open.");
return;
}
AtomicInteger index = new AtomicInteger(0);
printElements(currentScreen.children(), 0, index);
}
private void printElements(List<? extends Element> elements, int depth, AtomicInteger index) {
String indent = " ".repeat(depth);
for (Element element : elements) {
System.out.printf("%s%d: %s%n", indent, index.getAndIncrement(), describeElement(element));
if (element instanceof ClickableWidget) {
ClickableWidget widget = (ClickableWidget) element;
System.out.printf("%s Message: %s%n", indent, widget.getMessage().getString());
}
if (element instanceof net.minecraft.client.gui.ParentElement pe) {
printElements(pe.children(), depth + 1, index);
} else if (element instanceof WorldListWidget wlw) {
printElements(wlw.children(), depth+1, index);
}// else if (element instanceof WorldListWidget.WorldEntry elw) {
// printElements(elw.get, depth+1, index);
// }
}
}
private String describeElement(Element element) {
return String.format("%s@%s", element.getClass().getSimpleName(), Integer.toHexString(element.hashCode()));
}
private Element findElement(int targetIndex) {
Screen currentScreen = self.currentScreen;
if (currentScreen == null) {
System.out.println("No screen is currently open.");
return null;
}
AtomicInteger index = new AtomicInteger(0);
return findElementByIndex(currentScreen.children(), targetIndex, index);
}
private void clickElement(int targetIndex) {
Element targetElement = findElement(targetIndex);
if (targetElement == null) {
System.out.println("Invalid element index.");
return;
}
if (targetElement instanceof ClickableWidget widget) {
self.execute(() -> {
widget.onClick(widget.getX(), widget.getY());
});
System.out.println("Clicked element: " + describeElement(widget));
if (widget.getMessage() != null) {
System.out.println("Message: " + widget.getMessage().getString());
}
} else {
System.out.println("Element is not clickable: " + describeElement(targetElement));
}
}
private void setElemText(int targetIndex, String text) {
Element targetElement = findElement(targetIndex);
if (targetElement == null) {
System.out.println("Invalid element index.");
return;
}
if (targetElement instanceof TextFieldWidget widget) {
self.execute(() -> {
widget.write(text);
});
System.out.println("Wrote in element: " + describeElement(widget));
} else {
System.out.println("Element is not a TextFieldWidget: " + describeElement(targetElement));
}
}
private Element findElementByIndex(List<? extends Element> elements, int targetIndex, AtomicInteger currentIndex) {
for (Element element : elements) {
if (currentIndex.getAndIncrement() == targetIndex) {
return element;
}
if (element instanceof net.minecraft.client.gui.ParentElement) {
Element found = findElementByIndex(((net.minecraft.client.gui.ParentElement) element).children(), targetIndex, currentIndex);
if (found != null) {
return found;
}
}
}
return null;
}
private void simulateKeyPress(String keyName) {
int keyCode = getKeyCode(keyName);
if (keyCode != GLFW.GLFW_KEY_UNKNOWN) {
long handle = self.getWindow().getHandle();
self.execute(() -> {
self.keyboard.onKey(handle, keyCode, 0, GLFW.GLFW_PRESS, 0);
self.keyboard.onKey(handle, keyCode, 0, GLFW.GLFW_RELEASE, 0);
});
System.out.println("Pressed key: " + keyName);
} else {
System.out.println("Unknown key: " + keyName);
}
}
private void simulateKeyDown(String keyName) {
int keyCode = getKeyCode(keyName);
if (keyCode != GLFW.GLFW_KEY_UNKNOWN) {
long handle = self.getWindow().getHandle();
self.execute(() -> {
self.keyboard.onKey(handle, keyCode, 0, GLFW.GLFW_PRESS, 0);
});
System.out.println("Key down: " + keyName);
} else {
System.out.println("Unknown key: " + keyName);
}
}
private void simulateKeyUp(String keyName) {
int keyCode = getKeyCode(keyName);
if (keyCode != GLFW.GLFW_KEY_UNKNOWN) {
long handle = self.getWindow().getHandle();
self.execute(() -> {
self.keyboard.onKey(handle, keyCode, 0, GLFW.GLFW_RELEASE, 0);
});
System.out.println("Key up: " + keyName);
} else {
System.out.println("Unknown key: " + keyName);
}
}
private static int getKeyCode(String keyName) {
// Handle special cases
switch (keyName.toLowerCase()) {
case "space": return GLFW.GLFW_KEY_SPACE;
case "enter": return GLFW.GLFW_KEY_ENTER;
case "tab": return GLFW.GLFW_KEY_TAB;
case "escape": return GLFW.GLFW_KEY_ESCAPE;
case "backspace": return GLFW.GLFW_KEY_BACKSPACE;
// case "rightclick": return GLFW.CLI;
// case "leftclick": return GLFW.GLFW_KEY_BACKSPACE;
// Add more special cases as needed
}
// For single characters, use their ASCII value
if (keyName.length() == 1) {
char c = Character.toUpperCase(keyName.charAt(0));
if (c >= 'A' && c <= 'Z') {
return GLFW.GLFW_KEY_A + (c - 'A');
}
if (c >= '0' && c <= '9') {
return GLFW.GLFW_KEY_0 + (c - '0');
}
}
// If not found, return unknown key
return GLFW.GLFW_KEY_UNKNOWN;
}
}
@@ -0,0 +1,15 @@
package com.example.addon.mixin;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ClickableWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(Screen.class)
public interface HeadlessWidgetAccessor {
@Accessor("children") List<Element> getChildren();
}
@@ -0,0 +1,43 @@
package com.example.addon.mixin;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.util.Window;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.atomic.AtomicLong;
@Mixin(Window.class)
public class HeadlessWindow {
@Final @Shadow private long handle;
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
// Do nothing, effectively preventing window creation
}
@Inject(method = "updateWindowRegion", at = @At("HEAD"), cancellable = true)
private void onUpdateWindowRegion(CallbackInfo ci) {
ci.cancel(); // Prevent window updates
}
@Inject(method = "swapBuffers", at = @At("HEAD"), cancellable = true)
private void onSwapBuffers(CallbackInfo ci) {
RenderSystem.replayQueue();
ci.cancel(); // Prevent buffer swapping
}
@Inject(method = "setTitle", at = @At("HEAD"), cancellable = true)
private void onSetTitle(String title, CallbackInfo ci) {
ci.cancel(); // Prevent title updates
}
@Inject(method = "toggleFullscreen", at = @At("HEAD"), cancellable = true)
private void onToggleFullscreen(CallbackInfo ci) {
ci.cancel(); // Prevent fullscreen toggling
}
}
@@ -3,7 +3,9 @@
"package": "com.example.addon.mixin", "package": "com.example.addon.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"client": [ "client": [
"MixinGameRenderer" "HeadlessWindow",
"HeadlessMinecraftClient",
"HeadlessWidgetAccessor"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1