├── forge ├── changelog.md ├── src │ └── main │ │ ├── resources │ │ ├── META-INF │ │ │ ├── MANIFEST.MF │ │ │ └── mods.toml │ │ ├── pack.mcmeta │ │ └── trade_cycling.mixins.json │ │ └── java │ │ └── de │ │ └── maxhenkel │ │ └── tradecycling │ │ ├── config │ │ └── ForgeTradeCyclingClientConfig.java │ │ ├── ForgeTradeCyclingMod.java │ │ └── ForgeTradeCyclingClientMod.java ├── gradle.properties └── build.gradle ├── neoforge ├── changelog.md ├── gradle.properties ├── src │ └── main │ │ ├── resources │ │ ├── pack.mcmeta │ │ ├── trade_cycling.mixins.json │ │ └── META-INF │ │ │ └── neoforge.mods.toml │ │ └── java │ │ └── de │ │ └── maxhenkel │ │ └── tradecycling │ │ ├── config │ │ └── NeoForgeTradeCyclingClientConfig.java │ │ ├── NeoForgeTradeCyclingMod.java │ │ └── NeoForgeTradeCyclingClientMod.java └── build.gradle ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ ├── translation.yml │ └── bug_report.yml ├── fabric ├── changelog.md ├── src │ └── main │ │ ├── resources │ │ ├── trade_cycling.mixins.json │ │ └── fabric.mod.json │ │ └── java │ │ └── de │ │ └── maxhenkel │ │ └── tradecycling │ │ ├── config │ │ └── FabricTradeCyclingClientConfig.java │ │ ├── mixin │ │ └── KeyboardMixin.java │ │ ├── FabricTradeCyclingMod.java │ │ └── FabricTradeCyclingClientMod.java ├── gradle.properties └── build.gradle ├── common ├── src │ └── main │ │ ├── resources │ │ ├── icon.png │ │ └── assets │ │ │ └── trade_cycling │ │ │ ├── lang │ │ │ ├── zh_tw.json │ │ │ ├── zh_cn.json │ │ │ ├── ru_ru.json │ │ │ ├── en_us.json │ │ │ ├── pt_br.json │ │ │ ├── de_de.json │ │ │ ├── es_mx.json │ │ │ ├── tr_tr.json │ │ │ └── pt_pt.json │ │ │ └── textures │ │ │ └── cycle_trades.png │ │ └── java │ │ └── de │ │ └── maxhenkel │ │ └── tradecycling │ │ ├── config │ │ └── TradeCyclingClientConfig.java │ │ ├── mixin │ │ ├── VillagerAccessor.java │ │ ├── AbstractContainerScreenAccessor.java │ │ └── MerchantMenuAccessor.java │ │ ├── net │ │ └── CycleTradesPacket.java │ │ ├── TradeCyclingMod.java │ │ ├── gui │ │ └── CycleTradesButton.java │ │ ├── TradeCyclingClientMod.java │ │ └── compatibility │ │ └── VisibleTraders.java ├── gradle.properties └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── readme.md ├── gradle.properties ├── .gitignore ├── gradlew.bat └── gradlew /forge/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /neoforge/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /fabric/changelog.md: -------------------------------------------------------------------------------- 1 | - Fixed compatibility with other mods 2 | -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | MixinConfigs: trade_cycling.mixins.json 2 | -------------------------------------------------------------------------------- /forge/gradle.properties: -------------------------------------------------------------------------------- 1 | mod_loader=forge 2 | forge_version=61.0.1 3 | forge_dependency=[61.0.1,) 4 | -------------------------------------------------------------------------------- /common/src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/trade-cycling/HEAD/common/src/main/resources/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/trade-cycling/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /neoforge/gradle.properties: -------------------------------------------------------------------------------- 1 | mod_loader=neoforge 2 | neoforge_version=21.11.0-beta 3 | neoforge_dependency=[21.11.0-beta,) 4 | -------------------------------------------------------------------------------- /common/gradle.properties: -------------------------------------------------------------------------------- 1 | mod_loader=fabric 2 | 3 | enable_curseforge_upload=false 4 | enable_modrinth_upload=false 5 | enable_mod_update=false 6 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "切換交易", 3 | "key.trade_cycling.cycle_trades": "切換交易" 4 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "刷新交易", 3 | "key.trade_cycling.cycle_trades": "刷新交易" 4 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Цикл продаж", 3 | "key.trade_cycling.cycle_trades": "Цикл продаж" 4 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Cycle Trades", 3 | "key.trade_cycling.cycle_trades": "Cycle Trades" 4 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/pt_br.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Alterar Trocas", 3 | "key.trade_cycling.cycle_trades": "Alterar Trocas" 4 | } 5 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Angebote erneuern", 3 | "key.trade_cycling.cycle_trades": "Angebote erneuern" 4 | } -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/es_mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Variar intercambios", 3 | "key.trade_cycling.cycle_trades": "Variar intercambios" 4 | } 5 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Teklifleri Değiştir", 3 | "key.trade_cycling.cycle_trades": "Teklifleri Değiştir" 4 | } 5 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/textures/cycle_trades.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henkelmax/trade-cycling/HEAD/common/src/main/resources/assets/trade_cycling/textures/cycle_trades.png -------------------------------------------------------------------------------- /forge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "trade_cycling resources", 4 | "min_format": [65, 0], 5 | "max_format": [999, 0] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "trade_cycling resources", 4 | "min_format": [65, 0], 5 | "max_format": [999, 0] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/resources/assets/trade_cycling/lang/pt_pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "tooltip.trade_cycling.cycle_trades": "Alterar Negociações", 3 | "key.trade_cycling.cycle_trades": "Alterar Negociações" 4 | } -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.gradleup.shadow' 2 | apply plugin: 'fabric-loom' 3 | 4 | apply from: "https://raw.githubusercontent.com/henkelmax/mod-gradle-scripts/${mod_gradle_script_version}/mod.gradle" 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/config/TradeCyclingClientConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.config; 2 | 3 | public abstract class TradeCyclingClientConfig { 4 | 5 | public abstract CycleTradesButtonLocation getCycleTradesButtonLocation(); 6 | 7 | public static enum CycleTradesButtonLocation { 8 | TOP_LEFT, TOP_RIGHT, NONE 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /fabric/src/main/resources/trade_cycling.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "de.maxhenkel.tradecycling.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "MerchantMenuAccessor", 8 | "VillagerAccessor" 9 | ], 10 | "client": [ 11 | "AbstractContainerScreenAccessor", 12 | "KeyboardMixin" 13 | ], 14 | "injectors": { 15 | "defaultRequire": 1 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /forge/src/main/resources/trade_cycling.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "de.maxhenkel.tradecycling.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "refmap": "trade_cycling.refmap.json", 7 | "mixins": [ 8 | "MerchantMenuAccessor", 9 | "VillagerAccessor" 10 | ], 11 | "client": [ 12 | "AbstractContainerScreenAccessor" 13 | ], 14 | "injectors": { 15 | "defaultRequire": 1 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /fabric/gradle.properties: -------------------------------------------------------------------------------- 1 | mod_loader=fabric 2 | fabric_loader_dependency=>=0.18.1 3 | fabric_api_version=0.139.4+1.21.11 4 | fabric_api_dependency_breaks=<0.139.4+1.21.11 5 | minecraft_dependency=1.21.11 6 | 7 | included_fabric_api_modules=\ 8 | fabric-api-base, \ 9 | fabric-resource-loader-v0, \ 10 | fabric-resource-loader-v1, \ 11 | fabric-lifecycle-events-v1, \ 12 | fabric-key-binding-api-v1, \ 13 | fabric-networking-api-v1, \ 14 | fabric-screen-api-v1 15 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/trade_cycling.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8.4", 4 | "package": "de.maxhenkel.tradecycling.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "refmap": "trade_cycling.refmap.json", 7 | "mixins": [ 8 | "MerchantMenuAccessor", 9 | "VillagerAccessor" 10 | ], 11 | "client": [ 12 | "AbstractContainerScreenAccessor" 13 | ], 14 | "injectors": { 15 | "defaultRequire": 1 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/mixin/VillagerAccessor.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.mixin; 2 | 3 | import net.minecraft.world.entity.npc.villager.Villager; 4 | import net.minecraft.world.entity.player.Player; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(Villager.class) 9 | public interface VillagerAccessor { 10 | 11 | @Invoker("updateSpecialPrices") 12 | void invokeUpdateSpecialPrices(Player player); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/mixin/AbstractContainerScreenAccessor.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.mixin; 2 | 3 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 4 | import org.spongepowered.asm.mixin.Mixin; 5 | import org.spongepowered.asm.mixin.gen.Accessor; 6 | 7 | @Mixin(AbstractContainerScreen.class) 8 | public interface AbstractContainerScreenAccessor { 9 | 10 | @Accessor 11 | int getLeftPos(); 12 | 13 | @Accessor 14 | int getTopPos(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { url = 'https://maven.fabricmc.net/' } 5 | maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } 6 | maven { url = 'https://maven.neoforged.net/releases' } 7 | maven { url = 'https://maven.maxhenkel.de/repository/public' } 8 | maven { url = 'https://maven.minecraftforge.net' } 9 | } 10 | } 11 | 12 | rootProject.name = 'trade-cycling' 13 | include('common', 'fabric', 'neoforge', 'forge') -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/mixin/MerchantMenuAccessor.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.mixin; 2 | 3 | import net.minecraft.world.inventory.MerchantContainer; 4 | import net.minecraft.world.inventory.MerchantMenu; 5 | import net.minecraft.world.item.trading.Merchant; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Accessor; 8 | 9 | @Mixin(MerchantMenu.class) 10 | public interface MerchantMenuAccessor { 11 | 12 | @Accessor 13 | Merchant getTrader(); 14 | 15 | @Accessor 16 | MerchantContainer getTradeContainer(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /neoforge/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.neoforged.gradle.userdev' version "${neogradle_version}" 3 | } 4 | 5 | apply plugin: 'com.gradleup.shadow' 6 | apply plugin: 'de.maxhenkel.cursegradle' 7 | apply plugin: 'com.modrinth.minotaur' 8 | apply plugin: 'mod-update' 9 | 10 | apply from: "https://raw.githubusercontent.com/henkelmax/mod-gradle-scripts/${mod_gradle_script_version}/mod.gradle" 11 | 12 | processResources { 13 | filesMatching('**/*.toml') { 14 | expand 'mod_version': mod_version, 15 | 'neoforge_dependency': neoforge_dependency, 16 | 'minecraft_version': minecraft_version 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Trade Cycling 2 | 3 | ## Links 4 | 5 | - [Modrinth](https://modrinth.com/mod/trade-cycling) 6 | - [CurseForge](https://www.curseforge.com/minecraft/mc-mods/trade-cycling) 7 | 8 | --- 9 | 10 | This mod adds the trade cycling functionality of [Easy Villagers](https://www.curseforge.com/minecraft/mc-mods/easy-villagers). 11 | 12 | 13 | Cycle the trades of every villager that hasn't been traded before without needing to wait for working hours or replacing the workstation block over and over again. 14 | 15 | ## Features 16 | 17 | - Trade cycling with a button in the trading GUI 18 | - Trade cycling with a keybind (C by default) 19 | - Configurable trade cycling button location 20 | 21 | ![](https://media.giphy.com/media/AX8FajusxpjFW9wEyP/giphy.gif) -------------------------------------------------------------------------------- /forge/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.minecraftforge.gradle' version "${forgegradle_version}" 3 | } 4 | 5 | apply plugin: 'com.gradleup.shadow' 6 | apply plugin: 'de.maxhenkel.cursegradle' 7 | apply plugin: 'com.modrinth.minotaur' 8 | apply plugin: 'mod-update' 9 | 10 | apply from: "https://raw.githubusercontent.com/henkelmax/mod-gradle-scripts/${mod_gradle_script_version}/mod.gradle" 11 | 12 | processResources { 13 | filesMatching('**/*.toml') { 14 | expand 'mod_version': mod_version, 15 | 'forge_dependency': forge_dependency, 16 | 'minecraft_version': minecraft_version 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation('net.sf.jopt-simple:jopt-simple:5.0.4') { version { strictly '5.0.4' } } 22 | } -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'fabric-loom' 2 | apply plugin: 'com.gradleup.shadow' 3 | apply plugin: 'de.maxhenkel.cursegradle' 4 | apply plugin: 'com.modrinth.minotaur' 5 | apply plugin: 'mod-update' 6 | 7 | apply from: "https://raw.githubusercontent.com/henkelmax/mod-gradle-scripts/${mod_gradle_script_version}/mod.gradle" 8 | 9 | processResources { 10 | filesMatching('fabric.mod.json') { 11 | expand 'java_version': java_version, 12 | 'mod_version': mod_version, 13 | 'minecraft_version': minecraft_version, 14 | 'minecraft_dependency': minecraft_dependency, 15 | 'fabric_loader_dependency': fabric_loader_dependency, 16 | 'fabric_api_dependency_breaks': fabric_api_dependency_breaks 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fabric/src/main/java/de/maxhenkel/tradecycling/config/FabricTradeCyclingClientConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.config; 2 | 3 | import de.maxhenkel.configbuilder.ConfigBuilder; 4 | import de.maxhenkel.configbuilder.entry.ConfigEntry; 5 | 6 | public class FabricTradeCyclingClientConfig extends TradeCyclingClientConfig { 7 | 8 | public final ConfigEntry cycleTradesButtonLocation; 9 | 10 | public FabricTradeCyclingClientConfig(ConfigBuilder builder) { 11 | cycleTradesButtonLocation = builder.enumEntry("cycle_trades_button_location", CycleTradesButtonLocation.TOP_LEFT); 12 | } 13 | 14 | @Override 15 | public CycleTradesButtonLocation getCycleTradesButtonLocation() { 16 | return cycleTradesButtonLocation.get(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fabric/src/main/java/de/maxhenkel/tradecycling/mixin/KeyboardMixin.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.mixin; 2 | 3 | import de.maxhenkel.tradecycling.FabricTradeCyclingClientMod; 4 | import net.minecraft.client.KeyboardHandler; 5 | import net.minecraft.client.input.KeyEvent; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(KeyboardHandler.class) 12 | public class KeyboardMixin { 13 | 14 | @Inject(at = @At("HEAD"), method = "keyPress") 15 | private void onKey(long handle, int action, KeyEvent keyEvent, CallbackInfo ci) { 16 | FabricTradeCyclingClientMod.instance().onCycleKeyPressed(keyEvent, action); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /forge/src/main/java/de/maxhenkel/tradecycling/config/ForgeTradeCyclingClientConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.config; 2 | 3 | import net.minecraftforge.common.ForgeConfigSpec; 4 | 5 | public class ForgeTradeCyclingClientConfig extends TradeCyclingClientConfig { 6 | 7 | public final ForgeConfigSpec.EnumValue cycleTradesButtonLocation; 8 | 9 | public ForgeTradeCyclingClientConfig(ForgeConfigSpec.Builder builder) { 10 | cycleTradesButtonLocation = builder 11 | .comment("The location of the cycle trades button") 12 | .defineEnum("cycle_trades_button_location", CycleTradesButtonLocation.TOP_LEFT); 13 | } 14 | 15 | @Override 16 | public CycleTradesButtonLocation getCycleTradesButtonLocation() { 17 | return cycleTradesButtonLocation.get(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /neoforge/src/main/java/de/maxhenkel/tradecycling/config/NeoForgeTradeCyclingClientConfig.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.config; 2 | 3 | import net.neoforged.neoforge.common.ModConfigSpec; 4 | 5 | public class NeoForgeTradeCyclingClientConfig extends TradeCyclingClientConfig { 6 | 7 | public final ModConfigSpec.EnumValue cycleTradesButtonLocation; 8 | 9 | public NeoForgeTradeCyclingClientConfig(ModConfigSpec.Builder builder) { 10 | cycleTradesButtonLocation = builder 11 | .comment("The location of the cycle trades button") 12 | .defineEnum("cycle_trades_button_location", CycleTradesButtonLocation.TOP_LEFT); 13 | } 14 | 15 | @Override 16 | public CycleTradesButtonLocation getCycleTradesButtonLocation() { 17 | return cycleTradesButtonLocation.get(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /forge/src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | license = "All rights reserved" 4 | issueTrackerURL = "https://github.com/henkelmax/trade-cycling/issues" 5 | [[mods]] 6 | modId = "trade_cycling" 7 | version = "${mod_version}" 8 | displayName = "Trade Cycling" 9 | updateJSONURL = "https://update.maxhenkel.de/forge/trade_cycling" 10 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/trade-cycling" 11 | logoFile = "icon.png" 12 | authors = "Max Henkel" 13 | description = '''The trade cycling functionality from Easy Villagers.''' 14 | [[dependencies.trade_cycling]] 15 | modId = "forge" 16 | mandatory = true 17 | versionRange = "${forge_dependency}" 18 | ordering = "NONE" 19 | side = "BOTH" 20 | [[dependencies.trade_cycling]] 21 | modId = "minecraft" 22 | mandatory = true 23 | versionRange = "[${minecraft_version}]" 24 | ordering = "NONE" 25 | side = "BOTH" -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | org.gradle.daemon=false 3 | 4 | java_version=21 5 | java_toolchain_version=21 6 | 7 | included_projects=:common 8 | 9 | minecraft_version=1.21.11 10 | fabric_loader_version=0.18.1 11 | 12 | # Mod information 13 | mod_version=1.21.11-1.0.20 14 | mod_id=trade_cycling 15 | mod_display_name=Trade Cycling 16 | 17 | # Script configuration 18 | use_mixins=true 19 | enable_configbuilder=true 20 | 21 | # Optional dependencies 22 | modrinth_upload_optional_dependencies=visible-traders 23 | 24 | # Project upload 25 | curseforge_upload_id=570431 26 | modrinth_upload_id=qpPoAL6m 27 | upload_release_type=beta 28 | upload_recommended=true 29 | 30 | # Gradle plugins 31 | mod_gradle_script_version=1.0.54 32 | fabric_loom_version=1.14-SNAPSHOT 33 | forgegradle_version=7.0.0-beta.51 34 | neogradle_version=[7.1.11,7.2) 35 | mod_update_version=2.0.0 36 | cursegradle_version=1.5.1 37 | shadow_version=9.2.2 38 | minotaur_version=2.+ 39 | -------------------------------------------------------------------------------- /neoforge/src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "*" 3 | license = "All rights reserved" 4 | issueTrackerURL = "https://github.com/henkelmax/trade-cycling/issues" 5 | [[mods]] 6 | modId = "trade_cycling" 7 | version = "${mod_version}" 8 | displayName = "Trade Cycling" 9 | updateJSONURL = "https://update.maxhenkel.de/neoforge/trade_cycling" 10 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/trade-cycling" 11 | logoFile = "icon.png" 12 | authors = "Max Henkel" 13 | description = '''The trade cycling functionality from Easy Villagers.''' 14 | [[mixins]] 15 | config = "trade_cycling.mixins.json" 16 | [[dependencies.trade_cycling]] 17 | modId = "neoforge" 18 | type = "required" 19 | versionRange = "${neoforge_dependency}" 20 | ordering = "NONE" 21 | side = "BOTH" 22 | [[dependencies.trade_cycling]] 23 | modId = "minecraft" 24 | type = "required" 25 | versionRange = "[${minecraft_version}]" 26 | ordering = "NONE" 27 | side = "BOTH" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/translation.yml: -------------------------------------------------------------------------------- 1 | name: Translation 2 | description: Submit a translation for this project 3 | labels: [translation] 4 | assignees: henkelmax 5 | body: 6 | - type: textarea 7 | id: notes 8 | attributes: 9 | label: Additional notes 10 | description: Additional information. 11 | validations: 12 | required: false 13 | - type: input 14 | id: locale_code 15 | attributes: 16 | label: Locale code 17 | description: The Minecraft locale code (See [this](https://minecraft.wiki/w/Language#Languages) for more information). 18 | placeholder: en_us 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: translation 23 | attributes: 24 | label: Translation json 25 | description: The contents of your translation file. 26 | render: json 27 | placeholder: | 28 | { 29 | "translation.key": "Translated value" 30 | } 31 | validations: 32 | required: true 33 | -------------------------------------------------------------------------------- /fabric/src/main/java/de/maxhenkel/tradecycling/FabricTradeCyclingMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.net.CycleTradesPacket; 4 | import net.fabricmc.api.ModInitializer; 5 | import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; 6 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 7 | import net.fabricmc.loader.api.FabricLoader; 8 | 9 | public class FabricTradeCyclingMod extends TradeCyclingMod implements ModInitializer { 10 | 11 | @Override 12 | public void onInitialize() { 13 | registerPacket(); 14 | init(); 15 | } 16 | 17 | private void registerPacket() { 18 | PayloadTypeRegistry.playC2S().register(CycleTradesPacket.CYCLE_TRADES, CycleTradesPacket.CODEC); 19 | ServerPlayNetworking.registerGlobalReceiver(CycleTradesPacket.CYCLE_TRADES, (payload, context) -> context.player().level().getServer().execute(() -> onCycleTrades(context.player()))); 20 | } 21 | 22 | @Override 23 | public boolean isModLoaded(String modId) { 24 | return FabricLoader.getInstance().isModLoaded(modId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/net/CycleTradesPacket.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.net; 2 | 3 | import de.maxhenkel.tradecycling.TradeCyclingMod; 4 | import net.minecraft.network.RegistryFriendlyByteBuf; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import net.minecraft.resources.Identifier; 8 | 9 | public class CycleTradesPacket implements CustomPacketPayload { 10 | 11 | public static final Type CYCLE_TRADES = new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath(TradeCyclingMod.MODID, "cycle_trades")); 12 | 13 | public static final StreamCodec CODEC = new StreamCodec<>() { 14 | @Override 15 | public void encode(RegistryFriendlyByteBuf buf, CycleTradesPacket packet) { 16 | } 17 | 18 | @Override 19 | public CycleTradesPacket decode(RegistryFriendlyByteBuf buf) { 20 | return new CycleTradesPacket(); 21 | } 22 | }; 23 | 24 | @Override 25 | public Type type() { 26 | return CYCLE_TRADES; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "trade_cycling", 4 | "version": "${mod_version}", 5 | "name": "Trade Cycling", 6 | "description": "The trade cycling functionality from Easy Villagers.", 7 | "authors": [ 8 | "Max Henkel" 9 | ], 10 | "contact": { 11 | "homepage": "https://www.curseforge.com/minecraft/mc-mods/trade-cycling", 12 | "issues": "https://github.com/henkelmax/trade-cycling/issues", 13 | "sources": "https://github.com/henkelmax/trade-cycling", 14 | "email": "info@modrepo.de" 15 | }, 16 | "license": "All Rights Reserved", 17 | "icon": "icon.png", 18 | "environment": "*", 19 | "entrypoints": { 20 | "main": [ 21 | "de.maxhenkel.tradecycling.FabricTradeCyclingMod" 22 | ], 23 | "client": [ 24 | "de.maxhenkel.tradecycling.FabricTradeCyclingClientMod" 25 | ] 26 | }, 27 | "mixins": [ 28 | "trade_cycling.mixins.json" 29 | ], 30 | "depends": { 31 | "fabricloader": "${fabric_loader_dependency}", 32 | "minecraft": "${minecraft_dependency}" 33 | }, 34 | "breaks": { 35 | "easy_villagers": "*", 36 | "fabric-api": "${fabric_api_dependency_breaks}" 37 | }, 38 | "suggests": { 39 | "visibletraders": ">=0.1.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /neoforge/src/main/java/de/maxhenkel/tradecycling/NeoForgeTradeCyclingMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.net.CycleTradesPacket; 4 | import net.minecraft.network.protocol.PacketFlow; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import net.neoforged.bus.api.IEventBus; 7 | import net.neoforged.fml.ModList; 8 | import net.neoforged.fml.common.Mod; 9 | import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; 10 | import net.neoforged.fml.loading.FMLEnvironment; 11 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 12 | import net.neoforged.neoforge.network.registration.PayloadRegistrar; 13 | 14 | @Mod(TradeCyclingMod.MODID) 15 | public class NeoForgeTradeCyclingMod extends TradeCyclingMod { 16 | 17 | private NeoForgeTradeCyclingClientMod clientMod; 18 | 19 | public NeoForgeTradeCyclingMod(IEventBus eventBus) { 20 | eventBus.addListener(this::commonSetup); 21 | eventBus.addListener(this::onRegisterPayloadHandler); 22 | 23 | if (FMLEnvironment.getDist().isClient()) { 24 | clientMod = new NeoForgeTradeCyclingClientMod(eventBus); 25 | } 26 | } 27 | 28 | public void commonSetup(FMLCommonSetupEvent event) { 29 | init(); 30 | } 31 | 32 | public void onRegisterPayloadHandler(RegisterPayloadHandlersEvent event) { 33 | PayloadRegistrar registrar = event.registrar(MODID).optional(); 34 | 35 | registrar.playToServer(CycleTradesPacket.CYCLE_TRADES, CycleTradesPacket.CODEC, (payload, context) -> { 36 | if (context.flow().equals(PacketFlow.SERVERBOUND) && context.player() instanceof ServerPlayer player) { 37 | context.enqueueWork(() -> onCycleTrades(player)); 38 | } 39 | }); 40 | } 41 | 42 | @Override 43 | public boolean isModLoaded(String modId) { 44 | return ModList.get().isLoaded(modId); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /forge/src/main/java/de/maxhenkel/tradecycling/ForgeTradeCyclingMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.net.CycleTradesPacket; 4 | import net.minecraftforge.event.network.CustomPayloadEvent; 5 | import net.minecraftforge.fml.ModList; 6 | import net.minecraftforge.fml.common.Mod; 7 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 8 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 9 | import net.minecraftforge.fml.loading.FMLEnvironment; 10 | import net.minecraftforge.network.ChannelBuilder; 11 | import net.minecraftforge.network.EventNetworkChannel; 12 | 13 | @Mod(TradeCyclingMod.MODID) 14 | public class ForgeTradeCyclingMod extends TradeCyclingMod { 15 | 16 | public static EventNetworkChannel CYCLE_TRADES_CHANNEL; 17 | 18 | private ForgeTradeCyclingClientMod clientMod; 19 | 20 | public ForgeTradeCyclingMod(FMLJavaModLoadingContext context) { 21 | FMLCommonSetupEvent.getBus(context.getModBusGroup()).addListener(this::commonSetup); 22 | 23 | if (FMLEnvironment.dist.isClient()) { 24 | clientMod = new ForgeTradeCyclingClientMod(context); 25 | } 26 | } 27 | 28 | public void commonSetup(FMLCommonSetupEvent event) { 29 | init(); 30 | registerPacket(); 31 | } 32 | 33 | public void registerPacket() { 34 | CYCLE_TRADES_CHANNEL = ChannelBuilder.named(CycleTradesPacket.CYCLE_TRADES.id()) 35 | .networkProtocolVersion(0) 36 | .acceptedVersions((status, version) -> true) 37 | .optional() 38 | .eventNetworkChannel(); 39 | CYCLE_TRADES_CHANNEL.addListener(event -> { 40 | CustomPayloadEvent.Context context = event.getSource(); 41 | if (context.isServerSide()) { 42 | context.enqueueWork(() -> onCycleTrades(context.getSender())); 43 | } 44 | }); 45 | } 46 | 47 | @Override 48 | public boolean isModLoaded(String modId) { 49 | return ModList.get().isLoaded(modId); 50 | } 51 | } -------------------------------------------------------------------------------- /fabric/src/main/java/de/maxhenkel/tradecycling/FabricTradeCyclingClientMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.configbuilder.ConfigBuilder; 4 | import de.maxhenkel.tradecycling.config.FabricTradeCyclingClientConfig; 5 | import de.maxhenkel.tradecycling.config.TradeCyclingClientConfig; 6 | import de.maxhenkel.tradecycling.net.CycleTradesPacket; 7 | import net.fabricmc.api.ClientModInitializer; 8 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; 9 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 10 | import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; 11 | import net.fabricmc.fabric.api.client.screen.v1.Screens; 12 | import net.fabricmc.loader.api.FabricLoader; 13 | 14 | public class FabricTradeCyclingClientMod extends TradeCyclingClientMod implements ClientModInitializer { 15 | 16 | private static FabricTradeCyclingClientMod instance; 17 | 18 | public FabricTradeCyclingClientMod() { 19 | instance = this; 20 | CONFIG = createClientConfig(); 21 | } 22 | 23 | @Override 24 | public void onInitializeClient() { 25 | clientInit(); 26 | ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { 27 | onOpenScreen(screen, guiEventListener -> { 28 | Screens.getButtons(screen).add(guiEventListener); 29 | }); 30 | }); 31 | } 32 | 33 | @Override 34 | public void registerKeyBindings() { 35 | KeyBindingHelper.registerKeyBinding(CYCLE_TRADES_KEY); 36 | } 37 | 38 | @Override 39 | public void sendCycleTradesPacket() { 40 | ClientPlayNetworking.send(new CycleTradesPacket()); 41 | } 42 | 43 | @Override 44 | public TradeCyclingClientConfig createClientConfig() { 45 | return ConfigBuilder.builder(FabricTradeCyclingClientConfig::new) 46 | .path(FabricLoader.getInstance().getConfigDir().resolve(TradeCyclingMod.MODID).resolve("trade_cycling.properties")) 47 | .build(); 48 | } 49 | 50 | public static FabricTradeCyclingClientMod instance() { 51 | return instance; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | 120 | curseforge_api_key.txt 121 | mod_update_api_key.txt 122 | modrinth_token.txt 123 | runs -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/TradeCyclingMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.compatibility.VisibleTraders; 4 | import de.maxhenkel.tradecycling.mixin.VillagerAccessor; 5 | import de.maxhenkel.tradecycling.mixin.MerchantMenuAccessor; 6 | import net.minecraft.server.level.ServerPlayer; 7 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 8 | import net.minecraft.world.entity.npc.villager.Villager; 9 | import net.minecraft.world.inventory.MerchantMenu; 10 | import net.minecraft.world.item.trading.Merchant; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import javax.annotation.Nullable; 15 | 16 | public abstract class TradeCyclingMod { 17 | 18 | public static final String MODID = "trade_cycling"; 19 | public static final Logger LOGGER = LogManager.getLogger(MODID); 20 | 21 | 22 | public void init() { 23 | VisibleTraders.init(this); 24 | } 25 | 26 | public abstract boolean isModLoaded(String modId); 27 | 28 | public static void onCycleTrades(@Nullable ServerPlayer player) { 29 | if (player == null) { 30 | return; 31 | } 32 | if (!(player.containerMenu instanceof MerchantMenuAccessor merchantAccessor)) { 33 | return; 34 | } 35 | if (!(player.containerMenu instanceof MerchantMenu container)) { 36 | return; 37 | } 38 | Merchant merchant = merchantAccessor.getTrader(); 39 | 40 | if (container.getTraderXp() > 0 && merchantAccessor.getTradeContainer().getActiveOffer() != null) { 41 | return; 42 | } 43 | 44 | if (!(merchant instanceof Villager villager)) { 45 | return; 46 | } 47 | if (!(merchant instanceof VillagerAccessor villagerAccessor)) { 48 | return; 49 | } 50 | 51 | if (villager.getBrain().getMemory(MemoryModuleType.JOB_SITE).isEmpty()) { 52 | return; 53 | } 54 | 55 | villager.setOffers(null); 56 | villager.getOffers(); 57 | villagerAccessor.invokeUpdateSpecialPrices(player); 58 | villager.setTradingPlayer(player); 59 | VisibleTraders.forceTradeGeneration(villager); 60 | sendOffers(player, container.containerId, villager); 61 | } 62 | 63 | private static void sendOffers(ServerPlayer player, int containerId, Villager villager) { 64 | player.sendMerchantOffers(containerId, VisibleTraders.getOffers(villager), VisibleTraders.getLevel(villager), villager.getVillagerXp(), villager.showProgressBar(), villager.canRestock()); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: [triage] 4 | assignees: henkelmax 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | > [!WARNING] 10 | > This form is **only for bug reports**! 11 | > Please don't abuse this for feature requests or questions. 12 | > Forms that are not filled out properly will be closed without response! 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Bug description 17 | description: A clear and concise description of what the bug is. 18 | validations: 19 | required: true 20 | - type: input 21 | id: mc_version 22 | attributes: 23 | label: Minecraft version 24 | description: The Minecraft version you are using. 25 | placeholder: 1.20.4 26 | validations: 27 | required: true 28 | - type: input 29 | id: mod_version 30 | attributes: 31 | label: Mod version 32 | description: The version of the mod. 33 | placeholder: 1.20.4-1.2.3 34 | validations: 35 | required: true 36 | - type: input 37 | id: mod_loader_version 38 | attributes: 39 | label: Mod loader and version 40 | description: The mod loader and mod loader version you are using. 41 | placeholder: Fabric Loader 0.15.6 / NeoForge 20.4.1 / Forge 48.1.0 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: steps 46 | attributes: 47 | label: Steps to reproduce 48 | description: | 49 | Steps to reproduce the issue. 50 | Please **don't** report issues that are not reproducible. 51 | placeholder: | 52 | 1. Go to '...' 53 | 2. Click on '...' 54 | 3. Scroll down to '...' 55 | 4. See error 56 | validations: 57 | required: true 58 | - type: textarea 59 | id: expected 60 | attributes: 61 | label: Expected behavior 62 | description: A clear and concise description of what you expected to happen. 63 | validations: 64 | required: false 65 | - type: input 66 | id: logs 67 | attributes: 68 | label: Log files 69 | description: | 70 | Please provide log files of the game session in which the problem occurred. 71 | Don't paste the complete logs into the issue. 72 | You can use [https://gist.github.com/](https://gist.github.com/). 73 | placeholder: https://gist.github.com/exampleuser/example 74 | validations: 75 | required: true 76 | - type: textarea 77 | id: screenshots 78 | attributes: 79 | label: Screenshots 80 | description: Screenshots of the issue. 81 | validations: 82 | required: false 83 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/gui/CycleTradesButton.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.gui; 2 | 3 | import de.maxhenkel.tradecycling.TradeCyclingMod; 4 | import de.maxhenkel.tradecycling.mixin.MerchantMenuAccessor; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.gui.GuiGraphics; 7 | import net.minecraft.client.gui.components.AbstractButton; 8 | import net.minecraft.client.gui.narration.NarrationElementOutput; 9 | import net.minecraft.client.gui.screens.inventory.MerchantScreen; 10 | import net.minecraft.client.input.InputWithModifiers; 11 | import net.minecraft.client.renderer.RenderPipelines; 12 | import net.minecraft.network.chat.Component; 13 | import net.minecraft.resources.Identifier; 14 | import net.minecraft.world.inventory.MerchantMenu; 15 | 16 | import java.util.Collections; 17 | import java.util.function.Consumer; 18 | 19 | public class CycleTradesButton extends AbstractButton { 20 | 21 | private static final Identifier ARROW_BUTTON = Identifier.fromNamespaceAndPath(TradeCyclingMod.MODID, "textures/cycle_trades.png"); 22 | 23 | public static final int WIDTH = 18; 24 | public static final int HEIGHT = 14; 25 | 26 | private MerchantScreen screen; 27 | private Consumer onPress; 28 | 29 | public CycleTradesButton(int x, int y, Consumer onPress, MerchantScreen screen) { 30 | super(x, y, WIDTH, HEIGHT, Component.empty()); 31 | this.onPress = onPress; 32 | this.screen = screen; 33 | } 34 | 35 | @Override 36 | public void onPress(InputWithModifiers inputWithModifiers) { 37 | onPress.accept(this); 38 | } 39 | 40 | @Override 41 | public void renderContents(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { 42 | visible = canCycle(screen.getMenu()); 43 | if (isHovered) { 44 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, ARROW_BUTTON, getX(), getY(), 0, 14, WIDTH, HEIGHT, 32, 32); 45 | guiGraphics.setTooltipForNextFrame(Minecraft.getInstance().font, Collections.singletonList(Component.translatable("tooltip.trade_cycling.cycle_trades").getVisualOrderText()), mouseX, mouseY); 46 | } else { 47 | guiGraphics.blit(RenderPipelines.GUI_TEXTURED, ARROW_BUTTON, getX(), getY(), 0, 0, WIDTH, HEIGHT, 32, 32); 48 | } 49 | } 50 | 51 | @Override 52 | protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { 53 | 54 | } 55 | 56 | public static boolean canCycle(MerchantMenu menu) { 57 | if (menu instanceof MerchantMenuAccessor m) { 58 | return menu.showProgressBar() && menu.getTraderXp() <= 0 && m.getTradeContainer().getActiveOffer() == null; 59 | } 60 | return false; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /forge/src/main/java/de/maxhenkel/tradecycling/ForgeTradeCyclingClientMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.config.ForgeTradeCyclingClientConfig; 4 | import de.maxhenkel.tradecycling.config.TradeCyclingClientConfig; 5 | import io.netty.buffer.Unpooled; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.input.KeyEvent; 8 | import net.minecraft.client.multiplayer.ClientPacketListener; 9 | import net.minecraft.network.FriendlyByteBuf; 10 | import net.minecraftforge.client.event.InputEvent; 11 | import net.minecraftforge.client.event.RegisterKeyMappingsEvent; 12 | import net.minecraftforge.client.event.ScreenEvent; 13 | import net.minecraftforge.common.ForgeConfigSpec; 14 | import net.minecraftforge.fml.config.ModConfig; 15 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 16 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 17 | 18 | public class ForgeTradeCyclingClientMod extends TradeCyclingClientMod { 19 | 20 | protected FMLJavaModLoadingContext context; 21 | 22 | public ForgeTradeCyclingClientMod(FMLJavaModLoadingContext context) { 23 | this.context = context; 24 | FMLClientSetupEvent.getBus(context.getModBusGroup()).addListener(this::clientSetup); 25 | RegisterKeyMappingsEvent.BUS.addListener(this::onRegisterKeyBinds); 26 | ScreenEvent.Init.Post.BUS.addListener(this::onInitScreen); 27 | InputEvent.Key.BUS.addListener(this::onKeyInput); 28 | CONFIG = createClientConfig(); 29 | } 30 | 31 | public void clientSetup(FMLClientSetupEvent event) { 32 | clientInit(); 33 | } 34 | 35 | @Override 36 | public void sendCycleTradesPacket() { 37 | ClientPacketListener connection = Minecraft.getInstance().getConnection(); 38 | if (connection != null) { 39 | ForgeTradeCyclingMod.CYCLE_TRADES_CHANNEL.send(new FriendlyByteBuf(Unpooled.buffer()), connection.getConnection()); 40 | } 41 | } 42 | 43 | public void onRegisterKeyBinds(RegisterKeyMappingsEvent event) { 44 | event.register(CYCLE_TRADES_KEY); 45 | } 46 | 47 | @Override 48 | public TradeCyclingClientConfig createClientConfig() { 49 | ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); 50 | ForgeTradeCyclingClientConfig config = new ForgeTradeCyclingClientConfig(builder); 51 | context.registerConfig(ModConfig.Type.CLIENT, builder.build()); 52 | return config; 53 | } 54 | 55 | public void onInitScreen(ScreenEvent.Init.Post event) { 56 | onOpenScreen(event.getScreen(), event::addListener); 57 | } 58 | 59 | public void onKeyInput(InputEvent.Key event) { 60 | onCycleKeyPressed(new KeyEvent(event.getKey(), event.getScanCode(), event.getModifiers()), event.getAction()); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /neoforge/src/main/java/de/maxhenkel/tradecycling/NeoForgeTradeCyclingClientMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.config.NeoForgeTradeCyclingClientConfig; 4 | import de.maxhenkel.tradecycling.config.TradeCyclingClientConfig; 5 | import de.maxhenkel.tradecycling.net.CycleTradesPacket; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.multiplayer.ClientPacketListener; 8 | import net.neoforged.bus.api.IEventBus; 9 | import net.neoforged.bus.api.SubscribeEvent; 10 | import net.neoforged.fml.ModContainer; 11 | import net.neoforged.fml.ModList; 12 | import net.neoforged.fml.config.ModConfig; 13 | import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; 14 | import net.neoforged.neoforge.client.event.InputEvent; 15 | import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; 16 | import net.neoforged.neoforge.client.event.ScreenEvent; 17 | import net.neoforged.neoforge.client.network.ClientPacketDistributor; 18 | import net.neoforged.neoforge.common.ModConfigSpec; 19 | import net.neoforged.neoforge.common.NeoForge; 20 | 21 | public class NeoForgeTradeCyclingClientMod extends TradeCyclingClientMod { 22 | 23 | public NeoForgeTradeCyclingClientMod(IEventBus eventBus) { 24 | eventBus.addListener(this::clientSetup); 25 | eventBus.addListener(this::onRegisterKeyBinds); 26 | CONFIG = createClientConfig(); 27 | } 28 | 29 | public void clientSetup(FMLClientSetupEvent event) { 30 | clientInit(); 31 | } 32 | 33 | @Override 34 | public void clientInit() { 35 | super.clientInit(); 36 | NeoForge.EVENT_BUS.register(this); 37 | } 38 | 39 | @Override 40 | public void sendCycleTradesPacket() { 41 | ClientPacketListener connection = Minecraft.getInstance().getConnection(); 42 | if (connection != null) { 43 | ClientPacketDistributor.sendToServer(new CycleTradesPacket()); 44 | } 45 | } 46 | 47 | public void onRegisterKeyBinds(RegisterKeyMappingsEvent event) { 48 | event.register(CYCLE_TRADES_KEY); 49 | } 50 | 51 | @Override 52 | public TradeCyclingClientConfig createClientConfig() { 53 | ModConfigSpec.Builder builder = new ModConfigSpec.Builder(); 54 | NeoForgeTradeCyclingClientConfig config = new NeoForgeTradeCyclingClientConfig(builder); 55 | ModContainer modContainer = ModList.get().getModContainerById(TradeCyclingMod.MODID).orElseThrow(() -> new RuntimeException("Could not find mod %s".formatted(TradeCyclingMod.MODID))); 56 | modContainer.registerConfig(ModConfig.Type.CLIENT, builder.build()); 57 | return config; 58 | } 59 | 60 | @SubscribeEvent 61 | public void onInitScreen(ScreenEvent.Init.Post event) { 62 | onOpenScreen(event.getScreen(), event::addListener); 63 | } 64 | 65 | @SubscribeEvent 66 | public void onKeyInput(InputEvent.Key event) { 67 | onCycleKeyPressed(event.getKeyEvent(), event.getAction()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/TradeCyclingClientMod.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling; 2 | 3 | import de.maxhenkel.tradecycling.config.TradeCyclingClientConfig; 4 | import de.maxhenkel.tradecycling.gui.CycleTradesButton; 5 | import de.maxhenkel.tradecycling.mixin.AbstractContainerScreenAccessor; 6 | import net.minecraft.client.KeyMapping; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.client.gui.components.AbstractWidget; 9 | import net.minecraft.client.gui.screens.Screen; 10 | import net.minecraft.client.gui.screens.inventory.MerchantScreen; 11 | import net.minecraft.client.input.KeyEvent; 12 | import net.minecraft.client.resources.sounds.SimpleSoundInstance; 13 | import net.minecraft.sounds.SoundEvents; 14 | import org.lwjgl.glfw.GLFW; 15 | 16 | import java.util.function.Consumer; 17 | 18 | public abstract class TradeCyclingClientMod { 19 | 20 | public static TradeCyclingClientConfig CONFIG; 21 | public static final KeyMapping CYCLE_TRADES_KEY = new KeyMapping("key.trade_cycling.cycle_trades", GLFW.GLFW_KEY_C, KeyMapping.Category.INVENTORY); 22 | 23 | public TradeCyclingClientMod() { 24 | 25 | } 26 | 27 | public void clientInit() { 28 | registerKeyBindings(); 29 | } 30 | 31 | public abstract void sendCycleTradesPacket(); 32 | 33 | public abstract TradeCyclingClientConfig createClientConfig(); 34 | 35 | public void registerKeyBindings() { 36 | 37 | } 38 | 39 | public void onOpenScreen(Screen screen, Consumer eventConsumer) { 40 | if (!(screen instanceof MerchantScreen merchantScreen)) { 41 | return; 42 | } 43 | 44 | if (!(screen instanceof AbstractContainerScreenAccessor s)) { 45 | return; 46 | } 47 | 48 | TradeCyclingClientConfig.CycleTradesButtonLocation loc = CONFIG.getCycleTradesButtonLocation(); 49 | 50 | if (loc.equals(TradeCyclingClientConfig.CycleTradesButtonLocation.NONE)) { 51 | return; 52 | } 53 | 54 | int posX; 55 | 56 | switch (loc) { 57 | case TOP_LEFT: 58 | default: 59 | posX = s.getLeftPos() + 107; 60 | break; 61 | case TOP_RIGHT: 62 | posX = s.getLeftPos() + 250; 63 | break; 64 | } 65 | 66 | eventConsumer.accept((T) new CycleTradesButton(posX, s.getTopPos() + 8, b -> sendCycleTradesPacket(), merchantScreen)); 67 | } 68 | 69 | public void onCycleKeyPressed(KeyEvent event, int action) { 70 | if (!CYCLE_TRADES_KEY.matches(event) || action != 1) { 71 | return; 72 | } 73 | 74 | Minecraft mc = Minecraft.getInstance(); 75 | Screen currentScreen = mc.screen; 76 | 77 | if (!(currentScreen instanceof MerchantScreen screen)) { 78 | return; 79 | } 80 | 81 | if (!screen.getMenu().showProgressBar() || screen.getMenu().getTraderXp() > 0) { 82 | return; 83 | } 84 | 85 | sendCycleTradesPacket(); 86 | mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1F)); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /common/src/main/java/de/maxhenkel/tradecycling/compatibility/VisibleTraders.java: -------------------------------------------------------------------------------- 1 | package de.maxhenkel.tradecycling.compatibility; 2 | 3 | import de.maxhenkel.tradecycling.TradeCyclingMod; 4 | import net.minecraft.world.entity.npc.villager.Villager; 5 | import net.minecraft.world.item.trading.MerchantOffers; 6 | 7 | import javax.annotation.Nullable; 8 | import java.lang.reflect.Method; 9 | 10 | public class VisibleTraders { 11 | 12 | @Nullable 13 | private static Method regenerateTrades; 14 | @Nullable 15 | private static Method getMerchantOffers; 16 | 17 | @Nullable 18 | private static Method getLevel; 19 | 20 | public static void forceTradeGeneration(Villager villager) { 21 | if (regenerateTrades == null) { 22 | return; 23 | } 24 | try { 25 | regenerateTrades.invoke(villager); 26 | } catch (Throwable e) { 27 | TradeCyclingMod.LOGGER.error("Failed to regenerate visible traders trades", e); 28 | regenerateTrades = null; 29 | } 30 | } 31 | 32 | public static MerchantOffers getOffers(Villager villager) { 33 | if (getMerchantOffers == null) { 34 | return villager.getOffers(); 35 | } 36 | try { 37 | return (MerchantOffers) getMerchantOffers.invoke(villager); 38 | } catch (Throwable e) { 39 | TradeCyclingMod.LOGGER.error("Failed to regenerate visible traders trades", e); 40 | getMerchantOffers = null; 41 | return villager.getOffers(); 42 | } 43 | } 44 | 45 | public static int getLevel(Villager villager) { 46 | if (getLevel == null) { 47 | return villager.getVillagerData().level(); 48 | } 49 | try { 50 | return (int) getLevel.invoke(villager); 51 | } catch (Throwable e) { 52 | TradeCyclingMod.LOGGER.error("Failed to regenerate visible traders trades", e); 53 | getLevel = null; 54 | return villager.getVillagerData().level(); 55 | } 56 | } 57 | 58 | public static void init(TradeCyclingMod mod) { 59 | if (!mod.isModLoaded("visibletraders")) { 60 | return; 61 | } 62 | regenerateTrades = getRegenerateMethod(); 63 | getMerchantOffers = getMerchantOffersMethod(); 64 | getLevel = getLevelMethod(); 65 | } 66 | 67 | private static Method getRegenerateMethod() { 68 | try { 69 | Method forceTradeGeneration = Villager.class.getDeclaredMethod("visibleTrades$regenerateTrades"); 70 | forceTradeGeneration.setAccessible(true); 71 | return forceTradeGeneration; 72 | } catch (Throwable e) { 73 | TradeCyclingMod.LOGGER.error("Failed to initialize visible traders integration", e); 74 | return null; 75 | } 76 | } 77 | 78 | private static Method getMerchantOffersMethod() { 79 | try { 80 | Method getMerchantOffers = Villager.class.getDeclaredMethod("visibleTraders$getCombinedOffers"); 81 | getMerchantOffers.setAccessible(true); 82 | return getMerchantOffers; 83 | } catch (Throwable e) { 84 | TradeCyclingMod.LOGGER.error("Failed to initialize visible traders integration", e); 85 | return null; 86 | } 87 | } 88 | 89 | private static Method getLevelMethod() { 90 | try { 91 | Method getLevel = Villager.class.getDeclaredMethod("visibleTraders$getShiftedLevel"); 92 | getLevel.setAccessible(true); 93 | return getLevel; 94 | } catch (Throwable e) { 95 | TradeCyclingMod.LOGGER.error("Failed to initialize visible traders integration", e); 96 | return null; 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | --------------------------------------------------------------------------------