mirror of
https://github.com/Astatin3/no-more-render.git
synced 2026-06-09 08:38:01 -06:00
Basically getting this thing to work
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
run/
|
||||||
|
build/
|
||||||
|
|
||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user