Refactor build system to support 1.21.10, 1.21.11, and 26.1.2
This commit is contained in:
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -7,15 +7,15 @@ jobs:
|
||||
environment: modrinth
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 21
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '21'
|
||||
java-version: '25'
|
||||
distribution: 'temurin'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Execute Gradle build
|
||||
run: ./gradlew build
|
||||
- name: Execute Gradle build (all Stonecutter targets)
|
||||
run: ./gradlew chiseledBuild
|
||||
- run: mkdir staging && cp build/libs/*.jar staging
|
||||
- run: cd build/libs && md5sum *.jar > ../../md5sum.txt
|
||||
- run: echo "filename=`ls build/libs/*.jar |xargs basename`" >> $GITHUB_ENV
|
||||
|
||||
25
.gitignore
vendored
25
.gitignore
vendored
@@ -1,3 +1,24 @@
|
||||
.gradle
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
.jdk21/
|
||||
|
||||
# Local JDK / toolchain installs (optional per-machine)
|
||||
.jdk21/
|
||||
|
||||
# Fabric Loom – dev client / game output
|
||||
run/
|
||||
runs/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs & local scratch
|
||||
*.log
|
||||
out.txt
|
||||
full_build.txt
|
||||
compile_err.txt
|
||||
|
||||
52
README.md
52
README.md
@@ -4,13 +4,13 @@
|
||||
|
||||
## Table of contents
|
||||
1. [Description](#description)
|
||||
2. [Build](#build-for-1203--1204)
|
||||
2. [Build](#build)
|
||||
3. [Known Issues](#known-issues)
|
||||
4. [Possible Setup](#possible-setup)
|
||||
5. [Void Trading Example & Settings](#void-trading-example--settings)
|
||||
6. [WDL](#wdl)
|
||||
|
||||
feel free to ask questions: https://github.com/sebseb7/autotrade-fabric/discussions
|
||||
Feel free to ask questions: https://github.com/sebseb7/autotrade-fabric/discussions
|
||||
|
||||
### Description
|
||||
|
||||
@@ -24,29 +24,51 @@ Beginning with version v0.0.10 you can select the sell/buy items using nametagge
|
||||
|
||||
From v0.0.13, those selections (and the set-buy / set-sell hotkeys) store exact enchantments when the stack is enchanted, so you can target a specific book or tool; a plain item id in config still matches any enchantment variant of that item.
|
||||
|
||||
if you can't access settings via the keybind, try modmenu https://modrinth.com/mod/modmenu
|
||||
If you can't access settings via the keybind, try Mod Menu https://modrinth.com/mod/modmenu
|
||||
|
||||
#### Supported Version:
|
||||
#### Supported versions (this branch)
|
||||
|
||||
- Minecraft 1.19.4 - 1.20.4 , 1.21.11, 26.1.2
|
||||
Fabric targets maintained in-tree ([Stonecutter](https://stonecutter.kikugie.dev/) + [Modstitch](https://github.com/modunion/modstitch)):
|
||||
|
||||
### Build for 1.20.3 / 1.20.4
|
||||
- **Minecraft 1.21.10** (`:1.21.10-fabric`)
|
||||
- **Minecraft 1.21.11** (`:1.21.11-fabric`)
|
||||
- **Minecraft 26.1.2** (`:26.1.2-fabric`)
|
||||
|
||||
```
|
||||
./gradlew build
|
||||
Older Minecraft releases (for example 1.19.x–1.20.x) are not built from this multi-target setup; use an older release tag or branch if you need those versions.
|
||||
|
||||
### Build
|
||||
|
||||
Requirements: a JDK suitable for the target (the build uses Java **21** for 1.21.x and **25** for 26.x via Gradle toolchains).
|
||||
|
||||
Build **every** configured game version (recommended before you commit):
|
||||
|
||||
```bash
|
||||
./gradlew chiseledBuild
|
||||
```
|
||||
|
||||
#### Build for older minecraft versions:
|
||||
If the parallel build is flaky, try:
|
||||
|
||||
```
|
||||
./gradlew build -Pminecraft_version_out=1.20.2 -Pminecraft_version=1.20.2 -Pminecraft_version_min=1.20.2 -Pmalilib_version=0.17.0 -Pmod_menu_version=8.0.1 -Pmappings_version=1.20.2+build.4
|
||||
./gradlew build -Pminecraft_version_out=1.20.1 -Pminecraft_version=1.20.1 -Pminecraft_version_min=1.20 -Pmalilib_version=0.16.1 -Pmod_menu_version=7.0.1 -Pmappings_version=1.20.1+build.10
|
||||
./gradlew build -Pminecraft_version_out=1.19.4 -Pminecraft_version=1.19.4 -Pminecraft_version_min=1.19.4 -Pmalilib_version=0.15.2 -Pmod_menu_version=6.1.0 -Pmappings_version=1.19.4+build.2
|
||||
```bash
|
||||
./gradlew chiseledBuild --max-workers=1
|
||||
```
|
||||
|
||||
#### Requires:
|
||||
Build **one** Fabric target (jar ends up under that version subproject, e.g. `versions/1.21.11-fabric/build/libs/`):
|
||||
|
||||
- malilib
|
||||
```bash
|
||||
./gradlew :1.21.10-fabric:build
|
||||
./gradlew :1.21.11-fabric:build
|
||||
./gradlew :26.1.2-fabric:build
|
||||
```
|
||||
|
||||
When using Stonecutter’s active-project workflow, reset it before committing:
|
||||
|
||||
```bash
|
||||
./gradlew resetActiveProject
|
||||
```
|
||||
|
||||
#### Requires
|
||||
|
||||
- malilib
|
||||
|
||||
### Known Issues
|
||||
|
||||
|
||||
86
build.gradle
86
build.gradle
@@ -1,86 +0,0 @@
|
||||
plugins {
|
||||
id 'net.fabricmc.fabric-loom' version '1.16-SNAPSHOT'
|
||||
id 'com.diffplug.spotless' version '6.19.0'
|
||||
id "com.modrinth.minotaur" version "2.+"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
name = 'Modrinth'
|
||||
url = 'https://api.modrinth.com/maven'
|
||||
}
|
||||
}
|
||||
filter {
|
||||
includeGroup 'maven.modrinth'
|
||||
}
|
||||
}
|
||||
maven { url 'https://maven.terraformersmc.com/releases/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
flatDir {
|
||||
dirs '.'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
implementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}"
|
||||
// Official 26.1 template uses `implementation` for fabric-api (not modImplementation).
|
||||
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
|
||||
implementation "maven.modrinth:malilib:${project.malilib_version}"
|
||||
compileOnly "com.terraformersmc:modmenu:${project.mod_menu_version}"
|
||||
}
|
||||
|
||||
group = project.group + "." + project.mod_id
|
||||
base {
|
||||
archivesName = project.mod_file_name + '-' + project.minecraft_version_out
|
||||
}
|
||||
version = project.mod_version
|
||||
|
||||
if (version.endsWith('-dev')) {
|
||||
version += "." + new Date().format('yyyyMMdd.HHmmss')
|
||||
}
|
||||
|
||||
processResources {
|
||||
inputs.property "mod_version", project.mod_version
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand([
|
||||
"mod_version" : project.version,
|
||||
"minecraft_version_min": project.property("minecraft_version_min"),
|
||||
"malilib_version" : project.property("malilib_version")
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
it.options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
importOrder()
|
||||
removeUnusedImports()
|
||||
cleanthat()
|
||||
eclipse()
|
||||
formatAnnotations()
|
||||
}
|
||||
}
|
||||
|
||||
modrinth {
|
||||
token = System.getenv("MODRINTH_TOKEN")
|
||||
syncBodyFrom = rootProject.file("README.md").text
|
||||
projectId = 'C1naQCmt'
|
||||
// Loom 1.16+ with net.fabricmc.fabric-loom (unobfuscated MC) does not create remapJar; ship the standard jar
|
||||
uploadFile = tasks.jar
|
||||
gameVersions = ['26.1.2']
|
||||
loaders = ['fabric']
|
||||
dependencies = []
|
||||
}
|
||||
120
build.gradle.kts
Normal file
120
build.gradle.kts
Normal file
@@ -0,0 +1,120 @@
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
|
||||
plugins {
|
||||
id("dev.isxander.modstitch.base") version "0.8.4"
|
||||
id("com.modrinth.minotaur") version "2.+"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://api.modrinth.com/maven")
|
||||
maven("https://maven.terraformersmc.com/releases/")
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
|
||||
val minecraft = findProperty("deps.minecraft")?.toString()
|
||||
?: error("deps.minecraft must be set by the active Stonecutter target (see versions/*/gradle.properties).")
|
||||
|
||||
val javaLanguageVersion = when (minecraft) {
|
||||
"1.21.10", "1.21.11" -> 21
|
||||
"26.1.2" -> 25
|
||||
else -> error("Add Java toolchain mapping for Minecraft $minecraft in build.gradle.kts.")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(javaLanguageVersion))
|
||||
}
|
||||
}
|
||||
|
||||
val rawModVersion = findProperty("mod_version")?.toString() ?: error("mod_version")
|
||||
val modReleaseVersion = if (rawModVersion.endsWith("-dev")) {
|
||||
"$rawModVersion.${DateTimeFormatter.ofPattern("yyyyMMdd.HHmmss").format(LocalDateTime.now())}"
|
||||
} else {
|
||||
rawModVersion
|
||||
}
|
||||
|
||||
modstitch {
|
||||
minecraftVersion = minecraft
|
||||
|
||||
metadata {
|
||||
modId = findProperty("mod_id")?.toString() ?: error("mod_id")
|
||||
modName = findProperty("mod_name")?.toString() ?: error("mod_name")
|
||||
modVersion = modReleaseVersion
|
||||
modGroup = findProperty("mod_group")?.toString() ?: error("mod_group")
|
||||
modAuthor = findProperty("author")?.toString() ?: error("author")
|
||||
|
||||
replacementProperties.put("mod_author", findProperty("author")?.toString() ?: error("author"))
|
||||
replacementProperties.put("mod_issue_tracker", "https://github.com/sebseb7/autotrade-fabric/issues")
|
||||
replacementProperties.put("mod_sources", "https://github.com/sebseb7/autotrade-fabric")
|
||||
replacementProperties.put("mod_homepage", "https://modrinth.com/mod/autotrade-fabric")
|
||||
replacementProperties.put("malilib_version", findProperty("malilib_version")?.toString() ?: error("malilib_version"))
|
||||
replacementProperties.put(
|
||||
"fabric_api_dependency",
|
||||
when (minecraft) {
|
||||
"26.1.2" -> ">=0.145.0"
|
||||
"1.21.11" -> ">=0.140.0"
|
||||
"1.21.10" -> ">=0.136.0"
|
||||
else -> error("fabric_api_dependency mapping for $minecraft")
|
||||
},
|
||||
)
|
||||
replacementProperties.put(
|
||||
"minecraft_dependency",
|
||||
">=" + (findProperty("minecraft_version_min")?.toString() ?: minecraft),
|
||||
)
|
||||
}
|
||||
|
||||
loom {
|
||||
fabricLoaderVersion = findProperty("fabric_loader_version")?.toString()
|
||||
?: error("fabric_loader_version")
|
||||
|
||||
configureLoom {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val loader = project.name.substringAfterLast("-")
|
||||
stonecutter {
|
||||
consts(
|
||||
"fabric" to loader.equals("fabric", ignoreCase = true),
|
||||
"neoforge" to loader.equals("neoforge", ignoreCase = true),
|
||||
"forge" to loader.equals("forge", ignoreCase = true),
|
||||
"vanilla" to loader.equals("vanilla", ignoreCase = true),
|
||||
"mc26" to (minecraft == "26.1.2"),
|
||||
"npcSplit" to (minecraft == "26.1.2" || minecraft == "1.21.11"),
|
||||
"npcFlat" to (minecraft == "1.21.10"),
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val fabricApi = findProperty("fabric_api_version")?.toString() ?: error("fabric_api_version")
|
||||
val malilib = findProperty("malilib_version")?.toString() ?: error("malilib_version")
|
||||
val modMenu = findProperty("mod_menu_version")?.toString() ?: error("mod_menu_version")
|
||||
|
||||
modstitchModImplementation("net.fabricmc.fabric-api:fabric-api:$fabricApi")
|
||||
modstitchModImplementation("maven.modrinth:malilib:$malilib")
|
||||
modstitchModCompileOnly("com.terraformersmc:modmenu:$modMenu")
|
||||
}
|
||||
|
||||
group = findProperty("mod_group")?.toString() ?: error("mod_group")
|
||||
base {
|
||||
archivesName.set("${findProperty("mod_file_name")}-$minecraft")
|
||||
}
|
||||
|
||||
version = modReleaseVersion
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
modrinth {
|
||||
token.set(System.getenv("MODRINTH_TOKEN"))
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
projectId.set("C1naQCmt")
|
||||
uploadFile.set(tasks.jar)
|
||||
gameVersions.addAll(listOf("26.1.2", "1.21.10", "1.21.11"))
|
||||
loaders.add("fabric")
|
||||
}
|
||||
@@ -3,20 +3,11 @@ org.gradle.jvmargs=-Xmx1G
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configuration-cache=false
|
||||
|
||||
group = com.github.sebseb7
|
||||
mod_id = autotrade
|
||||
mod_name = AutoTrade
|
||||
author = sebseb7
|
||||
mod_file_name = autotrade-fabric
|
||||
group=com.github.sebseb7
|
||||
mod_id=autotrade
|
||||
mod_name=AutoTrade
|
||||
mod_group=com.github.sebseb7.autotrade
|
||||
author=sebseb7
|
||||
mod_file_name=autotrade-fabric
|
||||
|
||||
mod_version = 0.0.14
|
||||
|
||||
malilib_version = 0.28.2
|
||||
minecraft_version_min = 26.1.2
|
||||
minecraft_version_out = 26.1.2
|
||||
|
||||
minecraft_version = 26.1.2
|
||||
|
||||
fabric_loader_version = 0.19.2
|
||||
fabric_api_version = 0.145.4+26.1.2
|
||||
mod_menu_version = 18.0.0-alpha.8
|
||||
mod_version=0.0.14
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
// Lets Gradle 21 run the build while a JDK 25 toolchain is used to compile (required by 26.1 Mod Menu, etc.)
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||
}
|
||||
|
||||
rootProject.name = 'autotrade-fabric'
|
||||
34
settings.gradle.kts
Normal file
34
settings.gradle.kts
Normal file
@@ -0,0 +1,34 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven("https://maven.isxander.dev/releases/")
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
maven("https://maven.kikugie.dev/releases")
|
||||
maven("https://maven.kikugie.dev/snapshots")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.kikugie.stonecutter") version "0.6+"
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
stonecutter {
|
||||
kotlinController = true
|
||||
centralScript = "build.gradle.kts"
|
||||
|
||||
create(rootProject) {
|
||||
fun mc(mcVersion: String, name: String = mcVersion, loaders: Iterable<String>) =
|
||||
loaders.forEach { vers("$name-$it", mcVersion) }
|
||||
|
||||
mc("26.1.2", loaders = listOf("fabric"))
|
||||
mc("1.21.10", loaders = listOf("fabric"))
|
||||
mc("1.21.11", loaders = listOf("fabric"))
|
||||
|
||||
vcsVersion = "26.1.2-fabric"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "autotrade-fabric"
|
||||
@@ -3,6 +3,7 @@ package com.github.sebseb7.autotrade;
|
||||
import com.github.sebseb7.autotrade.config.Configs;
|
||||
import com.github.sebseb7.autotrade.event.InputHandler;
|
||||
import com.github.sebseb7.autotrade.event.KeybindCallbacks;
|
||||
import com.github.sebseb7.autotrade.gui.MerchantScreenButtonInjector;
|
||||
import com.github.sebseb7.autotrade.render.TraderHighlightRenderer;
|
||||
import com.github.sebseb7.autotrade.render.VillagerTradeOverlayRenderer;
|
||||
import fi.dy.masa.malilib.config.ConfigManager;
|
||||
@@ -19,6 +20,7 @@ public class InitHandler implements IInitializationHandler {
|
||||
|
||||
TraderHighlightRenderer.register();
|
||||
VillagerTradeOverlayRenderer.register();
|
||||
MerchantScreenButtonInjector.register();
|
||||
|
||||
InputHandler handler = new InputHandler();
|
||||
InputEventHandler.getKeybindManager().registerKeybindProvider(handler);
|
||||
|
||||
@@ -2,16 +2,21 @@ package com.github.sebseb7.autotrade.config;
|
||||
|
||||
import com.github.sebseb7.autotrade.Reference;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import fi.dy.masa.malilib.config.ConfigUtils;
|
||||
import fi.dy.masa.malilib.config.IConfigHandler;
|
||||
import fi.dy.masa.malilib.config.IConfigValue;
|
||||
import fi.dy.masa.malilib.config.options.ConfigBoolean;
|
||||
import fi.dy.masa.malilib.config.options.ConfigInteger;
|
||||
import fi.dy.masa.malilib.config.options.ConfigString;
|
||||
import fi.dy.masa.malilib.util.JsonUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public class Configs implements IConfigHandler {
|
||||
@@ -60,25 +65,32 @@ public class Configs implements IConfigHandler {
|
||||
30000000, "delay in ticks; to get signal from trapped chest");
|
||||
public static final ConfigBoolean SHOW_TRADES = new ConfigBoolean("showTrades", true,
|
||||
"Display villager/wandering-trader trades above their heads (requires trading with them once to cache the offers)");
|
||||
public static final ConfigString SELECTED_ENCHANTMENTS = new ConfigString("selectedEnchantments", "",
|
||||
"Comma-separated list of selected enchantment IDs (set via the \"Select Enchantments\" button on a librarian's trade screen)");
|
||||
|
||||
public static final ImmutableList<IConfigValue> OPTIONS = ImmutableList.of(ENABLED, ITEM_FRAME, GLASS_BLOCK,
|
||||
SELECTOR_OFFSET, ENABLE_SELL, SELL_ITEM, SELL_LIMIT, ENABLE_BUY, BUY_ITEM, BUY_LIMIT, MAX_INPUT_ITEMS,
|
||||
INPUT_CONTAINER_X, INPUT_CONTAINER_Y, INPUT_CONTAINER_Z, OUTPUT_CONTAINER_X, OUTPUT_CONTAINER_Y,
|
||||
OUTPUT_CONTAINER_Z, VOID_TRADING_DELAY, VOID_TRADING_DELAY_AFTER_TELEPORT, CONTAINER_CLOSE_DELAY,
|
||||
SHOW_TRADES);
|
||||
SHOW_TRADES, SELECTED_ENCHANTMENTS);
|
||||
}
|
||||
|
||||
public static void loadFromFile() {
|
||||
File configFile = new File(getConfigDirectory(), CONFIG_FILE_NAME);
|
||||
|
||||
if (configFile.exists() && configFile.isFile() && configFile.canRead()) {
|
||||
JsonElement element = JsonUtils.parseJsonFile(configFile.toPath());
|
||||
try {
|
||||
String json = Files.readString(configFile.toPath(), StandardCharsets.UTF_8);
|
||||
JsonElement element = JsonParser.parseString(json);
|
||||
|
||||
if (element != null && element.isJsonObject()) {
|
||||
JsonObject root = element.getAsJsonObject();
|
||||
if (element != null && element.isJsonObject()) {
|
||||
JsonObject root = element.getAsJsonObject();
|
||||
|
||||
ConfigUtils.readConfigBase(root, "Generic", Generic.OPTIONS);
|
||||
ConfigUtils.readConfigBase(root, "Hotkeys", Hotkeys.HOTKEY_LIST);
|
||||
ConfigUtils.readConfigBase(root, "Generic", Generic.OPTIONS);
|
||||
ConfigUtils.readConfigBase(root, "Hotkeys", Hotkeys.HOTKEY_LIST);
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// Malformed or unreadable config; defaults stay active.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,12 +99,16 @@ public class Configs implements IConfigHandler {
|
||||
File dir = getConfigDirectory();
|
||||
|
||||
if ((dir.exists() && dir.isDirectory()) || dir.mkdirs()) {
|
||||
JsonObject root = new JsonObject();
|
||||
try {
|
||||
JsonObject root = new JsonObject();
|
||||
|
||||
ConfigUtils.writeConfigBase(root, "Generic", Generic.OPTIONS);
|
||||
ConfigUtils.writeConfigBase(root, "Hotkeys", Hotkeys.HOTKEY_LIST);
|
||||
ConfigUtils.writeConfigBase(root, "Generic", Generic.OPTIONS);
|
||||
ConfigUtils.writeConfigBase(root, "Hotkeys", Hotkeys.HOTKEY_LIST);
|
||||
|
||||
JsonUtils.writeJsonToFile(root, new File(dir, CONFIG_FILE_NAME).toPath());
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
Files.writeString(new File(dir, CONFIG_FILE_NAME).toPath(), gson.toJson(root), StandardCharsets.UTF_8);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,14 @@ import net.minecraft.network.protocol.game.ServerboundSelectTradePacket;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
//? if npcSplit {
|
||||
import net.minecraft.world.entity.npc.villager.Villager;
|
||||
import net.minecraft.world.entity.npc.wanderingtrader.WanderingTrader;
|
||||
//?}
|
||||
//? if npcFlat {
|
||||
import net.minecraft.world.entity.npc.Villager;
|
||||
import net.minecraft.world.entity.npc.WanderingTrader;
|
||||
//?}
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -33,7 +39,9 @@ import net.minecraft.world.item.trading.MerchantOffers;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
//? if mc26 {
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
//?}
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
final class AutoTradeClientTick {
|
||||
@@ -144,8 +152,12 @@ final class AutoTradeClientTick {
|
||||
if (!newVillagersInRange.contains(entity)) {
|
||||
found = true;
|
||||
newVillagersInRange.add(entity);
|
||||
//? if mc26 {
|
||||
EntityHitResult ehr = new EntityHitResult(entity, entity.position());
|
||||
mc.gameMode.interact(mc.player, entity, ehr, InteractionHand.MAIN_HAND);
|
||||
//?} else {
|
||||
mc.gameMode.interact(mc.player, entity, InteractionHand.MAIN_HAND);
|
||||
//?}
|
||||
voidDelay = Configs.Generic.VOID_TRADING_DELAY.getIntegerValue();
|
||||
villagerActive = entity.getId();
|
||||
state = false;
|
||||
@@ -338,6 +350,7 @@ final class AutoTradeClientTick {
|
||||
}
|
||||
}
|
||||
screen.onClose();
|
||||
ContainerIoHelper.syncPlayerInventoryAfterMerchant(mc);
|
||||
startTraderGlow(mc, villagerActive);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ import java.util.Map;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
//? if mc26 {
|
||||
import net.minecraft.world.inventory.ContainerInput;
|
||||
//?} else {
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
//?}
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -20,7 +24,28 @@ final class ContainerIoHelper {
|
||||
|
||||
static void quickMoveResultSlot(Minecraft mc, AbstractContainerMenu menu, int slotIndex) {
|
||||
Slot slot = menu.getSlot(slotIndex);
|
||||
//? if mc26 {
|
||||
mc.gameMode.handleContainerInput(menu.containerId, slot.index, 0, ContainerInput.QUICK_MOVE, mc.player);
|
||||
//?} else {
|
||||
mc.gameMode.handleInventoryMouseClick(menu.containerId, slot.index, 0, ClickType.QUICK_MOVE, mc.player);
|
||||
//?}
|
||||
}
|
||||
|
||||
/**
|
||||
* After automated merchant packets (select trade + shift-clicks), client-side
|
||||
* prediction can drift from the server. Flush the carried/cursor stack and run
|
||||
* again on the next tick so pending slot updates have landed.
|
||||
*/
|
||||
static void syncPlayerInventoryAfterMerchant(Minecraft mc) {
|
||||
if (mc.player == null || mc.gameMode == null) {
|
||||
return;
|
||||
}
|
||||
mc.gameMode.ensureHasSentCarriedItem();
|
||||
mc.execute(() -> {
|
||||
if (mc.player != null && mc.gameMode != null) {
|
||||
mc.gameMode.ensureHasSentCarriedItem();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final String EMERALD_SPEC = "minecraft:emerald";
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.github.sebseb7.autotrade.gui;
|
||||
|
||||
import com.github.sebseb7.autotrade.config.Configs;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.client.Minecraft;
|
||||
//? if mc26 {
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
//?} else {
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
//?}
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Checkbox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.enchantment.Enchantment;
|
||||
|
||||
/**
|
||||
* Full-screen GUI that lists every registered enchantment at every possible
|
||||
* level with a checkbox. For example, Sharpness (max level 5) produces five
|
||||
* rows: {@code minecraft:sharpness=1} through {@code minecraft:sharpness=5}.
|
||||
*
|
||||
* <p>
|
||||
* Ticked entries are persisted into
|
||||
* {@link Configs.Generic#SELECTED_ENCHANTMENTS} as a comma-separated list (e.g.
|
||||
* {@code minecraft:sharpness=5,minecraft:mending=1}).
|
||||
*/
|
||||
public class EnchantmentSelectionScreen extends Screen {
|
||||
private final Screen parent;
|
||||
|
||||
/**
|
||||
* Every (enchantment, level) pair in alphabetical order. Each entry's
|
||||
* {@link EnchantmentEntry#key} is the string that gets saved to config (e.g.
|
||||
* {@code minecraft:sharpness=3}).
|
||||
*/
|
||||
private List<EnchantmentEntry> entries = List.of();
|
||||
|
||||
/** Currently selected set (mutable copy while the screen is open). */
|
||||
private final Set<String> selected = new LinkedHashSet<>();
|
||||
|
||||
private static final int ROW_HEIGHT = 24;
|
||||
private static final int LEFT_PADDING = 10;
|
||||
|
||||
/** Current scroll offset (in pixels). */
|
||||
private int scrollOffset = 0;
|
||||
|
||||
/** Total content height (number of rows × ROW_HEIGHT). */
|
||||
private int contentHeight = 0;
|
||||
|
||||
/** Checkboxes created for the current scroll window. */
|
||||
private final List<CheckboxEntry> checkboxEntries = new ArrayList<>();
|
||||
|
||||
public EnchantmentSelectionScreen(Screen parent) {
|
||||
super(Component.literal("Select Enchantments"));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
// Load currently-selected enchantments from config.
|
||||
selected.clear();
|
||||
String cfg = Configs.Generic.SELECTED_ENCHANTMENTS.getStringValue().trim();
|
||||
if (!cfg.isEmpty()) {
|
||||
Arrays.stream(cfg.split(",")).map(String::trim).filter(s -> !s.isEmpty()).forEach(selected::add);
|
||||
}
|
||||
|
||||
// Build (enchantment, level) pairs from the dynamic registry.
|
||||
entries = new ArrayList<>();
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level != null) {
|
||||
Registry<Enchantment> registry = mc.level.registryAccess().lookupOrThrow(Registries.ENCHANTMENT);
|
||||
var ids = new ArrayList<>(registry.keySet());
|
||||
ids.sort((a, b) -> a.toString().compareTo(b.toString()));
|
||||
for (var id : ids) {
|
||||
Enchantment ench = registry.getValue(id);
|
||||
if (ench == null) {
|
||||
continue;
|
||||
}
|
||||
int maxLevel = ench.getMaxLevel();
|
||||
for (int level = 1; level <= maxLevel; level++) {
|
||||
String key = id.toString() + "=" + level;
|
||||
String label = id.toString() + " " + level;
|
||||
entries.add(new EnchantmentEntry(key, label));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight = entries.size() * ROW_HEIGHT;
|
||||
|
||||
// "Done" button at the bottom.
|
||||
this.addRenderableWidget(Button.builder(Component.literal("Done"), btn -> onDone())
|
||||
.bounds(this.width / 2 - 100, this.height - 28, 200, 20).build());
|
||||
|
||||
rebuildCheckboxes();
|
||||
}
|
||||
|
||||
/** Rebuild the checkbox widgets for the current scroll offset. */
|
||||
private void rebuildCheckboxes() {
|
||||
// Remove old checkboxes.
|
||||
for (CheckboxEntry entry : checkboxEntries) {
|
||||
this.removeWidget(entry.checkbox);
|
||||
}
|
||||
checkboxEntries.clear();
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
int rowY = 30 + i * ROW_HEIGHT - scrollOffset;
|
||||
if (rowY + ROW_HEIGHT < 30 || rowY > this.height - 36) {
|
||||
continue; // off-screen
|
||||
}
|
||||
EnchantmentEntry e = entries.get(i);
|
||||
boolean checked = selected.contains(e.key);
|
||||
|
||||
Checkbox cb = Checkbox.builder(Component.literal(e.label), this.font).pos(LEFT_PADDING, rowY)
|
||||
.selected(checked).build();
|
||||
this.addRenderableWidget(cb);
|
||||
checkboxEntries.add(new CheckboxEntry(i, cb));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||
int maxScroll = Math.max(0, contentHeight - (this.height - 60));
|
||||
scrollOffset = (int) Math.max(0, Math.min(maxScroll, scrollOffset - verticalAmount * ROW_HEIGHT));
|
||||
syncCheckboxSelections();
|
||||
rebuildCheckboxes();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Before rebuilding, read back checkbox state into our selected set. */
|
||||
private void syncCheckboxSelections() {
|
||||
for (CheckboxEntry entry : checkboxEntries) {
|
||||
String key = entries.get(entry.index).key;
|
||||
if (entry.checkbox.selected()) {
|
||||
selected.add(key);
|
||||
} else {
|
||||
selected.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//? if mc26 {
|
||||
@Override
|
||||
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) {
|
||||
super.extractRenderState(graphics, mouseX, mouseY, partialTick);
|
||||
graphics.centeredText(this.font, this.title, this.width / 2, 10, 0xFFFFFF);
|
||||
}
|
||||
//?} else {
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 10, 0xFFFFFF);
|
||||
}
|
||||
//?}
|
||||
|
||||
private void onDone() {
|
||||
syncCheckboxSelections();
|
||||
String value = selected.stream().sorted().collect(Collectors.joining(","));
|
||||
Configs.Generic.SELECTED_ENCHANTMENTS.setValueFromString(value);
|
||||
Configs.saveToFile();
|
||||
this.minecraft.setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
onDone();
|
||||
}
|
||||
|
||||
/** An enchantment at a specific level. */
|
||||
private record EnchantmentEntry(String key, String label) {
|
||||
}
|
||||
|
||||
/** Pairs a checkbox widget with its index in {@link #entries}. */
|
||||
private record CheckboxEntry(int index, Checkbox checkbox) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.github.sebseb7.autotrade.gui;
|
||||
|
||||
import com.github.sebseb7.autotrade.render.VillagerTradeCache;
|
||||
import java.util.List;
|
||||
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
|
||||
import net.fabricmc.fabric.api.client.screen.v1.Screens;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MerchantScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
//? if npcSplit {
|
||||
import net.minecraft.world.entity.npc.villager.Villager;
|
||||
import net.minecraft.world.entity.npc.wanderingtrader.WanderingTrader;
|
||||
//?}
|
||||
//? if npcFlat {
|
||||
import net.minecraft.world.entity.npc.Villager;
|
||||
import net.minecraft.world.entity.npc.WanderingTrader;
|
||||
//?}
|
||||
import net.minecraft.world.item.trading.MerchantOffers;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
public final class MerchantScreenButtonInjector {
|
||||
|
||||
private MerchantScreenButtonInjector() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
ScreenEvents.AFTER_INIT.register(MerchantScreenButtonInjector::onScreenInit);
|
||||
}
|
||||
|
||||
private static void onScreenInit(Minecraft client, Screen screen, int scaledWidth, int scaledHeight) {
|
||||
if (!(screen instanceof MerchantScreen merchantScreen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add button during AFTER_INIT so it is properly registered as renderable.
|
||||
// We position it safely to the right of the merchant GUI.
|
||||
// The merchant GUI is 276 pixels wide and centered.
|
||||
Button button = Button
|
||||
.builder(Component.literal("Select Enchantments"),
|
||||
btn -> client.setScreen(new EnchantmentSelectionScreen(merchantScreen)))
|
||||
.bounds(scaledWidth / 2 + 140, scaledHeight / 2 - 83, 120, 20).build();
|
||||
Screen asScreen = merchantScreen;
|
||||
//? if mc26 {
|
||||
Screens.getWidgets(asScreen).add(button);
|
||||
//?} else {
|
||||
Screens.getButtons(asScreen).add(button);
|
||||
//?}
|
||||
|
||||
// Offers arrive via a server packet after the screen opens.
|
||||
// Register a per-screen tick handler to wait for offers and cache them.
|
||||
final boolean[] handled = {false};
|
||||
ScreenEvents.afterTick(merchantScreen).register(s -> {
|
||||
if (handled[0]) {
|
||||
return;
|
||||
}
|
||||
MerchantOffers offers = merchantScreen.getMenu().getOffers();
|
||||
if (offers == null || offers.isEmpty()) {
|
||||
return; // not yet synced
|
||||
}
|
||||
handled[0] = true;
|
||||
|
||||
cacheOffersForNearestTrader(client, offers);
|
||||
});
|
||||
}
|
||||
|
||||
private static void cacheOffersForNearestTrader(Minecraft mc, MerchantOffers offers) {
|
||||
if (mc.player == null || mc.level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AABB searchBox = mc.player.getBoundingBox().inflate(10.0);
|
||||
List<Entity> nearby = mc.level.getEntitiesOfClass(Entity.class, searchBox);
|
||||
|
||||
Entity closest = null;
|
||||
double closestDist = Double.MAX_VALUE;
|
||||
|
||||
for (Entity entity : nearby) {
|
||||
if (entity instanceof Villager || entity instanceof WanderingTrader) {
|
||||
double dist = entity.distanceToSqr(mc.player);
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
closest = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closest != null) {
|
||||
VillagerTradeCache.put(closest.getUUID(), offers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.sebseb7.autotrade.render;
|
||||
|
||||
//? if mc26 {
|
||||
import com.github.sebseb7.autotrade.config.Configs;
|
||||
import com.github.sebseb7.autotrade.event.KeybindCallbacks;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
@@ -16,6 +17,7 @@ import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
//?}
|
||||
|
||||
/**
|
||||
* Client wireframe highlights: last-traded villager, and input/output container
|
||||
@@ -23,6 +25,17 @@ import net.minecraft.world.phys.shapes.Shapes;
|
||||
* boxes).
|
||||
*/
|
||||
public final class TraderHighlightRenderer {
|
||||
|
||||
private TraderHighlightRenderer() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
//? if mc26 {
|
||||
LevelRenderEvents.AFTER_SOLID_FEATURES.register(TraderHighlightRenderer::renderLevel);
|
||||
//?}
|
||||
}
|
||||
|
||||
//? if mc26 {
|
||||
private static final ShapeRenderer SHAPE_RENDERER = new ShapeRenderer();
|
||||
|
||||
private static final int TRADER_OUTLINE_COLOR = 0xFF66FF66;
|
||||
@@ -31,14 +44,7 @@ public final class TraderHighlightRenderer {
|
||||
|
||||
private static final float LINE_WIDTH = 2.5F;
|
||||
|
||||
private TraderHighlightRenderer() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
LevelRenderEvents.AFTER_SOLID_FEATURES.register(TraderHighlightRenderer::render);
|
||||
}
|
||||
|
||||
private static void render(LevelRenderContext context) {
|
||||
private static void renderLevel(LevelRenderContext context) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
@@ -57,14 +63,18 @@ public final class TraderHighlightRenderer {
|
||||
Vec3 camera = mc.gameRenderer.getMainCamera().position();
|
||||
float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(true);
|
||||
|
||||
renderBoxes(mc, drawPose, consumer, camera, tickDelta, trader, inTicks, outTicks);
|
||||
}
|
||||
|
||||
private static void renderBoxes(Minecraft mc, PoseStack drawPose, VertexConsumer consumer, Vec3 camera,
|
||||
float tickDelta, Entity trader, int inTicks, int outTicks) {
|
||||
if (trader != null) {
|
||||
double offX = Mth.lerp(tickDelta, trader.xOld, trader.getX()) - trader.getX();
|
||||
double offY = Mth.lerp(tickDelta, trader.yOld, trader.getY()) - trader.getY();
|
||||
double offZ = Mth.lerp(tickDelta, trader.zOld, trader.getZ()) - trader.getZ();
|
||||
AABB worldBox = trader.getBoundingBox().move(offX, offY, offZ);
|
||||
AABB cameraRelative = worldBox.move(-camera.x, -camera.y, -camera.z);
|
||||
SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D,
|
||||
TRADER_OUTLINE_COLOR, LINE_WIDTH);
|
||||
renderShape(drawPose, consumer, cameraRelative, TRADER_OUTLINE_COLOR);
|
||||
}
|
||||
|
||||
if (inTicks > 0) {
|
||||
@@ -86,7 +96,12 @@ public final class TraderHighlightRenderer {
|
||||
int color) {
|
||||
AABB world = AABB.encapsulatingFullBlocks(pos, pos);
|
||||
AABB cameraRelative = world.move(-camera.x, -camera.y, -camera.z);
|
||||
renderShape(drawPose, consumer, cameraRelative, color);
|
||||
}
|
||||
|
||||
private static void renderShape(PoseStack drawPose, VertexConsumer consumer, AABB cameraRelative, int color) {
|
||||
SHAPE_RENDERER.renderShape(drawPose, consumer, Shapes.create(cameraRelative), 0.0D, 0.0D, 0.0D, color,
|
||||
LINE_WIDTH);
|
||||
}
|
||||
//?}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import net.minecraft.world.item.trading.MerchantOffers;
|
||||
* Client-side cache of villager/wandering-trader trade offers, keyed by entity
|
||||
* UUID.
|
||||
*
|
||||
* <p>Populated by {@code AutoTradeClientTick} when the mod opens a merchant
|
||||
* <p>
|
||||
* Populated by {@code AutoTradeClientTick} when the mod opens a merchant
|
||||
* screen; consumed by {@link VillagerTradeOverlayRenderer} to draw trade labels
|
||||
* above each villager's head.
|
||||
*/
|
||||
@@ -24,7 +25,10 @@ public final class VillagerTradeCache {
|
||||
CACHE.put(entityUuid, offers);
|
||||
}
|
||||
|
||||
/** Retrieve cached offers, or {@code null} if we haven't seen this entity trade yet. */
|
||||
/**
|
||||
* Retrieve cached offers, or {@code null} if we haven't seen this entity trade
|
||||
* yet.
|
||||
*/
|
||||
public static MerchantOffers get(UUID entityUuid) {
|
||||
return CACHE.get(entityUuid);
|
||||
}
|
||||
|
||||
@@ -2,34 +2,36 @@ package com.github.sebseb7.autotrade.render;
|
||||
|
||||
import com.github.sebseb7.autotrade.config.Configs;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
//? if mc26 {
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents;
|
||||
//?}
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
//? if mc26 {
|
||||
import net.minecraft.world.entity.npc.villager.Villager;
|
||||
import net.minecraft.world.entity.npc.wanderingtrader.WanderingTrader;
|
||||
//?}
|
||||
import net.minecraft.world.item.trading.MerchantOffer;
|
||||
import net.minecraft.world.item.trading.MerchantOffers;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Renders a compact summary of each villager's known trades above their head.
|
||||
*
|
||||
* <p>Trade data comes from {@link VillagerTradeCache}, which is populated when
|
||||
* the mod opens a merchant screen. Villagers whose trades haven't been seen
|
||||
* yet show nothing.
|
||||
* <p>
|
||||
* Trade data comes from {@link VillagerTradeCache}, which is populated when the
|
||||
* mod opens a merchant screen. Villagers whose trades haven't been seen yet
|
||||
* show nothing.
|
||||
*/
|
||||
public final class VillagerTradeOverlayRenderer {
|
||||
|
||||
/** Vertical gap between successive trade lines (in world-space blocks). */
|
||||
private static final float LINE_SPACING = 0.25F;
|
||||
|
||||
/** World-space scale of the text (vanilla name-tags use ~0.025). */
|
||||
private static final float TEXT_SCALE = 0.02F;
|
||||
|
||||
@@ -46,10 +48,13 @@ public final class VillagerTradeOverlayRenderer {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
LevelRenderEvents.AFTER_SOLID_FEATURES.register(VillagerTradeOverlayRenderer::render);
|
||||
//? if mc26 {
|
||||
LevelRenderEvents.COLLECT_SUBMITS.register(VillagerTradeOverlayRenderer::renderLevel);
|
||||
//?}
|
||||
}
|
||||
|
||||
private static void render(LevelRenderContext context) {
|
||||
//? if mc26 {
|
||||
private static void renderLevel(LevelRenderContext context) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) {
|
||||
return;
|
||||
@@ -59,7 +64,6 @@ public final class VillagerTradeOverlayRenderer {
|
||||
}
|
||||
|
||||
Font font = mc.font;
|
||||
MultiBufferSource.BufferSource bufferSource = context.bufferSource();
|
||||
Vec3 camera = mc.gameRenderer.getMainCamera().position();
|
||||
float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(true);
|
||||
|
||||
@@ -67,7 +71,6 @@ public final class VillagerTradeOverlayRenderer {
|
||||
if (!(entity instanceof Villager) && !(entity instanceof WanderingTrader)) {
|
||||
continue;
|
||||
}
|
||||
// Only render for villagers within a reasonable distance.
|
||||
if (entity.distanceToSqr(mc.player) > 64.0 * 64.0) {
|
||||
continue;
|
||||
}
|
||||
@@ -77,29 +80,24 @@ public final class VillagerTradeOverlayRenderer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build compact trade lines: "CostA [+ CostB] → Result (uses/max)"
|
||||
List<TradeLineEntry> lines = buildTradeLines(offers);
|
||||
if (lines.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Interpolated entity position relative to camera.
|
||||
double x = Mth.lerp(tickDelta, entity.xOld, entity.getX()) - camera.x;
|
||||
double y = Mth.lerp(tickDelta, entity.yOld, entity.getY()) - camera.y;
|
||||
double z = Mth.lerp(tickDelta, entity.zOld, entity.getZ()) - camera.z;
|
||||
|
||||
// Place the first line above the entity's head (entity height + small gap).
|
||||
float baseY = entity.getBbHeight() + 0.6F;
|
||||
|
||||
PoseStack poseStack = new PoseStack();
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(x, y + baseY, z);
|
||||
|
||||
// Face the camera (billboard).
|
||||
poseStack.mulPose(mc.gameRenderer.getMainCamera().rotation());
|
||||
poseStack.scale(-TEXT_SCALE, -TEXT_SCALE, TEXT_SCALE);
|
||||
|
||||
// Draw lines from top (highest index) to bottom (index 0).
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
TradeLineEntry entry = lines.get(i);
|
||||
float lineOffsetY = -(lines.size() - 1 - i) * (font.lineHeight + 2);
|
||||
@@ -111,46 +109,38 @@ public final class VillagerTradeOverlayRenderer {
|
||||
int textWidth = font.width(entry.text);
|
||||
float textX = -textWidth / 2.0F;
|
||||
|
||||
// Background
|
||||
font.drawInBatch(entry.text, textX, 0, entry.color, false, matrix, bufferSource,
|
||||
Font.DisplayMode.SEE_THROUGH, BG_COLOR, 0xF000F0);
|
||||
// Foreground
|
||||
font.drawInBatch(entry.text, textX, 0, entry.color, false, matrix, bufferSource,
|
||||
Font.DisplayMode.NORMAL, 0, 0xF000F0);
|
||||
context.submitNodeCollector().submitText(poseStack, textX, 0,
|
||||
net.minecraft.network.chat.Component.literal(entry.text).getVisualOrderText(), false,
|
||||
Font.DisplayMode.NORMAL, entry.color, 0xF000F0, BG_COLOR, 0);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
//?}
|
||||
|
||||
private static List<TradeLineEntry> buildTradeLines(MerchantOffers offers) {
|
||||
List<TradeLineEntry> lines = new ArrayList<>();
|
||||
for (int i = 0; i < offers.size(); i++) {
|
||||
MerchantOffer offer = offers.get(i);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Cost A
|
||||
if (!offer.getCostA().isEmpty()) {
|
||||
sb.append(offer.getCostA().getCount()).append("× ")
|
||||
.append(offer.getCostA().getHoverName().getString());
|
||||
sb.append(offer.getCostA().getCount()).append("× ").append(offer.getCostA().getHoverName().getString());
|
||||
}
|
||||
|
||||
// Cost B (optional)
|
||||
if (!offer.getCostB().isEmpty()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(" + ");
|
||||
}
|
||||
sb.append(offer.getCostB().getCount()).append("× ")
|
||||
.append(offer.getCostB().getHoverName().getString());
|
||||
sb.append(offer.getCostB().getCount()).append("× ").append(offer.getCostB().getHoverName().getString());
|
||||
}
|
||||
|
||||
sb.append(" → ");
|
||||
|
||||
// Result
|
||||
sb.append(offer.getResult().getCount()).append("× ")
|
||||
.append(offer.getResult().getHoverName().getString());
|
||||
sb.append(offer.getResult().getCount()).append("× ").append(offer.getResult().getHoverName().getString());
|
||||
|
||||
// Remaining uses
|
||||
int remaining = offer.getMaxUses() - offer.getUses();
|
||||
sb.append(" (").append(remaining).append("/").append(offer.getMaxUses()).append(")");
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "autotrade",
|
||||
"name": "Auto Trade",
|
||||
"id": "${mod_id}",
|
||||
"name": "${mod_name}",
|
||||
"version": "${mod_version}",
|
||||
|
||||
"description": "AFK trade with villagers",
|
||||
"authors": [
|
||||
"sebseb7"
|
||||
"${mod_author}"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://modrinth.com/mod/autotrade-fabric",
|
||||
"issues": "https://github.com/sebseb7/autotrade-fabric/issues",
|
||||
"sources": "https://github.com/sebseb7/autotrade-fabric"
|
||||
"homepage": "${mod_homepage}",
|
||||
"issues": "${mod_issue_tracker}",
|
||||
"sources": "${mod_sources}"
|
||||
},
|
||||
|
||||
"license": "0BSD",
|
||||
@@ -30,8 +30,8 @@
|
||||
],
|
||||
|
||||
"depends": {
|
||||
"minecraft": ">=${minecraft_version_min}",
|
||||
"minecraft": "${minecraft_dependency}",
|
||||
"malilib": ">=${malilib_version}",
|
||||
"fabric-api": ">=0.145.0"
|
||||
"fabric-api": "${fabric_api_dependency}"
|
||||
}
|
||||
}
|
||||
10
stonecutter.gradle.kts
Normal file
10
stonecutter.gradle.kts
Normal file
@@ -0,0 +1,10 @@
|
||||
plugins {
|
||||
id("dev.kikugie.stonecutter")
|
||||
}
|
||||
|
||||
stonecutter active "26.1.2-fabric"
|
||||
|
||||
stonecutter registerChiseled tasks.register("chiseledBuild", stonecutter.chiseled) {
|
||||
group = "project"
|
||||
ofTask("build")
|
||||
}
|
||||
8
versions/1.21.10-fabric/gradle.properties
Normal file
8
versions/1.21.10-fabric/gradle.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
modstitch.platform=fabric-loom-remap
|
||||
deps.minecraft=1.21.10
|
||||
|
||||
fabric_loader_version=0.19.2
|
||||
fabric_api_version=0.138.4+1.21.10
|
||||
malilib_version=0.26.8
|
||||
mod_menu_version=16.0.1
|
||||
minecraft_version_min=1.21.10
|
||||
8
versions/1.21.11-fabric/gradle.properties
Normal file
8
versions/1.21.11-fabric/gradle.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
modstitch.platform=fabric-loom-remap
|
||||
deps.minecraft=1.21.11
|
||||
|
||||
fabric_loader_version=0.19.2
|
||||
fabric_api_version=0.136.0+1.21.11
|
||||
malilib_version=0.27.3
|
||||
mod_menu_version=17.0.0
|
||||
minecraft_version_min=1.21.11
|
||||
8
versions/26.1.2-fabric/gradle.properties
Normal file
8
versions/26.1.2-fabric/gradle.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
modstitch.platform=fabric-loom
|
||||
deps.minecraft=26.1.2
|
||||
|
||||
fabric_loader_version=0.19.2
|
||||
fabric_api_version=0.145.4+26.1.2
|
||||
malilib_version=0.28.2
|
||||
mod_menu_version=18.0.0-alpha.8
|
||||
minecraft_version_min=26.1.2
|
||||
Reference in New Issue
Block a user